diff --git a/api/swagger.yaml b/api/swagger.yaml index 90685ad77a..93c6f82598 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -2455,6 +2455,10 @@ definitions: VIP: type: "string" x-omitempty: false + x-go-type: + type: Addr + import: + package: net/netip Ports: type: "array" x-omitempty: false @@ -2464,6 +2468,8 @@ definitions: type: "integer" format: "int" x-omitempty: false + x-go-type: + type: int Tasks: type: "array" x-omitempty: false @@ -2487,6 +2493,10 @@ definitions: EndpointIP: type: "string" x-omitempty: false + x-go-type: + type: Addr + import: + package: net/netip Info: type: "object" x-omitempty: false @@ -2617,10 +2627,18 @@ definitions: type: "string" x-omitempty: false example: "172.19.0.2/16" + x-go-type: + type: Prefix + import: + package: net/netip IPv6Address: type: "string" x-omitempty: false example: "" + x-go-type: + type: Prefix + import: + package: net/netip PeerInfo: description: > @@ -2640,6 +2658,10 @@ definitions: type: "string" x-omitempty: false example: "10.133.77.91" + x-go-type: + type: Addr + import: + package: net/netip NetworkCreateResponse: description: "OK response to NetworkCreate operation" @@ -2910,6 +2932,10 @@ definitions: IPv4 address. type: "string" example: "172.17.0.4" + x-go-type: + type: Addr + import: + package: net/netip IPPrefixLen: description: | Mask length of the IPv4 address. @@ -2920,11 +2946,19 @@ definitions: IPv6 gateway address. type: "string" example: "2001:db8:2::100" + x-go-type: + type: Addr + import: + package: net/netip GlobalIPv6Address: description: | Global IPv6 address. type: "string" example: "2001:db8::5689" + x-go-type: + type: Addr + import: + package: net/netip GlobalIPv6PrefixLen: description: | Mask length of the global IPv6 address. @@ -2956,13 +2990,25 @@ definitions: IPv4Address: type: "string" example: "172.20.30.33" + x-go-type: + type: Addr + import: + package: net/netip IPv6Address: type: "string" example: "2001:db8:abcd::3033" + x-go-type: + type: Addr + import: + package: net/netip LinkLocalIPs: type: "array" items: type: "string" + x-go-type: + type: Addr + import: + package: net/netip example: - "169.254.34.68" - "fe80::3468" diff --git a/api/types/network/endpoint.go b/api/types/network/endpoint.go index 30f51d2f5f..ee5223d052 100644 --- a/api/types/network/endpoint.go +++ b/api/types/network/endpoint.go @@ -2,6 +2,7 @@ package network import ( "maps" + "net/netip" "slices" ) @@ -25,11 +26,11 @@ type EndpointSettings struct { // Operational data NetworkID string EndpointID string - Gateway string - IPAddress string + Gateway netip.Addr + IPAddress netip.Addr IPPrefixLen int - IPv6Gateway string - GlobalIPv6Address string + IPv6Gateway netip.Addr + GlobalIPv6Address netip.Addr GlobalIPv6PrefixLen int // DNSNames holds all the (non fully qualified) DNS names associated to this endpoint. First entry is used to // generate PTR records. @@ -54,9 +55,9 @@ func (es *EndpointSettings) Copy() *EndpointSettings { // EndpointIPAMConfig represents IPAM configurations for the endpoint type EndpointIPAMConfig struct { - IPv4Address string `json:",omitempty"` - IPv6Address string `json:",omitempty"` - LinkLocalIPs []string `json:",omitempty"` + IPv4Address netip.Addr `json:",omitempty"` + IPv6Address netip.Addr `json:",omitempty"` + LinkLocalIPs []netip.Addr `json:",omitempty"` } // Copy makes a copy of the endpoint ipam config diff --git a/api/types/network/endpoint_resource.go b/api/types/network/endpoint_resource.go index 9780253baf..6ff25b1bb6 100644 --- a/api/types/network/endpoint_resource.go +++ b/api/types/network/endpoint_resource.go @@ -5,6 +5,10 @@ package network // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command +import ( + "net/netip" +) + // EndpointResource contains network resources allocated and used for a container in a network. // // swagger:model EndpointResource @@ -24,8 +28,8 @@ type EndpointResource struct { // IPv4 address // Example: 172.19.0.2/16 - IPv4Address string `json:"IPv4Address"` + IPv4Address netip.Prefix `json:"IPv4Address"` // IPv6 address - IPv6Address string `json:"IPv6Address"` + IPv6Address netip.Prefix `json:"IPv6Address"` } diff --git a/api/types/network/ipam.go b/api/types/network/ipam.go index af83421780..e57be481b7 100644 --- a/api/types/network/ipam.go +++ b/api/types/network/ipam.go @@ -13,10 +13,10 @@ type IPAM struct { // IPAMConfig represents IPAM configurations type IPAMConfig struct { - Subnet string `json:",omitempty"` - IPRange string `json:",omitempty"` - Gateway string `json:",omitempty"` - AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` + Subnet netip.Prefix `json:",omitempty"` + IPRange netip.Prefix `json:",omitempty"` + Gateway netip.Addr `json:",omitempty"` + AuxAddress map[string]netip.Addr `json:"AuxiliaryAddresses,omitempty"` } type SubnetStatuses = map[netip.Prefix]SubnetStatus diff --git a/api/types/network/network_types.go b/api/types/network/network_types.go index 413e5fbed0..5401f55f82 100644 --- a/api/types/network/network_types.go +++ b/api/types/network/network_types.go @@ -30,14 +30,6 @@ type CreateRequest struct { Labels map[string]string // Labels holds metadata specific to the network being created. } -// ServiceInfo represents service parameters with the list of service's tasks -type ServiceInfo struct { - VIP string - Ports []string - LocalLBIndex int - Tasks []Task -} - // 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 { diff --git a/api/types/network/peer_info.go b/api/types/network/peer_info.go index 0522a2392d..dc88ec16fa 100644 --- a/api/types/network/peer_info.go +++ b/api/types/network/peer_info.go @@ -5,6 +5,10 @@ package network // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command +import ( + "net/netip" +) + // PeerInfo represents one peer of an overlay network. // // swagger:model PeerInfo @@ -16,5 +20,5 @@ type PeerInfo struct { // IP-address of the peer-node in the Swarm cluster. // Example: 10.133.77.91 - IP string `json:"IP"` + IP netip.Addr `json:"IP"` } diff --git a/api/types/network/service_info.go b/api/types/network/service_info.go new file mode 100644 index 0000000000..fdd92f1611 --- /dev/null +++ b/api/types/network/service_info.go @@ -0,0 +1,28 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package network + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/netip" +) + +// ServiceInfo represents service parameters with the list of service's tasks +// +// swagger:model ServiceInfo +type ServiceInfo struct { + + // v IP + VIP netip.Addr `json:"VIP"` + + // ports + Ports []string `json:"Ports"` + + // local l b index + LocalLBIndex int `json:"LocalLBIndex"` + + // tasks + Tasks []Task `json:"Tasks"` +} diff --git a/api/types/network/task.go b/api/types/network/task.go index 9a55fa1737..a547523a44 100644 --- a/api/types/network/task.go +++ b/api/types/network/task.go @@ -5,6 +5,10 @@ package network // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command +import ( + "net/netip" +) + // Task carries the information about one backend task // // swagger:model Task @@ -17,7 +21,7 @@ type Task struct { EndpointID string `json:"EndpointID"` // endpoint IP - EndpointIP string `json:"EndpointIP"` + EndpointIP netip.Addr `json:"EndpointIP"` // info Info map[string]string `json:"Info"` diff --git a/daemon/cluster/convert/network.go b/daemon/cluster/convert/network.go index f615b6648a..55ce2b033c 100644 --- a/daemon/cluster/convert/network.go +++ b/daemon/cluster/convert/network.go @@ -1,6 +1,7 @@ package convert import ( + "maps" "net/netip" "strings" "time" @@ -9,9 +10,11 @@ import ( "github.com/moby/moby/api/types/network" types "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/v2/daemon/cluster/convert/netextra" + "github.com/moby/moby/v2/daemon/internal/netipstringer" "github.com/moby/moby/v2/daemon/internal/netiputil" "github.com/moby/moby/v2/daemon/internal/sliceutil" "github.com/moby/moby/v2/daemon/libnetwork/scope" + "github.com/moby/moby/v2/internal/iterutil" swarmapi "github.com/moby/swarmkit/v2/api" ) @@ -159,12 +162,22 @@ func BasicNetworkFromGRPC(n swarmapi.Network) network.Network { } ipam.Config = make([]network.IPAMConfig, 0, len(n.IPAM.Configs)) for _, ic := range n.IPAM.Configs { - ipam.Config = append(ipam.Config, network.IPAMConfig{ - Subnet: ic.Subnet, - IPRange: ic.Range, - Gateway: ic.Gateway, - AuxAddress: ic.Reserved, - }) + // Best-effort parse of user suppplied values that have + // been round-tripped through Swarm's Raft store. It is + // far too late to reject bogus values. + subnet, _ := netiputil.ParseCIDR(ic.Subnet) + iprange, _ := netiputil.ParseCIDR(ic.Range) + gw, _ := netip.ParseAddr(ic.Gateway) + cfg := network.IPAMConfig{ + Subnet: subnet.Masked(), + IPRange: iprange.Masked(), + Gateway: gw.Unmap(), + } + cfg.AuxAddress = maps.Collect(iterutil.Map2(maps.All(ic.Reserved), func(k, v string) (string, netip.Addr) { + addr, _ := netip.ParseAddr(v) + return k, addr.Unmap() + })) + ipam.Config = append(ipam.Config, cfg) } } @@ -239,9 +252,9 @@ func BasicNetworkCreateToGRPC(create network.CreateRequest) swarmapi.NetworkSpec ipamSpec := make([]*swarmapi.IPAMConfig, 0, len(create.IPAM.Config)) for _, ipamConfig := range create.IPAM.Config { ipamSpec = append(ipamSpec, &swarmapi.IPAMConfig{ - Subnet: ipamConfig.Subnet, - Range: ipamConfig.IPRange, - Gateway: ipamConfig.Gateway, + Subnet: netipstringer.Prefix(netiputil.Unmap(ipamConfig.Subnet).Masked()), + Range: netipstringer.Prefix(netiputil.Unmap(ipamConfig.IPRange).Masked()), + Gateway: netipstringer.Addr(ipamConfig.Gateway.Unmap()), }) } ns.IPAM.Configs = ipamSpec diff --git a/daemon/cluster/executor/container/container.go b/daemon/cluster/executor/container/container.go index bc851f7a1b..10e4d5d14a 100644 --- a/daemon/cluster/executor/container/container.go +++ b/daemon/cluster/executor/container/container.go @@ -6,6 +6,7 @@ import ( "fmt" "math" "net" + "net/netip" "strconv" "strings" @@ -21,6 +22,7 @@ import ( "github.com/moby/moby/v2/daemon/cluster/convert" executorpkg "github.com/moby/moby/v2/daemon/cluster/executor" clustertypes "github.com/moby/moby/v2/daemon/cluster/provider" + "github.com/moby/moby/v2/daemon/internal/netiputil" "github.com/moby/moby/v2/daemon/libnetwork/scope" "github.com/moby/swarmkit/v2/agent/exec" "github.com/moby/swarmkit/v2/api" @@ -548,20 +550,18 @@ func (c *containerConfig) createNetworkingConfig(b executorpkg.Backend) *network } func getEndpointConfig(na *api.NetworkAttachment, b executorpkg.Backend) *network.EndpointSettings { - var ipv4, ipv6 string + var ipv4, ipv6 netip.Addr for _, addr := range na.Addresses { - ip, _, err := net.ParseCIDR(addr) + pfx, err := netiputil.ParseCIDR(addr) if err != nil { continue } + ip := pfx.Addr() - if ip.To4() != nil { - ipv4 = ip.String() - continue - } - - if ip.To16() != nil { - ipv6 = ip.String() + if ip.Is4() { + ipv4 = ip + } else { + ipv6 = ip } } @@ -673,11 +673,18 @@ func networkCreateRequest(name string, nw *api.Network) clustertypes.NetworkCrea Options: nw.IPAM.Driver.Options, } for _, ic := range nw.IPAM.Configs { - req.IPAM.Config = append(req.IPAM.Config, network.IPAMConfig{ - Subnet: ic.Subnet, - IPRange: ic.Range, - Gateway: ic.Gateway, - }) + // The daemon validates the IPAM configs before creating + // the network in Swarm's Raft store, so these values + // should always either be empty strings or well-formed + // values. + cfg, err := ipamConfig(ic) + if err != nil { + log.G(context.TODO()).WithFields(log.Fields{ + "network": name, + "error": err, + }).Warn("invalid Swarm network IPAM config") + } + req.IPAM.Config = append(req.IPAM.Config, cfg) } } diff --git a/daemon/cluster/executor/container/executor.go b/daemon/cluster/executor/container/executor.go index 982e676b35..d575b52aa3 100644 --- a/daemon/cluster/executor/container/executor.go +++ b/daemon/cluster/executor/container/executor.go @@ -225,10 +225,9 @@ func (e *executor) Configure(ctx context.Context, node *api.Node) error { } for _, ic := range ingressNA.Network.IPAM.Configs { - c := network.IPAMConfig{ - Subnet: ic.Subnet, - IPRange: ic.Range, - Gateway: ic.Gateway, + c, err := ipamConfig(ic) + if err != nil { + swarmlog.G(ctx).WithError(err).Warn("invalid IPAM config for Swarm ingress network") } networkCreateRequest.IPAM.Config = append(networkCreateRequest.IPAM.Config, c) } diff --git a/daemon/cluster/executor/container/network.go b/daemon/cluster/executor/container/network.go new file mode 100644 index 0000000000..ddc32bd0ae --- /dev/null +++ b/daemon/cluster/executor/container/network.go @@ -0,0 +1,32 @@ +package container + +import ( + "errors" + "fmt" + + "github.com/moby/moby/api/types/network" + "github.com/moby/moby/v2/daemon/internal/netiputil" + "github.com/moby/swarmkit/v2/api" +) + +func ipamConfig(ic *api.IPAMConfig) (network.IPAMConfig, error) { + var ( + cfg network.IPAMConfig + errs []error + err error + ) + cfg.Subnet, err = netiputil.MaybeParseCIDR(ic.Subnet) + if err != nil { + errs = append(errs, fmt.Errorf("invalid subnet: %w", err)) + } + cfg.IPRange, err = netiputil.MaybeParseCIDR(ic.Range) + if err != nil { + errs = append(errs, fmt.Errorf("invalid ip range: %w", err)) + } + gw, err := netiputil.MaybeParseAddr(ic.Gateway) + cfg.Gateway = gw.Unmap() + if err != nil { + errs = append(errs, fmt.Errorf("invalid gateway: %w", err)) + } + return cfg, errors.Join(errs...) +} diff --git a/daemon/container_operations.go b/daemon/container_operations.go index 7bdd9fab4f..e43832859d 100644 --- a/daemon/container_operations.go +++ b/daemon/container_operations.go @@ -103,10 +103,10 @@ func buildSandboxOptions(cfg *config.Config, ctr *container.Container) ([]libnet return nil, errors.New("unable to derive the IP value for host-gateway") } for _, gip := range cfg.HostGatewayIPs { - sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(host, gip.String())) + sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(host, gip.Unmap())) } } else { - sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(host, ip)) + sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(host, netip.MustParseAddr(ip).Unmap())) } } @@ -297,11 +297,11 @@ func (daemon *Daemon) findAndAttachNetwork(ctr *container.Container, idOrName st var addresses []string if epConfig != nil && epConfig.IPAMConfig != nil { - if epConfig.IPAMConfig.IPv4Address != "" { - addresses = append(addresses, epConfig.IPAMConfig.IPv4Address) + if epConfig.IPAMConfig.IPv4Address.IsValid() { + addresses = append(addresses, epConfig.IPAMConfig.IPv4Address.Unmap().String()) } - if epConfig.IPAMConfig.IPv6Address != "" { - addresses = append(addresses, epConfig.IPAMConfig.IPv6Address) + if epConfig.IPAMConfig.IPv6Address.IsValid() { + addresses = append(addresses, epConfig.IPAMConfig.IPv6Address.Unmap().String()) } } @@ -544,7 +544,7 @@ func validateEndpointSettings(nw *libnetwork.Network, nwName string, epConfig *n // 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 != "" + hasStaticAddresses := ipamConfig.IPv4Address.IsValid() || ipamConfig.IPv6Address.IsValid() // On Linux, user specified IP address is accepted only by networks with user specified subnets. if hasStaticAddresses && !enableIPOnPredefinedNetwork() { errs = append(errs, cerrdefs.ErrInvalidArgument.WithMessage("user specified IP address is supported on user defined networks only")) @@ -554,7 +554,7 @@ func validateEndpointSettings(nw *libnetwork.Network, nwName string, epConfig *n } } - errs = validateEndpointIPAMConfig(errs, ipamConfig) + errs = normalizeEndpointIPAMConfig(errs, ipamConfig) if nw != nil { _, _, v4Configs, v6Configs := nw.IpamConfig() @@ -590,28 +590,34 @@ func validateEndpointSettings(nw *libnetwork.Network, nwName string, epConfig *n return nil } -// validateEndpointIPAMConfig checks whether cfg is valid. -func validateEndpointIPAMConfig(errs []error, cfg *networktypes.EndpointIPAMConfig) []error { +// normalizeEndpointIPAMConfig checks whether cfg is valid and normalizes cfg in-place. +func normalizeEndpointIPAMConfig(errs []error, cfg *networktypes.EndpointIPAMConfig) []error { if cfg == nil { return errs } - if cfg.IPv4Address != "" { - if addr := net.ParseIP(cfg.IPv4Address); addr == nil || addr.To4() == nil || addr.IsUnspecified() { + if cfg.IPv4Address.IsValid() { + if !cfg.IPv4Address.Is4() && !cfg.IPv4Address.Is4In6() || cfg.IPv4Address.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() { + if cfg.IPv6Address.IsValid() { + if !cfg.IPv6Address.Is6() || cfg.IPv6Address.Is4In6() || cfg.IPv6Address.IsUnspecified() || cfg.IPv6Address.Zone() != "" { 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() { + if !addr.IsValid() || addr.IsUnspecified() { errs = append(errs, fmt.Errorf("invalid link-local IP address: %s", addr)) } } + cfg.IPv4Address = cfg.IPv4Address.Unmap() + cfg.IPv6Address = cfg.IPv6Address.Unmap() + for i, addr := range cfg.LinkLocalIPs { + cfg.LinkLocalIPs[i] = addr.Unmap() + } + return errs } @@ -626,17 +632,16 @@ func validateIPAMConfigIsInRange(errs []error, cfg *networktypes.EndpointIPAMCon return errs } -func validateEndpointIPAddress(epAddr string, ipamSubnets []*libnetwork.IpamConf) error { - if epAddr == "" { +func validateEndpointIPAddress(epAddr netip.Addr, ipamSubnets []*libnetwork.IpamConf) error { + if !epAddr.IsValid() { return nil } var staticSubnet bool - parsedAddr := net.ParseIP(epAddr) for _, subnet := range ipamSubnets { if subnet.IsStatic() { staticSubnet = true - if subnet.Contains(parsedAddr) { + if subnet.Contains(epAddr) { return nil } } @@ -652,11 +657,11 @@ func validateEndpointIPAddress(epAddr string, ipamSubnets []*libnetwork.IpamConf // cleanOperationalData resets the operational data from the passed endpoint settings func cleanOperationalData(es *network.EndpointSettings) { es.EndpointID = "" - es.Gateway = "" - es.IPAddress = "" + es.Gateway = netip.Addr{} + es.IPAddress = netip.Addr{} es.IPPrefixLen = 0 - es.IPv6Gateway = "" - es.GlobalIPv6Address = "" + es.IPv6Gateway = netip.Addr{} + es.GlobalIPv6Address = netip.Addr{} es.GlobalIPv6PrefixLen = 0 es.MacAddress = "" if es.IPAMOperational { @@ -743,7 +748,7 @@ func (daemon *Daemon) connectToNetwork(ctx context.Context, cfg *config.Config, endpointConfig.IPAMOperational = false if nwCfg != nil { if epConfig, ok := nwCfg.EndpointsConfig[nwName]; ok { - if endpointConfig.IPAMConfig == nil || (endpointConfig.IPAMConfig.IPv4Address == "" && endpointConfig.IPAMConfig.IPv6Address == "" && len(endpointConfig.IPAMConfig.LinkLocalIPs) == 0) { + if endpointConfig.IPAMConfig == nil || (!endpointConfig.IPAMConfig.IPv4Address.IsValid() && !endpointConfig.IPAMConfig.IPv6Address.IsValid() && len(endpointConfig.IPAMConfig.LinkLocalIPs) == 0) { endpointConfig.IPAMOperational = true } @@ -855,11 +860,11 @@ func updateJoinInfo(networkSettings *network.Settings, n *libnetwork.Network, ep // It is not an error to get an empty endpoint info return nil } - if epInfo.Gateway() != nil { - networkSettings.Networks[n.Name()].Gateway = epInfo.Gateway().String() + if gw, ok := netip.AddrFromSlice(epInfo.Gateway()); ok { + networkSettings.Networks[n.Name()].Gateway = gw.Unmap() } - if epInfo.GatewayIPv6().To16() != nil { - networkSettings.Networks[n.Name()].IPv6Gateway = epInfo.GatewayIPv6().String() + if gw6, ok := netip.AddrFromSlice(epInfo.GatewayIPv6()); ok && gw6.Is6() { + networkSettings.Networks[n.Name()].IPv6Gateway = gw6 } return nil } diff --git a/daemon/container_operations_test.go b/daemon/container_operations_test.go index df4279e3fd..bf58594003 100644 --- a/daemon/container_operations_test.go +++ b/daemon/container_operations_test.go @@ -3,6 +3,7 @@ package daemon import ( "encoding/json" "errors" + "net/netip" "testing" containertypes "github.com/moby/moby/api/types/container" @@ -67,9 +68,9 @@ func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) { { name: "valid config", ipamConfig: &networktypes.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"}, + IPv4Address: netip.MustParseAddr("192.168.100.10"), + IPv6Address: netip.MustParseAddr("2a01:d2:af:420b:25c1:1816:bb33:855c"), + LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.169.254"), netip.MustParseAddr("fe80::42:a8ff:fe33:6230")}, }, v4Subnets: []*libnetwork.IpamConf{ {PreferredPool: "192.168.100.0/24"}, @@ -81,8 +82,8 @@ func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) { { name: "static addresses out of range", ipamConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "192.168.100.10", - IPv6Address: "2a01:d2:af:420b:25c1:1816:bb33:855c", + IPv4Address: netip.MustParseAddr("192.168.100.10"), + IPv6Address: netip.MustParseAddr("2a01:d2:af:420b:25c1:1816:bb33:855c"), }, v4Subnets: []*libnetwork.IpamConf{ {PreferredPool: "192.168.255.0/24"}, @@ -98,8 +99,8 @@ func TestEndpointIPAMConfigWithOutOfRangeAddrs(t *testing.T) { { name: "static addresses with dynamic network subnets", ipamConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "192.168.100.10", - IPv6Address: "2a01:d2:af:420b:25c1:1816:bb33:855c", + IPv4Address: netip.MustParseAddr("192.168.100.10"), + IPv6Address: netip.MustParseAddr("2a01:d2:af:420b:25c1:1816:bb33:855c"), }, v4Subnets: []*libnetwork.IpamConf{ {}, @@ -141,38 +142,42 @@ func TestEndpointIPAMConfigWithInvalidConfig(t *testing.T) { { name: "valid config", ipamConfig: &networktypes.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"}, + IPv4Address: netip.MustParseAddr("192.168.100.10"), + IPv6Address: netip.MustParseAddr("2a01:d2:af:420b:25c1:1816:bb33:855c"), + LinkLocalIPs: []netip.Addr{netip.MustParseAddr("169.254.169.254"), netip.MustParseAddr("fe80::42:a8ff:fe33:6230")}, }, }, { name: "invalid IP addresses", ipamConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "foo", - IPv6Address: "bar", - LinkLocalIPs: []string{"baz", "foobar"}, + IPv4Address: netip.MustParseAddr("2001::1"), + IPv6Address: netip.MustParseAddr("1.2.3.4"), }, expectedErrors: []string{ - "invalid IPv4 address: foo", - "invalid IPv6 address: bar", - "invalid link-local IP address: baz", - "invalid link-local IP address: foobar", + "invalid IPv4 address: 2001::1", + "invalid IPv6 address: 1.2.3.4", }, }, { name: "ipv6 address with a zone", - ipamConfig: &networktypes.EndpointIPAMConfig{IPv6Address: "fe80::1cc0:3e8c:119f:c2e1%ens18"}, + ipamConfig: &networktypes.EndpointIPAMConfig{IPv6Address: netip.MustParseAddr("fe80::1cc0:3e8c:119f:c2e1%ens18")}, expectedErrors: []string{ "invalid IPv6 address: fe80::1cc0:3e8c:119f:c2e1%ens18", }, }, + { + name: "ipv6-mapped ipv4 address", + ipamConfig: &networktypes.EndpointIPAMConfig{IPv6Address: netip.MustParseAddr("::ffff:192.168.100.10")}, + expectedErrors: []string{ + "invalid IPv6 address: ::ffff:192.168.100.10", + }, + }, { name: "unspecified address is invalid", ipamConfig: &networktypes.EndpointIPAMConfig{ - IPv4Address: "0.0.0.0", - IPv6Address: "::", - LinkLocalIPs: []string{"0.0.0.0", "::"}, + IPv4Address: netip.IPv4Unspecified(), + IPv6Address: netip.IPv6Unspecified(), + LinkLocalIPs: []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified()}, }, expectedErrors: []string{ "invalid IPv4 address: 0.0.0.0", @@ -184,7 +189,7 @@ func TestEndpointIPAMConfigWithInvalidConfig(t *testing.T) { { name: "empty link-local", ipamConfig: &networktypes.EndpointIPAMConfig{ - LinkLocalIPs: []string{""}, + LinkLocalIPs: make([]netip.Addr, 1), }, expectedErrors: []string{"invalid link-local IP address:"}, }, @@ -192,7 +197,7 @@ func TestEndpointIPAMConfigWithInvalidConfig(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - errs := validateEndpointIPAMConfig(nil, tc.ipamConfig) + errs := normalizeEndpointIPAMConfig(nil, tc.ipamConfig) if tc.expectedErrors == nil { assert.NilError(t, errors.Join(errs...)) return diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index abe656063c..146025ff89 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -14,6 +14,7 @@ import ( "github.com/containerd/log" "github.com/moby/moby/v2/daemon/config" "github.com/moby/moby/v2/daemon/container" + "github.com/moby/moby/v2/daemon/internal/netipstringer" "github.com/moby/moby/v2/daemon/internal/stringid" "github.com/moby/moby/v2/daemon/libnetwork" "github.com/moby/moby/v2/daemon/libnetwork/drivers/bridge" @@ -50,8 +51,8 @@ func (daemon *Daemon) setupLinkedContainers(ctr *container.Container) ([]string, // Allow users to restore the old behavior through this escape hatch. if os.Getenv("DOCKER_KEEP_DEPRECATED_LEGACY_LINKS_ENV_VARS") == "1" { linkEnvVars := links.EnvVars( - bridgeSettings.IPAddress, - childBridgeSettings.IPAddress, + netipstringer.Addr(bridgeSettings.IPAddress.Unmap()), + netipstringer.Addr(childBridgeSettings.IPAddress.Unmap()), linkAlias, child.Config.Env, child.Config.ExposedPorts, @@ -104,12 +105,12 @@ func (daemon *Daemon) addLegacyLinks( aliasList = aliasList + " " + child.Name[1:] } defaultNW := child.NetworkSettings.Networks[network.DefaultNetwork] - if defaultNW.IPAddress != "" { - if err := sb.AddHostsEntry(ctx, aliasList, defaultNW.IPAddress); err != nil { + if defaultNW.IPAddress.IsValid() { + if err := sb.AddHostsEntry(ctx, aliasList, defaultNW.IPAddress.Unmap()); err != nil { return errors.Wrapf(err, "failed to add address to /etc/hosts for link to %s", child.Name) } } - if defaultNW.GlobalIPv6Address != "" { + if defaultNW.GlobalIPv6Address.IsValid() { if err := sb.AddHostsEntry(ctx, aliasList, defaultNW.GlobalIPv6Address); err != nil { return errors.Wrapf(err, "failed to add IPv6 address to /etc/hosts for link to %s", child.Name) } @@ -130,7 +131,7 @@ func (daemon *Daemon) addLegacyLinks( return errors.Wrapf(err, "failed to update /etc/hosts of %s for alias %s with IP %s", parent.ID, alias, epConfig.IPAddress) } - if epConfig.GlobalIPv6Address != "" { + if epConfig.GlobalIPv6Address.IsValid() { if err := psb.UpdateHostsEntry(alias, epConfig.GlobalIPv6Address); err != nil { return errors.Wrapf(err, "failed to update /etc/hosts of %s for alias %s with IP %s", parent.ID, alias, epConfig.GlobalIPv6Address) diff --git a/daemon/internal/netipstringer/stringer.go b/daemon/internal/netipstringer/stringer.go new file mode 100644 index 0000000000..8fe7b0829f --- /dev/null +++ b/daemon/internal/netipstringer/stringer.go @@ -0,0 +1,25 @@ +// Package netipstringer provides utilities to convert netip types to strings +// which return the empty string for invalid values. +package netipstringer + +import ( + "net/netip" +) + +// Addr returns the string representation of addr. +// The empty string is returned if addr is not valid. +func Addr(addr netip.Addr) string { + if !addr.IsValid() { + return "" + } + return addr.String() +} + +// Prefix returns the string representation of prefix. +// The empty string is returned if prefix is not valid. +func Prefix(prefix netip.Prefix) string { + if !prefix.IsValid() { + return "" + } + return prefix.String() +} diff --git a/daemon/internal/netiputil/netiputil.go b/daemon/internal/netiputil/netiputil.go index a88c0183d3..0a24d2869a 100644 --- a/daemon/internal/netiputil/netiputil.go +++ b/daemon/internal/netiputil/netiputil.go @@ -109,3 +109,21 @@ func ParseCIDR(s string) (netip.Prefix, error) { prefix, err := netip.ParsePrefix(s) return Unmap(prefix), err } + +// MaybeParse decorates a parse function to return no error when parsing the +// empty string. +func MaybeParse[T any](parse func(string) (T, error)) func(string) (T, error) { + return func(s string) (T, error) { + var zero T + if s == "" { + return zero, nil + } + return parse(s) + } +} + +var ( + MaybeParseAddr = MaybeParse(netip.ParseAddr) + MaybeParsePrefix = MaybeParse(netip.ParsePrefix) + MaybeParseCIDR = MaybeParse(ParseCIDR) +) diff --git a/daemon/internal/netiputil/netiputil_test.go b/daemon/internal/netiputil/netiputil_test.go index 876aefcd4d..376e73b940 100644 --- a/daemon/internal/netiputil/netiputil_test.go +++ b/daemon/internal/netiputil/netiputil_test.go @@ -6,6 +6,7 @@ import ( "testing" "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" ) func TestLastAddr(t *testing.T) { @@ -70,3 +71,19 @@ func TestParseCIDR(t *testing.T) { assert.Check(t, err != nil, "expected error for invalid input") assert.Check(t, !got.IsValid(), "expected invalid result for invalid input") } + +func TestMaybeParse(t *testing.T) { + addr := MaybeParse(netip.ParseAddr) + got, err := addr("") + assert.Check(t, err) + assert.Check(t, !got.IsValid()) + + got, err = addr("bogus") + assert.Check(t, err != nil) + assert.Check(t, !got.IsValid()) + + got, err = addr("1.2.3.4") + if assert.Check(t, err) { + assert.Check(t, is.Equal(got, netip.MustParseAddr("1.2.3.4"))) + } +} diff --git a/daemon/libnetwork/cmd/diagnostic/main.go b/daemon/libnetwork/cmd/diagnostic/main.go index 0ecccbb0ca..baa18b70fb 100644 --- a/daemon/libnetwork/cmd/diagnostic/main.go +++ b/daemon/libnetwork/cmd/diagnostic/main.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "net/http" + "net/netip" "os" "strings" @@ -67,7 +68,7 @@ func main() { httpIsOk(resp.Body) clusterPeers := fetchNodePeers(*ipPtr, *portPtr, "") - var networkPeers map[string]string + var networkPeers map[string]netip.Addr var joinedNetwork bool if *networkPtr != "" { if *joinPtr { @@ -103,7 +104,7 @@ func main() { } } -func fetchNodePeers(ip string, port int, network string) map[string]string { +func fetchNodePeers(ip string, port int, network string) map[string]netip.Addr { if network == "" { log.G(context.TODO()).Infof("Fetch cluster peers") } else { @@ -134,7 +135,7 @@ func fetchNodePeers(ip string, port int, network string) map[string]string { } log.G(context.TODO()).Debugf("Parsing JSON response") - result := make(map[string]string, output.Details.(*diagnostic.TablePeersResult).Length) + result := make(map[string]netip.Addr, output.Details.(*diagnostic.TablePeersResult).Length) for _, v := range output.Details.(*diagnostic.TablePeersResult).Elements { log.G(context.TODO()).Debugf("name:%s ip:%s", v.Name, v.IP) result[v.Name] = v.IP @@ -142,7 +143,7 @@ func fetchNodePeers(ip string, port int, network string) map[string]string { return result } -func fetchTable(ip string, port int, network, tableName string, clusterPeers, networkPeers map[string]string, remediate bool) { +func fetchTable(ip string, port int, network, tableName string, clusterPeers, networkPeers map[string]netip.Addr, remediate bool) { log.G(context.TODO()).Infof("Fetch %s table and check owners", tableName) resp, err := http.Get(fmt.Sprintf(dumpTable, ip, port, network, tableName)) if err != nil { diff --git a/daemon/libnetwork/diagnostic/types.go b/daemon/libnetwork/diagnostic/types.go index e6b4831263..a800d4464b 100644 --- a/daemon/libnetwork/diagnostic/types.go +++ b/daemon/libnetwork/diagnostic/types.go @@ -1,6 +1,9 @@ package diagnostic -import "fmt" +import ( + "fmt" + "net/netip" +) // StringInterface interface that has to be implemented by messages type StringInterface interface { @@ -88,9 +91,9 @@ func (t *TableObj) String() string { // PeerEntryObj entry in the networkdb peer table type PeerEntryObj struct { - Index int `json:"-"` - Name string `json:"-=name"` - IP string `json:"ip"` + Index int `json:"-"` + Name string `json:"-=name"` + IP netip.Addr `json:"ip"` } func (p *PeerEntryObj) String() string { diff --git a/daemon/libnetwork/driverapi/ipamdata.go b/daemon/libnetwork/driverapi/ipamdata.go index ecf0d7f161..f7a89a59eb 100644 --- a/daemon/libnetwork/driverapi/ipamdata.go +++ b/daemon/libnetwork/driverapi/ipamdata.go @@ -3,9 +3,14 @@ package driverapi import ( "encoding/json" "fmt" + "maps" "net" + "net/netip" + "github.com/moby/moby/api/types/network" + "github.com/moby/moby/v2/daemon/internal/netiputil" "github.com/moby/moby/v2/daemon/libnetwork/types" + "github.com/moby/moby/v2/internal/iterutil" ) // MarshalJSON encodes IPAMData into json message @@ -101,3 +106,19 @@ func (i *IPAMData) IsV6() bool { func (i *IPAMData) String() string { return fmt.Sprintf("AddressSpace: %s\nPool: %v\nGateway: %v\nAddresses: %v", i.AddressSpace, i.Pool, i.Gateway, i.AuxAddresses) } + +func (i *IPAMData) IPAMConfig() network.IPAMConfig { + var c network.IPAMConfig + c.Subnet, _ = netiputil.ToPrefix(i.Pool) + if i.Gateway != nil { + gw, _ := netip.AddrFromSlice(i.Gateway.IP) + c.Gateway = gw.Unmap() + } + if i.AuxAddresses != nil { + c.AuxAddress = maps.Collect(iterutil.Map2(maps.All(i.AuxAddresses), func(k string, v *net.IPNet) (string, netip.Addr) { + a, _ := netip.AddrFromSlice(v.IP) + return k, a.Unmap() + })) + } + return c +} diff --git a/daemon/libnetwork/endpoint_info.go b/daemon/libnetwork/endpoint_info.go index ff0f32d020..2036d08eb4 100644 --- a/daemon/libnetwork/endpoint_info.go +++ b/daemon/libnetwork/endpoint_info.go @@ -4,8 +4,10 @@ import ( "encoding/json" "fmt" "net" + "net/netip" "slices" + "github.com/moby/moby/v2/daemon/internal/netiputil" "github.com/moby/moby/v2/daemon/libnetwork/driverapi" "github.com/moby/moby/v2/daemon/libnetwork/types" ) @@ -265,11 +267,21 @@ func (epi *EndpointInterface) Address() *net.IPNet { return types.GetIPNetCopy(epi.addr) } +func (epi *EndpointInterface) Addr() netip.Prefix { + p, _ := netiputil.ToPrefix(epi.addr) + return p +} + // AddressIPv6 returns the IPv6 address assigned to the endpoint. func (epi *EndpointInterface) AddressIPv6() *net.IPNet { return types.GetIPNetCopy(epi.addrv6) } +func (epi *EndpointInterface) AddrIPv6() netip.Prefix { + p, _ := netiputil.ToPrefix(epi.addrv6) + return p +} + // LinkLocalAddresses returns the list of link-local (IPv4/IPv6) addresses assigned to the endpoint. func (epi *EndpointInterface) LinkLocalAddresses() []*net.IPNet { return epi.llAddrs diff --git a/daemon/libnetwork/internal/resolvconf/resolvconf_test.go b/daemon/libnetwork/internal/resolvconf/resolvconf_test.go index e0fdfc2540..4bf0b3f69c 100644 --- a/daemon/libnetwork/internal/resolvconf/resolvconf_test.go +++ b/daemon/libnetwork/internal/resolvconf/resolvconf_test.go @@ -213,7 +213,7 @@ func TestRCModify(t *testing.T) { } rc, err := Parse(bytes.NewBufferString(input), "") assert.NilError(t, err) - assert.Check(t, is.DeepEqual(a2s(rc.NameServers()), tc.inputNS)) + assert.Check(t, is.DeepEqual(a2s(rc.NameServers()), tc.inputNS, cmpopts.EquateEmpty())) assert.Check(t, is.DeepEqual(rc.Search(), tc.inputSearch)) assert.Check(t, is.DeepEqual(rc.Options(), tc.inputOptions)) diff --git a/daemon/libnetwork/libnetwork_linux_test.go b/daemon/libnetwork/libnetwork_linux_test.go index f7f1c56922..39478be1f1 100644 --- a/daemon/libnetwork/libnetwork_linux_test.go +++ b/daemon/libnetwork/libnetwork_linux_test.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "net/http/httptest" + "net/netip" "os" "os/exec" "path/filepath" @@ -85,7 +86,7 @@ func TestNull(t *testing.T) { cnt, err := controller.NewSandbox(context.Background(), "null_container", libnetwork.OptionHostname("test"), libnetwork.OptionDomainname("example.com"), - libnetwork.OptionExtraHost("web", "192.168.0.1")) + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.1"))) assert.NilError(t, err) network, err := createTestNetwork(controller, "null", "testnull", options.Generic{}, nil, nil) @@ -657,7 +658,7 @@ func TestEndpointDeleteWithActiveContainer(t *testing.T) { cnt, err := controller.NewSandbox(context.Background(), containerID, libnetwork.OptionHostname("test"), libnetwork.OptionDomainname("example.com"), - libnetwork.OptionExtraHost("web", "192.168.0.1")) + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.1"))) assert.NilError(t, err) defer func() { assert.Check(t, cnt.Delete(context.Background())) @@ -702,7 +703,7 @@ func TestEndpointMultipleJoins(t *testing.T) { sbx1, err := controller.NewSandbox(context.Background(), containerID, libnetwork.OptionHostname("test"), libnetwork.OptionDomainname("example.com"), - libnetwork.OptionExtraHost("web", "192.168.0.1"), + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.1")), ) assert.NilError(t, err) defer func() { @@ -797,7 +798,7 @@ func TestContainerInvalidLeave(t *testing.T) { cnt, err := controller.NewSandbox(context.Background(), containerID, libnetwork.OptionHostname("test"), libnetwork.OptionDomainname("example.com"), - libnetwork.OptionExtraHost("web", "192.168.0.1")) + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.1"))) assert.NilError(t, err) defer func() { assert.Check(t, cnt.Delete(context.Background())) @@ -845,7 +846,7 @@ func TestEndpointUpdateParent(t *testing.T) { sbx1, err := controller.NewSandbox(context.Background(), containerID, libnetwork.OptionHostname("test"), libnetwork.OptionDomainname("example.com"), - libnetwork.OptionExtraHost("web", "192.168.0.1")) + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.1"))) assert.NilError(t, err) defer func() { assert.Check(t, sbx1.Delete(context.Background())) @@ -855,7 +856,7 @@ func TestEndpointUpdateParent(t *testing.T) { libnetwork.OptionHostname("test2"), libnetwork.OptionDomainname("example.com"), libnetwork.OptionHostsPath("/var/lib/docker/test_network/container2/hosts"), - libnetwork.OptionExtraHost("web", "192.168.0.2")) + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.2"))) assert.NilError(t, err) defer func() { assert.Check(t, sbx2.Delete(context.Background())) @@ -979,7 +980,7 @@ func TestHost(t *testing.T) { sbx1, err := controller.NewSandbox(context.Background(), "host_c1", libnetwork.OptionHostname("test1"), libnetwork.OptionDomainname("example.com"), - libnetwork.OptionExtraHost("web", "192.168.0.1"), + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.1")), libnetwork.OptionUseDefaultSandbox()) assert.NilError(t, err) defer func() { @@ -989,7 +990,7 @@ func TestHost(t *testing.T) { sbx2, err := controller.NewSandbox(context.Background(), "host_c2", libnetwork.OptionHostname("test2"), libnetwork.OptionDomainname("example.com"), - libnetwork.OptionExtraHost("web", "192.168.0.1"), + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.1")), libnetwork.OptionUseDefaultSandbox()) assert.NilError(t, err) defer func() { @@ -1025,7 +1026,7 @@ func TestHost(t *testing.T) { cnt3, err := controller.NewSandbox(context.Background(), "host_c3", libnetwork.OptionHostname("test3"), libnetwork.OptionDomainname("example.com"), - libnetwork.OptionExtraHost("web", "192.168.0.1"), + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.1")), libnetwork.OptionUseDefaultSandbox()) assert.NilError(t, err) defer func() { @@ -1126,7 +1127,7 @@ func TestEndpointJoin(t *testing.T) { sb, err := controller.NewSandbox(context.Background(), containerID, libnetwork.OptionHostname("test"), libnetwork.OptionDomainname("example.com"), - libnetwork.OptionExtraHost("web", "192.168.0.1")) + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.1"))) assert.NilError(t, err) defer func() { assert.Check(t, sb.Delete(context.Background())) @@ -1229,7 +1230,7 @@ func externalKeyTest(t *testing.T, reexec bool) { libnetwork.OptionHostname("test"), libnetwork.OptionDomainname("example.com"), libnetwork.OptionUseExternalKey(), - libnetwork.OptionExtraHost("web", "192.168.0.1")) + libnetwork.OptionExtraHost("web", netip.MustParseAddr("192.168.0.1"))) assert.NilError(t, err) defer func() { assert.Check(t, cnt.Delete(context.Background())) diff --git a/daemon/libnetwork/network.go b/daemon/libnetwork/network.go index 3a1275163c..9dabf00788 100644 --- a/daemon/libnetwork/network.go +++ b/daemon/libnetwork/network.go @@ -108,7 +108,7 @@ func (c *IpamConf) Validate() error { } // Contains checks whether the ipam master address pool contains [addr]. -func (c *IpamConf) Contains(addr net.IP) bool { +func (c *IpamConf) Contains(addr netip.Addr) bool { if c == nil { return false } @@ -116,7 +116,7 @@ func (c *IpamConf) Contains(addr net.IP) bool { return false } - _, allowedRange, _ := net.ParseCIDR(c.PreferredPool) + allowedRange, _ := netiputil.ParseCIDR(c.PreferredPool) return allowedRange.Contains(addr) } @@ -126,6 +126,30 @@ func (c *IpamConf) IsStatic() bool { return c != nil && c.PreferredPool != "" } +func (c *IpamConf) IPAMConfig() network.IPAMConfig { + if c == nil { + return network.IPAMConfig{} + } + + subnet, _ := netiputil.ParseCIDR(c.PreferredPool) + ipr, _ := netiputil.ParseCIDR(c.SubPool) + gw, _ := netip.ParseAddr(c.Gateway) + + conf := network.IPAMConfig{ + Subnet: subnet.Masked(), + IPRange: ipr.Masked(), + Gateway: gw.Unmap(), + } + + if c.AuxAddresses != nil { + conf.AuxAddress = maps.Collect(iterutil.Map2(maps.All(c.AuxAddresses), func(k, v string) (string, netip.Addr) { + a, _ := netip.ParseAddr(v) + return k, a.Unmap() + })) + } + return conf +} + // IpamInfo contains all the ipam related operational info for a network type IpamInfo struct { PoolID string diff --git a/daemon/libnetwork/networkdb/networkdb.go b/daemon/libnetwork/networkdb/networkdb.go index fffdaee838..34821d5a8f 100644 --- a/daemon/libnetwork/networkdb/networkdb.go +++ b/daemon/libnetwork/networkdb/networkdb.go @@ -7,6 +7,7 @@ import ( cryptorand "crypto/rand" "fmt" "math/rand/v2" + "net/netip" "os" "strings" "sync" @@ -120,7 +121,7 @@ type NetworkDB struct { // PeerInfo represents the peer (gossip cluster) nodes of a network type PeerInfo struct { Name string - IP string + IP netip.Addr } // PeerClusterInfo represents the peer (gossip cluster) nodes @@ -341,9 +342,10 @@ func (nDB *NetworkDB) ClusterPeers() []PeerInfo { defer nDB.RUnlock() peers := make([]PeerInfo, 0, len(nDB.nodes)) for _, node := range nDB.nodes { + ip, _ := netip.AddrFromSlice(node.Node.Addr) peers = append(peers, PeerInfo{ Name: node.Name, - IP: node.Node.Addr.String(), + IP: ip.Unmap(), }) } return peers @@ -356,14 +358,15 @@ func (nDB *NetworkDB) Peers(nid string) []PeerInfo { peers := make([]PeerInfo, 0, len(nDB.networkNodes[nid])) for _, nodeName := range nDB.networkNodes[nid] { if node, ok := nDB.nodes[nodeName]; ok { + ip, _ := netip.AddrFromSlice(node.Node.Addr) peers = append(peers, PeerInfo{ Name: node.Name, - IP: node.Addr.String(), + IP: ip.Unmap(), }) } else { // Added for testing purposes, this condition should never happen else mean that the network list // is out of sync with the node list - peers = append(peers, PeerInfo{Name: nodeName, IP: "unknown"}) + peers = append(peers, PeerInfo{Name: nodeName}) } } return peers diff --git a/daemon/libnetwork/networkdb/networkdbdiagnostic.go b/daemon/libnetwork/networkdb/networkdbdiagnostic.go index 501d8165a7..e977b97732 100644 --- a/daemon/libnetwork/networkdb/networkdbdiagnostic.go +++ b/daemon/libnetwork/networkdb/networkdbdiagnostic.go @@ -92,7 +92,7 @@ func (nDB *NetworkDB) dbPeers(w http.ResponseWriter, r *http.Request) { peers := nDB.Peers(r.Form["nid"][0]) rsp := &diagnostic.TableObj{Length: len(peers)} for i, peerInfo := range peers { - if peerInfo.IP == "unknown" { + if !peerInfo.IP.IsValid() { rsp.Elements = append(rsp.Elements, &diagnostic.PeerEntryObj{Index: i, Name: "orphan-" + peerInfo.Name, IP: peerInfo.IP}) } else { rsp.Elements = append(rsp.Elements, &diagnostic.PeerEntryObj{Index: i, Name: peerInfo.Name, IP: peerInfo.IP}) diff --git a/daemon/libnetwork/sandbox.go b/daemon/libnetwork/sandbox.go index af19bc2ce2..4d0236f2e4 100644 --- a/daemon/libnetwork/sandbox.go +++ b/daemon/libnetwork/sandbox.go @@ -81,7 +81,7 @@ type hostsPathConfig struct { type extraHost struct { name string - IP string + IP netip.Addr } // These are the container configs used to customize container /etc/resolv.conf file. diff --git a/daemon/libnetwork/sandbox_dns_unix.go b/daemon/libnetwork/sandbox_dns_unix.go index 2c5aad9954..a5aac066e9 100644 --- a/daemon/libnetwork/sandbox_dns_unix.go +++ b/daemon/libnetwork/sandbox_dns_unix.go @@ -4,7 +4,6 @@ package libnetwork import ( "context" - "fmt" "io/fs" "net/netip" "os" @@ -29,15 +28,15 @@ const ( ) // AddHostsEntry adds an entry to /etc/hosts. -func (sb *Sandbox) AddHostsEntry(ctx context.Context, name, ip string) error { +func (sb *Sandbox) AddHostsEntry(ctx context.Context, name string, ip netip.Addr) error { sb.config.extraHosts = append(sb.config.extraHosts, extraHost{name: name, IP: ip}) return sb.rebuildHostsFile(ctx) } // UpdateHostsEntry updates the IP address in a /etc/hosts entry where the // name matches the regular expression regexp. -func (sb *Sandbox) UpdateHostsEntry(regexp, ip string) error { - return etchosts.Update(sb.config.hostsPath, ip, regexp) +func (sb *Sandbox) UpdateHostsEntry(regexp string, ip netip.Addr) error { + return etchosts.Update(sb.config.hostsPath, ip.String(), regexp) } // rebuildHostsFile builds the container's /etc/hosts file, based on the current @@ -142,11 +141,7 @@ func (sb *Sandbox) buildHostsFile(ctx context.Context, ifaceIPs []netip.Addr) er extraContent := make([]etchosts.Record, 0, len(sb.config.extraHosts)+len(ifaceIPs)) for _, host := range sb.config.extraHosts { - addr, err := netip.ParseAddr(host.IP) - if err != nil { - return errdefs.InvalidParameter(fmt.Errorf("could not parse extra host IP %s: %v", host.IP, err)) - } - extraContent = append(extraContent, etchosts.Record{Hosts: host.name, IP: addr}) + extraContent = append(extraContent, etchosts.Record{Hosts: host.name, IP: host.IP}) } extraContent = append(extraContent, sb.makeHostsRecs(ifaceIPs)...) diff --git a/daemon/libnetwork/sandbox_options.go b/daemon/libnetwork/sandbox_options.go index cf25b38347..ba05582dec 100644 --- a/daemon/libnetwork/sandbox_options.go +++ b/daemon/libnetwork/sandbox_options.go @@ -42,7 +42,7 @@ func OptionOriginHostsPath(path string) SandboxOption { // OptionExtraHost function returns an option setter for extra /etc/hosts options // which is a name and IP as strings. -func OptionExtraHost(name string, IP string) SandboxOption { +func OptionExtraHost(name string, IP netip.Addr) SandboxOption { return func(sb *Sandbox) { sb.config.extraHosts = append(sb.config.extraHosts, extraHost{name: name, IP: IP}) } diff --git a/daemon/network.go b/daemon/network.go index 1d12f0620a..8bef13a5d5 100644 --- a/daemon/network.go +++ b/daemon/network.go @@ -19,6 +19,8 @@ import ( "github.com/moby/moby/v2/daemon/config" "github.com/moby/moby/v2/daemon/container" "github.com/moby/moby/v2/daemon/internal/multierror" + "github.com/moby/moby/v2/daemon/internal/netipstringer" + "github.com/moby/moby/v2/daemon/internal/netiputil" "github.com/moby/moby/v2/daemon/internal/otelutil" "github.com/moby/moby/v2/daemon/libnetwork" lncluster "github.com/moby/moby/v2/daemon/libnetwork/cluster" @@ -32,6 +34,7 @@ import ( "github.com/moby/moby/v2/daemon/pkg/opts" "github.com/moby/moby/v2/daemon/server/backend" "github.com/moby/moby/v2/errdefs" + "github.com/moby/moby/v2/internal/iterutil" "github.com/moby/moby/v2/pkg/plugingetter" "go.opentelemetry.io/otel/baggage" ) @@ -447,13 +450,8 @@ func (daemon *Daemon) pluginRefCount(driver, capability string, mode int) { func validateIpamConfig(data []networktypes.IPAMConfig, enableIPv6 bool) error { var errs []error for _, cfg := range data { - subnet, err := netip.ParsePrefix(cfg.Subnet) - if err != nil { - errs = append(errs, fmt.Errorf("invalid subnet %s: invalid CIDR block notation", cfg.Subnet)) - continue - } subnetFamily := 4 - if subnet.Addr().Is6() { + if cfg.Subnet.Addr().Is6() { subnetFamily = 6 } @@ -461,20 +459,20 @@ func validateIpamConfig(data []networktypes.IPAMConfig, enableIPv6 bool) error { continue } - if subnet != subnet.Masked() { - errs = append(errs, fmt.Errorf("invalid subnet %s: it should be %s", subnet, subnet.Masked())) + if cfg.Subnet != cfg.Subnet.Masked() { + errs = append(errs, fmt.Errorf("invalid subnet %s: it should be %s", cfg.Subnet, cfg.Subnet.Masked())) } - if ipRangeErrs := validateIPRange(cfg.IPRange, subnet, subnetFamily); len(ipRangeErrs) > 0 { + if ipRangeErrs := validateIPRange(cfg.IPRange, cfg.Subnet, subnetFamily); len(ipRangeErrs) > 0 { errs = append(errs, ipRangeErrs...) } - if err := validateAddress(cfg.Gateway, subnet, subnetFamily); err != nil { + if err := validateAddress(cfg.Gateway, cfg.Subnet, subnetFamily); err != nil { errs = append(errs, fmt.Errorf("invalid gateway %s: %w", cfg.Gateway, err)) } for auxName, aux := range cfg.AuxAddress { - if err := validateAddress(aux, subnet, subnetFamily); err != nil { + if err := validateAddress(aux, cfg.Subnet, subnetFamily); err != nil { errs = append(errs, fmt.Errorf("invalid auxiliary address %s: %w", auxName, err)) } } @@ -487,16 +485,12 @@ func validateIpamConfig(data []networktypes.IPAMConfig, enableIPv6 bool) error { return nil } -func validateIPRange(ipRange string, subnet netip.Prefix, subnetFamily int) []error { - if ipRange == "" { +func validateIPRange(ipRange, subnet netip.Prefix, subnetFamily int) []error { + if !ipRange.IsValid() { return nil } - prefix, err := netip.ParsePrefix(ipRange) - if err != nil { - return []error{fmt.Errorf("invalid ip-range %s: invalid CIDR block notation", ipRange)} - } family := 4 - if prefix.Addr().Is6() { + if ipRange.Addr().Is6() { family = 6 } @@ -505,27 +499,23 @@ func validateIPRange(ipRange string, subnet netip.Prefix, subnetFamily int) []er } var errs []error - if prefix.Bits() < subnet.Bits() { + if ipRange.Bits() < subnet.Bits() { errs = append(errs, fmt.Errorf("invalid ip-range %s: CIDR block is bigger than its parent subnet %s", ipRange, subnet)) } - if prefix != prefix.Masked() { - errs = append(errs, fmt.Errorf("invalid ip-range %s: it should be %s", prefix, prefix.Masked())) + if ipRange != ipRange.Masked() { + errs = append(errs, fmt.Errorf("invalid ip-range %s: it should be %s", ipRange, ipRange.Masked())) } - if !subnet.Overlaps(prefix) { + if !subnet.Overlaps(ipRange) { errs = append(errs, fmt.Errorf("invalid ip-range %s: parent subnet %s doesn't contain ip-range", ipRange, subnet)) } return errs } -func validateAddress(address string, subnet netip.Prefix, subnetFamily int) error { - if address == "" { +func validateAddress(addr netip.Addr, subnet netip.Prefix, subnetFamily int) error { + if !addr.IsValid() { return nil } - addr, err := netip.ParseAddr(address) - if err != nil { - return errors.New("invalid address") - } family := 4 if addr.Is6() { family = 6 @@ -545,16 +535,15 @@ func getIpamConfig(data []networktypes.IPAMConfig) ([]*libnetwork.IpamConf, []*l ipamV4Cfg := []*libnetwork.IpamConf{} ipamV6Cfg := []*libnetwork.IpamConf{} for _, d := range data { - iCfg := libnetwork.IpamConf{} - iCfg.PreferredPool = d.Subnet - iCfg.SubPool = d.IPRange - iCfg.Gateway = d.Gateway - iCfg.AuxAddresses = d.AuxAddress - ip, _, err := net.ParseCIDR(d.Subnet) - if err != nil { - return nil, nil, fmt.Errorf("Invalid subnet %s : %v", d.Subnet, err) + iCfg := libnetwork.IpamConf{ + PreferredPool: netipstringer.Prefix(netiputil.Unmap(d.Subnet).Masked()), + SubPool: netipstringer.Prefix(netiputil.Unmap(d.IPRange).Masked()), + Gateway: netipstringer.Addr(d.Gateway.Unmap()), + AuxAddresses: maps.Collect(iterutil.Map2(maps.All(d.AuxAddress), func(k string, v netip.Addr) (string, string) { + return k, v.Unmap().String() + })), } - if ip.To4() != nil { + if d.Subnet.Addr().Unmap().Is4() { ipamV4Cfg = append(ipamV4Cfg, &iCfg) } else { ipamV6Cfg = append(ipamV6Cfg, &iCfg) @@ -782,15 +771,17 @@ func buildServiceAttachments(nw *libnetwork.Network) map[string]networktypes.Ser for name, service := range nw.Services() { tasks := make([]networktypes.Task, 0, len(service.Tasks)) for _, t := range service.Tasks { + eip, _ := netip.ParseAddr(t.EndpointIP) tasks = append(tasks, networktypes.Task{ Name: t.Name, EndpointID: t.EndpointID, - EndpointIP: t.EndpointIP, + EndpointIP: eip.Unmap(), Info: t.Info, }) } + vip, _ := netip.ParseAddr(service.VIP) services[name] = networktypes.ServiceInfo{ - VIP: service.VIP, + VIP: vip.Unmap(), Ports: service.Ports, Tasks: tasks, LocalLBIndex: service.LocalLBIndex, @@ -826,12 +817,7 @@ func buildIPAMResources(nw *libnetwork.Network) networktypes.IPAM { continue } hasIPv4Config = true - ipamConfig = append(ipamConfig, networktypes.IPAMConfig{ - Subnet: cfg.PreferredPool, - IPRange: cfg.SubPool, - Gateway: cfg.Gateway, - AuxAddress: cfg.AuxAddresses, - }) + ipamConfig = append(ipamConfig, cfg.IPAMConfig()) } hasIPv6Config := false @@ -840,26 +826,14 @@ func buildIPAMResources(nw *libnetwork.Network) networktypes.IPAM { continue } hasIPv6Config = true - ipamConfig = append(ipamConfig, networktypes.IPAMConfig{ - Subnet: cfg.PreferredPool, - IPRange: cfg.SubPool, - Gateway: cfg.Gateway, - AuxAddress: cfg.AuxAddresses, - }) + ipamConfig = append(ipamConfig, cfg.IPAMConfig()) } if !hasIPv4Config || !hasIPv6Config { ipv4Info, ipv6Info := nw.IpamInfo() if !hasIPv4Config { for _, info := range ipv4Info { - var gw string - if info.IPAMData.Gateway != nil { - gw = info.IPAMData.Gateway.IP.String() - } - ipamConfig = append(ipamConfig, networktypes.IPAMConfig{ - Subnet: info.IPAMData.Pool.String(), - Gateway: gw, - }) + ipamConfig = append(ipamConfig, info.IPAMData.IPAMConfig()) } } @@ -868,14 +842,7 @@ func buildIPAMResources(nw *libnetwork.Network) networktypes.IPAM { if info.IPAMData.Pool == nil { continue } - var gw string - if info.IPAMData.Gateway != nil { - gw = info.IPAMData.Gateway.IP.String() - } - ipamConfig = append(ipamConfig, networktypes.IPAMConfig{ - Subnet: info.IPAMData.Pool.String(), - Gateway: gw, - }) + ipamConfig = append(ipamConfig, info.IPAMData.IPAMConfig()) } } } @@ -895,15 +862,9 @@ func buildEndpointResource(ep *libnetwork.Endpoint, info libnetwork.EndpointInfo Name: ep.Name(), } if iface := info.Iface(); iface != nil { - if mac := iface.MacAddress(); mac != nil { - er.MacAddress = mac.String() - } - if ip := iface.Address(); ip != nil && len(ip.IP) > 0 { - er.IPv4Address = ip.String() - } - if ip := iface.AddressIPv6(); ip != nil && len(ip.IP) > 0 { - er.IPv6Address = ip.String() - } + er.MacAddress = iface.MacAddress().String() + er.IPv4Address = netiputil.Unmap(iface.Addr()) + er.IPv6Address = iface.AddrIPv6() } return er } @@ -946,25 +907,22 @@ func buildCreateEndpointOptions(c *container.Container, n *libnetwork.Network, e if epConfig != nil { if ipam := epConfig.IPAMConfig; ipam != nil { var ipList []net.IP - for _, ips := range ipam.LinkLocalIPs { - linkIP := net.ParseIP(ips) - if linkIP == nil && ips != "" { + for _, linkIP := range ipam.LinkLocalIPs { + if !linkIP.IsValid() { return nil, fmt.Errorf("invalid link-local IP address: %s", ipam.LinkLocalIPs) } - ipList = append(ipList, linkIP) + ipList = append(ipList, linkIP.AsSlice()) } - ip := net.ParseIP(ipam.IPv4Address) - if ip == nil && ipam.IPv4Address != "" { + if ipam.IPv4Address.IsValid() && !ipam.IPv4Address.Is4() && !ipam.IPv4Address.Is4In6() { return nil, fmt.Errorf("invalid IPv4 address: %s", ipam.IPv4Address) } - ip6 := net.ParseIP(ipam.IPv6Address) - if ip6 == nil && ipam.IPv6Address != "" { + if ipam.IPv6Address.IsValid() && !ipam.IPv6Address.Is6() { return nil, fmt.Errorf("invalid IPv6 address: %s", ipam.IPv6Address) } - createOptions = append(createOptions, libnetwork.CreateOptionIPAM(ip, ip6, ipList)) + createOptions = append(createOptions, libnetwork.CreateOptionIPAM(ipam.IPv4Address.AsSlice(), ipam.IPv6Address.AsSlice(), ipList)) } createOptions = append(createOptions, libnetwork.CreateOptionDNSNames(epConfig.DNSNames)) @@ -1197,17 +1155,18 @@ func buildEndpointInfo(networkSettings *network.Settings, n *libnetwork.Network, if iface.Address() != nil { ones, _ := iface.Address().Mask.Size() - networkSettings.Networks[nwName].IPAddress = iface.Address().IP.String() + addr, _ := netip.AddrFromSlice(iface.Address().IP) + networkSettings.Networks[nwName].IPAddress = addr.Unmap() networkSettings.Networks[nwName].IPPrefixLen = ones } if iface.AddressIPv6() != nil && iface.AddressIPv6().IP.To16() != nil { onesv6, _ := iface.AddressIPv6().Mask.Size() - networkSettings.Networks[nwName].GlobalIPv6Address = iface.AddressIPv6().IP.String() + networkSettings.Networks[nwName].GlobalIPv6Address, _ = netip.AddrFromSlice(iface.AddressIPv6().IP) networkSettings.Networks[nwName].GlobalIPv6PrefixLen = onesv6 } else { // If IPv6 was disabled on the interface, and its address was removed, remove it here too. - networkSettings.Networks[nwName].GlobalIPv6Address = "" + networkSettings.Networks[nwName].GlobalIPv6Address = netip.Addr{} networkSettings.Networks[nwName].GlobalIPv6PrefixLen = 0 } diff --git a/daemon/network_test.go b/daemon/network_test.go index 9f3f0812d0..cd8d8efdad 100644 --- a/daemon/network_test.go +++ b/daemon/network_test.go @@ -1,6 +1,7 @@ package daemon import ( + "net/netip" "testing" "github.com/moby/moby/api/types/network" @@ -18,10 +19,10 @@ func TestValidateIPAM(t *testing.T) { { name: "IP version mismatch", ipam: []network.IPAMConfig{{ - Subnet: "10.10.10.0/24", - IPRange: "2001:db8::/32", - Gateway: "2001:db8::1", - AuxAddress: map[string]string{"DefaultGatewayIPv4": "2001:db8::1"}, + Subnet: netip.MustParsePrefix("10.10.10.0/24"), + IPRange: netip.MustParsePrefix("2001:db8::/32"), + Gateway: netip.MustParseAddr("2001:db8::1"), + AuxAddress: map[string]netip.Addr{"DefaultGatewayIPv4": netip.MustParseAddr("2001:db8::1")}, }}, expectedErrors: []string{ "invalid ip-range 2001:db8::/32: parent subnet is an IPv4 block", @@ -32,34 +33,13 @@ func TestValidateIPAM(t *testing.T) { { // Regression test for https://github.com/moby/moby/issues/47202 name: "IPv6 subnet is discarded with no error when IPv6 is disabled", - ipam: []network.IPAMConfig{{Subnet: "2001:db8::/32"}}, + ipam: []network.IPAMConfig{{Subnet: netip.MustParsePrefix("2001:db8::/32")}}, ipv6: false, }, - { - name: "Invalid data - Subnet", - ipam: []network.IPAMConfig{{Subnet: "foobar"}}, - expectedErrors: []string{ - `invalid subnet foobar: invalid CIDR block notation`, - }, - }, - { - name: "Invalid data", - ipam: []network.IPAMConfig{{ - Subnet: "10.10.10.0/24", - IPRange: "foobar", - Gateway: "1001.10.5.3", - AuxAddress: map[string]string{"DefaultGatewayIPv4": "dummy"}, - }}, - expectedErrors: []string{ - "invalid ip-range foobar: invalid CIDR block notation", - "invalid gateway 1001.10.5.3: invalid address", - "invalid auxiliary address DefaultGatewayIPv4: invalid address", - }, - }, { name: "IPRange bigger than its subnet", ipam: []network.IPAMConfig{ - {Subnet: "10.10.10.0/24", IPRange: "10.0.0.0/8"}, + {Subnet: netip.MustParsePrefix("10.10.10.0/24"), IPRange: netip.MustParsePrefix("10.0.0.0/8")}, }, expectedErrors: []string{ "invalid ip-range 10.0.0.0/8: CIDR block is bigger than its parent subnet 10.10.10.0/24", @@ -68,10 +48,10 @@ func TestValidateIPAM(t *testing.T) { { name: "Out of range prefix & addresses", ipam: []network.IPAMConfig{{ - Subnet: "10.0.0.0/8", - IPRange: "192.168.0.1/24", - Gateway: "192.168.0.1", - AuxAddress: map[string]string{"DefaultGatewayIPv4": "192.168.0.1"}, + Subnet: netip.MustParsePrefix("10.0.0.0/8"), + IPRange: netip.MustParsePrefix("192.168.0.1/24"), + Gateway: netip.MustParseAddr("192.168.0.1"), + AuxAddress: map[string]netip.Addr{"DefaultGatewayIPv4": netip.MustParseAddr("192.168.0.1")}, }}, expectedErrors: []string{ "invalid ip-range 192.168.0.1/24: it should be 192.168.0.0/24", @@ -83,15 +63,15 @@ func TestValidateIPAM(t *testing.T) { { name: "Subnet with host fragment set", ipam: []network.IPAMConfig{{ - Subnet: "10.10.10.0/8", + Subnet: netip.MustParsePrefix("10.10.10.0/8"), }}, expectedErrors: []string{"invalid subnet 10.10.10.0/8: it should be 10.0.0.0/8"}, }, { name: "IPRange with host fragment set", ipam: []network.IPAMConfig{{ - Subnet: "10.0.0.0/8", - IPRange: "10.10.10.0/16", + Subnet: netip.MustParsePrefix("10.0.0.0/8"), + IPRange: netip.MustParsePrefix("10.10.10.0/16"), }}, expectedErrors: []string{"invalid ip-range 10.10.10.0/16: it should be 10.10.0.0/16"}, }, @@ -101,10 +81,10 @@ func TestValidateIPAM(t *testing.T) { { name: "Valid IPAM", ipam: []network.IPAMConfig{{ - Subnet: "10.0.0.0/8", - IPRange: "10.10.0.0/16", - Gateway: "10.10.0.1", - AuxAddress: map[string]string{"DefaultGatewayIPv4": "10.10.0.1"}, + Subnet: netip.MustParsePrefix("10.0.0.0/8"), + IPRange: netip.MustParsePrefix("10.10.0.0/16"), + Gateway: netip.MustParseAddr("10.10.0.1"), + AuxAddress: map[string]netip.Addr{"DefaultGatewayIPv4": netip.MustParseAddr("10.10.0.1")}, }}, }, } diff --git a/hack/generate-swagger-api.sh b/hack/generate-swagger-api.sh index 5921e12d36..fc7be42434 100755 --- a/hack/generate-swagger-api.sh +++ b/hack/generate-swagger-api.sh @@ -71,6 +71,7 @@ generate_model types/network --keep-spec-order --additional-initialism=IPAM <<- NetworkSummary NetworkTaskInfo PeerInfo + ServiceInfo SubnetStatus EOT diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go index 43227f1c72..0403fa2437 100644 --- a/integration-cli/docker_api_inspect_test.go +++ b/integration-cli/docker_api_inspect_test.go @@ -110,5 +110,5 @@ func (s *DockerAPISuite) TestInspectAPIBridgeNetworkSettings121(c *testing.T) { settings := inspectJSON.NetworkSettings assert.Assert(c, settings.Networks["bridge"] != nil) - assert.Assert(c, settings.Networks["bridge"].IPAddress != "") + assert.Assert(c, settings.Networks["bridge"].IPAddress.IsValid()) } diff --git a/integration-cli/docker_api_network_test.go b/integration-cli/docker_api_network_test.go index 9c1a889adc..9ac9422afa 100644 --- a/integration-cli/docker_api_network_test.go +++ b/integration-cli/docker_api_network_test.go @@ -3,8 +3,8 @@ package main import ( "encoding/json" "fmt" - "net" "net/http" + "net/netip" "strings" "testing" @@ -37,9 +37,7 @@ func (s *DockerAPISuite) TestAPINetworkInspectBridge(c *testing.T) { _, ok := nr.Containers[containerID] assert.Assert(c, ok) - ip, _, err := net.ParseCIDR(nr.Containers[containerID].IPv4Address) - assert.NilError(c, err) - assert.Equal(c, ip.String(), containerIP) + assert.Equal(c, nr.Containers[containerID].IPv4Address.Addr().String(), containerIP) } func (s *DockerAPISuite) TestAPINetworkInspectUserDefinedNetwork(c *testing.T) { @@ -47,7 +45,7 @@ func (s *DockerAPISuite) TestAPINetworkInspectUserDefinedNetwork(c *testing.T) { // IPAM configuration inspect ipam := &network.IPAM{ Driver: "default", - Config: []network.IPAMConfig{{Subnet: "172.28.0.0/16", IPRange: "172.28.5.0/24", Gateway: "172.28.5.254"}}, + Config: []network.IPAMConfig{{Subnet: netip.MustParsePrefix("172.28.0.0/16"), IPRange: netip.MustParsePrefix("172.28.5.0/24"), Gateway: netip.MustParseAddr("172.28.5.254")}}, } config := network.CreateRequest{ Name: "br0", @@ -60,9 +58,9 @@ func (s *DockerAPISuite) TestAPINetworkInspectUserDefinedNetwork(c *testing.T) { nr := getNetworkResource(c, id0) assert.Equal(c, len(nr.IPAM.Config), 1) - assert.Equal(c, nr.IPAM.Config[0].Subnet, "172.28.0.0/16") - assert.Equal(c, nr.IPAM.Config[0].IPRange, "172.28.5.0/24") - assert.Equal(c, nr.IPAM.Config[0].Gateway, "172.28.5.254") + assert.Equal(c, nr.IPAM.Config[0].Subnet, netip.MustParsePrefix("172.28.0.0/16")) + assert.Equal(c, nr.IPAM.Config[0].IPRange, netip.MustParsePrefix("172.28.5.0/24")) + assert.Equal(c, nr.IPAM.Config[0].Gateway, netip.MustParseAddr("172.28.5.254")) assert.Equal(c, nr.Options["foo"], "bar") assert.Equal(c, nr.Options["opts"], "dopts") @@ -98,10 +96,8 @@ func (s *DockerAPISuite) TestAPINetworkConnectDisconnect(c *testing.T) { assert.Assert(c, ok) // check if container IP matches network inspect - ip, _, err := net.ParseCIDR(nr.Containers[containerID].IPv4Address) - assert.NilError(c, err) containerIP := findContainerIP(c, "test", "testnetwork") - assert.Equal(c, ip.String(), containerIP) + assert.Equal(c, nr.Containers[containerID].IPv4Address.Addr().String(), containerIP) // disconnect container from the network disconnectNetwork(c, nr.ID, containerID) @@ -118,7 +114,7 @@ func (s *DockerAPISuite) TestAPINetworkIPAMMultipleBridgeNetworks(c *testing.T) // test0 bridge network ipam0 := &network.IPAM{ Driver: "default", - Config: []network.IPAMConfig{{Subnet: "192.178.0.0/16", IPRange: "192.178.128.0/17", Gateway: "192.178.138.100"}}, + Config: []network.IPAMConfig{{Subnet: netip.MustParsePrefix("192.178.0.0/16"), IPRange: netip.MustParsePrefix("192.178.128.0/17"), Gateway: netip.MustParseAddr("192.178.138.100")}}, } config0 := network.CreateRequest{ Name: "test0", @@ -130,7 +126,7 @@ func (s *DockerAPISuite) TestAPINetworkIPAMMultipleBridgeNetworks(c *testing.T) ipam1 := &network.IPAM{ Driver: "default", - Config: []network.IPAMConfig{{Subnet: "192.178.128.0/17", Gateway: "192.178.128.1"}}, + Config: []network.IPAMConfig{{Subnet: netip.MustParsePrefix("192.178.128.0/17"), Gateway: netip.MustParseAddr("192.178.128.1")}}, } // test1 bridge network overlaps with test0 config1 := network.CreateRequest{ @@ -143,7 +139,7 @@ func (s *DockerAPISuite) TestAPINetworkIPAMMultipleBridgeNetworks(c *testing.T) ipam2 := &network.IPAM{ Driver: "default", - Config: []network.IPAMConfig{{Subnet: "192.169.0.0/16", Gateway: "192.169.100.100"}}, + Config: []network.IPAMConfig{{Subnet: netip.MustParsePrefix("192.169.0.0/16"), Gateway: netip.MustParseAddr("192.169.100.100")}}, } // test2 bridge network does not overlap config2 := network.CreateRequest{ diff --git a/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go index 3a0c194e24..f91f79ceaf 100644 --- a/integration-cli/docker_cli_network_unix_test.go +++ b/integration-cli/docker_cli_network_unix_test.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "net/http/httptest" + "net/netip" "os" "strings" "testing" @@ -573,10 +574,8 @@ func (s *DockerNetworkSuite) TestDockerNetworkConnectDisconnect(c *testing.T) { assert.Equal(c, len(nr.Containers), 1) // check if container IP matches network inspect - ip, _, err := net.ParseCIDR(nr.Containers[containerID].IPv4Address) - assert.NilError(c, err) containerIP := findContainerIP(c, "test", "test") - assert.Equal(c, ip.String(), containerIP) + assert.Equal(c, nr.Containers[containerID].IPv4Address.Addr().String(), containerIP) // disconnect container from the network cli.DockerCmd(c, "network", "disconnect", "test", containerID) @@ -686,8 +685,8 @@ func (s *DockerNetworkSuite) TestDockerNetworkNullIPAMDriver(c *testing.T) { nr := getNetworkResource(c, "test000") assert.Equal(c, nr.IPAM.Driver, "null") assert.Equal(c, len(nr.IPAM.Config), 1) - assert.Equal(c, nr.IPAM.Config[0].Subnet, "0.0.0.0/0") - assert.Equal(c, nr.IPAM.Config[0].Gateway, "") + assert.Equal(c, nr.IPAM.Config[0].Subnet, netip.MustParsePrefix("0.0.0.0/0")) + assert.Assert(c, !nr.IPAM.Config[0].Gateway.IsValid()) } func (s *DockerNetworkSuite) TestDockerNetworkInspectDefault(c *testing.T) { @@ -744,9 +743,9 @@ func (s *DockerNetworkSuite) TestDockerNetworkInspectCustomSpecified(c *testing. assert.Equal(c, nr.EnableIPv6, true) assert.Equal(c, nr.IPAM.Driver, "default") assert.Equal(c, len(nr.IPAM.Config), 2) - assert.Equal(c, nr.IPAM.Config[0].Subnet, "172.28.0.0/16") - assert.Equal(c, nr.IPAM.Config[0].IPRange, "172.28.5.0/24") - assert.Equal(c, nr.IPAM.Config[0].Gateway, "172.28.5.254") + assert.Equal(c, nr.IPAM.Config[0].Subnet, netip.MustParsePrefix("172.28.0.0/16")) + assert.Equal(c, nr.IPAM.Config[0].IPRange, netip.MustParsePrefix("172.28.5.0/24")) + assert.Equal(c, nr.IPAM.Config[0].Gateway, netip.MustParseAddr("172.28.5.254")) assert.Equal(c, nr.Internal, false) cli.DockerCmd(c, "network", "rm", "br0") assertNwNotAvailable(c, "br0") @@ -1733,9 +1732,9 @@ func (s *DockerNetworkSuite) TestDockerNetworkValidateIP(c *testing.T) { verifyIPAddresses(c, "mynet0", "mynet", "172.28.99.88", "2001:db8:1234::9988") _, _, err = dockerCmdWithError("run", "--net=mynet", "--ip", "mynet_ip", "--ip6", "2001:db8:1234::9999", "busybox", "top") - assert.ErrorContains(c, err, "invalid IPv4 address") + assert.ErrorContains(c, err, "unable to parse IP") _, _, err = dockerCmdWithError("run", "--net=mynet", "--ip", "172.28.99.99", "--ip6", "mynet_ip6", "busybox", "top") - assert.ErrorContains(c, err, "invalid IPv6 address") + assert.ErrorContains(c, err, "unable to parse IP") // This is a case of IPv4 address to `--ip6` _, _, err = dockerCmdWithError("run", "--net=mynet", "--ip6", "172.28.99.99", "busybox", "top") diff --git a/integration/container/daemon_linux_test.go b/integration/container/daemon_linux_test.go index d08b27025c..c4c1517641 100644 --- a/integration/container/daemon_linux_test.go +++ b/integration/container/daemon_linux_test.go @@ -155,7 +155,7 @@ func TestDaemonHostGatewayIP(t *testing.T) { assert.Equal(t, 0, res.ExitCode) inspect, err := c.NetworkInspect(ctx, "bridge", client.NetworkInspectOptions{}) assert.NilError(t, err) - assert.Check(t, is.Contains(res.Stdout(), inspect.IPAM.Config[0].Gateway)) + assert.Check(t, is.Contains(res.Stdout(), inspect.IPAM.Config[0].Gateway.String())) c.ContainerRemove(ctx, cID, client.ContainerRemoveOptions{Force: true}) d.Stop(t) diff --git a/integration/daemon/daemon_linux_test.go b/integration/daemon/daemon_linux_test.go index 94b49b651e..f3125eef30 100644 --- a/integration/daemon/daemon_linux_test.go +++ b/integration/daemon/daemon_linux_test.go @@ -7,6 +7,7 @@ import ( "slices" "testing" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/moby/moby/api/types/network" swarmtypes "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/client" @@ -58,8 +59,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) { "--default-address-pool", `base=fdd1:8161:2d2c::/56,size=64`, }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/24", Gateway: "192.168.176.1"}, - {Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::1"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.1")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c::1")}, }, }, { @@ -69,8 +70,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) { "--fixed-cidr-v6", "fdd1:8161:2d2c::/64", }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24"}, - {Subnet: "fdd1:8161:2d2c::/64", IPRange: "fdd1:8161:2d2c::/64"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/24"), IPRange: netip.MustParsePrefix("192.168.176.0/24")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c::/64")}, }, }, { @@ -80,16 +81,16 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) { "--bip6", "fdd1:8161:2d2c::8888/64", }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/24", Gateway: "192.168.176.88"}, - {Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.88")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c::8888")}, }, }, { name: "existing bridge address only", initialBridgeAddrs: []string{"192.168.176.88/24", "fdd1:8161:2d2c::8888/64"}, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/24", Gateway: "192.168.176.88"}, - {Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.88")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c::8888")}, }, }, { @@ -111,8 +112,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) { // and it'd be a breaking change for anyone relying on the existing // behaviour. expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"}, - {Subnet: "fdd1:8161:2d2c::/56", IPRange: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/20"), IPRange: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.88")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/56"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c::8888")}, }, }, { @@ -122,8 +123,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) { "--fixed-cidr-v6", "fe80::/64", }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24"}, - {Subnet: "fe80::/64", IPRange: "fe80::/64", Gateway: llGwPlaceholder}, + {Subnet: netip.MustParsePrefix("192.168.176.0/24"), IPRange: netip.MustParsePrefix("192.168.176.0/24")}, + {Subnet: netip.MustParsePrefix("fe80::/64"), IPRange: netip.MustParsePrefix("fe80::/64"), Gateway: llGwPlaceholder}, }, }, { @@ -134,8 +135,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) { "--fixed-cidr-v6", "fe80:1234::/64", }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"}, - {Subnet: "fe80:1234::/56", IPRange: "fe80:1234::/64", Gateway: "fe80:1234::88"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/20"), IPRange: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.88")}, + {Subnet: netip.MustParsePrefix("fe80:1234::/56"), IPRange: netip.MustParsePrefix("fe80:1234::/64"), Gateway: netip.MustParseAddr("fe80:1234::88")}, }, }, { @@ -146,8 +147,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) { "--fixed-cidr-v6", "fdd1:8161:2d2c::/64", "--bip6", "fdd1:8161:2d2c::9999/64", }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24", Gateway: "192.168.176.99"}, - {Subnet: "fdd1:8161:2d2c::/64", IPRange: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::9999"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/24"), IPRange: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.99")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c::9999")}, }, }, { @@ -158,8 +159,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) { "--fixed-cidr-v6", "fdd1:8161:2d2c::/56", }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/20", Gateway: "192.168.176.88"}, - {Subnet: "fdd1:8161:2d2c::/56", IPRange: "fdd1:8161:2d2c::/56", Gateway: "fdd1:8161:2d2c::8888"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/20"), IPRange: netip.MustParsePrefix("192.168.176.0/20"), Gateway: netip.MustParseAddr("192.168.176.88")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/56"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c::/56"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c::8888")}, }, }, { @@ -172,8 +173,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) { // The bridge's address/subnet should be ignored, this is a change // of fixed-cidr. expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.177.0/24", IPRange: "192.168.177.0/24"}, - {Subnet: "fdd1:8161:2d2c:1::/64", IPRange: "fdd1:8161:2d2c:1::/64"}, + {Subnet: netip.MustParsePrefix("192.168.177.0/24"), IPRange: netip.MustParsePrefix("192.168.177.0/24")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c:1::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c:1::/64")}, // No Gateway is configured, because the address could not be learnt from the // bridge. An address will have been allocated but, because there's config (the // fixed-cidr), inspect shows just the config. (Surprisingly, when there's no @@ -191,8 +192,8 @@ func TestDaemonDefaultBridgeIPAM_Docker0(t *testing.T) { "--fixed-cidr-v6", "fdd1:8161:2d2c:1::/64", "--bip6", "fdd1:8161:2d2c:1::9999/64", }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.177.0/24", IPRange: "192.168.177.0/24", Gateway: "192.168.177.99"}, - {Subnet: "fdd1:8161:2d2c:1::/64", IPRange: "fdd1:8161:2d2c:1::/64", Gateway: "fdd1:8161:2d2c:1::9999"}, + {Subnet: netip.MustParsePrefix("192.168.177.0/24"), IPRange: netip.MustParsePrefix("192.168.177.0/24"), Gateway: netip.MustParseAddr("192.168.177.99")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c:1::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c:1::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c:1::9999")}, }, }, } @@ -213,8 +214,8 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) { name: "bridge only", initialBridgeAddrs: []string{"192.168.176.88/20", "fdd1:8161:2d2c::8888/64"}, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/20", Gateway: "192.168.176.88"}, - {Subnet: "fdd1:8161:2d2c::/64", Gateway: "fdd1:8161:2d2c::8888"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/20"), Gateway: netip.MustParseAddr("192.168.176.88")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c::8888")}, }, }, { @@ -224,8 +225,8 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) { "--fixed-cidr-v6", "fdd1:8161:2d2c::/64", }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/24", IPRange: "192.168.176.0/24"}, - {Subnet: "fdd1:8161:2d2c::/64", IPRange: "fdd1:8161:2d2c::/64"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/24"), IPRange: netip.MustParsePrefix("192.168.176.0/24")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c::/64"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c::/64")}, }, }, { @@ -240,8 +241,8 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) { }, // Selected bip should be the one within fixed-cidr expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"}, - {Subnet: "fdd1:8161:2d2c:10::/60", IPRange: "fdd1:8161:2d2c:10::/64", Gateway: "fdd1:8161:2d2c:10::8888"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/20"), IPRange: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.88")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c:10::/60"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c:10::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c:10::8888")}, }, }, { @@ -256,8 +257,8 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) { }, // Selected bridge subnet should be the one that encompasses fixed-cidr. expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/20", IPRange: "192.168.177.0/24", Gateway: "192.168.176.88"}, - {Subnet: "fdd1:8161:2d2c:10::/60", IPRange: "fdd1:8161:2d2c:11::/64", Gateway: "fdd1:8161:2d2c:10::8888"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/20"), IPRange: netip.MustParsePrefix("192.168.177.0/24"), Gateway: netip.MustParseAddr("192.168.176.88")}, + {Subnet: netip.MustParsePrefix("fdd1:8161:2d2c:10::/60"), IPRange: netip.MustParsePrefix("fdd1:8161:2d2c:11::/64"), Gateway: netip.MustParseAddr("fdd1:8161:2d2c:10::8888")}, }, }, { @@ -268,8 +269,8 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) { "--fixed-cidr-v6", "fe80::/64", }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"}, - {Subnet: "fe80::/64", IPRange: "fe80::/64", Gateway: llGwPlaceholder}, + {Subnet: netip.MustParsePrefix("192.168.176.0/20"), IPRange: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.88")}, + {Subnet: netip.MustParsePrefix("fe80::/64"), IPRange: netip.MustParsePrefix("fe80::/64"), Gateway: llGwPlaceholder}, }, }, { @@ -280,8 +281,8 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) { "--fixed-cidr-v6", "fe80:1234::/64", }, expIPAMConfig: []network.IPAMConfig{ - {Subnet: "192.168.176.0/20", IPRange: "192.168.176.0/24", Gateway: "192.168.176.88"}, - {Subnet: "fe80:1234::/56", IPRange: "fe80:1234::/64", Gateway: "fe80:1234::88"}, + {Subnet: netip.MustParsePrefix("192.168.176.0/20"), IPRange: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.88")}, + {Subnet: netip.MustParsePrefix("fe80:1234::/56"), IPRange: netip.MustParsePrefix("fe80:1234::/64"), Gateway: netip.MustParseAddr("fe80:1234::88")}, }, }, { @@ -295,7 +296,7 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) { // would normally result in a docker network that allocated addresses // within the selected subnet. So, fixed-cidr is dropped, making the // whole subnet allocatable. - expIPAMConfig: []network.IPAMConfig{{Subnet: "192.168.176.0/24", Gateway: "192.168.176.88"}}, + expIPAMConfig: []network.IPAMConfig{{Subnet: netip.MustParsePrefix("192.168.176.0/24"), Gateway: netip.MustParseAddr("192.168.176.88")}}, }, { name: "no bridge ip within fixed-cidr", @@ -308,7 +309,7 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) { // would normally result in a docker network that allocated addresses // within the selected subnet. So, fixed-cidr is dropped, making the // whole subnet allocatable. - expIPAMConfig: []network.IPAMConfig{{Subnet: "192.168.160.0/20", Gateway: "192.168.160.88"}}, + expIPAMConfig: []network.IPAMConfig{{Subnet: netip.MustParsePrefix("192.168.160.0/20"), Gateway: netip.MustParseAddr("192.168.160.88")}}, }, { name: "fixed-cidr contains bridge subnet", @@ -322,7 +323,7 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) { // within the selected subnet. So, fixed-cidr is dropped, making the // whole subnet allocatable. ipv4Only: true, - expIPAMConfig: []network.IPAMConfig{{Subnet: "192.168.177.0/24", Gateway: "192.168.177.1"}}, + expIPAMConfig: []network.IPAMConfig{{Subnet: netip.MustParsePrefix("192.168.177.0/24"), Gateway: netip.MustParseAddr("192.168.177.1")}}, }, { @@ -364,7 +365,7 @@ func TestDaemonDefaultBridgeIPAM_UserBr(t *testing.T) { // llGwPlaceholder can be used as a value for "Gateway" in expected IPAM config, // before comparison with actual results it'll be replaced by the kernel assigned // link local IPv6 address for the bridge. -const llGwPlaceholder = "ll-gateway-placeholder" +var llGwPlaceholder = netip.MustParseAddr("fe80::1").WithZone("ll-gateway-placeholder") type defaultBridgeIPAMTestCase struct { name string @@ -389,7 +390,7 @@ func testDefaultBridgeIPAM(ctx context.Context, t *testing.T, tc defaultBridgeIP defer cleanup() host.Do(t, func() { - llAddr := createBridge(t, tc.bridgeName, tc.initialBridgeAddrs) + llAddr, _ := netip.AddrFromSlice(createBridge(t, tc.bridgeName, tc.initialBridgeAddrs)) var dArgs []string if !tc.ipv4Only { @@ -424,10 +425,10 @@ func testDefaultBridgeIPAM(ctx context.Context, t *testing.T, tc defaultBridgeIP expIPAMConfig := slices.Clone(tc.expIPAMConfig) for i := range expIPAMConfig { if expIPAMConfig[i].Gateway == llGwPlaceholder { - expIPAMConfig[i].Gateway = llAddr.String() + expIPAMConfig[i].Gateway = llAddr } } - assert.Check(t, is.DeepEqual(insp.IPAM.Config, expIPAMConfig)) + assert.Check(t, is.DeepEqual(insp.IPAM.Config, expIPAMConfig, cmpopts.EquateComparable(netip.Addr{}, netip.Prefix{}))) }) }) } diff --git a/integration/internal/container/ops.go b/integration/internal/container/ops.go index e407df9d75..e584829aeb 100644 --- a/integration/internal/container/ops.go +++ b/integration/internal/container/ops.go @@ -2,6 +2,7 @@ package container import ( "maps" + "net/netip" "slices" "strings" @@ -173,7 +174,7 @@ func WithIPv4(networkName, ip string) func(*TestContainerConfig) { if c.NetworkingConfig.EndpointsConfig[networkName].IPAMConfig == nil { c.NetworkingConfig.EndpointsConfig[networkName].IPAMConfig = &network.EndpointIPAMConfig{} } - c.NetworkingConfig.EndpointsConfig[networkName].IPAMConfig.IPv4Address = ip + c.NetworkingConfig.EndpointsConfig[networkName].IPAMConfig.IPv4Address = netip.MustParseAddr(ip) } } @@ -189,7 +190,7 @@ func WithIPv6(networkName, ip string) func(*TestContainerConfig) { if c.NetworkingConfig.EndpointsConfig[networkName].IPAMConfig == nil { c.NetworkingConfig.EndpointsConfig[networkName].IPAMConfig = &network.EndpointIPAMConfig{} } - c.NetworkingConfig.EndpointsConfig[networkName].IPAMConfig.IPv6Address = ip + c.NetworkingConfig.EndpointsConfig[networkName].IPAMConfig.IPv6Address = netip.MustParseAddr(ip) } } diff --git a/integration/internal/network/ops.go b/integration/internal/network/ops.go index ec82d038eb..70625738d3 100644 --- a/integration/internal/network/ops.go +++ b/integration/internal/network/ops.go @@ -1,6 +1,8 @@ package network import ( + "net/netip" + "github.com/moby/moby/api/types/network" "github.com/moby/moby/client" ) @@ -137,12 +139,19 @@ func WithIPAM(subnet, gateway string) func(*client.NetworkCreateOptions) { // WithIPAMRange adds an IPAM with the specified Subnet, IPRange and Gateway to the network func WithIPAMRange(subnet, iprange, gateway string) func(*client.NetworkCreateOptions) { - return WithIPAMConfig(network.IPAMConfig{ - Subnet: subnet, - IPRange: iprange, - Gateway: gateway, - AuxAddress: map[string]string{}, - }) + c := network.IPAMConfig{ + AuxAddress: map[string]netip.Addr{}, + } + if subnet != "" { + c.Subnet = netip.MustParsePrefix(subnet) + } + if iprange != "" { + c.IPRange = netip.MustParsePrefix(iprange) + } + if gateway != "" { + c.Gateway = netip.MustParseAddr(gateway) + } + return WithIPAMConfig(c) } // WithIPAMConfig adds the provided IPAM configurations to the network diff --git a/integration/network/bridge/bridge_linux_test.go b/integration/network/bridge/bridge_linux_test.go index 1d8750e4f7..90ba9d3024 100644 --- a/integration/network/bridge/bridge_linux_test.go +++ b/integration/network/bridge/bridge_linux_test.go @@ -71,8 +71,7 @@ func TestCreateWithIPv6DefaultsToULAPrefix(t *testing.T) { assert.NilError(t, err) for _, ipam := range nw.IPAM.Config { - ipr := netip.MustParsePrefix(ipam.Subnet) - if netip.MustParsePrefix("fd00::/8").Overlaps(ipr) { + if netip.MustParsePrefix("fd00::/8").Overlaps(ipam.Subnet) { return } } @@ -98,8 +97,7 @@ func TestCreateWithIPv6WithoutEnableIPv6Flag(t *testing.T) { assert.NilError(t, err) for _, ipam := range nw.IPAM.Config { - ipr := netip.MustParsePrefix(ipam.Subnet) - if netip.MustParsePrefix("fd00::/8").Overlaps(ipr) { + if netip.MustParsePrefix("fd00::/8").Overlaps(ipam.Subnet) { return } } @@ -680,13 +678,13 @@ func TestLegacyLink(t *testing.T) { }{ { name: "no link", - host: svrAddr, + host: svrAddr.String(), expect: "download timed out", }, { name: "access by address", links: []string{svrName}, - host: svrAddr, + host: svrAddr.String(), expect: "404 Not Found", // Got a response, but the server has nothing to serve. }, { @@ -773,7 +771,7 @@ func TestRemoveLegacyLink(t *testing.T) { // Check the icc=false rules now block access by address. svrAddr := inspSvr.NetworkSettings.Networks["bridge"].IPAddress - res = ctr.ExecT(ctx, t, c, clientId, []string{"wget", "-T3", "http://" + svrAddr}) + res = ctr.ExecT(ctx, t, c, clientId, []string{"wget", "-T3", "http://" + svrAddr.String()}) assert.Check(t, is.Contains(res.Stderr(), "download timed out")) } @@ -1109,22 +1107,22 @@ func TestBridgeIPAMStatus(t *testing.T) { network.CreateNoError(ctx, t, c, netName, network.WithIPv4(true), network.WithIPAMConfig(networktypes.IPAMConfig{ - Subnet: cidrv4.String(), - IPRange: ipv4Range, - Gateway: ipv4gw, - AuxAddress: map[string]string{ - "reserved": auxIPv4FromRange, - "reserved_1": auxIPv4OutOfRange, + Subnet: cidrv4, + IPRange: netip.MustParsePrefix(ipv4Range), + Gateway: netip.MustParseAddr(ipv4gw), + AuxAddress: map[string]netip.Addr{ + "reserved": netip.MustParseAddr(auxIPv4FromRange), + "reserved_1": netip.MustParseAddr(auxIPv4OutOfRange), }, }), network.WithIPv6(), network.WithIPAMConfig(networktypes.IPAMConfig{ - Subnet: cidrv6.String(), - IPRange: ipv6Range, - Gateway: ipv6gw, - AuxAddress: map[string]string{ - "reserved1": auxIPv6FromRange, - "reserved2": auxIPv6OutOfRange, + Subnet: cidrv6, + IPRange: netip.MustParsePrefix(ipv6Range), + Gateway: netip.MustParseAddr(ipv6gw), + AuxAddress: map[string]netip.Addr{ + "reserved1": netip.MustParseAddr(auxIPv6FromRange), + "reserved2": netip.MustParseAddr(auxIPv6OutOfRange), }, }), ) @@ -1205,7 +1203,7 @@ func TestBridgeIPAMStatus(t *testing.T) { network.WithIPv4(false), network.WithIPv6(), network.WithIPAMConfig(networktypes.IPAMConfig{ - Subnet: cidr.String(), + Subnet: cidr, }), ) defer c.NetworkRemove(ctx, netName) diff --git a/integration/network/inspect_test.go b/integration/network/inspect_test.go index 8e08eba8b6..e92ba6dbac 100644 --- a/integration/network/inspect_test.go +++ b/integration/network/inspect_test.go @@ -49,12 +49,12 @@ func TestInspectNetwork(t *testing.T) { networkName := "Overlay" + t.Name() cidrv4 := netip.MustParsePrefix("192.168.0.0/24") - const ipv4Range = "192.168.0.0/25" + ipv4Range := netip.MustParsePrefix("192.168.0.0/25") overlayID := network.CreateNoError(ctx, t, c1, networkName, network.WithDriver("overlay"), network.WithIPAMConfig(networktypes.IPAMConfig{ - Subnet: cidrv4.String(), + Subnet: cidrv4, IPRange: ipv4Range, }), ) @@ -133,8 +133,8 @@ func TestInspectNetwork(t *testing.T) { assert.Check(t, nw.IPAM.Config != nil) for _, cfg := range nw.IPAM.Config { - assert.Assert(t, cfg.Gateway != "") - assert.Assert(t, cfg.Subnet != "") + assert.Assert(t, cfg.Gateway.IsValid()) + assert.Assert(t, cfg.Subnet.IsValid()) } if d.CachedInfo.Swarm.ControlAvailable { diff --git a/integration/network/ipvlan/ipvlan_test.go b/integration/network/ipvlan/ipvlan_test.go index c685cf7069..797b1ab4f0 100644 --- a/integration/network/ipvlan/ipvlan_test.go +++ b/integration/network/ipvlan/ipvlan_test.go @@ -271,15 +271,15 @@ func testIpvlanL2MultiSubnet(t *testing.T, ctx context.Context, client dclient.A c1, err := client.ContainerInspect(ctx, id1) assert.NilError(t, err) // Inspect the v4 gateway to ensure no default GW was assigned - assert.Check(t, is.Equal(c1.NetworkSettings.Networks[netName].Gateway, "")) + assert.Check(t, !c1.NetworkSettings.Networks[netName].Gateway.IsValid()) // Inspect the v6 gateway to ensure no default GW was assigned - assert.Check(t, is.Equal(c1.NetworkSettings.Networks[netName].IPv6Gateway, "")) + assert.Check(t, !c1.NetworkSettings.Networks[netName].IPv6Gateway.IsValid()) // verify ipv4 connectivity to the explicit --ip address second to first - _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress}) + _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress.String()}) assert.NilError(t, err) // verify ipv6 connectivity to the explicit --ip6 address second to first - _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address}) + _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address.String()}) assert.NilError(t, err) // start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.102.0/24 and 2001:db8:abc4::/64 @@ -297,21 +297,21 @@ func testIpvlanL2MultiSubnet(t *testing.T, ctx context.Context, client dclient.A assert.NilError(t, err) if parent == "" { // Inspect the v4 gateway to ensure no default GW was assigned - assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].Gateway, "")) + assert.Check(t, !c3.NetworkSettings.Networks[netName].Gateway.IsValid()) // Inspect the v6 gateway to ensure no default GW was assigned - assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].IPv6Gateway, "")) + assert.Check(t, !c3.NetworkSettings.Networks[netName].IPv6Gateway.IsValid()) } else { // Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned - assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].Gateway, "172.28.202.254")) + assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].Gateway, netip.MustParseAddr("172.28.202.254"))) // Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned - assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc6::254")) + assert.Check(t, is.Equal(c3.NetworkSettings.Networks[netName].IPv6Gateway, netip.MustParseAddr("2001:db8:abc6::254"))) } // verify ipv4 connectivity to the explicit --ip address from third to fourth - _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress}) + _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress.String()}) assert.NilError(t, err) // verify ipv6 connectivity to the explicit --ip6 address from third to fourth - _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address}) + _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address.String()}) assert.NilError(t, err) } @@ -342,10 +342,10 @@ func testIpvlanL3MultiSubnet(t *testing.T, ctx context.Context, client dclient.A assert.NilError(t, err) // verify ipv4 connectivity to the explicit --ipv address second to first - _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress}) + _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress.String()}) assert.NilError(t, err) // verify ipv6 connectivity to the explicit --ipv6 address second to first - _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address}) + _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address.String()}) assert.NilError(t, err) // start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.102.0/24 and 2001:db8:abc4::/64 @@ -363,20 +363,20 @@ func testIpvlanL3MultiSubnet(t *testing.T, ctx context.Context, client dclient.A assert.NilError(t, err) // verify ipv4 connectivity to the explicit --ipv address from third to fourth - _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress}) + _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress.String()}) assert.NilError(t, err) // verify ipv6 connectivity to the explicit --ipv6 address from third to fourth - _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address}) + _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address.String()}) assert.NilError(t, err) // Inspect the v4 gateway to ensure no next hop is assigned in L3 mode - assert.Equal(t, c1.NetworkSettings.Networks[netName].Gateway, "") + assert.Check(t, !c1.NetworkSettings.Networks[netName].Gateway.IsValid()) // Inspect the v6 gateway to ensure the explicitly specified default GW is ignored per L3 mode enabled - assert.Equal(t, c1.NetworkSettings.Networks[netName].IPv6Gateway, "") + assert.Check(t, !c1.NetworkSettings.Networks[netName].IPv6Gateway.IsValid()) // Inspect the v4 gateway to ensure no next hop is assigned in L3 mode - assert.Equal(t, c3.NetworkSettings.Networks[netName].Gateway, "") + assert.Check(t, !c3.NetworkSettings.Networks[netName].Gateway.IsValid()) // Inspect the v6 gateway to ensure the explicitly specified default GW is ignored per L3 mode enabled - assert.Equal(t, c3.NetworkSettings.Networks[netName].IPv6Gateway, "") + assert.Check(t, !c3.NetworkSettings.Networks[netName].IPv6Gateway.IsValid()) } // Verify ipvlan l2 mode sets the proper default gateway routes via netlink @@ -495,11 +495,11 @@ func TestIpvlanIPAM(t *testing.T) { net.WithIPv4(tc.enableIPv4), net.WithIPAMConfig( network.IPAMConfig{ - Subnet: subnetv4.String(), + Subnet: subnetv4, }, network.IPAMConfig{ - Subnet: subnetv6.String(), - IPRange: "2001:db8:abcd::100/120", + Subnet: subnetv6, + IPRange: netip.MustParsePrefix("2001:db8:abcd::100/120"), }, ), } @@ -608,16 +608,16 @@ func TestIpvlanIPAMOverlap(t *testing.T) { net.WithIPv6(), net.WithIPAMConfig( network.IPAMConfig{ - Subnet: cidrv4.String(), - IPRange: "192.168.0.0/25", - Gateway: "192.168.0.1", - AuxAddress: map[string]string{ - "reserved": "192.168.0.100", + Subnet: cidrv4, + IPRange: netip.MustParsePrefix("192.168.0.0/25"), + Gateway: netip.MustParseAddr("192.168.0.1"), + AuxAddress: map[string]netip.Addr{ + "reserved": netip.MustParseAddr("192.168.0.100"), }, }, network.IPAMConfig{ - Subnet: cidrv6.String(), - IPRange: "2001:db8:abcd::/124", + Subnet: cidrv6, + IPRange: netip.MustParsePrefix("2001:db8:abcd::/124"), }, ), ) @@ -640,12 +640,12 @@ func TestIpvlanIPAMOverlap(t *testing.T) { net.WithIPv6(), net.WithIPAMConfig( network.IPAMConfig{ - Subnet: cidrv4.String(), - IPRange: "192.168.0.0/24", + Subnet: cidrv4, + IPRange: netip.MustParsePrefix("192.168.0.0/24"), }, network.IPAMConfig{ - Subnet: cidrv6.String(), - IPRange: "2001:db8:abcd::/120", + Subnet: cidrv6, + IPRange: netip.MustParsePrefix("2001:db8:abcd::/120"), }, ), ) @@ -669,12 +669,12 @@ func TestIpvlanIPAMOverlap(t *testing.T) { net.WithIPv6(), net.WithIPAMConfig( network.IPAMConfig{ - Subnet: cidrv4.String(), - IPRange: "192.168.0.128/25", + Subnet: cidrv4, + IPRange: netip.MustParsePrefix("192.168.0.128/25"), }, network.IPAMConfig{ - Subnet: cidrv6.String(), - IPRange: "2001:db8:abcd::80/124", + Subnet: cidrv6, + IPRange: netip.MustParsePrefix("2001:db8:abcd::80/124"), }, ), ) diff --git a/integration/network/macvlan/macvlan_test.go b/integration/network/macvlan/macvlan_test.go index 0fb8f1b8b1..32d109df0e 100644 --- a/integration/network/macvlan/macvlan_test.go +++ b/integration/network/macvlan/macvlan_test.go @@ -359,15 +359,15 @@ func testMacvlanMultiSubnet(t *testing.T, ctx context.Context, client client.API c1, err := client.ContainerInspect(ctx, id1) assert.NilError(t, err) // Inspect the v4 gateway to ensure no default GW was assigned - assert.Check(t, is.Equal(c1.NetworkSettings.Networks["dualstackbridge"].Gateway, "")) + assert.Check(t, !c1.NetworkSettings.Networks["dualstackbridge"].Gateway.IsValid()) // Inspect the v6 gateway to ensure no default GW was assigned - assert.Check(t, is.Equal(c1.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "")) + assert.Check(t, !c1.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway.IsValid()) // verify ipv4 connectivity to the explicit --ip address second to first - _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks["dualstackbridge"].IPAddress}) + _, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks["dualstackbridge"].IPAddress.String()}) assert.NilError(t, err) // verify ipv6 connectivity to the explicit --ip6 address second to first - _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks["dualstackbridge"].GlobalIPv6Address}) + _, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks["dualstackbridge"].GlobalIPv6Address.String()}) assert.NilError(t, err) // start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.102.0/24 and 2001:db8:abc4::/64 @@ -385,21 +385,21 @@ func testMacvlanMultiSubnet(t *testing.T, ctx context.Context, client client.API assert.NilError(t, err) if parent == "" { // Inspect the v4 gateway to ensure no default GW was assigned - assert.Check(t, is.Equal(c3.NetworkSettings.Networks["dualstackbridge"].Gateway, "")) + assert.Check(t, !c3.NetworkSettings.Networks["dualstackbridge"].Gateway.IsValid()) // Inspect the v6 gateway to ensure no default GW was assigned - assert.Check(t, is.Equal(c3.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "")) + assert.Check(t, !c3.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway.IsValid()) } else { // Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned - assert.Check(t, is.Equal(c3.NetworkSettings.Networks["dualstackbridge"].Gateway, "172.28.102.254")) + assert.Check(t, is.Equal(c3.NetworkSettings.Networks["dualstackbridge"].Gateway, netip.MustParseAddr("172.28.102.254"))) // Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned - assert.Check(t, is.Equal(c3.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "2001:db8:abc4::254")) + assert.Check(t, is.Equal(c3.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, netip.MustParseAddr("2001:db8:abc4::254"))) } // verify ipv4 connectivity to the explicit --ip address from third to fourth - _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks["dualstackbridge"].IPAddress}) + _, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks["dualstackbridge"].IPAddress.String()}) assert.NilError(t, err) // verify ipv6 connectivity to the explicit --ip6 address from third to fourth - _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks["dualstackbridge"].GlobalIPv6Address}) + _, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks["dualstackbridge"].GlobalIPv6Address.String()}) assert.NilError(t, err) } @@ -492,17 +492,17 @@ func TestMacvlanIPAM(t *testing.T) { net.WithIPv4(tc.enableIPv4), net.WithIPAMConfig( network.IPAMConfig{ - Subnet: subnetv4.String(), - IPRange: "10.66.77.64/30", - Gateway: "10.66.77.1", - AuxAddress: map[string]string{ - "inrange": "10.66.77.65", - "outofrange": "10.66.77.128", + Subnet: subnetv4, + IPRange: netip.MustParsePrefix("10.66.77.64/30"), + Gateway: netip.MustParseAddr("10.66.77.1"), + AuxAddress: map[string]netip.Addr{ + "inrange": netip.MustParseAddr("10.66.77.65"), + "outofrange": netip.MustParseAddr("10.66.77.128"), }, }, network.IPAMConfig{ - Subnet: subnetv6.String(), - IPRange: "2001:db8:abcd::/120", + Subnet: subnetv6, + IPRange: netip.MustParsePrefix("2001:db8:abcd::/120"), }, ), } @@ -611,16 +611,16 @@ func TestMacvlanIPAMOverlap(t *testing.T) { net.WithIPv6(), net.WithIPAMConfig( network.IPAMConfig{ - Subnet: cidrv4.String(), - IPRange: "192.168.0.0/25", - Gateway: "192.168.0.1", - AuxAddress: map[string]string{ - "reserved": "192.168.0.100", + Subnet: cidrv4, + IPRange: netip.MustParsePrefix("192.168.0.0/25"), + Gateway: netip.MustParseAddr("192.168.0.1"), + AuxAddress: map[string]netip.Addr{ + "reserved": netip.MustParseAddr("192.168.0.100"), }, }, network.IPAMConfig{ - Subnet: cidrv6.String(), - IPRange: "2001:db8:abcd::/124", + Subnet: cidrv6, + IPRange: netip.MustParsePrefix("2001:db8:abcd::/124"), }, ), ) @@ -643,12 +643,12 @@ func TestMacvlanIPAMOverlap(t *testing.T) { net.WithIPv6(), net.WithIPAMConfig( network.IPAMConfig{ - Subnet: cidrv4.String(), - IPRange: "192.168.0.0/24", + Subnet: cidrv4, + IPRange: netip.MustParsePrefix("192.168.0.0/24"), }, network.IPAMConfig{ - Subnet: cidrv6.String(), - IPRange: "2001:db8:abcd::/120", + Subnet: cidrv6, + IPRange: netip.MustParsePrefix("2001:db8:abcd::/120"), }, ), ) @@ -672,12 +672,12 @@ func TestMacvlanIPAMOverlap(t *testing.T) { net.WithIPv6(), net.WithIPAMConfig( network.IPAMConfig{ - Subnet: cidrv4.String(), - IPRange: "192.168.0.128/25", + Subnet: cidrv4, + IPRange: netip.MustParsePrefix("192.168.0.128/25"), }, network.IPAMConfig{ - Subnet: cidrv6.String(), - IPRange: "2001:db8:abcd::80/124", + Subnet: cidrv6, + IPRange: netip.MustParsePrefix("2001:db8:abcd::80/124"), }, ), ) diff --git a/integration/network/network_linux_test.go b/integration/network/network_linux_test.go index 4f936693f0..b4d62eb9ad 100644 --- a/integration/network/network_linux_test.go +++ b/integration/network/network_linux_test.go @@ -98,7 +98,7 @@ func TestHostIPv4BridgeLabel(t *testing.T) { out.IPAM.Config[0].Subnet, ipv4SNATAddr) assert.Check(t, is.Contains(chain, exp)) } else { - testutil.RunCommand(ctx, "iptables", "-t", "nat", "-C", "POSTROUTING", "-s", out.IPAM.Config[0].Subnet, "!", "-o", bridgeName, "-j", "SNAT", "--to-source", ipv4SNATAddr).Assert(t, icmd.Success) + testutil.RunCommand(ctx, "iptables", "-t", "nat", "-C", "POSTROUTING", "-s", out.IPAM.Config[0].Subnet.String(), "!", "-o", bridgeName, "-j", "SNAT", "--to-source", ipv4SNATAddr).Assert(t, icmd.Success) } } diff --git a/integration/network/service_test.go b/integration/network/service_test.go index ea82589d45..a6fb9ef164 100644 --- a/integration/network/service_test.go +++ b/integration/network/service_test.go @@ -84,7 +84,7 @@ func TestDaemonDefaultNetworkPools(t *testing.T) { // Verify bridge network's subnet out, err := c.NetworkInspect(ctx, "bridge", client.NetworkInspectOptions{}) assert.NilError(t, err) - assert.Equal(t, out.IPAM.Config[0].Subnet, "175.30.0.0/16") + assert.Equal(t, out.IPAM.Config[0].Subnet, netip.MustParsePrefix("175.30.0.0/16")) // Create a bridge network and verify its subnet is the second default pool name := "elango" + t.Name() @@ -94,7 +94,7 @@ func TestDaemonDefaultNetworkPools(t *testing.T) { defer network.RemoveNoError(ctx, t, c, name) out, err = c.NetworkInspect(ctx, name, client.NetworkInspectOptions{}) assert.NilError(t, err) - assert.Check(t, is.Equal(out.IPAM.Config[0].Subnet, "175.33.0.0/24")) + assert.Check(t, is.Equal(out.IPAM.Config[0].Subnet, netip.MustParsePrefix("175.33.0.0/24"))) // Create a bridge network and verify its subnet is the third default pool name = "saanvi" + t.Name() @@ -104,7 +104,7 @@ func TestDaemonDefaultNetworkPools(t *testing.T) { defer network.RemoveNoError(ctx, t, c, name) out, err = c.NetworkInspect(ctx, name, client.NetworkInspectOptions{}) assert.NilError(t, err) - assert.Check(t, is.Equal(out.IPAM.Config[0].Subnet, "175.33.1.0/24")) + assert.Check(t, is.Equal(out.IPAM.Config[0].Subnet, netip.MustParsePrefix("175.33.1.0/24"))) } func TestDaemonRestartWithExistingNetwork(t *testing.T) { @@ -220,7 +220,7 @@ func TestDaemonWithBipAndDefaultNetworkPool(t *testing.T) { out, err := c.NetworkInspect(ctx, "bridge", client.NetworkInspectOptions{}) assert.NilError(t, err) // Make sure BIP IP doesn't get override with new default address pool . - assert.Equal(t, out.IPAM.Config[0].Subnet, "172.60.0.0/16") + assert.Equal(t, out.IPAM.Config[0].Subnet, netip.MustParsePrefix("172.60.0.0/16")) } func TestServiceWithPredefinedNetwork(t *testing.T) { @@ -451,13 +451,13 @@ func TestServiceWithDefaultAddressPoolInit(t *testing.T) { // pool (whereas before, the subnet for the ingress network was hard-coded. // This means that the ingress network gets the subnet 20.20.0.0/24, and // the network we just created gets subnet 20.20.1.0/24. - assert.Equal(t, out.IPAM.Config[0].Subnet, "20.20.1.0/24") + assert.Equal(t, out.IPAM.Config[0].Subnet, netip.MustParsePrefix("20.20.1.0/24")) // Also inspect ingress network and make sure its in the same subnet out, err = cli.NetworkInspect(ctx, "ingress", client.NetworkInspectOptions{Verbose: true}) assert.NilError(t, err) assert.Assert(t, len(out.IPAM.Config) > 0) - assert.Equal(t, out.IPAM.Config[0].Subnet, "20.20.0.0/24") + assert.Equal(t, out.IPAM.Config[0].Subnet, netip.MustParsePrefix("20.20.0.0/24")) err = cli.ServiceRemove(ctx, serviceID) poll.WaitOn(t, noServices(ctx, cli), swarm.ServicePoll) diff --git a/integration/networking/bridge_linux_test.go b/integration/networking/bridge_linux_test.go index 2764115e72..d8b9e0a9e8 100644 --- a/integration/networking/bridge_linux_test.go +++ b/integration/networking/bridge_linux_test.go @@ -191,7 +191,7 @@ func TestBridgeICC(t *testing.T) { if pingHost == "" { if tc.isLinkLocal { inspect := container.Inspect(ctx, t, c, id1) - pingHost = inspect.NetworkSettings.Networks[bridgeName].GlobalIPv6Address + "%eth0" + pingHost = inspect.NetworkSettings.Networks[bridgeName].GlobalIPv6Address.WithZone("eth0").String() } else { pingHost = ctr1Name } @@ -329,7 +329,7 @@ func TestBridgeINC(t *testing.T) { targetAddr = ctr1Info.NetworkSettings.Networks[bridge1].GlobalIPv6Address } - pingCmd := []string{"ping", "-c1", "-W3", targetAddr} + pingCmd := []string{"ping", "-c1", "-W3", targetAddr.String()} ctr2Name := sanitizeCtrName(t.Name() + "-ctr2") attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) @@ -400,8 +400,8 @@ func TestBridgeINCRouted(t *testing.T) { insp := container.Inspect(ctx, t, c, ctrId) return ctrDesc{ id: ctrId, - ipv4: insp.NetworkSettings.Networks[netName].IPAddress, - ipv6: insp.NetworkSettings.Networks[netName].GlobalIPv6Address, + ipv4: insp.NetworkSettings.Networks[netName].IPAddress.String(), + ipv6: insp.NetworkSettings.Networks[netName].GlobalIPv6Address.String(), } } @@ -589,11 +589,11 @@ func TestAccessToPublishedPort(t *testing.T) { assert.NilError(t, err) for _, ipamCfg := range insp.IPAM.Config { ipv := "ipv4" - if strings.Contains(ipamCfg.Gateway, ":") { + if ipamCfg.Gateway.Is6() { ipv = "ipv6" } t.Run(ipv, func(t *testing.T) { - url := "http://" + net.JoinHostPort(ipamCfg.Gateway, "8080") + url := "http://" + net.JoinHostPort(ipamCfg.Gateway.String(), "8080") res := container.RunAttach(ctx, t, c, container.WithNetworkMode(clientNetName), container.WithCmd("wget", "-O-", "-T3", url), @@ -732,10 +732,10 @@ func TestInterNetworkDirectRouting(t *testing.T) { } } t.Run("w", func(t *testing.T) { // Wait for the parallel tests to complete. - t.Run("ipv4/pub", checkHTTP(pub4, tc.expPubResp)) - t.Run("ipv6/pub", checkHTTP(pub6, tc.expPubResp)) - t.Run("ipv4/unpub", checkHTTP(unpub4, tc.expUnpubResp)) - t.Run("ipv6/unpub", checkHTTP(unpub6, tc.expUnpubResp)) + t.Run("ipv4/pub", checkHTTP(pub4.String(), tc.expPubResp)) + t.Run("ipv6/pub", checkHTTP(pub6.String(), tc.expPubResp)) + t.Run("ipv4/unpub", checkHTTP(unpub4.String(), tc.expUnpubResp)) + t.Run("ipv6/unpub", checkHTTP(unpub6.String(), tc.expUnpubResp)) }) }) } @@ -796,7 +796,7 @@ func TestDefaultBridgeIPv6(t *testing.T) { defer cancel() res := container.RunAttach(attachCtx, t, c, container.WithImage("busybox:latest"), - container.WithCmd("ping", "-c1", "-W3", gIPv6), + container.WithCmd("ping", "-c1", "-W3", gIPv6.String()), ) defer c.ContainerRemove(ctx, res.ContainerID, client.ContainerRemoveOptions{ Force: true, @@ -1079,7 +1079,7 @@ func TestDisableIPv6OnInterface(t *testing.T) { // Inspect should not show an IPv6 container address. inspRes2 := container.Inspect(ctx, t, c, ctrId) - assert.Check(t, is.Equal("", inspRes2.NetworkSettings.Networks[tc.netName].GlobalIPv6Address)) + assert.Check(t, is.Equal(netip.Addr{}, inspRes2.NetworkSettings.Networks[tc.netName].GlobalIPv6Address)) assert.Check(t, is.Equal(0, inspRes2.NetworkSettings.Networks[tc.netName].GlobalIPv6PrefixLen)) // Port mappings should be IPv4-only - but can't see the proxy processes in the rootless netns. @@ -1410,11 +1410,12 @@ func TestContainerDisabledIPv6(t *testing.T) { defer c.Close() const netName = "ipv6br" + subnet6 := netip.MustParsePrefix("fd64:40cd:7fb4:8971::/64") network.CreateNoError(ctx, t, c, netName, network.WithDriver("bridge"), network.WithOption(bridge.BridgeName, netName), network.WithIPv6(), - network.WithIPAM("fd64:40cd:7fb4:8971::/64", "fd64:40cd:7fb4:8971::1"), + network.WithIPAM(subnet6.String(), "fd64:40cd:7fb4:8971::1"), ) defer network.RemoveNoError(ctx, t, c, netName) @@ -1425,7 +1426,7 @@ func TestContainerDisabledIPv6(t *testing.T) { defer c.ContainerRemove(ctx, ctrWith6, client.ContainerRemoveOptions{Force: true}) inspect := container.Inspect(ctx, t, c, ctrWith6) addr := inspect.NetworkSettings.Networks[netName].GlobalIPv6Address - assert.Check(t, is.Contains(addr, "fd64:40cd:7fb4:8971")) + assert.Check(t, subnet6.Contains(addr)) // Run a container with IPv6 disabled. const ctrNo6Name = "ctrNo6" @@ -1437,7 +1438,7 @@ func TestContainerDisabledIPv6(t *testing.T) { defer c.ContainerRemove(ctx, ctrNo6, client.ContainerRemoveOptions{Force: true}) inspect = container.Inspect(ctx, t, c, ctrNo6) addr = inspect.NetworkSettings.Networks[netName].GlobalIPv6Address - assert.Check(t, is.Equal(addr, "")) + assert.Check(t, !addr.IsValid()) execCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() @@ -1574,7 +1575,7 @@ func checkProxies(ctx context.Context, t *testing.T, c *client.Client, daemonPid if e.ctrIPv4 { ctrIP = nw.IPAddress } - wantProxies = append(wantProxies, makeExpStr(e.proto, e.hostIP, e.hostPort, ctrIP, e.ctrPort)) + wantProxies = append(wantProxies, makeExpStr(e.proto, e.hostIP, e.hostPort, ctrIP.String(), e.ctrPort)) } gotProxies := make([]string, 0, len(exp)) @@ -1903,8 +1904,7 @@ func TestNetworkInspectGateway(t *testing.T) { insp, err := c.NetworkInspect(ctx, nid, client.NetworkInspectOptions{}) assert.NilError(t, err) for _, ipamCfg := range insp.IPAM.Config { - _, err := netip.ParseAddr(ipamCfg.Gateway) - assert.Check(t, err) + assert.Check(t, ipamCfg.Gateway.IsValid()) } } diff --git a/integration/networking/etchosts_test.go b/integration/networking/etchosts_test.go index 09a44ff6ce..0fa52f68fb 100644 --- a/integration/networking/etchosts_test.go +++ b/integration/networking/etchosts_test.go @@ -98,9 +98,9 @@ ff02::2 ip6-allrouters // Append the container's own addresses/name to the expected hosts file content. inspect := container.Inspect(ctx, t, c, ctrId) bridgeEp := inspect.NetworkSettings.Networks["bridge"] - exp := tc.expEtcHosts + bridgeEp.IPAddress + "\t" + inspect.Config.Hostname + "\n" + exp := tc.expEtcHosts + bridgeEp.IPAddress.String() + "\t" + inspect.Config.Hostname + "\n" if tc.expIPv6Enabled { - exp += bridgeEp.GlobalIPv6Address + "\t" + inspect.Config.Hostname + "\n" + exp += bridgeEp.GlobalIPv6Address.String() + "\t" + inspect.Config.Hostname + "\n" } assert.Check(t, is.Equal(stdout, exp)) }) diff --git a/integration/networking/mac_addr_test.go b/integration/networking/mac_addr_test.go index 8c13fef240..8fdd882782 100644 --- a/integration/networking/mac_addr_test.go +++ b/integration/networking/mac_addr_test.go @@ -1,6 +1,7 @@ package networking import ( + "net/netip" "testing" "github.com/moby/moby/client" @@ -276,6 +277,6 @@ func TestWatchtowerCreate(t *testing.T) { // Check that the container got the expected addresses. inspect := container.Inspect(ctx, t, c, ctrName) netSettings := inspect.NetworkSettings.Networks[netName] - assert.Check(t, is.Equal(netSettings.IPAddress, ctrIP)) + assert.Check(t, is.Equal(netSettings.IPAddress, netip.MustParseAddr(ctrIP))) assert.Check(t, is.Equal(netSettings.MacAddress, ctrMAC)) } diff --git a/integration/networking/port_mapping_linux_test.go b/integration/networking/port_mapping_linux_test.go index d59405029a..25c34729f1 100644 --- a/integration/networking/port_mapping_linux_test.go +++ b/integration/networking/port_mapping_linux_test.go @@ -716,8 +716,8 @@ func TestDirectRoutingOpenPorts(t *testing.T) { insp := container.Inspect(ctx, t, c, ctrId) return ctrDesc{ id: ctrId, - ipv4: insp.NetworkSettings.Networks[netName].IPAddress, - ipv6: insp.NetworkSettings.Networks[netName].GlobalIPv6Address, + ipv4: insp.NetworkSettings.Networks[netName].IPAddress.String(), + ipv6: insp.NetworkSettings.Networks[netName].GlobalIPv6Address.String(), } } @@ -894,16 +894,16 @@ func TestAcceptFwMark(t *testing.T) { test := func(name string, expPing int, expHttp string) { t.Run(name, func(t *testing.T) { t.Run("v4/ping", func(t *testing.T) { - testPing(t, "ping", ctrIPv4, expPing) + testPing(t, "ping", ctrIPv4.String(), expPing) }) t.Run("v6/ping", func(t *testing.T) { - testPing(t, "ping6", ctrIPv6, expPing) + testPing(t, "ping6", ctrIPv6.String(), expPing) }) t.Run("v4/http", func(t *testing.T) { - testHttp(t, ctrIPv4, "80", expHttp) + testHttp(t, ctrIPv4.String(), "80", expHttp) }) t.Run("v6/http", func(t *testing.T) { - testHttp(t, ctrIPv6, "80", expHttp) + testHttp(t, ctrIPv6.String(), "80", expHttp) }) }) } @@ -1029,25 +1029,25 @@ func TestRoutedNonGateway(t *testing.T) { }, { name: "nat/direct/v4", - addr: insp.NetworkSettings.Networks[natNetName].IPAddress, + addr: insp.NetworkSettings.Networks[natNetName].IPAddress.String(), port: "80", expHttp: httpFail, }, { name: "nat/direct/v6", - addr: insp.NetworkSettings.Networks[natNetName].GlobalIPv6Address, + addr: insp.NetworkSettings.Networks[natNetName].GlobalIPv6Address.String(), port: "80", expHttp: httpFail, }, { name: "routed/direct/v4", - addr: insp.NetworkSettings.Networks[routedNetName].IPAddress, + addr: insp.NetworkSettings.Networks[routedNetName].IPAddress.String(), port: "80", expHttp: httpSuccess, }, { name: "routed/direct/v6", - addr: insp.NetworkSettings.Networks[routedNetName].GlobalIPv6Address, + addr: insp.NetworkSettings.Networks[routedNetName].GlobalIPv6Address.String(), port: "80", expHttp: httpSuccess, }, @@ -1342,7 +1342,7 @@ func testDirectRemoteAccessOnExposedPort(t *testing.T, ctx context.Context, d *d }), container.WithNetworkMode(bridgeName), container.WithEndpointSettings(bridgeName, &networktypes.EndpointSettings{ - IPAddress: ctrIP.String(), + IPAddress: ctrIP, IPPrefixLen: ctrIP.BitLen(), })) defer c.ContainerRemove(ctx, serverID, client.ContainerRemoveOptions{Force: true}) diff --git a/integration/networking/resolvconf_test.go b/integration/networking/resolvconf_test.go index e011747cee..0b468d2237 100644 --- a/integration/networking/resolvconf_test.go +++ b/integration/networking/resolvconf_test.go @@ -180,7 +180,7 @@ func TestInternalNetworkLocalDNS(t *testing.T) { // Query the internal network's DNS server (via the daemon's internal DNS server). res := container.RunAttach(ctx, t, c, container.WithNetworkMode(intNetName), - container.WithDNS([]string{serverIP}), + container.WithDNS([]string{serverIP.String()}), container.WithCmd("nslookup", "-type=A", "foo.example"), ) defer c.ContainerRemove(ctx, res.ContainerID, client.ContainerRemoveOptions{Force: true}) diff --git a/integration/service/network_linux_test.go b/integration/service/network_linux_test.go index cdea32bf05..2650643a7b 100644 --- a/integration/service/network_linux_test.go +++ b/integration/service/network_linux_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/moby/moby/api/types/network" swarmtypes "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/client" @@ -121,7 +122,7 @@ func TestDockerNetworkReConnect(t *testing.T) { n2, err := apiClient.ContainerInspect(ctx, c1) assert.NilError(t, err) - assert.Check(t, is.DeepEqual(n1, n2)) + assert.Check(t, is.DeepEqual(n1, n2, cmpopts.EquateComparable(netip.Addr{}, netip.Prefix{}))) } // Check that a swarm-scoped network can't have EnableIPv4=false. diff --git a/internal/iterutil/iterutil.go b/internal/iterutil/iterutil.go index 5a5296f779..fff2582270 100644 --- a/internal/iterutil/iterutil.go +++ b/internal/iterutil/iterutil.go @@ -53,3 +53,26 @@ func Chain2[K, V any](iters ...iter.Seq2[K, V]) iter.Seq2[K, V] { } } } + +// Map applies a function to each element of the input sequence. +func Map[T, U any](s iter.Seq[T], fn func(T) U) iter.Seq[U] { + return func(yield func(U) bool) { + for v := range s { + if !yield(fn(v)) { + return + } + } + } +} + +// Map2 applies a function to each element of the input sequence. +func Map2[K1, V1, K2, V2 any](s iter.Seq2[K1, V1], fn func(K1, V1) (K2, V2)) iter.Seq2[K2, V2] { + return func(yield func(K2, V2) bool) { + for k1, v1 := range s { + k2, v2 := fn(k1, v1) + if !yield(k2, v2) { + return + } + } + } +} diff --git a/internal/iterutil/iterutil_test.go b/internal/iterutil/iterutil_test.go index c520d5a8c7..9f97013209 100644 --- a/internal/iterutil/iterutil_test.go +++ b/internal/iterutil/iterutil_test.go @@ -3,6 +3,8 @@ package iterutil import ( "maps" "slices" + "strconv" + "strings" "testing" "gotest.tools/v3/assert" @@ -66,3 +68,17 @@ func TestChain2(t *testing.T) { assert.DeepEqual(t, maps.Collect(ab), expab) assert.DeepEqual(t, maps.Collect(abc), expabc) } + +func TestMap(t *testing.T) { + a := []int{1, 2, 3} + b := slices.Collect(Map(slices.Values(a), strconv.Itoa)) + assert.DeepEqual(t, b, []string{"1", "2", "3"}) +} + +func TestMap2(t *testing.T) { + a := map[string]int{"a": 1, "b": 2, "c": 3} + b := maps.Collect(Map2(maps.All(a), func(k string, v int) (string, string) { + return strings.ToUpper(k), strconv.Itoa(v) + })) + assert.DeepEqual(t, b, map[string]string{"A": "1", "B": "2", "C": "3"}) +} diff --git a/vendor/github.com/moby/moby/api/types/network/endpoint.go b/vendor/github.com/moby/moby/api/types/network/endpoint.go index 30f51d2f5f..ee5223d052 100644 --- a/vendor/github.com/moby/moby/api/types/network/endpoint.go +++ b/vendor/github.com/moby/moby/api/types/network/endpoint.go @@ -2,6 +2,7 @@ package network import ( "maps" + "net/netip" "slices" ) @@ -25,11 +26,11 @@ type EndpointSettings struct { // Operational data NetworkID string EndpointID string - Gateway string - IPAddress string + Gateway netip.Addr + IPAddress netip.Addr IPPrefixLen int - IPv6Gateway string - GlobalIPv6Address string + IPv6Gateway netip.Addr + GlobalIPv6Address netip.Addr GlobalIPv6PrefixLen int // DNSNames holds all the (non fully qualified) DNS names associated to this endpoint. First entry is used to // generate PTR records. @@ -54,9 +55,9 @@ func (es *EndpointSettings) Copy() *EndpointSettings { // EndpointIPAMConfig represents IPAM configurations for the endpoint type EndpointIPAMConfig struct { - IPv4Address string `json:",omitempty"` - IPv6Address string `json:",omitempty"` - LinkLocalIPs []string `json:",omitempty"` + IPv4Address netip.Addr `json:",omitempty"` + IPv6Address netip.Addr `json:",omitempty"` + LinkLocalIPs []netip.Addr `json:",omitempty"` } // Copy makes a copy of the endpoint ipam config diff --git a/vendor/github.com/moby/moby/api/types/network/endpoint_resource.go b/vendor/github.com/moby/moby/api/types/network/endpoint_resource.go index 9780253baf..6ff25b1bb6 100644 --- a/vendor/github.com/moby/moby/api/types/network/endpoint_resource.go +++ b/vendor/github.com/moby/moby/api/types/network/endpoint_resource.go @@ -5,6 +5,10 @@ package network // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command +import ( + "net/netip" +) + // EndpointResource contains network resources allocated and used for a container in a network. // // swagger:model EndpointResource @@ -24,8 +28,8 @@ type EndpointResource struct { // IPv4 address // Example: 172.19.0.2/16 - IPv4Address string `json:"IPv4Address"` + IPv4Address netip.Prefix `json:"IPv4Address"` // IPv6 address - IPv6Address string `json:"IPv6Address"` + IPv6Address netip.Prefix `json:"IPv6Address"` } diff --git a/vendor/github.com/moby/moby/api/types/network/ipam.go b/vendor/github.com/moby/moby/api/types/network/ipam.go index af83421780..e57be481b7 100644 --- a/vendor/github.com/moby/moby/api/types/network/ipam.go +++ b/vendor/github.com/moby/moby/api/types/network/ipam.go @@ -13,10 +13,10 @@ type IPAM struct { // IPAMConfig represents IPAM configurations type IPAMConfig struct { - Subnet string `json:",omitempty"` - IPRange string `json:",omitempty"` - Gateway string `json:",omitempty"` - AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` + Subnet netip.Prefix `json:",omitempty"` + IPRange netip.Prefix `json:",omitempty"` + Gateway netip.Addr `json:",omitempty"` + AuxAddress map[string]netip.Addr `json:"AuxiliaryAddresses,omitempty"` } type SubnetStatuses = map[netip.Prefix]SubnetStatus diff --git a/vendor/github.com/moby/moby/api/types/network/network_types.go b/vendor/github.com/moby/moby/api/types/network/network_types.go index 413e5fbed0..5401f55f82 100644 --- a/vendor/github.com/moby/moby/api/types/network/network_types.go +++ b/vendor/github.com/moby/moby/api/types/network/network_types.go @@ -30,14 +30,6 @@ type CreateRequest struct { Labels map[string]string // Labels holds metadata specific to the network being created. } -// ServiceInfo represents service parameters with the list of service's tasks -type ServiceInfo struct { - VIP string - Ports []string - LocalLBIndex int - Tasks []Task -} - // 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 { diff --git a/vendor/github.com/moby/moby/api/types/network/peer_info.go b/vendor/github.com/moby/moby/api/types/network/peer_info.go index 0522a2392d..dc88ec16fa 100644 --- a/vendor/github.com/moby/moby/api/types/network/peer_info.go +++ b/vendor/github.com/moby/moby/api/types/network/peer_info.go @@ -5,6 +5,10 @@ package network // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command +import ( + "net/netip" +) + // PeerInfo represents one peer of an overlay network. // // swagger:model PeerInfo @@ -16,5 +20,5 @@ type PeerInfo struct { // IP-address of the peer-node in the Swarm cluster. // Example: 10.133.77.91 - IP string `json:"IP"` + IP netip.Addr `json:"IP"` } diff --git a/vendor/github.com/moby/moby/api/types/network/service_info.go b/vendor/github.com/moby/moby/api/types/network/service_info.go new file mode 100644 index 0000000000..fdd92f1611 --- /dev/null +++ b/vendor/github.com/moby/moby/api/types/network/service_info.go @@ -0,0 +1,28 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package network + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/netip" +) + +// ServiceInfo represents service parameters with the list of service's tasks +// +// swagger:model ServiceInfo +type ServiceInfo struct { + + // v IP + VIP netip.Addr `json:"VIP"` + + // ports + Ports []string `json:"Ports"` + + // local l b index + LocalLBIndex int `json:"LocalLBIndex"` + + // tasks + Tasks []Task `json:"Tasks"` +} diff --git a/vendor/github.com/moby/moby/api/types/network/task.go b/vendor/github.com/moby/moby/api/types/network/task.go index 9a55fa1737..a547523a44 100644 --- a/vendor/github.com/moby/moby/api/types/network/task.go +++ b/vendor/github.com/moby/moby/api/types/network/task.go @@ -5,6 +5,10 @@ package network // This file was generated by the swagger tool. // Editing this file might prove futile when you re-run the swagger generate command +import ( + "net/netip" +) + // Task carries the information about one backend task // // swagger:model Task @@ -17,7 +21,7 @@ type Task struct { EndpointID string `json:"EndpointID"` // endpoint IP - EndpointIP string `json:"EndpointIP"` + EndpointIP netip.Addr `json:"EndpointIP"` // info Info map[string]string `json:"Info"`