daemon: move most of validateEndpointSettings into api/t/net

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
This commit is contained in:
Albin Kerouanton 2023-09-15 12:44:30 +02:00
parent 81ab8db1c3
commit 3092b261e2
No known key found for this signature in database
GPG key ID: 630B8E1DCBDB1864
3 changed files with 129 additions and 53 deletions

View file

@ -1,5 +1,13 @@
package network
import (
"errors"
"fmt"
"net"
"github.com/docker/docker/internal/multierror"
)
// EndpointSettings stores the network endpoint details
type EndpointSettings struct {
// Configurations
@ -52,3 +60,76 @@ func (cfg *EndpointIPAMConfig) Copy() *EndpointIPAMConfig {
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

@ -563,9 +563,8 @@ func (daemon *Daemon) allocateNetwork(cfg *config.Config, container *container.C
return nil
}
// validateEndpointSettings checks whether the given epConfig is valid. The nw parameter might be nil as a container
// can be created with a reference to a network that don't exist yet. In that case, only partial validation will be
// done.
// 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
@ -578,6 +577,8 @@ func validateEndpointSettings(nw *libnetwork.Network, nwName string, epConfig *n
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.
@ -589,63 +590,33 @@ func validateEndpointSettings(nw *libnetwork.Network, nwName string, epConfig *n
}
}
if ipamConfig.IPv4Address != "" {
if addr := net.ParseIP(ipamConfig.IPv4Address); addr == nil || addr.To4() == nil || addr.IsUnspecified() {
errs = append(errs, fmt.Errorf("invalid IPv4 address: %s", ipamConfig.IPv4Address))
}
// TODO(aker): add a proper multierror.Append
if err := ipamConfig.Validate(); err != nil {
errs = append(errs, err.(interface{ Unwrap() []error }).Unwrap()...)
}
if ipamConfig.IPv6Address != "" {
if addr := net.ParseIP(ipamConfig.IPv6Address); addr == nil || addr.To4() != nil || addr.IsUnspecified() {
errs = append(errs, fmt.Errorf("invalid IPv6 address: %s", ipamConfig.IPv6Address))
if nw != nil {
_, _, v4Configs, v6Configs := nw.IpamConfig()
var nwIPv4Subnets, nwIPv6Subnets []networktypes.NetworkSubnet
for _, nwIPAMConfig := range v4Configs {
nwIPv4Subnets = append(nwIPv4Subnets, nwIPAMConfig)
}
}
for _, addr := range ipamConfig.LinkLocalIPs {
if parsed := net.ParseIP(addr); parsed == nil || parsed.IsUnspecified() {
errs = append(errs, fmt.Errorf("invalid link-local IP address %s", addr))
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 nw == nil {
return multierror.Join(errs...)
if err := multierror.Join(errs...); err != nil {
return fmt.Errorf("invalid endpoint settings:\n%w", err)
}
_, _, nwIPv4Configs, nwIPv6Configs := nw.IpamConfig()
if err := validateEndpointIPAddress(nwIPv4Configs, ipamConfig.IPv4Address); err != nil {
errs = append(errs, err)
}
if err := validateEndpointIPAddress(nwIPv6Configs, ipamConfig.IPv6Address); err != nil {
errs = append(errs, err)
}
return multierror.Join(errs...)
}
func validateEndpointIPAddress(nwIPAMConfig []*libnetwork.IpamConf, epAddr string) error {
if epAddr == "" {
return nil
}
var customSubnet bool
parsedAddr := net.ParseIP(epAddr)
for _, conf := range nwIPAMConfig {
if conf.PreferredPool != "" {
customSubnet = true
_, allowedRange, _ := net.ParseCIDR(conf.PreferredPool)
if conf.SubPool != "" {
_, allowedRange, _ = net.ParseCIDR(conf.SubPool)
}
if allowedRange.Contains(parsedAddr) {
return nil
}
}
}
if customSubnet {
return fmt.Errorf("no predefined subnet or ip-range contain the IP address: %s", epAddr)
}
return runconfig.ErrUnsupportedNetworkNoSubnetAndIP
return nil
}
// cleanOperationalData resets the operational data from the passed endpoint settings

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