Bring an administratively down interface up in AddAddress before writing its addresses and routes. The kernel installs an address's connected route only while its link is up, so adding a gateway to a down interface was rejected as unreachable. A link raised this way is part of the pre-change state AddAddress restores, so it is returned to down on any path that does not complete. Also fix the rollback masking its own error: deleting the address it added takes the default route with it, since that route resolved its nexthop through the address's connected subnet. The following RouteDel then answered ESRCH and was reported in place of "aborted operation due to loss of internet".
514 lines
18 KiB
Go
514 lines
18 KiB
Go
package netconfig
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/vishvananda/netlink"
|
|
"github.com/vishvananda/netns"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Function to setup a test namespace for test network interfaces.
|
|
func setupNetlinkTest(t testing.TB) func() {
|
|
t.Helper()
|
|
|
|
if os.Getuid() != 0 {
|
|
t.Skip("Test requires root privileges.")
|
|
}
|
|
|
|
runtime.LockOSThread()
|
|
ns, err := netns.New()
|
|
require.NoError(t, err, "Failed to create new netns")
|
|
|
|
link, err := netlink.LinkByName("lo")
|
|
require.NoError(t, err, "Failed to find \"lo\" in new netns")
|
|
err = netlink.LinkSetUp(link)
|
|
require.NoError(t, err, "Failed to bring up \"lo\" in new netns")
|
|
|
|
return func() {
|
|
ns.Close()
|
|
runtime.UnlockOSThread()
|
|
}
|
|
}
|
|
|
|
// isPrimary reports whether the given IP is currently the primary address of
|
|
// its subnet on the link, i.e. the kernel has not flagged it IFA_F_SECONDARY.
|
|
// It also confirms the address is present at all.
|
|
func isPrimary(t testing.TB, h *netlink.Handle, link netlink.Link, ip net.IP) (present, primary bool) {
|
|
t.Helper()
|
|
addrs, err := h.AddrList(link, netlink.FAMILY_ALL)
|
|
require.NoError(t, err, "AddrList")
|
|
for _, a := range addrs {
|
|
if a.IPNet.IP.Equal(ip) {
|
|
return true, a.Flags&unix.IFA_F_SECONDARY == 0
|
|
}
|
|
}
|
|
return false, false
|
|
}
|
|
|
|
// TestLinuxSetPrimaryAddress exercises the netlink reordering performed by
|
|
// SetPrimaryAddress: promoting a secondary same-subnet address to primary,
|
|
// idempotency when already primary, and the error when the address is absent.
|
|
func TestLinuxSetPrimaryAddress(t *testing.T) {
|
|
tearDown := setupNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
h, err := netlink.NewHandle()
|
|
require.NoError(t, err)
|
|
defer h.Close()
|
|
|
|
// Setup test link.
|
|
name := "test_enp1s0"
|
|
link := netlink.Link(&netlink.Dummy{LinkAttrs: netlink.LinkAttrs{
|
|
Name: name,
|
|
HardwareAddr: net.HardwareAddr{0x52, 0x54, 0x00, 0x8b, 0x0d, 0x93},
|
|
}})
|
|
require.NoError(t, h.LinkAdd(link))
|
|
require.NoError(t, h.LinkSetUp(link))
|
|
|
|
// Add two addresses in the same subnet. The first added is the primary.
|
|
primaryIP := &net.IPNet{IP: net.ParseIP("1.2.3.4"), Mask: net.CIDRMask(24, 32)}
|
|
secondaryIP := &net.IPNet{IP: net.ParseIP("1.2.3.5"), Mask: net.CIDRMask(24, 32)}
|
|
require.NoError(t, h.AddrAdd(link, &netlink.Addr{IPNet: primaryIP}))
|
|
require.NoError(t, h.AddrAdd(link, &netlink.Addr{IPNet: secondaryIP}))
|
|
|
|
// Add a default route via the subnet so connectivity verification has a
|
|
// gateway to work with.
|
|
gw := net.ParseIP("1.2.3.1")
|
|
require.NoError(t, h.RouteAdd(&netlink.Route{
|
|
Family: netlink.FAMILY_V4,
|
|
LinkIndex: link.Attrs().Index,
|
|
Dst: &net.IPNet{IP: net.IPv4zero, Mask: net.CIDRMask(0, 32)},
|
|
Gw: gw,
|
|
}))
|
|
|
|
// Sanity check the initial primary/secondary assignment.
|
|
_, primary := isPrimary(t, h, link, primaryIP.IP)
|
|
require.True(t, primary, "expected 1.2.3.4 to start as primary")
|
|
_, primary = isPrimary(t, h, link, secondaryIP.IP)
|
|
require.False(t, primary, "expected 1.2.3.5 to start as secondary")
|
|
|
|
// Configurator with the success sentinel and a capturing backend.
|
|
backend := &fakeIfaceBackend{}
|
|
c := &linuxConfigurator{configOptions: &configOptions{testAddress: "test_success"}}
|
|
c.ifaceBackends = append(c.ifaceBackends, namedIfaceBackend{"fake", backend})
|
|
|
|
// Promote the secondary to primary.
|
|
require.NoError(t, c.SetPrimaryAddress(context.Background(), name, secondaryIP), "SetPrimaryAddress")
|
|
|
|
// The kernel should now treat 1.2.3.5 as primary and 1.2.3.4 as secondary,
|
|
// with both addresses still present and the default route intact.
|
|
present, primary := isPrimary(t, h, link, secondaryIP.IP)
|
|
assert.True(t, present && primary, "after promotion 1.2.3.5: present=%v primary=%v, want both true", present, primary)
|
|
present, primary = isPrimary(t, h, link, primaryIP.IP)
|
|
assert.True(t, present && !primary, "after promotion 1.2.3.4: present=%v primary=%v, want present true primary false", present, primary)
|
|
routes, err := h.RouteList(link, netlink.FAMILY_V4)
|
|
require.NoError(t, err)
|
|
haveDefault := false
|
|
for _, r := range routes {
|
|
if ones, _ := r.Dst.Mask.Size(); ones == 0 && r.Gw.Equal(gw) {
|
|
haveDefault = true
|
|
}
|
|
}
|
|
assert.True(t, haveDefault, "default route missing after promotion")
|
|
|
|
// The persisted config must receive the reordered list, primary first.
|
|
assert.True(t, len(backend.lastAddrs) != 0 && backend.lastAddrs[0].IP.Equal(secondaryIP.IP), "backend primary = %v, want 1.2.3.5 first", backend.lastAddrs)
|
|
|
|
// Promoting the already-primary address is a no-op that still succeeds.
|
|
assert.NoError(t, c.SetPrimaryAddress(context.Background(), name, secondaryIP), "SetPrimaryAddress (idempotent)")
|
|
_, primary = isPrimary(t, h, link, secondaryIP.IP)
|
|
assert.True(t, primary, "1.2.3.5 should remain primary after idempotent call")
|
|
|
|
// Promoting an address not on the interface must error before any change.
|
|
absent := &net.IPNet{IP: net.ParseIP("1.2.3.99"), Mask: net.CIDRMask(24, 32)}
|
|
err = c.SetPrimaryAddress(context.Background(), name, absent)
|
|
assert.EqualError(t, err, "address not found on interface")
|
|
}
|
|
|
|
// adminUp reports whether the named link is administratively up, re-reading it
|
|
// from the kernel rather than trusting a cached netlink.Link.
|
|
func adminUp(t testing.TB, h *netlink.Handle, name string) bool {
|
|
t.Helper()
|
|
link, err := h.LinkByName(name)
|
|
require.NoError(t, err, "LinkByName")
|
|
return link.Attrs().Flags&net.FlagUp != 0
|
|
}
|
|
|
|
// hasDefaultRoute reports whether the link carries a default route via gw.
|
|
func hasDefaultRoute(t testing.TB, h *netlink.Handle, link netlink.Link, gw net.IP) bool {
|
|
t.Helper()
|
|
routes, err := h.RouteList(link, netlink.FAMILY_V4)
|
|
require.NoError(t, err, "RouteList")
|
|
for _, r := range routes {
|
|
if ones, _ := r.Dst.Mask.Size(); ones == 0 && r.Gw.Equal(gw) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// newDownLink creates a dummy link and deliberately leaves it administratively
|
|
// down, the starting state for the AddAddress tests below.
|
|
func newDownLink(t testing.TB, h *netlink.Handle, name string) netlink.Link {
|
|
t.Helper()
|
|
link := netlink.Link(&netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: name}})
|
|
require.NoError(t, h.LinkAdd(link), "LinkAdd")
|
|
require.False(t, adminUp(t, h, name), "%s should start down", name)
|
|
return link
|
|
}
|
|
|
|
// newPingerConfigurator builds a configurator whose ping settings are short
|
|
// enough that the unanswerable probes against a dummy link do not stall the
|
|
// test for the twenty second default.
|
|
func newPingerConfigurator(testAddress string) *linuxConfigurator {
|
|
c := &linuxConfigurator{configOptions: &configOptions{
|
|
testAddress: testAddress,
|
|
pingCount: 1,
|
|
pingTimeout: 100 * time.Millisecond,
|
|
}}
|
|
c.ifaceBackends = append(c.ifaceBackends, namedIfaceBackend{"fake", &fakeIfaceBackend{}})
|
|
return c
|
|
}
|
|
|
|
// TestLinuxAddAddressBringsLinkUp covers AddAddress against an administratively
|
|
// down interface. The kernel installs no connected route for an address on a
|
|
// down link, so the default route would be rejected as unreachable unless the
|
|
// link is raised first.
|
|
func TestLinuxAddAddressBringsLinkUp(t *testing.T) {
|
|
tearDown := setupNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
h, err := netlink.NewHandle()
|
|
require.NoError(t, err)
|
|
defer h.Close()
|
|
|
|
name := "test_enp1s0"
|
|
link := newDownLink(t, h, name)
|
|
addr := &net.IPNet{IP: net.ParseIP("1.2.3.4"), Mask: net.CIDRMask(24, 32)}
|
|
gw := net.ParseIP("1.2.3.1")
|
|
|
|
require.NoError(t, newPingerConfigurator("test_success").AddAddress(context.Background(), name, addr, gw), "AddAddress")
|
|
assert.True(t, adminUp(t, h, name), "interface should be up after AddAddress")
|
|
assert.True(t, hasDefaultRoute(t, h, link, gw), "default route via %s missing", gw)
|
|
present, _ := isPrimary(t, h, link, addr.IP)
|
|
assert.True(t, present, "address missing after AddAddress")
|
|
}
|
|
|
|
// TestLinuxAddAddressRestoresDownLink verifies that a link AddAddress raised is
|
|
// part of the pre-change state it restores: when the connectivity check fails,
|
|
// the interface goes back down along with the address and route rolled back.
|
|
// Its own namespace keeps the default route it adds from colliding with the one
|
|
// TestLinuxAddAddressBringsLinkUp installs, which the kernel refuses as EEXIST.
|
|
func TestLinuxAddAddressRestoresDownLink(t *testing.T) {
|
|
tearDown := setupNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
h, err := netlink.NewHandle()
|
|
require.NoError(t, err)
|
|
defer h.Close()
|
|
|
|
name := "test_enp1s0"
|
|
link := newDownLink(t, h, name)
|
|
addr := &net.IPNet{IP: net.ParseIP("1.2.3.4"), Mask: net.CIDRMask(24, 32)}
|
|
gw := net.ParseIP("1.2.3.1")
|
|
|
|
err = newPingerConfigurator("test_fail").AddAddress(context.Background(), name, addr, gw)
|
|
assert.EqualError(t, err, "aborted operation due to loss of internet")
|
|
assert.False(t, adminUp(t, h, name), "interface should be down again after rollback")
|
|
assert.False(t, hasDefaultRoute(t, h, link, gw), "default route should be rolled back")
|
|
present, _ := isPrimary(t, h, link, addr.IP)
|
|
assert.False(t, present, "address should be rolled back")
|
|
}
|
|
|
|
// TestLinuxRemovePrimaryRefused verifies that RemoveAddress refuses to remove
|
|
// the primary address of its family by default, and that WithAllowPrimaryRemoval
|
|
// overrides the refusal.
|
|
func TestLinuxRemovePrimaryRefused(t *testing.T) {
|
|
tearDown := setupNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
h, err := netlink.NewHandle()
|
|
require.NoError(t, err)
|
|
defer h.Close()
|
|
|
|
name := "test_enp1s0"
|
|
link := netlink.Link(&netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: name}})
|
|
require.NoError(t, h.LinkAdd(link))
|
|
require.NoError(t, h.LinkSetUp(link))
|
|
|
|
primaryIP := &net.IPNet{IP: net.ParseIP("1.2.3.4"), Mask: net.CIDRMask(24, 32)}
|
|
require.NoError(t, h.AddrAdd(link, &netlink.Addr{IPNet: primaryIP}))
|
|
|
|
c := &linuxConfigurator{configOptions: &configOptions{testAddress: "test_success"}}
|
|
c.ifaceBackends = append(c.ifaceBackends, namedIfaceBackend{"fake", &fakeIfaceBackend{}})
|
|
|
|
// Default: removing the primary is refused.
|
|
err = c.RemoveAddress(context.Background(), name, primaryIP)
|
|
assert.ErrorContains(t, err, "refusing to remove primary address")
|
|
|
|
// With the override enabled, removal succeeds.
|
|
c.allowPrimaryRemoval = true
|
|
assert.NoError(t, c.RemoveAddress(context.Background(), name, primaryIP), "RemoveAddress with allowPrimaryRemoval")
|
|
}
|
|
|
|
func TestLinuxConfigurator(t *testing.T) {
|
|
tearDown := setupNetlinkTest(t)
|
|
defer tearDown()
|
|
|
|
// Connect to netlink.
|
|
h, err := netlink.NewHandle()
|
|
require.NoError(t, err)
|
|
defer h.Close()
|
|
|
|
// Setup test link.
|
|
eth0Name := "test_enp1s0"
|
|
eth0Link := netlink.Link(&netlink.Dummy{LinkAttrs: netlink.LinkAttrs{
|
|
Name: eth0Name,
|
|
HardwareAddr: net.HardwareAddr{0x52, 0x54, 0x00, 0x8b, 0x0d, 0x93},
|
|
}})
|
|
err = h.LinkAdd(eth0Link)
|
|
require.NoError(t, err)
|
|
|
|
// Set test link up.
|
|
err = h.LinkSetUp(eth0Link)
|
|
require.NoError(t, err)
|
|
|
|
// Add test IP.
|
|
testIP1 := &net.IPNet{
|
|
IP: net.ParseIP("1.2.3.4"),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
err = h.AddrAdd(eth0Link, &netlink.Addr{IPNet: testIP1})
|
|
require.NoError(t, err)
|
|
|
|
// Add default route.
|
|
testGW1 := net.ParseIP("1.2.3.1")
|
|
defaultRoute := &netlink.Route{
|
|
Family: netlink.FAMILY_V4,
|
|
LinkIndex: eth0Link.Attrs().Index,
|
|
Dst: &net.IPNet{
|
|
IP: net.IPv4zero,
|
|
Mask: net.CIDRMask(0, 32),
|
|
},
|
|
Gw: testGW1,
|
|
}
|
|
err = h.RouteAdd(defaultRoute)
|
|
require.NoError(t, err)
|
|
|
|
// Setup test link.
|
|
eth1Name := "test_enp2s0"
|
|
eth1Link := &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{
|
|
Name: eth1Name,
|
|
HardwareAddr: net.HardwareAddr{0x52, 0x54, 0x00, 0x8b, 0xad, 0x93},
|
|
}}
|
|
err = h.LinkAdd(eth1Link)
|
|
require.NoError(t, err)
|
|
|
|
// Set test link up.
|
|
err = h.LinkSetUp(eth1Link)
|
|
require.NoError(t, err)
|
|
|
|
// Add test IP.
|
|
testIP2 := &net.IPNet{
|
|
IP: net.ParseIP("fc00:5aa8:7160:d9eb:1:0:1:3"),
|
|
Mask: net.CIDRMask(64, 128),
|
|
}
|
|
err = h.AddrAdd(eth1Link, &netlink.Addr{IPNet: testIP2})
|
|
require.NoError(t, err)
|
|
|
|
// Add default route.
|
|
testGW2 := net.ParseIP("fe80::1")
|
|
ipv6Dest := &net.IPNet{
|
|
IP: net.ParseIP("fc00:5aa8:7160:d9eb::1"),
|
|
Mask: net.CIDRMask(128, 128),
|
|
}
|
|
ipv6Route := &netlink.Route{
|
|
Family: netlink.FAMILY_V6,
|
|
LinkIndex: eth1Link.Attrs().Index,
|
|
Dst: ipv6Dest,
|
|
Gw: testGW2,
|
|
Priority: 200,
|
|
}
|
|
err = h.RouteAdd(ipv6Route)
|
|
require.NoError(t, err)
|
|
|
|
// Setup configurator with test URL to test server.
|
|
c := &linuxConfigurator{configOptions: &configOptions{}}
|
|
c.testAddress = "test_success"
|
|
// The test intentionally removes addresses that are primary at runtime;
|
|
// enable the override so the removal calls succeed.
|
|
c.allowPrimaryRemoval = true
|
|
|
|
// Setup test results path.
|
|
testDir, err := filepath.Abs("./tests/configurator_linux")
|
|
require.NoError(t, err)
|
|
resultsDir := filepath.Join(testDir, "results")
|
|
tmpDir, err := os.MkdirTemp("", "")
|
|
require.NoError(t, err)
|
|
configPath := filepath.Join(tmpDir, "50-cloud-init.yaml")
|
|
err = fileCopy(filepath.Join(testDir, "50-cloud-init.yaml"), configPath)
|
|
require.NoError(t, err)
|
|
|
|
// Setup ifupdown and parse test file.
|
|
ci, err := newCloudInitWith(configPath, filepath.Join(tmpDir, "nothing.json"), filepath.Join(tmpDir, "cloud.cfg.d"))
|
|
require.NoError(t, err)
|
|
c.ifaceBackends = append(c.ifaceBackends, namedIfaceBackend{"cloud-init", ci})
|
|
|
|
// Get list of interfaces.
|
|
interfaces, err := c.GetInterfaces(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify interfaces read from file.
|
|
err = testVerifyInterfaces(interfaces, resultsDir, 1)
|
|
require.NoError(t, err)
|
|
|
|
// Test add addresses.
|
|
testIP3 := &net.IPNet{IP: net.ParseIP("1.2.3.5"), Mask: net.CIDRMask(24, 32)}
|
|
err = c.AddAddress(context.Background(), eth0Name, testIP3, nil)
|
|
assert.NoError(t, err)
|
|
testIP4 := &net.IPNet{IP: net.ParseIP("fc00:5aa8:7160:d9eb:1:0:1:5"), Mask: net.CIDRMask(64, 128)}
|
|
err = c.AddAddress(context.Background(), eth1Name, testIP4, net.ParseIP("fc00:5aa8:7160:d9eb::"))
|
|
assert.NoError(t, err)
|
|
|
|
// Get list of interfaces.
|
|
interfaces, err = c.GetInterfaces(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify interfaces read from file.
|
|
err = testVerifyInterfaces(interfaces, resultsDir, 2)
|
|
require.NoError(t, err)
|
|
|
|
// Read the current file and expected state.
|
|
err = testVerifyResults(resultsDir, tmpDir, 1)
|
|
require.NoError(t, err)
|
|
|
|
// Test remove addresses.
|
|
err = c.RemoveAddress(context.Background(), eth0Name, testIP1)
|
|
assert.NoError(t, err)
|
|
err = c.RemoveAddress(context.Background(), eth1Name, testIP2)
|
|
assert.NoError(t, err)
|
|
|
|
// Get list of interfaces.
|
|
interfaces, err = c.GetInterfaces(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify interfaces read from file.
|
|
err = testVerifyInterfaces(interfaces, resultsDir, 3)
|
|
require.NoError(t, err)
|
|
|
|
// Read the current file and expected state.
|
|
err = testVerifyResults(resultsDir, tmpDir, 2)
|
|
require.NoError(t, err)
|
|
|
|
// Test rollback on failures.
|
|
c.testAddress = "test_fail"
|
|
err = c.AddAddress(context.Background(), eth0Name, testIP1, nil)
|
|
assert.EqualError(t, err, "aborted operation due to loss of internet", "Failed rollback test")
|
|
|
|
// Get list of interfaces.
|
|
interfaces, err = c.GetInterfaces(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify interfaces read from file.
|
|
err = testVerifyInterfaces(interfaces, resultsDir, 3)
|
|
require.NoError(t, err)
|
|
|
|
// Read the current file and expected state.
|
|
err = testVerifyResults(resultsDir, tmpDir, 2)
|
|
require.NoError(t, err)
|
|
|
|
// Test remove gateway.
|
|
err = c.AddAddress(context.Background(), eth0Name, testIP3, make(net.IP, 4))
|
|
assert.NoError(t, err)
|
|
|
|
// Get list of interfaces.
|
|
interfaces, err = c.GetInterfaces(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify interfaces read from file.
|
|
err = testVerifyInterfaces(interfaces, resultsDir, 4)
|
|
require.NoError(t, err)
|
|
|
|
// Read the current file and expected state.
|
|
err = testVerifyResults(resultsDir, tmpDir, 3)
|
|
require.NoError(t, err)
|
|
|
|
// Test add gateway rollback.
|
|
err = c.AddAddress(context.Background(), eth0Name, testIP3, testGW1)
|
|
assert.EqualError(t, err, "aborted operation due to loss of internet", "Failed rollback test")
|
|
|
|
// Get list of interfaces.
|
|
interfaces, err = c.GetInterfaces(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify interfaces read from file.
|
|
err = testVerifyInterfaces(interfaces, resultsDir, 4)
|
|
require.NoError(t, err)
|
|
|
|
// Read the current file and expected state.
|
|
err = testVerifyResults(resultsDir, tmpDir, 3)
|
|
require.NoError(t, err)
|
|
|
|
// Test remove gateway.
|
|
c.testAddress = "test_success"
|
|
err = c.AddAddress(context.Background(), eth0Name, testIP3, testGW1)
|
|
assert.NoError(t, err)
|
|
|
|
// Get list of interfaces.
|
|
interfaces, err = c.GetInterfaces(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify interfaces read from file.
|
|
err = testVerifyInterfaces(interfaces, resultsDir, 3)
|
|
require.NoError(t, err)
|
|
|
|
// Read the current file and expected state.
|
|
err = testVerifyResults(resultsDir, tmpDir, 2)
|
|
require.NoError(t, err)
|
|
|
|
// Test removing route.
|
|
err = c.RemoveRoute(context.Background(), eth1Name, ipv6Dest, testGW2)
|
|
assert.NoError(t, err)
|
|
|
|
// Get list of interfaces.
|
|
interfaces, err = c.GetInterfaces(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify interfaces read from file.
|
|
err = testVerifyInterfaces(interfaces, resultsDir, 5)
|
|
require.NoError(t, err)
|
|
|
|
// Read the current file and expected state.
|
|
err = testVerifyResults(resultsDir, tmpDir, 4)
|
|
require.NoError(t, err)
|
|
|
|
// Test add route.
|
|
ipv4Dest := &net.IPNet{
|
|
IP: net.ParseIP("10.0.10.0"),
|
|
Mask: net.CIDRMask(24, 32),
|
|
}
|
|
testGW3 := net.ParseIP("1.2.3.254")
|
|
err = c.AddRoute(context.Background(), eth0Name, ipv4Dest, testGW3, 100)
|
|
assert.NoError(t, err)
|
|
|
|
// Get list of interfaces.
|
|
interfaces, err = c.GetInterfaces(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify interfaces read from file.
|
|
err = testVerifyInterfaces(interfaces, resultsDir, 6)
|
|
require.NoError(t, err)
|
|
|
|
// Read the current file and expected state.
|
|
err = testVerifyResults(resultsDir, tmpDir, 5)
|
|
require.NoError(t, err)
|
|
}
|