Use default ULA prefix if fixed-cidr-v6 is not specified

Use the same logic to generate IPAMConf for IPv6 as for IPv4.

- When no fixed-cidr-v6 is specified, rather than error out, use
  the default address pools (as for an IPv4 default bridge with no
  fixed-cidr, and as for user-defined networks).
- Add daemon option --bip6, similar to --bip.
  - Necessary because it's the only way to override an old address
    on docker0 (daemon-managed default bridge), as illustrated by
    test cases.
- For a user-managed default bridge (--bridge), use IPv6 addresses
  on the user's bridge to determine the pool, sub-pool and gateway.
  Following the same rules as IPv4.
- Don't set up IPv6 IPAMConf if IPv6 is not enabled.

Signed-off-by: Rob Murray <rob.murray@docker.com>
This commit is contained in:
Rob Murray
2024-08-10 20:03:21 +01:00
parent fdd2591cbe
commit 0b5b1db1c1
7 changed files with 277 additions and 182 deletions

View File

@@ -31,7 +31,8 @@ func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
flags.BoolVar(&conf.BridgeConfig.DisableFilterForwardDrop, "ip-forward-no-drop", false, "Do not set the filter-FORWARD policy to DROP when enabling IP forwarding")
flags.BoolVar(&conf.BridgeConfig.EnableIPMasq, "ip-masq", true, "Enable IP masquerading")
flags.BoolVar(&conf.BridgeConfig.EnableIPv6, "ipv6", false, "Enable IPv6 networking")
flags.StringVar(&conf.BridgeConfig.IP, "bip", "", "Specify network bridge IP")
flags.StringVar(&conf.BridgeConfig.IP, "bip", "", "Specify default-bridge IPv4 network")
flags.StringVar(&conf.BridgeConfig.IP6, "bip6", "", "Specify default-bridge IPv6 network")
flags.StringVarP(&conf.BridgeConfig.Iface, "bridge", "b", "", "Attach containers to a network bridge")
flags.StringVar(&conf.BridgeConfig.FixedCIDR, "fixed-cidr", "", "IPv4 subnet for fixed IPs")
flags.StringVar(&conf.BridgeConfig.FixedCIDRv6, "fixed-cidr-v6", "", "IPv6 subnet for fixed IPs")

View File

@@ -33,7 +33,7 @@ func TestLoadDaemonCliConfigWithDaemonFlags(t *testing.T) {
}
func TestLoadDaemonConfigWithNetwork(t *testing.T) {
content := `{"bip": "127.0.0.2", "ip": "127.0.0.1"}`
content := `{"bip": "127.0.0.2/8", "bip6": "fd98:e5f2:e637::1/64", "ip": "127.0.0.1"}`
tempFile := fs.NewFile(t, "config", fs.WithContent(content))
defer tempFile.Remove()
@@ -42,8 +42,9 @@ func TestLoadDaemonConfigWithNetwork(t *testing.T) {
assert.NilError(t, err)
assert.Assert(t, loadedConfig != nil)
assert.Check(t, is.Equal("127.0.0.2", loadedConfig.IP))
assert.Check(t, is.Equal("127.0.0.1", loadedConfig.DefaultIP.String()))
assert.Check(t, is.Equal(loadedConfig.IP, "127.0.0.2/8"))
assert.Check(t, is.Equal(loadedConfig.IP6, "fd98:e5f2:e637::1/64"))
assert.Check(t, is.Equal(loadedConfig.DefaultIP.String(), "127.0.0.1"))
}
func TestLoadDaemonConfigWithMapOptions(t *testing.T) {

View File

@@ -60,6 +60,7 @@ type DefaultBridgeConfig struct {
MTU int `json:"mtu,omitempty"`
DefaultIP net.IP `json:"ip,omitempty"`
IP string `json:"bip,omitempty"`
IP6 string `json:"bip6,omitempty"`
DefaultGatewayIPv4 net.IP `json:"default-gateway,omitempty"`
DefaultGatewayIPv6 net.IP `json:"default-gateway-v6,omitempty"`
InterContainerCommunication bool `json:"icc,omitempty"`

View File

@@ -737,6 +737,9 @@ func verifyDaemonSettings(conf *config.Config) error {
if conf.BridgeConfig.Iface != "" && conf.BridgeConfig.IP != "" {
return fmt.Errorf("You specified -b & --bip, mutually exclusive options. Please specify only one")
}
if conf.BridgeConfig.Iface != "" && conf.BridgeConfig.IP6 != "" {
return fmt.Errorf("You specified -b & --bip6, mutually exclusive options. Please specify only one")
}
if !conf.BridgeConfig.InterContainerCommunication {
if !conf.BridgeConfig.EnableIPTables {
return fmt.Errorf("You specified --iptables=false with --icc=false. ICC=false uses iptables to function. Please set --icc or --iptables to true")
@@ -926,6 +929,47 @@ func driverOptions(config *config.Config) nwconfig.Option {
})
}
type defBrOptsV4 struct {
cfg config.BridgeConfig
}
func (o defBrOptsV4) nlFamily() int {
return netlink.FAMILY_V4
}
func (o defBrOptsV4) fixedCIDR() (fCIDR, optName string) {
return o.cfg.FixedCIDR, "fixed-cidr"
}
func (o defBrOptsV4) bip() (bip, optName string) {
return o.cfg.IP, "bip"
}
func (o defBrOptsV4) defGw() (gw net.IP, optName, auxAddrLabel string) {
return o.cfg.DefaultGatewayIPv4, "default-gateway", "DefaultGatewayIPv4"
}
type defBrOptsV6 struct {
cfg config.BridgeConfig
}
func (o defBrOptsV6) nlFamily() int {
return netlink.FAMILY_V6
}
func (o defBrOptsV6) fixedCIDR() (fCIDR, optName string) {
return o.cfg.FixedCIDRv6, "fixed-cidr-v6"
}
func (o defBrOptsV6) bip() (bip, optName string) {
return o.cfg.IP6, "bip6"
}
func (o defBrOptsV6) defGw() (gw net.IP, optName, auxAddrLabel string) {
return o.cfg.DefaultGatewayIPv6, "default-gateway-v6", "DefaultGatewayIPv6"
}
type defBrOpts interface {
nlFamily() int
fixedCIDR() (fCIDR, optName string)
bip() (bip, optName string)
defGw() (gw net.IP, optName, auxAddrLabel string)
}
func initBridgeDriver(controller *libnetwork.Controller, cfg config.BridgeConfig) error {
bridgeName, userManagedBridge := getDefaultBridgeName(cfg)
netOption := map[string]string{
@@ -940,70 +984,43 @@ func initBridgeDriver(controller *libnetwork.Controller, cfg config.BridgeConfig
netOption[bridge.DefaultBindingIP] = cfg.DefaultIP.String()
}
ipamV4Conf, err := getDefaultBridgeIPAMConf(bridgeName, userManagedBridge, cfg, netlink.FAMILY_V4)
ipamV4Conf, err := getDefaultBridgeIPAMConf(bridgeName, userManagedBridge, defBrOptsV4{cfg})
if err != nil {
return err
}
var (
deferIPv6Alloc bool
ipamV6Conf *libnetwork.IpamConf
)
if cfg.EnableIPv6 && cfg.FixedCIDRv6 == "" {
return errdefs.InvalidParameter(errors.New("IPv6 is enabled for the default bridge, but no subnet is configured. Specify an IPv6 subnet using --fixed-cidr-v6"))
} else if cfg.FixedCIDRv6 != "" {
_, fCIDRv6, err := net.ParseCIDR(cfg.FixedCIDRv6)
var deferIPv6Alloc bool
var ipamV6Conf []*libnetwork.IpamConf
if cfg.EnableIPv6 {
ipamV6Conf, err = getDefaultBridgeIPAMConf(bridgeName, userManagedBridge, defBrOptsV6{cfg})
if err != nil {
return err
}
// In case user has specified the daemon flag --fixed-cidr-v6 and the passed network has
// at least 48 host bits, we need to guarantee the current behavior where the containers'
// IPv6 addresses will be constructed based on the containers' interface MAC address.
// We do so by telling libnetwork to defer the IPv6 address allocation for the endpoints
// on this network until after the driver has created the endpoint and returned the
// constructed address. Libnetwork will then reserve this address with the ipam driver.
ones, _ := fCIDRv6.Mask.Size()
deferIPv6Alloc = ones <= 80
ipamV6Conf = &libnetwork.IpamConf{
AuxAddresses: make(map[string]string),
PreferredPool: fCIDRv6.String(),
}
// In case the --fixed-cidr-v6 is specified and the current docker0 bridge IPv6
// address belongs to the same network, we need to inform libnetwork about it, so
// that it can be reserved with IPAM and it will not be given away to somebody else
nw6List, err := ifaceAddrs(bridgeName, netlink.FAMILY_V6)
if err != nil {
return errors.Wrap(err, "list bridge IPv6 addresses failed")
}
for _, nw6 := range nw6List {
if fCIDRv6.Contains(nw6.IP) {
ipamV6Conf.Gateway = nw6.IP.String()
break
// If the subnet has at least 48 host bits, preserve the legacy default bridge
// behaviour of constructing a MAC address from the IPv4 address, then
// constructing an IPv6 addresses based on that MAC address. Tell libnetwork to
// defer the IPv6 address allocation for endpoints on this network until after
// the driver has created the endpoint and proposed an IPv4 address. Libnetwork
// will then reserve this address with the ipam driver. If no preferred pool has
// been set the built-in ULA prefix will be used, assume it has at-least 48-bits.
if len(ipamV6Conf) == 0 || ipamV6Conf[0].PreferredPool == "" {
deferIPv6Alloc = true
} else {
_, ppNet, err := net.ParseCIDR(ipamV6Conf[0].PreferredPool)
if err != nil {
return err
}
ones, _ := ppNet.Mask.Size()
deferIPv6Alloc = ones <= 80
}
}
if cfg.DefaultGatewayIPv6 != nil {
if ipamV6Conf == nil {
ipamV6Conf = &libnetwork.IpamConf{AuxAddresses: make(map[string]string)}
}
ipamV6Conf.AuxAddresses["DefaultGatewayIPv6"] = cfg.DefaultGatewayIPv6.String()
}
v6Conf := []*libnetwork.IpamConf{}
if ipamV6Conf != nil {
v6Conf = append(v6Conf, ipamV6Conf)
}
// Initialize default network on "bridge" with the same name
_, err = controller.NewNetwork("bridge", network.NetworkBridge, "",
libnetwork.NetworkOptionEnableIPv4(true),
libnetwork.NetworkOptionEnableIPv6(cfg.EnableIPv6),
libnetwork.NetworkOptionDriverOpts(netOption),
libnetwork.NetworkOptionIpam("default", "", ipamV4Conf, v6Conf, nil),
libnetwork.NetworkOptionIpam("default", "", ipamV4Conf, ipamV6Conf, nil),
libnetwork.NetworkOptionDeferIPv6Alloc(deferIPv6Alloc))
if err != nil {
return fmt.Errorf(`error creating default %q network: %v`, network.NetworkBridge, err)
@@ -1084,8 +1101,7 @@ func getDefaultBridgeName(cfg config.BridgeConfig) (bridgeName string, userManag
func getDefaultBridgeIPAMConf(
bridgeName string,
userManagedBridge bool,
cfg config.BridgeConfig,
family int,
opts defBrOpts,
) ([]*libnetwork.IpamConf, error) {
var (
fCidrIP, bIP net.IP
@@ -1093,18 +1109,18 @@ func getDefaultBridgeIPAMConf(
err error
)
if cfg.FixedCIDR != "" {
if fCidrIP, fCidrIPNet, err = net.ParseCIDR(cfg.FixedCIDR); err != nil {
return nil, errors.Wrap(err, "parse fixed-cidr failed")
if fixedCIDR, fixedCIDROpt := opts.fixedCIDR(); fixedCIDR != "" {
if fCidrIP, fCidrIPNet, err = net.ParseCIDR(fixedCIDR); err != nil {
return nil, errors.Wrap(err, "parse "+fixedCIDROpt+" failed")
}
}
if cfg.IP != "" {
if bIP, bIPNet, err = net.ParseCIDR(cfg.IP); err != nil {
return nil, err
if cfgBIP, cfgBIPOpt := opts.bip(); cfgBIP != "" {
if bIP, bIPNet, err = net.ParseCIDR(cfgBIP); err != nil {
return nil, errors.Wrap(err, "parse "+cfgBIPOpt+" failed")
}
} else {
if bIP, bIPNet, err = selectBIP(userManagedBridge, bridgeName, family, fCidrIP, fCidrIPNet); err != nil {
if bIP, bIPNet, err = selectBIP(userManagedBridge, bridgeName, opts.nlFamily(), fCidrIP, fCidrIPNet); err != nil {
return nil, err
}
}
@@ -1114,7 +1130,8 @@ func getDefaultBridgeIPAMConf(
ipamConf.PreferredPool = bIPNet.String()
ipamConf.Gateway = bIP.String()
} else if !userManagedBridge && ipamConf.PreferredPool != "" {
log.G(context.TODO()).Infof("Default bridge (%s) is assigned with an IP address %s. Daemon option --bip can be used to set a preferred IP address", bridgeName, ipamConf.PreferredPool)
_, bipOptName := opts.bip()
log.G(context.TODO()).Infof("Default bridge (%s) is assigned with an IP address %s. Daemon option --"+bipOptName+" can be used to set a preferred IP address", bridgeName, ipamConf.PreferredPool)
}
if fCidrIP != nil && fCidrIPNet != nil {
@@ -1128,21 +1145,27 @@ func getDefaultBridgeIPAMConf(
// Don't allow SubPool (the range of allocatable addresses) to be outside, or
// bigger than, the network itself. This is a configuration error, either the
// user-managed bridge is missing an address to match fixed-cidr, or fixed-cidr
// is wrong. But, just log rather than raise an error that would cause daemon
// is wrong.
fixedCIDR, fixedCIDROpt := opts.fixedCIDR()
if opts.nlFamily() == netlink.FAMILY_V6 {
return nil, fmt.Errorf("%s=%s is outside any subnet implied by addresses on the user-managed default bridge",
fixedCIDROpt, fixedCIDR)
}
// For IPv4, just log rather than raise an error that would cause daemon
// startup to fail, because this has been allowed by earlier versions. Remove
// the SubPool, so that addresses are allocated from the whole of PreferredPool.
log.G(context.TODO()).WithFields(log.Fields{
"bridge": bridgeName,
"fixed-cidr": cfg.FixedCIDR,
fixedCIDROpt: fixedCIDR,
"bridge-network": bIPNet.String(),
}).Warn("fixed-cidr is outside the subnet implied by addresses on the user-managed default bridge, this may be treated as an error in a future release")
}).Warn(fixedCIDROpt + " is outside any subnet implied by addresses on the user-managed default bridge, this may be treated as an error in a future release")
ipamConf.SubPool = ""
}
}
}
if cfg.DefaultGatewayIPv4 != nil {
ipamConf.AuxAddresses["DefaultGatewayIPv4"] = cfg.DefaultGatewayIPv4.String()
if defGw, _, auxAddrLabel := opts.defGw(); defGw != nil {
ipamConf.AuxAddresses[auxAddrLabel] = defGw.String()
}
return []*libnetwork.IpamConf{ipamConf}, nil

View File

@@ -44,39 +44,55 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) {
testcases := []defaultBridgeIPAMTestCase{
{
name: "fixed-cidr only",
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/24"},
name: "no config",
// No config for the bridge, but override default-address-pools to
// get a predictable result for IPv6 (rather than the daemon's ULA).
daemonArgs: []string{
"--default-address-pool", `base=192.168.176.0/20,size=24`,
"--default-address-pool", `base=fdd1:8161:2d2c::/56,size=64`,
},
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/24",
IPRange: "192.168.176.0/24",
},
{Subnet: "192.168.176.0/24", Gateway: "192.168.176.1"},
{Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::1/64"},
},
},
{
name: "bip only",
daemonArgs: []string{"--bip", "192.168.176.88/24"},
name: "fixed-cidr only",
daemonArgs: []string{
"--fixed-cidr", "192.168.176.0/24",
"--fixed-cidr-v6", "fdd1:8161:2d2c::/64",
},
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/24",
Gateway: "192.168.176.88",
},
{Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24"},
{Subnet: "fdd1:8161:2d2c::/64", IPRange: "fdd1:8161:2d2c::/64"},
},
},
{
name: "bip only",
daemonArgs: []string{
"--bip", "192.168.176.88/24",
"--bip6", "fdd1:8161:2d2c::8888/64",
},
expIPAMConfig: []network.IPAMConfig{
{Subnet: "192.168.176.0/24", Gateway: "192.168.176.88"},
{Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"},
},
},
{
name: "existing bridge address only",
initialBridgeAddrs: []string{"192.168.176.88/24"},
initialBridgeAddrs: []string{"192.168.176.88/24", "fdd1:8161:2d2c::8888/64"},
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/24",
Gateway: "192.168.176.88",
},
{Subnet: "192.168.176.0/24", Gateway: "192.168.176.88"},
{Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"},
},
},
{
name: "fixed-cidr within old bridge subnet",
initialBridgeAddrs: []string{"192.168.176.88/20"},
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/24"},
initialBridgeAddrs: []string{"192.168.176.88/20", "fdd1:8161:2d2c::8888/56"},
daemonArgs: []string{
"--fixed-cidr", "192.168.176.0/24",
"--fixed-cidr-v6", "fdd1:8161:2d2c::/64",
},
// There's no --bip to dictate the subnet, so it's derived from an
// existing bridge address. If fixed-cidr's subnet is made smaller
// following a daemon restart, a user might reasonably expect the
@@ -89,67 +105,65 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) {
// and it'd be a breaking change for anyone relying on the existing
// behaviour.
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/20",
IPRange: "192.168.176.0/24",
Gateway: "192.168.176.88",
},
{Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"},
{Subnet: "fdd1:8161:2d2c::/56", IPRange: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"},
},
},
{
name: "fixed-cidr within old bridge subnet with new bip",
initialBridgeAddrs: []string{"192.168.176.88/20"},
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/24", "--bip", "192.168.176.99/24"},
initialBridgeAddrs: []string{"192.168.176.88/20", "fdd1:8161:2d2c::/56"},
daemonArgs: []string{
"--fixed-cidr", "192.168.176.0/24", "--bip", "192.168.176.99/24",
"--fixed-cidr-v6", "fdd1:8161:2d2c::/64", "--bip6", "fdd1:8161:2d2c::9999/64",
},
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/24",
IPRange: "192.168.176.0/24",
Gateway: "192.168.176.99",
},
{Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24", Gateway: "192.168.176.99"},
{Subnet: "fdd1:8161:2d2c::/64", IPRange: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::9999"},
},
},
{
name: "old bridge subnet within fixed-cidr",
initialBridgeAddrs: []string{"192.168.176.88/24"},
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/20"},
initialBridgeAddrs: []string{"192.168.176.88/24", "fdd1:8161:2d2c::8888/64"},
daemonArgs: []string{
"--fixed-cidr", "192.168.176.0/20",
"--fixed-cidr-v6", "fdd1:8161:2d2c::/56",
},
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/20",
IPRange: "192.168.176.0/20",
Gateway: "192.168.176.88",
},
{Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/20", Gateway: "192.168.176.88"},
{Subnet: "fdd1:8161:2d2c::/56", IPRange: "fdd1:8161:2d2c::/56", Gateway: "fdd1:8161:2d2c::8888"},
},
},
{
name: "old bridge subnet outside fixed-cidr",
initialBridgeAddrs: []string{"192.168.176.88/24"},
daemonArgs: []string{"--fixed-cidr", "192.168.177.0/24"},
initialBridgeAddrs: []string{"192.168.176.88/24", "fdd1:8161:2d2c::8888/64"},
daemonArgs: []string{
"--fixed-cidr", "192.168.177.0/24",
"--fixed-cidr-v6", "fdd1:8161:2d2c:1::/64",
},
// The bridge's address/subnet should be ignored, this is a change
// of fixed-cidr.
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.177.0/24",
IPRange: "192.168.177.0/24",
// No Gateway is configured, because the address could not be learnt from the
// bridge. An address will have been allocated but, because there's config (the
// fixed-cidr), inspect shows just the config. (Surprisingly, when there's no
// config at all, the inspect output still says its showing config but actually
// shows the running state.) When the daemon is restarted, after a gateway
// address has been assigned to the bridge, that address will become config - so
// a Gateway address will show up in the inspect output.
},
{Subnet: "192.168.177.0/24", IPRange: "192.168.177.0/24"},
{Subnet: "fdd1:8161:2d2c:1::/64", IPRange: "fdd1:8161:2d2c:1::/64"},
// No Gateway is configured, because the address could not be learnt from the
// bridge. An address will have been allocated but, because there's config (the
// fixed-cidr), inspect shows just the config. (Surprisingly, when there's no
// config at all, the inspect output still says its showing config but actually
// shows the running state.) When the daemon is restarted, after a gateway
// address has been assigned to the bridge, that address will become config - so
// a Gateway address will show up in the inspect output.
},
},
{
name: "old bridge subnet outside fixed-cidr with bip",
initialBridgeAddrs: []string{"192.168.176.88/24"},
daemonArgs: []string{"--fixed-cidr", "192.168.177.0/24", "--bip", "192.168.177.99/24"},
initialBridgeAddrs: []string{"192.168.176.88/24", "fdd1:8161:2d2c::8888/64"},
daemonArgs: []string{
"--fixed-cidr", "192.168.177.0/24", "--bip", "192.168.177.99/24",
"--fixed-cidr-v6", "fdd1:8161:2d2c:1::/64", "--bip6", "fdd1:8161:2d2c:1::9999/64",
},
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.177.0/24",
IPRange: "192.168.177.0/24",
Gateway: "192.168.177.99",
},
{Subnet: "192.168.177.0/24", IPRange: "192.168.177.0/24", Gateway: "192.168.177.99"},
{Subnet: "fdd1:8161:2d2c:1::/64", IPRange: "fdd1:8161:2d2c:1::/64", Gateway: "fdd1:8161:2d2c:1::9999"},
},
},
}
@@ -167,83 +181,80 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) {
testcases := []defaultBridgeIPAMTestCase{
{
name: "bridge only",
initialBridgeAddrs: []string{"192.168.176.88/20"},
initialBridgeAddrs: []string{"192.168.176.88/20", "fdd1:8161:2d2c::8888/64"},
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/20",
Gateway: "192.168.176.88",
},
{Subnet: "192.168.176.0/20", Gateway: "192.168.176.88"},
{Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"},
},
},
{
name: "fixed-cidr only",
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/24"},
name: "fixed-cidr only",
daemonArgs: []string{
"--fixed-cidr", "192.168.176.0/24",
"--fixed-cidr-v6", "fdd1:8161:2d2c::/64",
},
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/24",
IPRange: "192.168.176.0/24",
},
{Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24"},
{Subnet: "fdd1:8161:2d2c::/64", IPRange: "fdd1:8161:2d2c::/64"},
},
},
{
name: "fcidr in bridge subnet and bridge ip in fcidr",
initialBridgeAddrs: []string{"192.168.160.88/20", "192.168.176.88/20", "192.168.192.88/20"},
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/24"},
name: "fcidr in bridge subnet and bridge ip in fcidr",
initialBridgeAddrs: []string{
"192.168.160.88/20", "192.168.176.88/20", "192.168.192.88/20",
"fdd1:8161:2d2c::8888/60", "fdd1:8161:2d2c:10::8888/60", "fdd1:8161:2d2c:20::8888/60",
},
daemonArgs: []string{
"--fixed-cidr", "192.168.176.0/24",
"--fixed-cidr-v6", "fdd1:8161:2d2c:10::/64",
},
// Selected bip should be the one within fixed-cidr
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/20",
IPRange: "192.168.176.0/24",
Gateway: "192.168.176.88",
},
{Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"},
{Subnet: "fdd1:8161:2d2c:10::/60", IPRange: "fdd1:8161:2d2c:10::/64", Gateway: "fdd1:8161:2d2c:10::8888"},
},
},
{
name: "fcidr in bridge subnet and bridge ip not in fcidr",
initialBridgeAddrs: []string{"192.168.160.88/20", "192.168.176.88/20", "192.168.192.88/20"},
daemonArgs: []string{"--fixed-cidr", "192.168.177.0/24"},
name: "fcidr in bridge subnet and bridge ip not in fcidr",
initialBridgeAddrs: []string{
"192.168.160.88/20", "192.168.176.88/20", "192.168.192.88/20",
"fdd1:8161:2d2c::8888/60", "fdd1:8161:2d2c:10::8888/60", "fdd1:8161:2d2c:20::8888/60",
},
daemonArgs: []string{
"--fixed-cidr", "192.168.177.0/24",
"--fixed-cidr-v6", "fdd1:8161:2d2c:11::8888/64",
},
// Selected bridge subnet should be the one that encompasses fixed-cidr.
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/20",
IPRange: "192.168.177.0/24",
Gateway: "192.168.176.88",
},
{Subnet: "192.168.176.0/20", IPRange: "192.168.177.0/24", Gateway: "192.168.176.88"},
{Subnet: "fdd1:8161:2d2c:10::/60", IPRange: "fdd1:8161:2d2c:11::/64", Gateway: "fdd1:8161:2d2c:10::8888"},
},
},
{
name: "fixed-cidr bigger than bridge subnet",
initialBridgeAddrs: []string{"192.168.176.88/24"},
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/20"},
ipv4Only: true,
// fixed-cidr (the range of allocatable addresses) is bigger than the
// bridge subnet - this is a configuration error, but has historically
// been allowed. Because IPRange is treated as an offset into Subnet, it
// would normally result in a docker network that allocated addresses
// within the selected subnet. So, fixed-cidr is dropped, making the
// whole subnet allocatable.
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.176.0/24",
Gateway: "192.168.176.88",
},
},
expIPAMConfig: []network.IPAMConfig{{Subnet: "192.168.176.0/24", Gateway: "192.168.176.88"}},
},
{
name: "no bridge ip within fixed-cidr",
initialBridgeAddrs: []string{"192.168.160.88/20", "192.168.192.88/20"},
initialBridgeAddrs: []string{"192.168.160.88/20"},
daemonArgs: []string{"--fixed-cidr", "192.168.176.0/24"},
ipv4Only: true,
// fixed-cidr (the range of allocatable addresses) is outside the bridge
// subnet - this is a configuration error, but has historically been
// allowed. Because IPRange is treated as an offset into Subnet, it
// would normally result in a docker network that allocated addresses
// within the selected subnet. So, fixed-cidr is dropped, making the
// whole subnet allocatable.
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.160.0/20",
Gateway: "192.168.160.88",
},
},
expIPAMConfig: []network.IPAMConfig{{Subnet: "192.168.160.0/20", Gateway: "192.168.160.88"}},
},
{
name: "fixed-cidr contains bridge subnet",
@@ -256,12 +267,37 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) {
// normally result in a docker network that allocated addresses
// within the selected subnet. So, fixed-cidr is dropped, making the
// whole subnet allocatable.
expIPAMConfig: []network.IPAMConfig{
{
Subnet: "192.168.177.0/24",
Gateway: "192.168.177.1",
},
},
ipv4Only: true,
expIPAMConfig: []network.IPAMConfig{{Subnet: "192.168.177.0/24", Gateway: "192.168.177.1"}},
},
{
name: "fixed-cidr-v6 bigger than bridge subnet",
initialBridgeAddrs: []string{"fdd1:8161:2d2c::8888/64"},
daemonArgs: []string{"--fixed-cidr-v6", "fdd1:8161:2d2c::/60"},
// fixed-cidr-v6 (the range of allocatable addresses) is bigger than the bridge
// subnet - this is a configuration error. Unlike IPv4, it has not historically
// been allowed, so it will prevent daemon startup.
expStartErr: true,
},
{
name: "no bridge ip within fixed-cidr-v6",
initialBridgeAddrs: []string{"fdd1:8161:2d2c::8888/60"},
daemonArgs: []string{"--fixed-cidr-v6", "fdd1:8161:2d2c:10::/64"},
// fixed-cidr-v6 (the range of allocatable addresses) is outside the bridge subnet -
// this is a configuration error. Unlike IPv4, it has not historically been
// allowed, so it will prevent daemon startup.
expStartErr: true,
},
{
name: "fixed-cidr-v6 contains bridge subnet",
initialBridgeAddrs: []string{"fdd1:8161:2d2c:10::1/64"},
daemonArgs: []string{"--fixed-cidr-v6", "fdd1:8161:2d2c:10::/60"},
// fixed-cidr-v6 (the range of allocatable addresses) is bigger than the
// bridge subnet, and the bridge's address is not within fixed-cidr.
// This is a configuration error, Unlike IPv4, it has not historically been
// allowed, so it will prevent daemon startup.
expStartErr: true,
},
}
for _, tc := range testcases {
@@ -275,6 +311,8 @@ type defaultBridgeIPAMTestCase struct {
userDefinedBridge bool
initialBridgeAddrs []string
daemonArgs []string
ipv4Only bool
expStartErr bool
expIPAMConfig []network.IPAMConfig
}
@@ -287,11 +325,14 @@ func testDefaultBridgeIPAM(ctx context.Context, t *testing.T, tc defaultBridgeIP
defer deleteInterface(t, bridgeName)
var dOpts []daemon.Option
dArgs := tc.daemonArgs
var dArgs []string
if !tc.ipv4Only {
dArgs = append(tc.daemonArgs, "--ipv6")
}
if tc.userDefinedBridge {
// If a bridge is supplied by the user, the daemon should use its addresses
// to infer --bip (which cannot be specified).
dArgs = append(dArgs, []string{"--bridge", bridgeName}...)
dArgs = append(dArgs, "--bridge", bridgeName)
} else {
// The bridge is created and managed by docker, it's always called "docker0",
// unless this test-only env var is set - to avoid conflict with the docker0
@@ -304,7 +345,14 @@ func testDefaultBridgeIPAM(ctx context.Context, t *testing.T, tc defaultBridgeIP
d.Stop(t)
d.Cleanup(t)
}()
d.StartWithBusybox(ctx, t, dArgs...)
if tc.expStartErr {
err := d.StartWithError(dArgs...)
assert.Check(t, is.ErrorContains(err, "daemon exited during startup"))
return
}
d.Start(t, dArgs...)
c := d.NewClientT(t)
defer c.Close()

View File

@@ -5,6 +5,7 @@ import (
"flag"
"fmt"
"net"
"net/netip"
"os/exec"
"regexp"
"strconv"
@@ -496,6 +497,9 @@ func TestDefaultBridgeIPv6(t *testing.T) {
name string
fixed_cidr_v6 string
}{
{
name: "built in ULA prefix",
},
{
name: "IPv6 ULA",
fixed_cidr_v6: "fd00:1234::/64",
@@ -515,10 +519,11 @@ func TestDefaultBridgeIPv6(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)
d := daemon.New(t)
d.StartWithBusybox(ctx, t,
"--ipv6",
"--fixed-cidr-v6", tc.fixed_cidr_v6,
)
if tc.fixed_cidr_v6 == "" {
d.StartWithBusybox(ctx, t, "--ipv6")
} else {
d.StartWithBusybox(ctx, t, "--ipv6", "--fixed-cidr-v6", tc.fixed_cidr_v6)
}
defer d.Stop(t)
c := d.NewClientT(t)
@@ -532,15 +537,26 @@ func TestDefaultBridgeIPv6(t *testing.T) {
Force: true,
})
networkName := "bridge"
const networkName = "bridge"
inspect := container.Inspect(ctx, t, c, cID)
pingHost := inspect.NetworkSettings.Networks[networkName].GlobalIPv6Address
gIPv6 := inspect.NetworkSettings.Networks[networkName].GlobalIPv6Address
// The container's MAC and IPv6 addresses should be derived from the
// IPAM-allocated IPv4 address.
addr4, err := netip.ParseAddr(inspect.NetworkSettings.Networks[networkName].IPAddress)
assert.NilError(t, err)
mac, err := net.ParseMAC(inspect.NetworkSettings.Networks[networkName].MacAddress)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(addr4.AsSlice(), []byte(mac)[2:]))
addr6, err := netip.ParseAddr(gIPv6)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(addr4.AsSlice(), addr6.AsSlice()[12:]))
attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
res := container.RunAttach(attachCtx, t, c,
container.WithImage("busybox:latest"),
container.WithCmd("ping", "-c1", "-W3", pingHost),
container.WithCmd("ping", "-c1", "-W3", gIPv6),
)
defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{
Force: true,

View File

@@ -10,6 +10,7 @@ dockerd - Enable daemon mode
[**--authorization-plugin**[=*[]*]]
[**-b**|**--bridge**[=*BRIDGE*]]
[**--bip**[=*BIP*]]
[**--bip6**[=*BIP*]]
[**--cgroup-parent**[=*[]*]]
[**--config-file**[=*path*]]
[**--containerd**[=*SOCKET-PATH*]]
@@ -146,7 +147,11 @@ $ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-ru
container networking
**--bip**=""
Use the provided CIDR notation address for the dynamically created bridge
Use the provided CIDR notation IPv4 address for the dynamically created bridge
(docker0); Mutually exclusive of \-b
**--bip6**=""
Use the provided CIDR notation IPv6 address for the dynamically created bridge
(docker0); Mutually exclusive of \-b
**--cgroup-parent**=""