internal/agent/netenv/named_test.go
Ref: Size: 24.3 KiB History
package netenv
import (
"bytes"
"context"
"errors"
"log/slog"
"net"
"os"
"strings"
"sync"
"testing"
"github.com/a73x/eitri/internal/agent/exec"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// snoopCall is one Listen the Net asked for. The context and the callback are
// kept so a test can play the part of the kernel: cancel like a teardown, or
// deliver an ACK like a guest.
type snoopCall struct {
ifname string
mac string
ctx context.Context
found func(ip string)
}
// fakeListen stands in for the AF_PACKET snoop. Production needs CAP_NET_RAW
// and a real tap; what this package owns is what it does with what the snoop
// hears, so the socket is injected out exactly like isTap and ifaces.
type fakeListen struct {
mu sync.Mutex
calls []snoopCall
err error
}
func (f *fakeListen) listen(ctx context.Context, ifname string, mac net.HardwareAddr, found func(ip string)) error {
f.mu.Lock()
defer f.mu.Unlock()
if f.err != nil {
return f.err
}
f.calls = append(f.calls, snoopCall{ifname: ifname, mac: mac.String(), ctx: ctx, found: found})
return nil
}
func (f *fakeListen) last(t *testing.T) snoopCall {
t.Helper()
f.mu.Lock()
defer f.mu.Unlock()
require.NotEmpty(t, f.calls, "expected a snoop to have been started")
return f.calls[len(f.calls)-1]
}
func (f *fakeListen) count() int {
f.mu.Lock()
defer f.mu.Unlock()
return len(f.calls)
}
// bridgedNet builds a Net on a host that declares networks and whose bridges
// all exist, with the snoop faked out.
func bridgedNet(t *testing.T, run exec.Runner, networks map[string]string) (*Net, *fakeListen) {
t.Helper()
n, err := New(run, "10.77.1.0/24", networks)
require.NoError(t, err)
n.isBridge = func(string) bool { return true }
snoop := &fakeListen{}
n.listen = snoop.listen
return n, snoop
}
// TestVerifyNetworksRefusesMissingBridge pins the startup gate: eitri attaches
// to bridges the operator declared and creates none, so a name whose link is
// absent is a host that cannot honor what it is about to advertise.
func TestVerifyNetworksRefusesMissingBridge(t *testing.T) {
noop := func(_ context.Context, _ string, _ ...string) (string, error) { return "", nil }
n, err := New(noop, "10.77.1.0/24", map[string]string{"lan": "br0"})
require.NoError(t, err)
n.isBridge = func(string) bool { return false }
err = n.VerifyNetworks()
require.Error(t, err)
assert.Contains(t, err.Error(), "lan", "the refusal must name the network")
assert.Contains(t, err.Error(), "br0", "and the link the operator has to declare")
n.isBridge = func(name string) bool { return name == "br0" }
assert.NoError(t, n.VerifyNetworks(), "a declared bridge passes")
}
// TestVerifyNetworksPassesWithNoNetworks pins that a host with no --host-network
// flags — every host today — starts as it always did.
func TestVerifyNetworksPassesWithNoNetworks(t *testing.T) {
noop := func(_ context.Context, _ string, _ ...string) (string, error) { return "", nil }
n, err := New(noop, "10.77.1.0/24", nil)
require.NoError(t, err)
assert.NoError(t, n.VerifyNetworks())
}
// networkedNet builds a Net whose vm1 is about to get both taps: neither link
// exists yet, so both `ip link show` probes fail and both adds run.
func networkedNet(t *testing.T, networks map[string]string) (*Net, *fakeListen, *[]call) {
t.Helper()
run, calls := recorder(nil, map[string]error{
"ip link show dev eit-vm1": errors.New(`Device "eit-vm1" does not exist.`),
"ip link show dev eil-vm1": errors.New(`Device "eil-vm1" does not exist.`),
})
n, snoop := bridgedNet(t, run, networks)
return n, snoop, calls
}
// TestNamedNetworkAddsASecondNIC is the shape of the whole feature: the guest
// keeps everything a NAT-only guest has — its tap on eitri0, its reservation,
// its address — and GAINS a second tap on the operator's bridge, watched for
// the address the site's own DHCP server grants it.
func TestNamedNetworkAddsASecondNIC(t *testing.T) {
n, snoop, calls := networkedNet(t, map[string]string{"lan": "br0"})
ip, err := n.ReserveIP("vm1")
require.NoError(t, err)
require.NoError(t, n.CreateTap(context.Background(), "vm1", ip, "lan"))
all := joinCalls(calls)
assert.Contains(t, all, "ip tuntap add dev eit-vm1 mode tap")
assert.Contains(t, all, "ip link set eit-vm1 master eitri0", "the NAT NIC is unconditional")
assert.Contains(t, all, "ip link set eit-vm1 up")
assert.Contains(t, all, "ip tuntap add dev eil-vm1 mode tap")
assert.Contains(t, all, "ip link set eil-vm1 master br0", "and the named NIC is additional")
assert.Contains(t, all, "ip link set eil-vm1 up")
mac, _ := net.ParseMAC(stateMAC("vm1"))
reserved, ok := n.dhcp.Lookup(mac)
require.True(t, ok, "a networked guest still holds a NAT reservation")
assert.Equal(t, ip, reserved.String())
assert.Equal(t, ip, n.Address("vm1"), "known before the guest boots, as ever")
started := snoop.last(t)
assert.Equal(t, "eil-vm1", started.ifname, "the snoop watches the named NIC's tap, not the NAT one")
netMAC, _ := net.ParseMAC(stateNetMAC("vm1"))
assert.Equal(t, netMAC.String(), started.mac,
"and listens for the second NIC's MAC — the NAT lease crosses the other tap and is this host's own answer")
}
// TestNICOrderIsTheNATTapFirst pins the guest ABI at the layer that creates the
// devices: eth0 is the NAT NIC, so its tap is made and enslaved before the
// named one exists. A guest whose NICs arrive in the other order has its
// management fabric on eth1 and everything that assumes eth0 is wrong.
func TestNICOrderIsTheNATTapFirst(t *testing.T) {
n, _, calls := networkedNet(t, map[string]string{"lan": "br0"})
require.NoError(t, n.CreateTap(context.Background(), "vm1", "10.77.1.2", "lan"))
all := joinCalls(calls)
assert.Less(t, strings.Index(all, "eit-vm1"), strings.Index(all, "eil-vm1"),
"the NAT tap is created first")
}
// TestUnknownNetworkIsPermanentAndCreatesNothing pins the one outcome the
// design forbids: an agent restarted without the flag fails the VM by name
// rather than quietly booting it with the NIC it asked for missing. It fails
// permanently — no retry on this agent can change the answer — and it fails
// before the first device is created, so the host is left as it was found.
func TestUnknownNetworkIsPermanentAndCreatesNothing(t *testing.T) {
run, calls := recorder(nil, nil)
n, _ := bridgedNet(t, run, map[string]string{"lan": "br0"})
err := n.CreateTap(context.Background(), "vm1", "10.77.1.2", "ghost")
require.Error(t, err)
assert.Contains(t, err.Error(), "ghost")
assert.Contains(t, err.Error(), "--host-network")
var p interface{ Permanent() bool }
assert.True(t, errors.As(err, &p) && p.Permanent(), "no retry on this agent can find the network")
assert.Empty(t, joinCalls(calls), "an unknown network creates nothing at all — not even the NAT tap")
}
// TestCreateTapFailsWhenSnoopWillNotStart pins the deliberate choice to fail the
// create: a named NIC's address arrives only through the snoop, so a guest
// booted without one is a guest whose LAN address nothing can ever learn.
func TestCreateTapFailsWhenSnoopWillNotStart(t *testing.T) {
n, snoop, _ := networkedNet(t, map[string]string{"lan": "br0"})
snoop.err = errors.New("operation not permitted")
err := n.CreateTap(context.Background(), "vm1", "10.77.1.2", "lan")
require.Error(t, err)
assert.Contains(t, err.Error(), "operation not permitted")
// Nothing armed behind the failure, or the create-retry would believe a
// snoop is already running and never start one.
snoop.err = nil
require.NoError(t, n.CreateTap(context.Background(), "vm1", "10.77.1.2", "lan"))
assert.Equal(t, 1, snoop.count(), "the retry starts the snoop the first attempt could not")
}
// TestNetworkAddressComesFromDiscovery pins the poll-until-known contract for
// the named NIC — and, in the same breath, that it never leaks into the address
// the rest of eitri uses. Two NICs, two addresses, two questions.
func TestNetworkAddressComesFromDiscovery(t *testing.T) {
n, snoop, _ := networkedNet(t, map[string]string{"lan": "br0"})
ip, err := n.ReserveIP("vm1")
require.NoError(t, err)
require.NoError(t, n.CreateTap(context.Background(), "vm1", ip, "lan"))
assert.Empty(t, n.NetworkAddress("vm1"), "no ACK seen yet — the honest answer is nothing")
assert.Equal(t, ip, n.Address("vm1"), "which never delays the address the gate splices to")
snoop.last(t).found("192.168.0.42")
assert.Equal(t, "192.168.0.42", n.NetworkAddress("vm1"))
assert.Equal(t, ip, n.Address("vm1"), "the LAN address must never be reported as the NAT one")
// A renewal that grants a different address is the guest's truth, not a
// conflict: the LAN moved it, and the report follows.
snoop.last(t).found("192.168.0.43")
assert.Equal(t, "192.168.0.43", n.NetworkAddress("vm1"))
}
// TestAdoptNetworkSeedsAddressAndRearmsSnoop pins the restart replay: the last
// known LAN address is readable again before the guest's next renewal is
// snooped, and the snoop is running again for when that renewal comes.
func TestAdoptNetworkSeedsAddressAndRearmsSnoop(t *testing.T) {
noop := func(_ context.Context, _ string, _ ...string) (string, error) { return "", nil }
n, snoop := bridgedNet(t, noop, map[string]string{"lan": "br0"})
n.isTap = func(string) bool { return true } // the guest survived the restart
n.AdoptNetwork("vm1", "lan", "192.168.0.7")
assert.Equal(t, "192.168.0.7", n.NetworkAddress("vm1"))
assert.Equal(t, "eil-vm1", snoop.last(t).ifname, "the re-armed snoop watches the named NIC")
assert.Empty(t, n.Address("vm1"),
"the NAT half of the replay is AddReservation's, and this must not fake it")
}
// captureLogs redirects the default logger into a buffer for the duration of
// the test — a replay that adopts a guest onto a network this host no longer
// serves has no caller to tell, and reports it the only way it can.
func captureLogs(t *testing.T) *bytes.Buffer {
t.Helper()
var logs bytes.Buffer
prev := slog.Default()
slog.SetDefault(slog.New(slog.NewTextHandler(&logs, nil)))
t.Cleanup(func() { slog.SetDefault(prev) })
return &logs
}
// TestAdoptNetworkWarnsWhenTheHostNoLongerServesTheNetwork pins the one moment
// an operator can be told about a dropped --host-network flag before it costs
// them a guest: the agent restarts, the running VM is adopted onto a network
// this host no longer has, and everything keeps working until the reboot that
// fails. The warning has to carry the consequence, because the state it
// describes looks entirely healthy from every other angle.
func TestAdoptNetworkWarnsWhenTheHostNoLongerServesTheNetwork(t *testing.T) {
logs := captureLogs(t)
noop := func(_ context.Context, _ string, _ ...string) (string, error) { return "", nil }
n, _ := bridgedNet(t, noop, map[string]string{"lan": "br0"})
n.isTap = func(string) bool { return true }
n.AdoptNetwork("vm1", "office", "192.168.0.7")
warned := logs.String()
assert.Contains(t, warned, "level=WARN")
assert.Contains(t, warned, "vm_id=vm1", "the operator has to know which guest")
assert.Contains(t, warned, "network=office", "and which network went missing")
assert.Contains(t, warned, "--host-network", "the flag that would put it back")
assert.Contains(t, warned, "next reboot fails", "and what happens to this guest if they do not")
assert.Equal(t, "192.168.0.7", n.NetworkAddress("vm1"),
"the running guest keeps the address it has — the warning is instead of dropping it, not before")
}
// TestAdoptNetworkIsSilentForANetworkThisHostServes pins the other side: the
// ordinary restart, every guest adopted back onto a network the flags still
// name, says nothing. A warning that fires on the healthy path is one nobody
// reads on the unhealthy one.
func TestAdoptNetworkIsSilentForANetworkThisHostServes(t *testing.T) {
logs := captureLogs(t)
noop := func(_ context.Context, _ string, _ ...string) (string, error) { return "", nil }
n, _ := bridgedNet(t, noop, map[string]string{"lan": "br0"})
n.isTap = func(string) bool { return true }
n.AdoptNetwork("vm1", "lan", "192.168.0.7")
assert.Empty(t, logs.String())
}
// TestReAttachKeepsWhatTheAttachmentAlreadyFound pins that re-attaching a VM
// that is already attached — a Boot retry, or a replay racing one — adds the
// network to what is there rather than starting the guest's state over: the
// address the snoop already found survives, and so does the snoop.
func TestReAttachKeepsWhatTheAttachmentAlreadyFound(t *testing.T) {
n, snoop, _ := networkedNet(t, map[string]string{"lan": "br0"})
require.NoError(t, n.CreateTap(context.Background(), "vm1", "10.77.1.2", "lan"))
snoop.last(t).found("192.168.0.42")
first := snoop.last(t)
n.isTap = func(string) bool { return true }
n.AdoptNetwork("vm1", "lan", "")
require.NoError(t, n.CreateTap(context.Background(), "vm1", "10.77.1.2", "lan"))
assert.Equal(t, "192.168.0.42", n.NetworkAddress("vm1"), "a re-attach is not a forgetting")
assert.Equal(t, 1, snoop.count(), "and the live snoop is still the only one")
require.NoError(t, n.DeleteTap(context.Background(), "vm1"))
assert.Error(t, first.ctx.Err(), "still the one teardown cancels")
}
// TestAdoptNetworkWithoutATapStartsNoSnoop pins the other half: a VM that is not
// running has no tap to watch, and Boot creates both.
func TestAdoptNetworkWithoutATapStartsNoSnoop(t *testing.T) {
noop := func(_ context.Context, _ string, _ ...string) (string, error) { return "", nil }
n, snoop := bridgedNet(t, noop, map[string]string{"lan": "br0"})
n.isTap = func(string) bool { return false }
n.AdoptNetwork("vm1", "lan", "")
assert.Zero(t, snoop.count(), "nothing to snoop on a VM with no tap")
assert.Empty(t, n.NetworkAddress("vm1"), "and no address to claim on its behalf")
}
// TestDeleteTapTearsDownBothNICs pins the teardown of everything a networked
// guest held: both taps (a named one left behind holds a port on the operator's
// own bridge), the reservation, the snoop goroutine, and the discovered
// address. It also pins the race the listener concedes: it checks its context
// between reads, so an ACK received in the last poll window can call back after
// the cancel, and a deleted VM must not come back holding an address.
func TestDeleteTapTearsDownBothNICs(t *testing.T) {
n, snoop, calls := networkedNet(t, map[string]string{"lan": "br0"})
ip, err := n.ReserveIP("vm1")
require.NoError(t, err)
require.NoError(t, n.CreateTap(context.Background(), "vm1", ip, "lan"))
started := snoop.last(t)
started.found("192.168.0.42")
require.NoError(t, n.DeleteTap(context.Background(), "vm1"))
all := joinCalls(calls)
assert.Contains(t, all, "ip link del eit-vm1")
assert.Contains(t, all, "ip link del eil-vm1", "the named NIC's tap goes too")
mac, _ := net.ParseMAC(stateMAC("vm1"))
_, reserved := n.dhcp.Lookup(mac)
assert.False(t, reserved, "the NAT reservation is released")
assert.Error(t, started.ctx.Err(), "the snoop's context must be cancelled")
assert.Empty(t, n.NetworkAddress("vm1"), "a deleted VM holds no address")
started.found("192.168.0.99") // the late frame from the last poll window
// Re-attach the id before reading, because that is the only way to see the
// write the guard prevents: with no entry for the VM, NetworkAddress answers
// "" whether or not the late ACK was recorded. A VM re-created under the same
// id would attach exactly like this, and it must not inherit the dead one's
// address.
n.AdoptNetwork("vm1", "lan", "")
assert.Empty(t, n.NetworkAddress("vm1"), "a late ACK must not resurrect a deleted VM")
}
// TestDeleteTapRemovesANamedTapThisAgentNeverSaw pins that the teardown asks
// the device, not the bookkeeping: the map of who has a named NIC is in-memory
// state, and a tap that outlives its guest holds a port on a bridge eitri does
// not own.
func TestDeleteTapRemovesANamedTapThisAgentNeverSaw(t *testing.T) {
run, calls := recorder(nil, nil)
n, _ := bridgedNet(t, run, map[string]string{"lan": "br0"})
require.NoError(t, n.DeleteTap(context.Background(), "vm1"))
assert.Contains(t, joinCalls(calls), "ip link del eil-vm1")
}
// TestDeleteTapRemovesTheNamedTapEvenWhenTheNATOneFails pins the ordering
// promise the comment makes: a named tap must never outlive its guest, so a
// NAT tap that refuses to go must not be allowed to keep the operator's bridge
// holding a port. Both are asked; both failures are reported.
func TestDeleteTapRemovesTheNamedTapEvenWhenTheNATOneFails(t *testing.T) {
run, calls := recorder(nil, map[string]error{
"ip link del eit-vm1": errors.New("RTNETLINK answers: Operation not permitted"),
})
n, _ := bridgedNet(t, run, map[string]string{"lan": "br0"})
err := n.DeleteTap(context.Background(), "vm1")
require.Error(t, err, "the NAT tap's failure is still reported")
assert.Contains(t, err.Error(), "Operation not permitted")
assert.Contains(t, joinCalls(calls), "ip link del eil-vm1",
"the named tap is asked regardless — it is on a bridge eitri does not own")
}
// TestCreateTapRetryStartsNoSecondSnoop pins the idempotency a Boot retry and
// the startup replay both lean on: a VM whose snoop is already running gets no
// second listener. Two goroutines on one tap would report every ACK twice and
// only one of them would answer the cancel — the other would outlive the guest.
func TestCreateTapRetryStartsNoSecondSnoop(t *testing.T) {
n, snoop, _ := networkedNet(t, map[string]string{"lan": "br0"})
ip, err := n.ReserveIP("vm1")
require.NoError(t, err)
require.NoError(t, n.CreateTap(context.Background(), "vm1", ip, "lan"))
require.Equal(t, 1, snoop.count())
first := snoop.last(t)
require.NoError(t, n.CreateTap(context.Background(), "vm1", ip, "lan"))
assert.Equal(t, 1, snoop.count(), "the live snoop is left alone")
require.NoError(t, n.DeleteTap(context.Background(), "vm1"))
assert.Error(t, first.ctx.Err(), "and it is still the one teardown cancels")
}
// TestCreateNamedTapRejectsANonTapDevice is TestCreateTapRejectsNonTapDevice's
// twin for the second NIC: a name collision on the operator's own bridge must
// fail here, legibly and permanently, rather than as an illegible
// cloud-hypervisor error after eitri has enslaved somebody else's device.
func TestCreateNamedTapRejectsANonTapDevice(t *testing.T) {
run, _ := recorder(nil, map[string]error{
"ip link show dev eit-vm1": errors.New(`Device "eit-vm1" does not exist.`),
})
n, snoop := bridgedNet(t, run, map[string]string{"lan": "br0"})
n.isTap = func(name string) bool { return name != "eil-vm1" } // eil-vm1 exists and is something else
err := n.CreateTap(context.Background(), "vm1", "10.77.1.5", "lan")
require.Error(t, err)
assert.Contains(t, err.Error(), "not a TAP device")
assert.Contains(t, err.Error(), "eil-vm1")
var p interface{ Permanent() bool }
assert.True(t, errors.As(err, &p) && p.Permanent(), "a collision never self-resolves")
assert.Zero(t, snoop.count(), "and nothing is watched on a device that is not the guest's")
}
// TestNATOnlyCreateTapStartsNoSnoopOrSecondTap pins that the guest without a
// named network gained nothing: one tap, no listener, and the reservation
// pinned exactly as before.
func TestNATOnlyCreateTapStartsNoSnoopOrSecondTap(t *testing.T) {
errs := map[string]error{
"ip link show dev eit-vm2": errors.New(`Device "eit-vm2" does not exist.`),
}
run, calls := recorder(nil, errs)
n, snoop := bridgedNet(t, run, map[string]string{"lan": "br0"})
ip, err := n.ReserveIP("vm2")
require.NoError(t, err)
require.NoError(t, n.CreateTap(context.Background(), "vm2", ip, ""))
assert.Zero(t, snoop.count(), "a guest with one NIC has nothing to discover")
all := joinCalls(calls)
assert.Contains(t, all, "ip link set eit-vm2 master eitri0")
assert.NotContains(t, all, "eil-vm2", "and no second tap is created for it")
assert.Equal(t, ip, n.Address("vm2"))
assert.Empty(t, n.NetworkAddress("vm2"))
}
// bridgeAddrShow is the post-condition output EnsureBridge insists on before it
// gets as far as any nft command.
var bridgeAddrShow = map[string]string{
"ip -o addr show dev eitri0": "2: eitri0 inet 10.77.1.1/24 brd 10.77.1.255 scope global eitri0",
}
// nftCalls returns the nft commands a run issued, in order and without the
// program name — the whole ruleset this host asked the kernel for.
func nftCalls(calls *[]call) []string {
var out []string
for _, c := range *calls {
if c.name == "nft" {
out = append(out, c.args)
}
}
return out
}
// bridgeFamily narrows nftCalls to the bridge-family half.
func bridgeFamily(all []string) []string {
var out []string
for _, c := range all {
if strings.Contains(c, "bridge eitri") {
out = append(out, c)
}
}
return out
}
// TestEnsureBridgeDropsGuestDHCPOnNamedNetworkHost pins the rule that stops a
// guest being a DHCP server on the operator's LAN: without it a guest can both
// answer its neighbours (minting an address the snoop cannot tell from the
// site's) and lease to the operator's real machines.
func TestEnsureBridgeDropsGuestDHCPOnNamedNetworkHost(t *testing.T) {
run, calls := recorder(bridgeAddrShow, nil)
n, _ := bridgedNet(t, run, map[string]string{"lan": "br0"})
require.NoError(t, n.EnsureBridge(context.Background()))
assert.Equal(t, []string{
"add table bridge eitri",
"add chain bridge eitri prerouting { type filter hook prerouting priority -200 ; }",
"flush chain bridge eitri prerouting",
`add rule bridge eitri prerouting iifname "` + netTapPrefix + `*" udp sport 67 drop`,
}, bridgeFamily(nftCalls(calls)),
"the flush precedes the rule, so restarts cannot stack copies of it")
assert.True(t, strings.HasPrefix(n.NetTapName("vm1"), netTapPrefix),
"and the pattern matches the taps this host actually creates")
}
// TestEnsureBridgeIssuesNoBridgeFamilyRuleWithoutNetworks pins the other side of
// it: a host with no --host-network flag — every host today — asks the kernel
// for exactly the ruleset it always did.
func TestEnsureBridgeIssuesNoBridgeFamilyRuleWithoutNetworks(t *testing.T) {
run, calls := recorder(bridgeAddrShow, nil)
n, err := New(run, "10.77.1.0/24", nil)
require.NoError(t, err)
require.NoError(t, n.EnsureBridge(context.Background()))
assert.Equal(t, []string{
"add table ip eitri",
"add chain ip eitri postrouting { type nat hook postrouting priority srcnat ; }",
"flush chain ip eitri postrouting",
"add rule ip eitri postrouting ip saddr 10.77.1.0/24 masquerade",
}, nftCalls(calls), "a NAT-only host's ruleset is untouched by named networks")
assert.Empty(t, bridgeFamily(nftCalls(calls)))
}
// TestEnsureBridgeSurfacesGuestDHCPRuleFailure pins that a kernel which will not
// take the rule stops the agent naming the command, rather than starting a host
// that serves named networks with the block missing.
func TestEnsureBridgeSurfacesGuestDHCPRuleFailure(t *testing.T) {
rule := `nft add rule bridge eitri prerouting iifname "` + netTapPrefix + `*" udp sport 67 drop`
run, _ := recorder(bridgeAddrShow, map[string]error{
rule: errors.New("Error: Could not process rule: Operation not supported"),
})
n, _ := bridgedNet(t, run, map[string]string{"lan": "br0"})
err := n.EnsureBridge(context.Background())
require.Error(t, err)
assert.Contains(t, err.Error(), "udp sport 67 drop", "the refusal names the command")
assert.Contains(t, err.Error(), "Operation not supported", "and what the kernel said")
}
// TestNetTapNameIsTruncatedBelowIFNAMSIZ pins the second device name against
// the same limit as the first, and against ever colliding with it.
func TestNetTapNameIsTruncatedBelowIFNAMSIZ(t *testing.T) {
noop := func(_ context.Context, _ string, _ ...string) (string, error) { return "", nil }
n, err := New(noop, "10.77.1.0/24", nil)
require.NoError(t, err)
assert.Equal(t, "eil-vm1", n.NetTapName("vm1"))
assert.Equal(t, "eil-abcdefgh", n.NetTapName("abcdefghijklmnop"))
assert.LessOrEqual(t, len(n.NetTapName("abcdefghijklmnop")), 15, "IFNAMSIZ is 16 including NUL")
assert.NotEqual(t, n.TapName("vm1"), n.NetTapName("vm1"), "one guest, two devices, two names")
}
// TestSysfsIsBridge exercises the production probe against a fabricated /sys
// tree: a bridge has a bridge/ directory, an ordinary link does not.
func TestSysfsIsBridge(t *testing.T) {
root := t.TempDir()
require.NoError(t, os.MkdirAll(root+"/br0/bridge", 0o755))
require.NoError(t, os.MkdirAll(root+"/eth0", 0o755))
require.NoError(t, os.MkdirAll(root+"/notabr", 0o755))
require.NoError(t, os.WriteFile(root+"/notabr/bridge", []byte("x"), 0o644))
assert.True(t, sysfsIsBridgeAt(root, "br0"))
assert.False(t, sysfsIsBridgeAt(root, "eth0"), "an ordinary link is not a bridge")
assert.False(t, sysfsIsBridgeAt(root, "notabr"), "a file named bridge is not the directory")
assert.False(t, sysfsIsBridgeAt(root, "absent"), "a missing link is not a bridge")
}