Files
moby/libnetwork/drivers/bridge/interface_linux_test.go
Rob Murray 141cb65e51 Check, then assume an IPv6 bridge has a subnet.
If IPv6 is enabled for a bridge network, by the time configuration
is applied, the bridge will always have an address. Assert that, by
raising an error when the configuration is validated.

Use that to simplify the logic used to calculate which addresses
should be assigned to a bridge. Also remove a redundant check in
setupGatewayIPv6() and the error associated with it.

Fix unit tests that enabled IPv6, but didn't supply an IPv6 IPAM
address/pool. Before this change, these tests passed but silently
left the bridge without an IPv6 address.

(The daemon already ensured there was an IPv6 address, this change
does not add a new restriction on config at that level.)

Signed-off-by: Rob Murray <rob.murray@docker.com>
2023-12-21 15:26:34 +00:00

189 lines
5.3 KiB
Go

package bridge
import (
"net"
"net/netip"
"strings"
"testing"
"github.com/docker/docker/internal/testutils/netnsutils"
"github.com/google/go-cmp/cmp"
"github.com/vishvananda/netlink"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func cidrToIPNet(t *testing.T, cidr string) *net.IPNet {
t.Helper()
ip, ipNet, err := net.ParseCIDR(cidr)
assert.Assert(t, is.Nil(err))
return &net.IPNet{IP: ip, Mask: ipNet.Mask}
}
func addAddr(t *testing.T, link netlink.Link, addr string) {
t.Helper()
ipNet := cidrToIPNet(t, addr)
err := netlink.AddrAdd(link, &netlink.Addr{IPNet: ipNet})
assert.Assert(t, is.Nil(err))
}
func prepTestBridge(t *testing.T, nc *networkConfiguration) *bridgeInterface {
t.Helper()
nh, err := netlink.NewHandle()
assert.Assert(t, err)
i, err := newInterface(nh, nc)
assert.Assert(t, err)
err = setupDevice(nc, i)
assert.Assert(t, err)
return i
}
func TestInterfaceDefaultName(t *testing.T) {
defer netnsutils.SetupTestOSContext(t)()
nh, err := netlink.NewHandle()
if err != nil {
t.Fatal(err)
}
config := &networkConfiguration{}
_, err = newInterface(nh, config)
assert.Check(t, err)
assert.Equal(t, config.BridgeName, DefaultBridgeName)
}
func TestAddressesNoInterface(t *testing.T) {
i := bridgeInterface{}
addrs, err := i.addresses(netlink.FAMILY_V6)
assert.NilError(t, err)
assert.Check(t, is.Len(addrs, 0))
}
func TestAddressesEmptyInterface(t *testing.T) {
defer netnsutils.SetupTestOSContext(t)()
nh, err := netlink.NewHandle()
assert.NilError(t, err)
inf, err := newInterface(nh, &networkConfiguration{})
assert.NilError(t, err)
addrsv4, err := inf.addresses(netlink.FAMILY_V4)
assert.NilError(t, err)
assert.Check(t, is.Len(addrsv4, 0))
addrsv6, err := inf.addresses(netlink.FAMILY_V6)
assert.NilError(t, err)
assert.Check(t, is.Len(addrsv6, 0))
}
func TestAddressesNonEmptyInterface(t *testing.T) {
defer netnsutils.SetupTestOSContext(t)()
i := prepTestBridge(t, &networkConfiguration{})
const expAddrV4, expAddrV6 = "192.168.1.2/24", "fd00:1234::/64"
addAddr(t, i.Link, expAddrV4)
addAddr(t, i.Link, expAddrV6)
addrs, err := i.addresses(netlink.FAMILY_V4)
assert.NilError(t, err)
assert.Check(t, is.Len(addrs, 1))
assert.Equal(t, addrs[0].IPNet.String(), expAddrV4)
addrs, err = i.addresses(netlink.FAMILY_V6)
assert.NilError(t, err)
assert.Check(t, is.Len(addrs, 1))
assert.Equal(t, addrs[0].IPNet.String(), expAddrV6)
}
func TestGetRequiredIPv6Addrs(t *testing.T) {
testcases := []struct {
name string
addressIPv6 string
expReqdAddrs []string
}{
{
name: "Regular address, expect default link local",
addressIPv6: "2000:3000::1/80",
expReqdAddrs: []string{"fe80::1/64", "2000:3000::1/80"},
},
{
name: "Standard link local address only",
addressIPv6: "fe80::1/64",
expReqdAddrs: []string{"fe80::1/64"},
},
{
name: "Nonstandard link local address",
addressIPv6: "fe80:abcd::1/42",
expReqdAddrs: []string{"fe80:abcd::1/42", "fe80::1/64"},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
config := &networkConfiguration{
AddressIPv6: cidrToIPNet(t, tc.addressIPv6),
}
expResult := map[netip.Addr]netip.Prefix{}
for _, addr := range tc.expReqdAddrs {
expResult[netip.MustParseAddr(strings.Split(addr, "/")[0])] = netip.MustParsePrefix(addr)
}
reqd, err := getRequiredIPv6Addrs(config)
assert.Check(t, is.Nil(err))
assert.Check(t, is.DeepEqual(reqd, expResult,
cmp.Comparer(func(a, b netip.Prefix) bool { return a == b })))
})
}
}
func TestProgramIPv6Addresses(t *testing.T) {
defer netnsutils.SetupTestOSContext(t)()
checkAddrs := func(i *bridgeInterface, nc *networkConfiguration, expAddrs []string) {
t.Helper()
exp := []netlink.Addr{}
for _, a := range expAddrs {
ipNet := cidrToIPNet(t, a)
exp = append(exp, netlink.Addr{IPNet: ipNet})
}
actual, err := i.addresses(netlink.FAMILY_V6)
assert.NilError(t, err)
assert.DeepEqual(t, exp, actual)
assert.Check(t, is.DeepEqual(i.bridgeIPv6, nc.AddressIPv6))
assert.Check(t, is.DeepEqual(i.gatewayIPv6, nc.AddressIPv6.IP))
}
nc := &networkConfiguration{}
i := prepTestBridge(t, nc)
// The bridge has no addresses, ask for a regular IPv6 network and expect it to
// be added to the bridge, with the default link local address.
nc.AddressIPv6 = cidrToIPNet(t, "2000:3000::1/64")
err := i.programIPv6Addresses(nc)
assert.NilError(t, err)
checkAddrs(i, nc, []string{"2000:3000::1/64", "fe80::1/64"})
// Shrink the subnet of that regular address, the prefix length of the address
// will not be modified - but it's informational-only, the address itself has
// not changed.
nc.AddressIPv6 = cidrToIPNet(t, "2000:3000::1/80")
err = i.programIPv6Addresses(nc)
assert.NilError(t, err)
checkAddrs(i, nc, []string{"2000:3000::1/64", "fe80::1/64"})
// Ask for link-local only, by specifying an address with the Link Local prefix.
// The regular address should be removed.
nc.AddressIPv6 = cidrToIPNet(t, "fe80::1/64")
err = i.programIPv6Addresses(nc)
assert.NilError(t, err)
checkAddrs(i, nc, []string{"fe80::1/64"})
// Swap the standard link local address for a nonstandard one.
nc.AddressIPv6 = cidrToIPNet(t, "fe80:5555::1/55")
err = i.programIPv6Addresses(nc)
assert.NilError(t, err)
checkAddrs(i, nc, []string{"fe80:5555::1/55", "fe80::1/64"})
}