Merge pull request #46183 from akerouanton/validate-NetworkingConfig
daemon: Improve NetworkingConfig & EndpointSettings validation
This commit is contained in:
commit
f13065b1f7
12 changed files with 428 additions and 138 deletions
|
@ -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:
|
||||
|
|
135
api/types/network/endpoint.go
Normal file
135
api/types/network/endpoint.go
Normal 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...)
|
||||
}
|
188
api/types/network/endpoint_test.go
Normal file
188
api/types/network/endpoint_test.go
Normal 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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue