diff --git a/daemon/network.go b/daemon/network.go index 45c159f875..c50c63bf8e 100644 --- a/daemon/network.go +++ b/daemon/network.go @@ -316,18 +316,33 @@ func (daemon *Daemon) createNetwork(cfg *config.Config, create networktypes.Crea } } + enableIPv4 := create.ConfigFrom == nil + if create.EnableIPv4 != nil { + enableIPv4 = *create.EnableIPv4 + } else if v, ok := networkOptions[netlabel.EnableIPv4]; ok { + var err error + if enableIPv4, err = strconv.ParseBool(v); err != nil { + return nil, errdefs.InvalidParameter(fmt.Errorf("driver-opt %q is not a valid bool", netlabel.EnableIPv4)) + } + } + if !enableIPv4 && !daemon.config().Experimental && create.ConfigFrom == nil { + return nil, errdefs.InvalidParameter( + errors.New("IPv4 can only be disabled if experimental features are enabled"), + ) + } + var enableIPv6 bool if create.EnableIPv6 != nil { enableIPv6 = *create.EnableIPv6 - } else { + } else if v, ok := networkOptions[netlabel.EnableIPv6]; ok { var err error - v, ok := networkOptions[netlabel.EnableIPv6] - if enableIPv6, err = strconv.ParseBool(v); ok && err != nil { + if enableIPv6, err = strconv.ParseBool(v); err != nil { return nil, errdefs.InvalidParameter(fmt.Errorf("driver-opt %q is not a valid bool", netlabel.EnableIPv6)) } } nwOptions := []libnetwork.NetworkOption{ + libnetwork.NetworkOptionEnableIPv4(enableIPv4), libnetwork.NetworkOptionEnableIPv6(enableIPv6), libnetwork.NetworkOptionDriverOpts(networkOptions), libnetwork.NetworkOptionLabels(create.Labels), @@ -620,6 +635,7 @@ func buildNetworkResource(nw *libnetwork.Network) networktypes.Inspect { Created: nw.Created(), Scope: nw.Scope(), Driver: nw.Type(), + EnableIPv4: nw.IPv4Enabled(), EnableIPv6: nw.IPv6Enabled(), IPAM: buildIPAMResources(nw), Internal: nw.Internal(), diff --git a/integration/internal/network/ops.go b/integration/internal/network/ops.go index 49f762ecae..b5a071c222 100644 --- a/integration/internal/network/ops.go +++ b/integration/internal/network/ops.go @@ -11,6 +11,14 @@ func WithDriver(driver string) func(*network.CreateOptions) { } } +// WithIPv4 enables/disables IPv4 on the network +func WithIPv4(enable bool) func(*network.CreateOptions) { + return func(n *network.CreateOptions) { + enableIPv4 := enable + n.EnableIPv4 = &enableIPv4 + } +} + // WithIPv6 Enables IPv6 on the network func WithIPv6() func(*network.CreateOptions) { return func(n *network.CreateOptions) { diff --git a/libnetwork/libnetwork_internal_test.go b/libnetwork/libnetwork_internal_test.go index 24126a5299..bc5bbd294d 100644 --- a/libnetwork/libnetwork_internal_test.go +++ b/libnetwork/libnetwork_internal_test.go @@ -29,6 +29,7 @@ func TestNetworkMarshalling(t *testing.T) { ipamType: "default", addrSpace: "viola", networkType: "bridge", + enableIPv4: true, enableIPv6: true, persist: true, configOnly: true, @@ -138,7 +139,7 @@ func TestNetworkMarshalling(t *testing.T) { } if n.name != nn.name || n.id != nn.id || n.networkType != nn.networkType || n.ipamType != nn.ipamType || - n.addrSpace != nn.addrSpace || n.enableIPv6 != nn.enableIPv6 || + n.addrSpace != nn.addrSpace || n.enableIPv4 != nn.enableIPv4 || n.enableIPv6 != nn.enableIPv6 || n.persist != nn.persist || !compareIpamConfList(n.ipamV4Config, nn.ipamV4Config) || !compareIpamInfoList(n.ipamV4Info, nn.ipamV4Info) || !compareIpamConfList(n.ipamV6Config, nn.ipamV6Config) || !compareIpamInfoList(n.ipamV6Info, nn.ipamV6Info) || diff --git a/libnetwork/netlabel/labels.go b/libnetwork/netlabel/labels.go index 5e243b8520..f7467b68a1 100644 --- a/libnetwork/netlabel/labels.go +++ b/libnetwork/netlabel/labels.go @@ -30,6 +30,9 @@ const ( // where the interface name is represented by the string "IFNAME". EndpointSysctls = Prefix + ".endpoint.sysctls" + // EnableIPv4 constant represents enabling IPV4 at network level + EnableIPv4 = Prefix + ".enable_ipv4" + // EnableIPv6 constant represents enabling IPV6 at network level EnableIPv6 = Prefix + ".enable_ipv6" diff --git a/libnetwork/network.go b/libnetwork/network.go index 3b0639553b..40f68ca2e7 100644 --- a/libnetwork/network.go +++ b/libnetwork/network.go @@ -187,6 +187,7 @@ type Network struct { ipamV6Config []*IpamConf ipamV4Info []*IpamInfo ipamV6Info []*IpamInfo + enableIPv4 bool enableIPv6 bool postIPv6 bool epCnt *endpointCnt @@ -368,7 +369,7 @@ func (n *Network) validateConfiguration() error { } if n.ipamType != "" && n.ipamType != defaultIpamForNetworkType(n.networkType) || - n.enableIPv6 || + n.enableIPv4 || n.enableIPv6 || len(n.labels) > 0 || len(n.ipamOptions) > 0 || len(n.ipamV4Config) > 0 || len(n.ipamV6Config) > 0 { return types.ForbiddenErrorf("user specified configurations are not supported if the network depends on a configuration network") @@ -401,6 +402,7 @@ func (n *Network) validateConfiguration() error { // applyConfigurationTo applies network specific configurations. func (n *Network) applyConfigurationTo(to *Network) error { + to.enableIPv4 = n.enableIPv4 to.enableIPv6 = n.enableIPv6 if len(n.labels) > 0 { to.labels = make(map[string]string, len(n.labels)) @@ -450,6 +452,7 @@ func (n *Network) CopyTo(o datastore.KVObject) error { dstN.scope = n.scope dstN.dynamic = n.dynamic dstN.ipamType = n.ipamType + dstN.enableIPv4 = n.enableIPv4 dstN.enableIPv6 = n.enableIPv6 dstN.persist = n.persist dstN.postIPv6 = n.postIPv6 @@ -539,6 +542,7 @@ func (n *Network) MarshalJSON() ([]byte, error) { netMap["ipamType"] = n.ipamType netMap["ipamOptions"] = n.ipamOptions netMap["addrSpace"] = n.addrSpace + netMap["enableIPv4"] = n.enableIPv4 netMap["enableIPv6"] = n.enableIPv6 if n.generic != nil { netMap["generic"] = n.generic @@ -601,6 +605,12 @@ func (n *Network) UnmarshalJSON(b []byte) (err error) { } } n.networkType = netMap["networkType"].(string) + if v, ok := netMap["enableIPv4"]; ok { + n.enableIPv4 = v.(bool) + } else { + // Set enableIPv4 for IPv4 networks created before the option was added. + n.enableIPv4 = len(n.ipamV4Info) > 0 + } n.enableIPv6 = netMap["enableIPv6"].(bool) // if we weren't unmarshaling to netMap we could simply set n.labels @@ -713,6 +723,9 @@ func NetworkOptionGeneric(generic map[string]interface{}) NetworkOption { if n.generic == nil { n.generic = make(map[string]interface{}) } + if val, ok := generic[netlabel.EnableIPv4]; ok { + n.enableIPv4 = val.(bool) + } if val, ok := generic[netlabel.EnableIPv6]; ok { n.enableIPv6 = val.(bool) } @@ -740,6 +753,17 @@ func NetworkOptionPersist(persist bool) NetworkOption { } } +// NetworkOptionEnableIPv4 returns an option setter to explicitly configure IPv4 +func NetworkOptionEnableIPv4(enableIPv4 bool) NetworkOption { + return func(n *Network) { + if n.generic == nil { + n.generic = make(map[string]interface{}) + } + n.enableIPv4 = enableIPv4 + n.generic[netlabel.EnableIPv4] = enableIPv4 + } +} + // NetworkOptionEnableIPv6 returns an option setter to explicitly configure IPv6 func NetworkOptionEnableIPv6(enableIPv6 bool) NetworkOption { return func(n *Network) { @@ -1842,6 +1866,13 @@ func (n *Network) Dynamic() bool { return n.dynamic } +func (n *Network) IPv4Enabled() bool { + n.mu.Lock() + defer n.mu.Unlock() + + return n.enableIPv4 +} + func (n *Network) IPv6Enabled() bool { n.mu.Lock() defer n.mu.Unlock()