Repository: moby/moby
File: daemon/network.go (Go)

Code before:

	nwOptions := []libnetwork.NetworkOption{
		libnetwork.NetworkOptionEnableIPv4(enableIPv4),
		libnetwork.NetworkOptionEnableIPv6(enableIPv6),
		libnetwork.NetworkOptionDriverOpts(networkOptions),
		libnetwork.NetworkOptionLabels(create.Labels),
		libnetwork.NetworkOptionAttachable(create.Attachable),
		libnetwork.NetworkOptionIngress(create.Ingress),
		libnetwork.NetworkOptionScope(create.Scope),
	}

	if create.ConfigOnly {
		nwOptions = append(nwOptions, libnetwork.NetworkOptionConfigOnly())
	}

	if create.IPAM != nil {
		// Normalize subnet strings that only specify the size, e.g. "/24"
		for i, cfg := range create.IPAM.Config {
			if strings.HasPrefix(cfg.Subnet, "/") {
				if enableIPv6 {
					create.IPAM.Config[i].Subnet = "::" + cfg.Subnet
				} else {
					create.IPAM.Config[i].Subnet = "0.0.0.0" + cfg.Subnet
				}
			}
		}
	}

	if err := networktypes.ValidateIPAM(create.IPAM, enableIPv6); err != nil {
		if agent {
			// This function is called with agent=false for all networks. For swarm-scoped
			// networks, the configuration is validated but ManagerRedirectError is returned
			// and the network is not created. Then, each time a swarm-scoped network is
			// needed, this function is called again with agent=true.
			//
			// Non-swarm networks created before ValidateIPAM was introduced continue to work
			// as they did before-upgrade, even if they would fail the new checks on creation
			// (for example, by having host-bits set in their subnet). Those networks are not
			// seen again here.
			//
			// By dropping errors for agent networks, existing swarm-scoped networks also
			// continue to behave as they did before upgrade - but new networks are still
			// validated.
			log.G(ctx).WithFields(log.Fields{
				"error":   err,
				"network": create.Name,
			}).Warn("Continuing with validation errors in agent IPAM")
		} else {
			return nil, errdefs.InvalidParameter(err)
		}
	}

Review comment (bug):
For a dual-stack network, it might be best not to make an assumption about the address family. For example, in this case the user might want an `/64` for IPv6 as normal, with a `/24` for IPv4 - but network creation fails because the prefix length is applied to IPv6 too, and the error message is a bit obscure ...
```
$ docker network create --ipv6 --subnet /24 n1
Error response from daemon: invalid address pool
```

Maybe something like this ...

```
		// Normalize subnet strings that only specify the size, e.g. "/24"
		for i, cfg := range create.IPAM.Config {
			if strings.HasPrefix(cfg.Subnet, "/") {
				if enableIPv4 && enableIPv6 {
					return nil, errdefs.InvalidParameter(
						fmt.Errorf(`for a dual-stack network, address family must be included in subnet size "%[1]s", use "0.0.0.0%[1]s" or "::%[1]s"`, cfg.Subnet))
				}
				if enableIPv6 {
					create.IPAM.Config[i].Subnet = "::" + cfg.Subnet
				} else {
					create.IPAM.Config[i].Subnet = "0.0.0.0" + cfg.Subnet
				}
			}
		}
```

Then the error is ...
```
$ docker network create --subnet /24 --ipv6  n1
Error response from daemon: for a dual-stack network, address family must be included in subnet size "/24", use "0.0.0.0/24" or "::/24"
```

For a prefix length > 32, perhaps it'd be worth just using it for IPv6. But, the common case is probably wanting to specify an IPv4 prefix length.

(The default-default address pool for IPv6 is a `/48`, so a prefix length less than 48 can only work for an IPv4 network. In that case, `--subnet /24 --subnet /96` would be unambiguous and maybe nice. But we can always make the rules more complicated to try to allow that sort of thing in future.)

Code after:
			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),
		libnetwork.NetworkOptionAttachable(create.Attachable),
		libnetwork.NetworkOptionIngress(create.Ingress),
		libnetwork.NetworkOptionScope(create.Scope),
	}

	if create.ConfigOnly {
		nwOptions = append(nwOptions, libnetwork.NetworkOptionConfigOnly())
	}

	if create.IPAM != nil {
		ipam := create.IPAM
		if err := validateIpamConfig(ipam.Config, enableIPv6); err != nil {
			if agent {
				// This function is called with agent=false for all networks. For swarm-scoped
				// networks, the configuration is validated but ManagerRedirectError is returned
				// and the network is not created. Then, each time a swarm-scoped network is
				// needed, this function is called again with agent=true.
				//
				// Non-swarm networks created before ValidateIPAM was introduced continue to work
				// as they did before-upgrade, even if they would fail the new checks on creation
				// (for example, by having host-bits set in their subnet). Those networks are not
				// seen again here.
				//
				// By dropping errors for agent networks, existing swarm-scoped networks also
				// continue to behave as they did before upgrade - but new networks are still
				// validated.
				log.G(ctx).WithFields(log.Fields{
					"error":   err,
					"network": create.Name,
				}).Warn("Continuing with validation errors in agent IPAM")
			} else {
				return nil, errdefs.InvalidParameter(err)
			}
		}
		v4Conf, v6Conf, err := getIpamConfig(ipam.Config)
		if err != nil {
			return nil, err
		}
		nwOptions = append(nwOptions, libnetwork.NetworkOptionIpam(ipam.Driver, "", v4Conf, v6Conf, ipam.Options))
	}

	if create.Internal {