Merge pull request #46183 from akerouanton/validate-NetworkingConfig

daemon: Improve NetworkingConfig & EndpointSettings validation
This commit is contained in:
Sebastiaan van Stijn 2023-09-18 22:09:29 +02:00 committed by GitHub
commit f13065b1f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 428 additions and 138 deletions

View file

@ -10038,6 +10038,10 @@ paths:
responses:
200:
description: "No error"
400:
description: "bad parameter"
schema:
$ref: "#/definitions/ErrorResponse"
403:
description: "Operation not supported for swarm scoped networks"
schema:

View file

@ -0,0 +1,135 @@
package network
import (
"errors"
"fmt"
"net"
"github.com/docker/docker/internal/multierror"
)
// EndpointSettings stores the network endpoint details
type EndpointSettings struct {
// Configurations
IPAMConfig *EndpointIPAMConfig
Links []string
Aliases []string
// Operational data
NetworkID string
EndpointID string
Gateway string
IPAddress string
IPPrefixLen int
IPv6Gateway string
GlobalIPv6Address string
GlobalIPv6PrefixLen int
MacAddress string
DriverOpts map[string]string
}
// Copy makes a deep copy of `EndpointSettings`
func (es *EndpointSettings) Copy() *EndpointSettings {
epCopy := *es
if es.IPAMConfig != nil {
epCopy.IPAMConfig = es.IPAMConfig.Copy()
}
if es.Links != nil {
links := make([]string, 0, len(es.Links))
epCopy.Links = append(links, es.Links...)
}
if es.Aliases != nil {
aliases := make([]string, 0, len(es.Aliases))
epCopy.Aliases = append(aliases, es.Aliases...)
}
return &epCopy
}
// EndpointIPAMConfig represents IPAM configurations for the endpoint
type EndpointIPAMConfig struct {
IPv4Address string `json:",omitempty"`
IPv6Address string `json:",omitempty"`
LinkLocalIPs []string `json:",omitempty"`
}
// Copy makes a copy of the endpoint ipam config
func (cfg *EndpointIPAMConfig) Copy() *EndpointIPAMConfig {
cfgCopy := *cfg
cfgCopy.LinkLocalIPs = make([]string, 0, len(cfg.LinkLocalIPs))
cfgCopy.LinkLocalIPs = append(cfgCopy.LinkLocalIPs, cfg.LinkLocalIPs...)
return &cfgCopy
}
// NetworkSubnet describes a user-defined subnet for a specific network. It's only used to validate if an
// EndpointIPAMConfig is valid for a specific network.
type NetworkSubnet interface {
// Contains checks whether the NetworkSubnet contains [addr].
Contains(addr net.IP) bool
// IsStatic checks whether the subnet was statically allocated (ie. user-defined).
IsStatic() bool
}
// IsInRange checks whether static IP addresses are valid in a specific network.
func (cfg *EndpointIPAMConfig) IsInRange(v4Subnets []NetworkSubnet, v6Subnets []NetworkSubnet) error {
var errs []error
if err := validateEndpointIPAddress(cfg.IPv4Address, v4Subnets); err != nil {
errs = append(errs, err)
}
if err := validateEndpointIPAddress(cfg.IPv6Address, v6Subnets); err != nil {
errs = append(errs, err)
}
return multierror.Join(errs...)
}
func validateEndpointIPAddress(epAddr string, ipamSubnets []NetworkSubnet) error {
if epAddr == "" {
return nil
}
var staticSubnet bool
parsedAddr := net.ParseIP(epAddr)
for _, subnet := range ipamSubnets {
if subnet.IsStatic() {
staticSubnet = true
if subnet.Contains(parsedAddr) {
return nil
}
}
}
if staticSubnet {
return fmt.Errorf("no configured subnet or ip-range contain the IP address %s", epAddr)
}
return errors.New("user specified IP address is supported only when connecting to networks with user configured subnets")
}
// Validate checks whether cfg is valid.
func (cfg *EndpointIPAMConfig) Validate() error {
if cfg == nil {
return nil
}
var errs []error
if cfg.IPv4Address != "" {
if addr := net.ParseIP(cfg.IPv4Address); addr == nil || addr.To4() == nil || addr.IsUnspecified() {
errs = append(errs, fmt.Errorf("invalid IPv4 address: %s", cfg.IPv4Address))
}
}
if cfg.IPv6Address != "" {
if addr := net.ParseIP(cfg.IPv6Address); addr == nil || addr.To4() != nil || addr.IsUnspecified() {
errs = append(errs, fmt.Errorf("invalid IPv6 address: %s", cfg.IPv6Address))
}
}
for _, addr := range cfg.LinkLocalIPs {
if parsed := net.ParseIP(addr); parsed == nil || parsed.IsUnspecified() {
errs = append(errs, fmt.Errorf("invalid link-local IP address: %s", addr))
}
}
return multierror.Join(errs...)
}

View file

@ -0,0 +1,188 @@
package network
import (
"net"
"testing"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
type subnetStub struct {
static bool
contains map[string]bool
}
func (stub subnetStub) IsStatic() bool {
return stub.static
}
func (stub subnetStub) Contains(addr net.IP) bool {
v, ok := stub.contains[addr.String()]
return ok && v
}
func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) {
testcases := []struct {
name string
ipamConfig *EndpointIPAMConfig
v4Subnets []NetworkSubnet
v6Subnets []NetworkSubnet
expectedErrors []string
}{
{
name: "valid config",
ipamConfig: &EndpointIPAMConfig{
IPv4Address: "192.168.100.10",
IPv6Address: "2a01:d2:af:420b:25c1:1816:bb33:855c",
LinkLocalIPs: []string{"169.254.169.254", "fe80::42:a8ff:fe33:6230"},
},
v4Subnets: []NetworkSubnet{
subnetStub{static: true, contains: map[string]bool{"192.168.100.10": true}},
},
v6Subnets: []NetworkSubnet{
subnetStub{static: true, contains: map[string]bool{"2a01:d2:af:420b:25c1:1816:bb33:855c": true}},
},
},
{
name: "static addresses out of range",
ipamConfig: &EndpointIPAMConfig{
IPv4Address: "192.168.100.10",
IPv6Address: "2a01:d2:af:420b:25c1:1816:bb33:855c",
},
v4Subnets: []NetworkSubnet{
subnetStub{static: true, contains: map[string]bool{"192.168.100.10": false}},
},
v6Subnets: []NetworkSubnet{
subnetStub{static: true, contains: map[string]bool{"2a01:d2:af:420b:25c1:1816:bb33:855c": false}},
},
expectedErrors: []string{
"no configured subnet or ip-range contain the IP address 192.168.100.10",
"no configured subnet or ip-range contain the IP address 2a01:d2:af:420b:25c1:1816:bb33:855c",
},
},
{
name: "static addresses with dynamic network subnets",
ipamConfig: &EndpointIPAMConfig{
IPv4Address: "192.168.100.10",
IPv6Address: "2a01:d2:af:420b:25c1:1816:bb33:855c",
},
v4Subnets: []NetworkSubnet{
subnetStub{static: false},
},
v6Subnets: []NetworkSubnet{
subnetStub{static: false},
},
expectedErrors: []string{
"user specified IP address is supported only when connecting to networks with user configured subnets",
"user specified IP address is supported only when connecting to networks with user configured subnets",
},
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := tc.ipamConfig.IsInRange(tc.v4Subnets, tc.v6Subnets)
if tc.expectedErrors == nil {
assert.NilError(t, err)
return
}
if _, ok := err.(interface{ Unwrap() []error }); !ok {
t.Fatal("returned error isn't a multierror")
}
errs := err.(interface{ Unwrap() []error }).Unwrap()
assert.Check(t, len(errs) == len(tc.expectedErrors), "errs: %+v", errs)
for _, expected := range tc.expectedErrors {
assert.Check(t, is.ErrorContains(err, expected))
}
})
}
}
func TestEndpointIPAMConfigWithInvalidConfig(t *testing.T) {
testcases := []struct {
name string
ipamConfig *EndpointIPAMConfig
expectedErrors []string
}{
{
name: "valid config",
ipamConfig: &EndpointIPAMConfig{
IPv4Address: "192.168.100.10",
IPv6Address: "2a01:d2:af:420b:25c1:1816:bb33:855c",
LinkLocalIPs: []string{"169.254.169.254", "fe80::42:a8ff:fe33:6230"},
},
},
{
name: "invalid IP addresses",
ipamConfig: &EndpointIPAMConfig{
IPv4Address: "foo",
IPv6Address: "bar",
LinkLocalIPs: []string{"baz", "foobar"},
},
expectedErrors: []string{
"invalid IPv4 address: foo",
"invalid IPv6 address: bar",
"invalid link-local IP address: baz",
"invalid link-local IP address: foobar",
},
},
{
name: "ipv6 address with a zone",
ipamConfig: &EndpointIPAMConfig{IPv6Address: "fe80::1cc0:3e8c:119f:c2e1%ens18"},
expectedErrors: []string{
"invalid IPv6 address: fe80::1cc0:3e8c:119f:c2e1%ens18",
},
},
{
name: "unspecified address is invalid",
ipamConfig: &EndpointIPAMConfig{
IPv4Address: "0.0.0.0",
IPv6Address: "::",
LinkLocalIPs: []string{"0.0.0.0", "::"},
},
expectedErrors: []string{
"invalid IPv4 address: 0.0.0.0",
"invalid IPv6 address: ::",
"invalid link-local IP address: 0.0.0.0",
"invalid link-local IP address: ::",
},
},
{
name: "empty link-local",
ipamConfig: &EndpointIPAMConfig{
LinkLocalIPs: []string{""},
},
expectedErrors: []string{"invalid link-local IP address:"},
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := tc.ipamConfig.Validate()
if tc.expectedErrors == nil {
assert.NilError(t, err)
return
}
if _, ok := err.(interface{ Unwrap() []error }); !ok {
t.Fatal("returned error isn't a multierror")
}
errs := err.(interface{ Unwrap() []error }).Unwrap()
assert.Check(t, len(errs) == len(tc.expectedErrors), "errs: %+v", errs)
for _, expected := range tc.expectedErrors {
assert.Check(t, is.ErrorContains(err, expected))
}
})
}
}

View file

@ -51,6 +51,8 @@ func HasIPv6Subnets(ipam *IPAM) bool {
return false
}
// ValidateIPAM checks whether the network's IPAM passed as argument is valid. It returns a joinError of the list of
// errors found.
func ValidateIPAM(ipam *IPAM) error {
if ipam == nil {
return nil

View file

@ -9,46 +9,12 @@ type Address struct {
PrefixLen int
}
// EndpointIPAMConfig represents IPAM configurations for the endpoint
type EndpointIPAMConfig struct {
IPv4Address string `json:",omitempty"`
IPv6Address string `json:",omitempty"`
LinkLocalIPs []string `json:",omitempty"`
}
// Copy makes a copy of the endpoint ipam config
func (cfg *EndpointIPAMConfig) Copy() *EndpointIPAMConfig {
cfgCopy := *cfg
cfgCopy.LinkLocalIPs = make([]string, 0, len(cfg.LinkLocalIPs))
cfgCopy.LinkLocalIPs = append(cfgCopy.LinkLocalIPs, cfg.LinkLocalIPs...)
return &cfgCopy
}
// PeerInfo represents one peer of an overlay network
type PeerInfo struct {
Name string
IP string
}
// EndpointSettings stores the network endpoint details
type EndpointSettings struct {
// Configurations
IPAMConfig *EndpointIPAMConfig
Links []string
Aliases []string
// Operational data
NetworkID string
EndpointID string
Gateway string
IPAddress string
IPPrefixLen int
IPv6Gateway string
GlobalIPv6Address string
GlobalIPv6PrefixLen int
MacAddress string
DriverOpts map[string]string
}
// Task carries the information about one backend task
type Task struct {
Name string
@ -65,25 +31,6 @@ type ServiceInfo struct {
Tasks []Task
}
// Copy makes a deep copy of `EndpointSettings`
func (es *EndpointSettings) Copy() *EndpointSettings {
epCopy := *es
if es.IPAMConfig != nil {
epCopy.IPAMConfig = es.IPAMConfig.Copy()
}
if es.Links != nil {
links := make([]string, 0, len(es.Links))
epCopy.Links = append(links, es.Links...)
}
if es.Aliases != nil {
aliases := make([]string, 0, len(es.Aliases))
epCopy.Aliases = append(aliases, es.Aliases...)
}
return &epCopy
}
// NetworkingConfig represents the container's networking configuration for each of its interfaces
// Carries the networking configs specified in the `docker run` and `docker network connect` commands
type NetworkingConfig struct {

View file

@ -18,6 +18,7 @@ import (
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/daemon/network"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/internal/multierror"
"github.com/docker/docker/libnetwork"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/options"
@ -562,54 +563,57 @@ func (daemon *Daemon) allocateNetwork(cfg *config.Config, container *container.C
return nil
}
// hasUserDefinedIPAddress returns whether the passed IPAM configuration contains IP address configuration
func hasUserDefinedIPAddress(ipamConfig *networktypes.EndpointIPAMConfig) bool {
return ipamConfig != nil && (len(ipamConfig.IPv4Address) > 0 || len(ipamConfig.IPv6Address) > 0)
}
// User specified ip address is acceptable only for networks with user specified subnets.
func validateNetworkingConfig(n *libnetwork.Network, epConfig *networktypes.EndpointSettings) error {
if n == nil || epConfig == nil {
// validateEndpointSettings checks whether the given epConfig is valid. The nw parameter can be nil, in which case it
// won't try to check if the endpoint IP addresses are within network's subnets.
func validateEndpointSettings(nw *libnetwork.Network, nwName string, epConfig *networktypes.EndpointSettings) error {
if epConfig == nil {
return nil
}
if !containertypes.NetworkMode(n.Name()).IsUserDefined() {
if hasUserDefinedIPAddress(epConfig.IPAMConfig) && !enableIPOnPredefinedNetwork() {
return runconfig.ErrUnsupportedNetworkAndIP
ipamConfig := &networktypes.EndpointIPAMConfig{}
if epConfig.IPAMConfig != nil {
ipamConfig = epConfig.IPAMConfig
}
var errs []error
// TODO(aker): move this into api/types/network/endpoint.go once enableIPOnPredefinedNetwork and
// serviceDiscoveryOnDefaultNetwork are removed.
if !containertypes.NetworkMode(nwName).IsUserDefined() {
hasStaticAddresses := ipamConfig.IPv4Address != "" || ipamConfig.IPv6Address != ""
// On Linux, user specified IP address is accepted only by networks with user specified subnets.
if hasStaticAddresses && !enableIPOnPredefinedNetwork() {
errs = append(errs, runconfig.ErrUnsupportedNetworkAndIP)
}
if len(epConfig.Aliases) > 0 && !serviceDiscoveryOnDefaultNetwork() {
return runconfig.ErrUnsupportedNetworkAndAlias
errs = append(errs, runconfig.ErrUnsupportedNetworkAndAlias)
}
}
if !hasUserDefinedIPAddress(epConfig.IPAMConfig) {
return nil
}
_, _, nwIPv4Configs, nwIPv6Configs := n.IpamConfig()
for _, s := range []struct {
ipConfigured bool
subnetConfigs []*libnetwork.IpamConf
}{
{
ipConfigured: len(epConfig.IPAMConfig.IPv4Address) > 0,
subnetConfigs: nwIPv4Configs,
},
{
ipConfigured: len(epConfig.IPAMConfig.IPv6Address) > 0,
subnetConfigs: nwIPv6Configs,
},
} {
if s.ipConfigured {
foundSubnet := false
for _, cfg := range s.subnetConfigs {
if len(cfg.PreferredPool) > 0 {
foundSubnet = true
break
}
}
if !foundSubnet {
return runconfig.ErrUnsupportedNetworkNoSubnetAndIP
}
// TODO(aker): add a proper multierror.Append
if err := ipamConfig.Validate(); err != nil {
errs = append(errs, err.(interface{ Unwrap() []error }).Unwrap()...)
}
if nw != nil {
_, _, v4Configs, v6Configs := nw.IpamConfig()
var nwIPv4Subnets, nwIPv6Subnets []networktypes.NetworkSubnet
for _, nwIPAMConfig := range v4Configs {
nwIPv4Subnets = append(nwIPv4Subnets, nwIPAMConfig)
}
for _, nwIPAMConfig := range v6Configs {
nwIPv6Subnets = append(nwIPv6Subnets, nwIPAMConfig)
}
// TODO(aker): add a proper multierror.Append
if err := ipamConfig.IsInRange(nwIPv4Subnets, nwIPv6Subnets); err != nil {
errs = append(errs, err.(interface{ Unwrap() []error }).Unwrap()...)
}
}
if err := multierror.Join(errs...); err != nil {
return fmt.Errorf("invalid endpoint settings:\n%w", err)
}
return nil
@ -657,7 +661,7 @@ func (daemon *Daemon) updateNetworkConfig(container *container.Container, n *lib
}
}
if err := validateNetworkingConfig(n, endpointConfig); err != nil {
if err := validateEndpointSettings(n, n.Name(), endpointConfig); err != nil {
return err
}

View file

@ -367,11 +367,13 @@ func isLinkable(child *container.Container) bool {
return ok
}
// TODO(aker): remove when we make the default bridge network behave like any other network
func enableIPOnPredefinedNetwork() bool {
return false
}
// serviceDiscoveryOnDefaultNetwork indicates if service discovery is supported on the default network
// TODO(aker): remove when we make the default bridge network behave like any other network
func serviceDiscoveryOnDefaultNetwork() bool {
return false
}

View file

@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"net"
"runtime"
"strings"
"time"
@ -21,6 +20,7 @@ import (
"github.com/docker/docker/daemon/images"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/image"
"github.com/docker/docker/internal/multierror"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/runconfig"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -98,7 +98,7 @@ func (daemon *Daemon) containerCreate(ctx context.Context, daemonCfg *configStor
}
}
err = verifyNetworkingConfig(opts.params.NetworkingConfig)
err = daemon.validateNetworkingConfig(opts.params.NetworkingConfig)
if err != nil {
return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err)
}
@ -321,30 +321,30 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *i
return nil
}
// verifyNetworkingConfig validates if the nwConfig is valid.
func verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
// validateNetworkingConfig checks whether a container's NetworkingConfig is valid.
func (daemon *Daemon) validateNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
if nwConfig == nil {
return nil
}
var errs []error
for k, v := range nwConfig.EndpointsConfig {
if v == nil {
return fmt.Errorf("no EndpointSettings for %s", k)
errs = append(errs, fmt.Errorf("invalid config for network %s: EndpointsConfig is nil", k))
continue
}
if v.IPAMConfig != nil {
if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil {
return fmt.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address)
}
if v.IPAMConfig.IPv6Address != "" {
n := net.ParseIP(v.IPAMConfig.IPv6Address)
// if the address is an invalid network address (ParseIP == nil) or if it is
// an IPv4 address (To4() != nil), then it is an invalid IPv6 address
if n == nil || n.To4() != nil {
return fmt.Errorf("invalid IPv6 address: %s", v.IPAMConfig.IPv6Address)
}
}
// The referenced network k might not exist when the container is created, so just ignore the error in that case.
nw, _ := daemon.FindNetwork(k)
if err := validateEndpointSettings(nw, k, v); err != nil {
errs = append(errs, fmt.Errorf("invalid config for network %s: %w", k, err))
}
}
if len(errs) > 0 {
return errdefs.InvalidParameter(multierror.Join(errs...))
}
return nil
}

View file

@ -1,21 +0,0 @@
package daemon // import "github.com/docker/docker/daemon"
import (
"testing"
"github.com/docker/docker/api/types/network"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
// Test case for 35752
func TestVerifyNetworkingConfig(t *testing.T) {
name := "mynet"
endpoints := make(map[string]*network.EndpointSettings, 1)
endpoints[name] = nil
nwConfig := &network.NetworkingConfig{
EndpointsConfig: endpoints,
}
err := verifyNetworkingConfig(nwConfig)
assert.Check(t, is.Error(err, "no EndpointSettings for mynet"), "should produce an error because no EndpointSettings were passed")
}

View file

@ -47,6 +47,11 @@ keywords: "API, Docker, rcli, REST, documentation"
1.44.
* `POST /containers/create` now accepts multiple `EndpointSettings` in
`NetworkingConfig.EndpointSettings`.
* `POST /containers/create` and `POST /networks/{id}/connect` will now catch
validation errors that were previously only returned during `POST /containers/{id}/start`.
These endpoints will also return the full set of validation errors they find,
instead of returning only the first one.
Note that this change is _unversioned_ and applies to all API versions.
## v1.43 API changes

View file

@ -1289,9 +1289,9 @@ func (s *DockerNetworkSuite) TestDockerNetworkConnectPreferredIP(c *testing.T) {
verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
// connect the container to the second network specifying an ip addresses
dockerCmd(c, "network", "connect", "--ip", "172.30.55.44", "--ip6", "2001:db8:abcd::5544", "n1", "c0")
verifyIPAddressConfig(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
dockerCmd(c, "network", "connect", "--ip", "172.30.5.44", "--ip6", "2001:db8:abcd::5544", "n1", "c0")
verifyIPAddressConfig(c, "c0", "n1", "172.30.5.44", "2001:db8:abcd::5544")
verifyIPAddresses(c, "c0", "n1", "172.30.5.44", "2001:db8:abcd::5544")
// Stop and restart the container
dockerCmd(c, "stop", "c0")
@ -1300,8 +1300,8 @@ func (s *DockerNetworkSuite) TestDockerNetworkConnectPreferredIP(c *testing.T) {
// verify requested addresses are applied and configs are still there
verifyIPAddressConfig(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
verifyIPAddressConfig(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
verifyIPAddressConfig(c, "c0", "n1", "172.30.5.44", "2001:db8:abcd::5544")
verifyIPAddresses(c, "c0", "n1", "172.30.5.44", "2001:db8:abcd::5544")
// Still it should fail to connect to the default network with a specified IP (whatever ip)
out, _, err := dockerCmdWithError("network", "connect", "--ip", "172.21.55.44", "bridge", "c0")

View file

@ -71,6 +71,8 @@ type networkDBTable struct {
}
// IpamConf contains all the ipam related configurations for a network
//
// TODO(aker): use proper net/* structs instead of string literals.
type IpamConf struct {
// PreferredPool is the master address pool for containers and network interfaces.
PreferredPool string
@ -92,6 +94,28 @@ func (c *IpamConf) Validate() error {
return nil
}
// Contains checks whether the ipamSubnet contains [addr].
func (c *IpamConf) Contains(addr net.IP) bool {
if c == nil {
return false
}
if c.PreferredPool == "" {
return false
}
_, allowedRange, _ := net.ParseCIDR(c.PreferredPool)
if c.SubPool != "" {
_, allowedRange, _ = net.ParseCIDR(c.SubPool)
}
return allowedRange.Contains(addr)
}
// IsStatic checks whether the subnet was statically allocated (ie. user-defined).
func (c *IpamConf) IsStatic() bool {
return c != nil && c.PreferredPool != ""
}
// IpamInfo contains all the ipam related operational info for a network
type IpamInfo struct {
PoolID string
@ -150,7 +174,7 @@ type Network struct {
created time.Time
scope string // network data scope
labels map[string]string
ipamType string
ipamType string // ipamType is the name of the IPAM driver
ipamOptions map[string]string
addrSpace string
ipamV4Config []*IpamConf