Files
moby/daemon/libnetwork/network.go
Cory Snider a90adb6dc1 api/types/network: use netip types as appropriate
And generate the ServiceInfo struct from the Swagger spec.

Signed-off-by: Cory Snider <csnider@mirantis.com>
2025-10-03 21:39:14 +02:00

2225 lines
58 KiB
Go

package libnetwork
import (
"context"
"encoding/json"
"errors"
"fmt"
"maps"
"net"
"net/netip"
"runtime"
"slices"
"strings"
"sync"
"time"
cerrdefs "github.com/containerd/errdefs"
"github.com/containerd/log"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/v2/daemon/internal/netiputil"
"github.com/moby/moby/v2/daemon/internal/sliceutil"
"github.com/moby/moby/v2/daemon/internal/stringid"
"github.com/moby/moby/v2/daemon/libnetwork/datastore"
"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
"github.com/moby/moby/v2/daemon/libnetwork/internal/setmatrix"
"github.com/moby/moby/v2/daemon/libnetwork/ipamapi"
"github.com/moby/moby/v2/daemon/libnetwork/ipams/defaultipam"
"github.com/moby/moby/v2/daemon/libnetwork/netlabel"
"github.com/moby/moby/v2/daemon/libnetwork/netutils"
"github.com/moby/moby/v2/daemon/libnetwork/networkdb"
"github.com/moby/moby/v2/daemon/libnetwork/options"
"github.com/moby/moby/v2/daemon/libnetwork/scope"
"github.com/moby/moby/v2/daemon/libnetwork/types"
"github.com/moby/moby/v2/errdefs"
"github.com/moby/moby/v2/internal/iterutil"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// EndpointWalker is a client provided function which will be used to walk the Endpoints.
// When the function returns true, the walk will stop.
type EndpointWalker func(ep *Endpoint) bool
// ipInfo is the reverse mapping from IP to service name to serve the PTR query.
// extResolver is set if an external server resolves a service name to this IP.
// It's an indication to defer PTR queries also to that external server.
type ipInfo struct {
name string
serviceID string
extResolver bool
}
// svcMapEntry is the body of the element into the svcMap
// The ip is a string because the SetMatrix does not accept non hashable values
type svcMapEntry struct {
ip string
serviceID string
}
type svcInfo struct {
svcMap setmatrix.SetMatrix[string, svcMapEntry]
svcIPv6Map setmatrix.SetMatrix[string, svcMapEntry]
ipMap setmatrix.SetMatrix[string, ipInfo]
service map[string][]servicePorts
}
// backing container or host's info
type serviceTarget struct {
name string
ip net.IP
port uint16
}
type servicePorts struct {
portName string
proto string
target []serviceTarget
}
type networkDBTable struct {
name string
objType driverapi.ObjectType
}
// IpamConf contains all the ipam related configurations for a network
//
// TODO(aker): use proper net/* structs instead of string literals.
type IpamConf struct {
// PreferredPool is the master address pool for containers and network interfaces.
PreferredPool string
// SubPool is a subset of the master pool. If specified,
// this becomes the container pool for automatic address allocations.
SubPool string
// Gateway is the preferred Network Gateway address (optional).
Gateway string
// AuxAddresses contains auxiliary addresses for network driver. Must be within the master pool.
// libnetwork will reserve them if they fall into the container pool.
AuxAddresses map[string]string
}
// Validate checks whether the configuration is valid
func (c *IpamConf) Validate() error {
if c.Gateway != "" && net.ParseIP(c.Gateway) == nil {
return types.InvalidParameterErrorf("invalid gateway address %s in Ipam configuration", c.Gateway)
}
return nil
}
// Contains checks whether the ipam master address pool contains [addr].
func (c *IpamConf) Contains(addr netip.Addr) bool {
if c == nil {
return false
}
if c.PreferredPool == "" {
return false
}
allowedRange, _ := netiputil.ParseCIDR(c.PreferredPool)
return allowedRange.Contains(addr)
}
// IsStatic checks whether the subnet was statically allocated (ie. user-defined).
func (c *IpamConf) IsStatic() bool {
return c != nil && c.PreferredPool != ""
}
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
Meta map[string]string
driverapi.IPAMData
}
// MarshalJSON encodes IpamInfo into json message
func (i *IpamInfo) MarshalJSON() ([]byte, error) {
m := map[string]any{
"PoolID": i.PoolID,
}
v, err := json.Marshal(&i.IPAMData)
if err != nil {
return nil, err
}
m["IPAMData"] = string(v)
if i.Meta != nil {
m["Meta"] = i.Meta
}
return json.Marshal(m)
}
// UnmarshalJSON decodes json message into PoolData
func (i *IpamInfo) UnmarshalJSON(data []byte) error {
var (
m map[string]any
err error
)
if err = json.Unmarshal(data, &m); err != nil {
return err
}
i.PoolID = m["PoolID"].(string)
if v, ok := m["Meta"]; ok {
b, _ := json.Marshal(v) //nolint:errchkjson // FIXME: handle json (Un)Marshal errors
if err = json.Unmarshal(b, &i.Meta); err != nil {
return err
}
}
if v, ok := m["IPAMData"]; ok {
if err = json.Unmarshal([]byte(v.(string)), &i.IPAMData); err != nil {
return err
}
}
return nil
}
// Network represents a logical connectivity zone that containers may
// join using the Link method. A network is managed by a specific driver.
type Network struct {
ctrlr *Controller
name string
networkType string // networkType is the name of the netdriver used by this network
id string
created time.Time
scope string // network data scope
labels map[string]string
ipamType string // ipamType is the name of the IPAM driver
ipamOptions map[string]string
addrSpace string
ipamV4Config []*IpamConf
ipamV6Config []*IpamConf
ipamV4Info []*IpamInfo
ipamV6Info []*IpamInfo
enableIPv4 bool
enableIPv6 bool
generic options.Generic
dbIndex uint64
dbExists bool
persist bool
drvOnce *sync.Once
resolver []*Resolver
internal bool
attachable bool
inDelete bool
ingress bool
driverTables []networkDBTable
dynamic bool
configOnly bool
configFrom string
loadBalancerIP net.IP
loadBalancerMode string
skipGwAllocIPv4 bool
skipGwAllocIPv6 bool
platformNetwork //nolint:nolintlint,unused // only populated on windows
mu sync.Mutex
}
const (
loadBalancerModeNAT = "NAT"
loadBalancerModeDSR = "DSR"
loadBalancerModeDefault = loadBalancerModeNAT
)
// Name returns a user chosen name for this network.
func (n *Network) Name() string {
n.mu.Lock()
defer n.mu.Unlock()
return n.name
}
// ID returns a system generated id for this network.
func (n *Network) ID() string {
n.mu.Lock()
defer n.mu.Unlock()
return n.id
}
func (n *Network) Created() time.Time {
n.mu.Lock()
defer n.mu.Unlock()
return n.created
}
// Type returns the type of network, which corresponds to its managing driver.
func (n *Network) Type() string {
n.mu.Lock()
defer n.mu.Unlock()
return n.networkType
}
// Driver is an alias for [Network.Type].
func (n *Network) Driver() string {
return n.Type()
}
func (n *Network) Resolvers() []*Resolver {
n.mu.Lock()
defer n.mu.Unlock()
return n.resolver
}
func (n *Network) Key() []string {
n.mu.Lock()
defer n.mu.Unlock()
return []string{datastore.NetworkKeyPrefix, n.id}
}
func (n *Network) KeyPrefix() []string {
return []string{datastore.NetworkKeyPrefix}
}
func (n *Network) Value() []byte {
n.mu.Lock()
defer n.mu.Unlock()
b, err := json.Marshal(n)
if err != nil {
return nil
}
return b
}
func (n *Network) SetValue(value []byte) error {
return json.Unmarshal(value, n)
}
func (n *Network) Index() uint64 {
n.mu.Lock()
defer n.mu.Unlock()
return n.dbIndex
}
func (n *Network) SetIndex(index uint64) {
n.mu.Lock()
n.dbIndex = index
n.dbExists = true
n.mu.Unlock()
}
func (n *Network) Exists() bool {
n.mu.Lock()
defer n.mu.Unlock()
return n.dbExists
}
func (n *Network) Skip() bool {
n.mu.Lock()
defer n.mu.Unlock()
return !n.persist
}
func (n *Network) New() datastore.KVObject {
n.mu.Lock()
defer n.mu.Unlock()
return &Network{
ctrlr: n.ctrlr,
drvOnce: &sync.Once{},
scope: n.scope,
}
}
// Copy returns a deep copy of the [IpamConf]. If the receiver is nil,
// Copy returns nil.
func (c *IpamConf) Copy() *IpamConf {
if c == nil {
return nil
}
return &IpamConf{
PreferredPool: c.PreferredPool,
SubPool: c.SubPool,
Gateway: c.Gateway,
AuxAddresses: maps.Clone(c.AuxAddresses),
}
}
// Copy returns a deep copy of [IpamInfo]. If the receiver is nil,
// Copy returns nil.
func (i *IpamInfo) Copy() *IpamInfo {
if i == nil {
return nil
}
var aux map[string]*net.IPNet
if i.IPAMData.AuxAddresses != nil {
aux = make(map[string]*net.IPNet, len(i.IPAMData.AuxAddresses))
for k, v := range i.AuxAddresses {
aux[k] = types.GetIPNetCopy(v)
}
}
return &IpamInfo{
PoolID: i.PoolID,
Meta: maps.Clone(i.Meta),
IPAMData: driverapi.IPAMData{
AddressSpace: i.AddressSpace,
Pool: types.GetIPNetCopy(i.IPAMData.Pool),
Gateway: types.GetIPNetCopy(i.IPAMData.Gateway),
AuxAddresses: aux,
},
}
}
func (n *Network) validateConfiguration() error {
if n.configOnly {
// Only supports network specific configurations.
// Network operator configurations are not supported.
if n.ingress || n.internal || n.attachable || n.scope != "" {
return types.ForbiddenErrorf("configuration network can only contain network " +
"specific fields. Network operator fields like " +
"[ ingress | internal | attachable | scope ] are not supported.")
}
}
if n.configFrom == "" {
if err := n.validateAdvertiseAddrConfig(); err != nil {
return err
}
} else {
if n.configOnly {
return types.ForbiddenErrorf("a configuration network cannot depend on another configuration network")
}
// Check that no config has been set for this --config-from network.
// (Note that the default for enableIPv4 is 'true', ipamType has its own default,
// and other settings are zero valued by default.)
if n.ipamType != "" &&
n.ipamType != defaultIpamForNetworkType(n.networkType) ||
!n.enableIPv4 || n.enableIPv6 ||
len(n.labels) > 0 || len(n.ipamOptions) > 0 ||
len(n.ipamV4Config) > 0 || len(n.ipamV6Config) > 0 {
return types.ForbiddenErrorf("user specified configurations are not supported if the network depends on a configuration network")
}
if len(n.generic) > 0 {
if data, ok := n.generic[netlabel.GenericData]; ok {
var (
driverOptions map[string]string
opts any
)
switch t := data.(type) {
case map[string]any, map[string]string:
opts = t
}
ba, err := json.Marshal(opts)
if err != nil {
return fmt.Errorf("failed to validate network configuration: %v", err)
}
if err := json.Unmarshal(ba, &driverOptions); err != nil {
return fmt.Errorf("failed to validate network configuration: %v", err)
}
if len(driverOptions) > 0 {
return types.ForbiddenErrorf("network driver options are not supported if the network depends on a configuration network")
}
}
}
}
return nil
}
// applyConfigurationTo applies network specific configurations.
func (n *Network) applyConfigurationTo(to *Network) error {
to.enableIPv4 = n.enableIPv4
to.enableIPv6 = n.enableIPv6
if len(n.labels) > 0 {
to.labels = make(map[string]string, len(n.labels))
for k, v := range n.labels {
if _, ok := to.labels[k]; !ok {
to.labels[k] = v
}
}
}
if n.ipamType != "" {
to.ipamType = n.ipamType
}
if len(n.ipamOptions) > 0 {
to.ipamOptions = make(map[string]string, len(n.ipamOptions))
for k, v := range n.ipamOptions {
if _, ok := to.ipamOptions[k]; !ok {
to.ipamOptions[k] = v
}
}
}
if len(n.ipamV4Config) > 0 {
to.ipamV4Config = make([]*IpamConf, 0, len(n.ipamV4Config))
to.ipamV4Config = append(to.ipamV4Config, n.ipamV4Config...)
}
if len(n.ipamV6Config) > 0 {
to.ipamV6Config = make([]*IpamConf, 0, len(n.ipamV6Config))
to.ipamV6Config = append(to.ipamV6Config, n.ipamV6Config...)
}
if len(n.generic) > 0 {
to.generic = options.Generic{}
for k, v := range n.generic {
to.generic[k] = v
}
}
// Network drivers only see generic flags. So, make sure they match.
if to.generic == nil {
to.generic = options.Generic{}
}
to.generic[netlabel.Internal] = to.internal
to.generic[netlabel.EnableIPv4] = to.enableIPv4
to.generic[netlabel.EnableIPv6] = to.enableIPv6
return nil
}
func (n *Network) CopyTo(o datastore.KVObject) error {
n.mu.Lock()
defer n.mu.Unlock()
dstN := o.(*Network)
dstN.name = n.name
dstN.id = n.id
dstN.created = n.created
dstN.networkType = n.networkType
dstN.scope = n.scope
dstN.dynamic = n.dynamic
dstN.ipamType = n.ipamType
dstN.enableIPv4 = n.enableIPv4
dstN.enableIPv6 = n.enableIPv6
dstN.persist = n.persist
dstN.dbIndex = n.dbIndex
dstN.dbExists = n.dbExists
dstN.drvOnce = n.drvOnce
dstN.internal = n.internal
dstN.attachable = n.attachable
dstN.inDelete = n.inDelete
dstN.ingress = n.ingress
dstN.configOnly = n.configOnly
dstN.configFrom = n.configFrom
dstN.loadBalancerIP = n.loadBalancerIP
dstN.loadBalancerMode = n.loadBalancerMode
dstN.skipGwAllocIPv4 = n.skipGwAllocIPv4
dstN.skipGwAllocIPv6 = n.skipGwAllocIPv6
// copy labels
if dstN.labels == nil {
dstN.labels = make(map[string]string, len(n.labels))
}
for k, v := range n.labels {
dstN.labels[k] = v
}
if n.ipamOptions != nil {
dstN.ipamOptions = make(map[string]string, len(n.ipamOptions))
for k, v := range n.ipamOptions {
dstN.ipamOptions[k] = v
}
}
for _, c := range n.ipamV4Config {
dstN.ipamV4Config = append(dstN.ipamV4Config, c.Copy())
}
for _, inf := range n.ipamV4Info {
dstN.ipamV4Info = append(dstN.ipamV4Info, inf.Copy())
}
for _, c := range n.ipamV6Config {
dstN.ipamV6Config = append(dstN.ipamV6Config, c.Copy())
}
for _, inf := range n.ipamV6Info {
dstN.ipamV6Info = append(dstN.ipamV6Info, inf.Copy())
}
dstN.generic = options.Generic{}
for k, v := range n.generic {
dstN.generic[k] = v
}
return nil
}
func (n *Network) validateAdvertiseAddrConfig() error {
var errs []error
_, err := n.validatedAdvertiseAddrNMsgs()
errs = append(errs, err)
_, err = n.validatedAdvertiseAddrInterval()
errs = append(errs, err)
return errors.Join(errs...)
}
func (n *Network) advertiseAddrNMsgs() (int, bool) {
v, err := n.validatedAdvertiseAddrNMsgs()
if err != nil || v == nil {
// On Linux, config was validated before network creation. This
// path is for un-set values and unsupported platforms.
return 0, false
}
return *v, true
}
func (n *Network) advertiseAddrInterval() (time.Duration, bool) {
v, err := n.validatedAdvertiseAddrInterval()
if err != nil || v == nil {
// On Linux, config was validated before network creation. This
// path is for un-set values and unsupported platforms.
return 0, false
}
return *v, true
}
func (n *Network) MarshalJSON() ([]byte, error) {
// TODO: Can be made much more generic with the help of reflection (but has some golang limitations)
netMap := make(map[string]any)
netMap["name"] = n.name
netMap["id"] = n.id
netMap["created"] = n.created
netMap["networkType"] = n.networkType
netMap["scope"] = n.scope
netMap["labels"] = n.labels
netMap["ipamType"] = n.ipamType
netMap["ipamOptions"] = n.ipamOptions
netMap["addrSpace"] = n.addrSpace
netMap["enableIPv4"] = n.enableIPv4
netMap["enableIPv6"] = n.enableIPv6
if n.generic != nil {
netMap["generic"] = n.generic
}
netMap["persist"] = n.persist
if len(n.ipamV4Config) > 0 {
ics, err := json.Marshal(n.ipamV4Config)
if err != nil {
return nil, err
}
netMap["ipamV4Config"] = string(ics)
}
if len(n.ipamV4Info) > 0 {
iis, err := json.Marshal(n.ipamV4Info)
if err != nil {
return nil, err
}
netMap["ipamV4Info"] = string(iis)
}
if len(n.ipamV6Config) > 0 {
ics, err := json.Marshal(n.ipamV6Config)
if err != nil {
return nil, err
}
netMap["ipamV6Config"] = string(ics)
}
if len(n.ipamV6Info) > 0 {
iis, err := json.Marshal(n.ipamV6Info)
if err != nil {
return nil, err
}
netMap["ipamV6Info"] = string(iis)
}
netMap["internal"] = n.internal
netMap["attachable"] = n.attachable
netMap["inDelete"] = n.inDelete
netMap["ingress"] = n.ingress
netMap["configOnly"] = n.configOnly
netMap["configFrom"] = n.configFrom
netMap["loadBalancerIP"] = n.loadBalancerIP
netMap["loadBalancerMode"] = n.loadBalancerMode
netMap["skipGwAllocIPv4"] = n.skipGwAllocIPv4
netMap["skipGwAllocIPv6"] = n.skipGwAllocIPv6
return json.Marshal(netMap)
}
func (n *Network) UnmarshalJSON(b []byte) (err error) {
// TODO: Can be made much more generic with the help of reflection (but has some golang limitations)
var netMap map[string]any
if err := json.Unmarshal(b, &netMap); err != nil {
return err
}
n.name = netMap["name"].(string)
n.id = netMap["id"].(string)
// "created" is not available in older versions
if v, ok := netMap["created"]; ok {
// n.created is time.Time but marshalled as string
if err = n.created.UnmarshalText([]byte(v.(string))); err != nil {
log.G(context.TODO()).Warnf("failed to unmarshal creation time %v: %v", v, err)
n.created = time.Time{}
}
}
n.networkType = netMap["networkType"].(string)
n.enableIPv4 = true // Default for networks created before the option to disable IPv4 was added.
if v, ok := netMap["enableIPv4"]; ok {
n.enableIPv4 = v.(bool)
}
n.enableIPv6 = netMap["enableIPv6"].(bool)
// if we weren't unmarshaling to netMap we could simply set n.labels
// unfortunately, we can't because map[string]interface{} != map[string]string
if labels, ok := netMap["labels"].(map[string]any); ok {
n.labels = make(map[string]string, len(labels))
for label, value := range labels {
n.labels[label] = value.(string)
}
}
if v, ok := netMap["ipamOptions"]; ok {
if iOpts, ok := v.(map[string]any); ok {
n.ipamOptions = make(map[string]string, len(iOpts))
for k, v := range iOpts {
n.ipamOptions[k] = v.(string)
}
}
}
if v, ok := netMap["generic"]; ok {
n.generic = v.(map[string]any)
// Restore opts in their map[string]string form
if gv, ok := n.generic[netlabel.GenericData]; ok {
var lmap map[string]string
ba, err := json.Marshal(gv)
if err != nil {
return err
}
if err := json.Unmarshal(ba, &lmap); err != nil {
return err
}
n.generic[netlabel.GenericData] = lmap
}
}
if v, ok := netMap["persist"]; ok {
n.persist = v.(bool)
}
if v, ok := netMap["ipamType"]; ok {
n.ipamType = v.(string)
} else {
n.ipamType = defaultipam.DriverName
}
if v, ok := netMap["addrSpace"]; ok {
n.addrSpace = v.(string)
}
if v, ok := netMap["ipamV4Config"]; ok {
if err := json.Unmarshal([]byte(v.(string)), &n.ipamV4Config); err != nil {
return err
}
}
if v, ok := netMap["ipamV4Info"]; ok {
if err := json.Unmarshal([]byte(v.(string)), &n.ipamV4Info); err != nil {
return err
}
}
if v, ok := netMap["ipamV6Config"]; ok {
if err := json.Unmarshal([]byte(v.(string)), &n.ipamV6Config); err != nil {
return err
}
}
if v, ok := netMap["ipamV6Info"]; ok {
if err := json.Unmarshal([]byte(v.(string)), &n.ipamV6Info); err != nil {
return err
}
}
if v, ok := netMap["internal"]; ok {
n.internal = v.(bool)
}
if v, ok := netMap["attachable"]; ok {
n.attachable = v.(bool)
}
if s, ok := netMap["scope"]; ok {
n.scope = s.(string)
}
if v, ok := netMap["inDelete"]; ok {
n.inDelete = v.(bool)
}
if v, ok := netMap["ingress"]; ok {
n.ingress = v.(bool)
}
if v, ok := netMap["configOnly"]; ok {
n.configOnly = v.(bool)
}
if v, ok := netMap["configFrom"]; ok {
n.configFrom = v.(string)
}
if v, ok := netMap["loadBalancerIP"]; ok {
n.loadBalancerIP = net.ParseIP(v.(string))
}
n.loadBalancerMode = loadBalancerModeDefault
if v, ok := netMap["loadBalancerMode"]; ok {
n.loadBalancerMode = v.(string)
}
if v, ok := netMap["skipGwAllocIPv4"]; ok {
n.skipGwAllocIPv4 = v.(bool)
}
if v, ok := netMap["skipGwAllocIPv6"]; ok {
n.skipGwAllocIPv6 = v.(bool)
}
return nil
}
// NetworkOption is an option setter function type used to pass various options to
// NewNetwork method. The various setter functions of type NetworkOption are
// provided by libnetwork, they look like NetworkOptionXXXX(...)
type NetworkOption func(n *Network)
// NetworkOptionGeneric function returns an option setter for a Generic option defined
// in a Dictionary of Key-Value pair
func NetworkOptionGeneric(generic map[string]any) NetworkOption {
return func(n *Network) {
if n.generic == nil {
n.generic = make(map[string]any)
}
if val, ok := generic[netlabel.EnableIPv4]; ok {
n.enableIPv4 = val.(bool)
}
if val, ok := generic[netlabel.EnableIPv6]; ok {
n.enableIPv6 = val.(bool)
}
if val, ok := generic[netlabel.Internal]; ok {
n.internal = val.(bool)
}
for k, v := range generic {
n.generic[k] = v
}
}
}
// NetworkOptionIngress returns an option setter to indicate if a network is
// an ingress network.
func NetworkOptionIngress(ingress bool) NetworkOption {
return func(n *Network) {
n.ingress = ingress
}
}
// NetworkOptionPersist returns an option setter to set persistence policy for a network
func NetworkOptionPersist(persist bool) NetworkOption {
return func(n *Network) {
n.persist = persist
}
}
// NetworkOptionEnableIPv4 returns an option setter to explicitly configure IPv4
func NetworkOptionEnableIPv4(enableIPv4 bool) NetworkOption {
return func(n *Network) {
if n.generic == nil {
n.generic = make(map[string]any)
}
n.enableIPv4 = enableIPv4
n.generic[netlabel.EnableIPv4] = enableIPv4
}
}
// NetworkOptionEnableIPv6 returns an option setter to explicitly configure IPv6
func NetworkOptionEnableIPv6(enableIPv6 bool) NetworkOption {
return func(n *Network) {
if n.generic == nil {
n.generic = make(map[string]any)
}
n.enableIPv6 = enableIPv6
n.generic[netlabel.EnableIPv6] = enableIPv6
}
}
// NetworkOptionInternalNetwork returns an option setter to config the network
// to be internal which disables default gateway service
func NetworkOptionInternalNetwork() NetworkOption {
return func(n *Network) {
if n.generic == nil {
n.generic = make(map[string]any)
}
n.internal = true
n.generic[netlabel.Internal] = true
}
}
// NetworkOptionAttachable returns an option setter to set attachable for a network
func NetworkOptionAttachable(attachable bool) NetworkOption {
return func(n *Network) {
n.attachable = attachable
}
}
// NetworkOptionScope returns an option setter to overwrite the network's scope.
// By default the network's scope is set to the network driver's datascope.
func NetworkOptionScope(scope string) NetworkOption {
return func(n *Network) {
n.scope = scope
}
}
// NetworkOptionIpam function returns an option setter for the ipam configuration for this network
func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf, opts map[string]string) NetworkOption {
return func(n *Network) {
if ipamDriver != "" {
n.ipamType = ipamDriver
if ipamDriver == defaultipam.DriverName {
n.ipamType = defaultIpamForNetworkType(n.Type())
}
}
n.ipamOptions = opts
n.addrSpace = addrSpace
n.ipamV4Config = ipV4
n.ipamV6Config = ipV6
}
}
// NetworkOptionLBEndpoint function returns an option setter for the configuration of the load balancer endpoint for this network
func NetworkOptionLBEndpoint(ip net.IP) NetworkOption {
return func(n *Network) {
n.loadBalancerIP = ip
}
}
// NetworkOptionDriverOpts function returns an option setter for any driver parameter described by a map
func NetworkOptionDriverOpts(opts map[string]string) NetworkOption {
return func(n *Network) {
if n.generic == nil {
n.generic = make(map[string]any)
}
if opts == nil {
opts = make(map[string]string)
}
// Store the options
n.generic[netlabel.GenericData] = opts
}
}
// NetworkOptionLabels function returns an option setter for labels specific to a network
func NetworkOptionLabels(labels map[string]string) NetworkOption {
return func(n *Network) {
n.labels = labels
}
}
// NetworkOptionDynamic function returns an option setter for dynamic option for a network
func NetworkOptionDynamic() NetworkOption {
return func(n *Network) {
n.dynamic = true
}
}
// NetworkOptionConfigOnly tells controller this network is
// a configuration only network. It serves as a configuration
// for other networks.
func NetworkOptionConfigOnly() NetworkOption {
return func(n *Network) {
n.configOnly = true
}
}
// NetworkOptionConfigFrom tells controller to pick the
// network configuration from a configuration only network
func NetworkOptionConfigFrom(name string) NetworkOption {
return func(n *Network) {
n.configFrom = name
}
}
func (n *Network) processOptions(options ...NetworkOption) {
for _, opt := range options {
if opt != nil {
opt(n)
}
}
}
type networkDeleteParams struct {
rmLBEndpoint bool
}
// NetworkDeleteOption is a type for optional parameters to pass to the
// Network.Delete() function.
type NetworkDeleteOption func(p *networkDeleteParams)
// NetworkDeleteOptionRemoveLB informs a Network.Delete() operation that should
// remove the load balancer endpoint for this network. Note that the Delete()
// method will automatically remove a load balancing endpoint for most networks
// when the network is otherwise empty. However, this does not occur for some
// networks. In particular, networks marked as ingress (which are supposed to
// be more permanent than other overlay networks) won't automatically remove
// the LB endpoint on Delete(). This method allows for explicit removal of
// such networks provided there are no other endpoints present in the network.
// If the network still has non-LB endpoints present, Delete() will not
// remove the LB endpoint and will return an error.
func NetworkDeleteOptionRemoveLB(p *networkDeleteParams) {
p.rmLBEndpoint = true
}
func (n *Network) resolveDriver(name string, load bool) (driverapi.Driver, driverapi.Capability, error) {
c := n.getController()
// Check if a driver for the specified network type is available
d, capabilities := c.drvRegistry.Driver(name)
if d == nil {
if load {
err := c.loadDriver(name)
if err != nil {
return nil, driverapi.Capability{}, err
}
d, capabilities = c.drvRegistry.Driver(name)
if d == nil {
return nil, driverapi.Capability{}, fmt.Errorf("could not resolve driver %s in registry", name)
}
} else {
// don't fail if driver loading is not required
return nil, driverapi.Capability{}, nil
}
}
return d, capabilities, nil
}
func (n *Network) driverIsMultihost() bool {
_, capabilities, err := n.resolveDriver(n.networkType, true)
if err != nil {
return false
}
return capabilities.ConnectivityScope == scope.Global
}
func (n *Network) driver(load bool) (driverapi.Driver, error) {
d, capabilities, err := n.resolveDriver(n.networkType, load)
if err != nil {
return nil, err
}
n.mu.Lock()
// If load is not required, driver, cap and err may all be nil
if n.scope == "" {
n.scope = capabilities.DataScope
}
if n.dynamic {
// If the network is dynamic, then it is swarm
// scoped regardless of the backing driver.
n.scope = scope.Swarm
}
n.mu.Unlock()
return d, nil
}
// Delete the network.
func (n *Network) Delete(options ...NetworkDeleteOption) error {
var params networkDeleteParams
for _, opt := range options {
opt(&params)
}
return n.delete(false, params.rmLBEndpoint)
}
// This function gets called in 3 ways:
// - Delete() -- (false, false)
// remove if endpoint count == 0 or endpoint count == 1 and
// there is a load balancer IP
// - Delete(libnetwork.NetworkDeleteOptionRemoveLB) -- (false, true)
// remove load balancer and network if endpoint count == 1
// - controller.networkCleanup() -- (true, true)
// remove the network no matter what
func (n *Network) delete(force bool, rmLBEndpoint bool) error {
n.mu.Lock()
c := n.ctrlr
name := n.name
id := n.id
n.mu.Unlock()
c.networkLocker.Lock(id)
defer c.networkLocker.Unlock(id) //nolint:errcheck
n, err := c.getNetworkFromStore(id)
if err != nil {
return errdefs.NotFound(fmt.Errorf("unknown network %s id %s", name, id))
}
// Only remove ingress on force removal or explicit LB endpoint removal
if n.ingress && !force && !rmLBEndpoint {
return &ActiveEndpointsError{name: n.name, id: n.id}
}
if !force && n.configOnly {
refNws := c.findNetworks(filterNetworkByConfigFrom(n.name))
if len(refNws) > 0 {
return types.ForbiddenErrorf("configuration network %q is in use", n.Name())
}
}
// Check that the network is empty
var emptyCount int
if n.hasLoadBalancerEndpoint() {
emptyCount = 1
}
eps := c.findEndpoints(filterEndpointByNetworkId(n.id))
if !force && len(eps) > emptyCount {
return &ActiveEndpointsError{
name: n.name,
id: n.id,
endpoints: sliceutil.Map(eps, func(ep *Endpoint) string {
return fmt.Sprintf(`name:%q id:%q`, ep.name, stringid.TruncateID(ep.id))
}),
}
}
if n.hasLoadBalancerEndpoint() {
// If we got to this point, then the following must hold:
// * force is true OR endpoint count == 1
if err := n.deleteLoadBalancerSandbox(); err != nil {
if !force {
return err
}
// continue deletion when force is true even on error
log.G(context.TODO()).Warnf("Error deleting load balancer sandbox: %v", err)
}
}
// Up to this point, errors that we returned were recoverable.
// From here on, any errors leave us in an inconsistent state.
// This is unfortunate, but there isn't a safe way to
// reconstitute a load-balancer endpoint after removing it.
// Mark the network for deletion
n.inDelete = true
if err = c.storeNetwork(context.TODO(), n); err != nil {
return fmt.Errorf("error marking network %s (%s) for deletion: %v", n.Name(), n.ID(), err)
}
if n.configOnly {
goto removeFromStore
}
n.ipamRelease()
// We are about to delete the network. Leave the gossip
// cluster for the network to stop all incoming network
// specific gossip updates before cleaning up all the service
// bindings for the network. But cleanup service binding
// before deleting the network from the store since service
// bindings cleanup requires the network in the store.
n.cancelDriverWatches()
if err = n.leaveCluster(); err != nil {
log.G(context.TODO()).Errorf("Failed leaving network %s from the agent cluster: %v", n.Name(), err)
}
// Cleanup the service discovery for this network
c.cleanupServiceDiscovery(n.ID())
// Cleanup the load balancer. On Windows this call is required
// to remove remote loadbalancers in VFP, and must be performed before
// dataplane network deletion.
if runtime.GOOS == "windows" {
c.cleanupServiceBindings(n.ID())
}
// Delete the network from the dataplane
if err = n.deleteNetwork(); err != nil {
if !force {
return err
}
log.G(context.TODO()).Debugf("driver failed to delete stale network %s (%s): %v", n.Name(), n.ID(), err)
}
removeFromStore:
// TODO(robmry) - remove this once downgrade past 28.1.0 is no longer supported.
// The endpoint count is no longer used, it's created in the store to make
// downgrade work, versions older than 28.1.0 expect to read it and error if they
// can't.
if err := c.store.DeleteObject(&endpointCnt{n: n}); err != nil {
if !errors.Is(err, datastore.ErrKeyNotFound) {
log.G(context.TODO()).WithFields(log.Fields{
"network": n.name,
"error": err,
}).Debug("Error deleting network endpoint count from store")
}
}
if err = c.deleteStoredNetwork(n); err != nil {
return fmt.Errorf("error deleting network from store: %v", err)
}
return nil
}
func (n *Network) deleteNetwork() error {
d, err := n.driver(true)
if err != nil {
return fmt.Errorf("failed deleting Network: %v", err)
}
if err := d.DeleteNetwork(n.ID()); err != nil {
// Forbidden Errors should be honored
if cerrdefs.IsPermissionDenied(err) {
return err
}
if _, ok := err.(types.MaskableError); !ok {
log.G(context.TODO()).Warnf("driver error deleting network %s : %v", n.name, err)
}
}
for _, resolver := range n.resolver {
resolver.Stop()
}
return nil
}
func (n *Network) addEndpoint(ctx context.Context, ep *Endpoint) error {
d, err := n.driver(true)
if err != nil {
return fmt.Errorf("failed to add endpoint: %v", err)
}
err = d.CreateEndpoint(ctx, n.id, ep.id, ep.Iface(), ep.generic)
if err != nil {
return types.InternalErrorf("failed to create endpoint %s on network %s: %v",
ep.Name(), n.Name(), err)
}
return nil
}
// CreateEndpoint creates a new endpoint to this network symbolically identified by the
// specified unique name. The options parameter carries driver specific options.
func (n *Network) CreateEndpoint(ctx context.Context, name string, options ...EndpointOption) (*Endpoint, error) {
var err error
if strings.TrimSpace(name) == "" {
return nil, types.InvalidParameterErrorf("invalid name: name is empty")
}
if n.ConfigOnly() {
return nil, types.ForbiddenErrorf("cannot create endpoint on configuration-only network")
}
if _, err = n.EndpointByName(name); err == nil {
return nil, types.ForbiddenErrorf("endpoint with name %s already exists in network %s", name, n.Name())
}
n.ctrlr.networkLocker.Lock(n.id)
defer n.ctrlr.networkLocker.Unlock(n.id) //nolint:errcheck
return n.createEndpoint(ctx, name, options...)
}
func (n *Network) createEndpoint(ctx context.Context, name string, options ...EndpointOption) (*Endpoint, error) {
var err error
ep := &Endpoint{name: name, generic: make(map[string]any), iface: &EndpointInterface{}}
ep.id = stringid.GenerateRandomID()
// Initialize ep.network with a possibly stale copy of n. We need this to get network from
// store. But once we get it from store we will have the most uptodate copy possibly.
ep.network = n
ep.network, err = ep.getNetworkFromStore()
if err != nil {
log.G(ctx).Errorf("failed to get network during CreateEndpoint: %v", err)
return nil, err
}
n = ep.network
ep.processOptions(options...)
for _, llIPNet := range ep.Iface().LinkLocalAddresses() {
if !llIPNet.IP.IsLinkLocalUnicast() {
return nil, types.InvalidParameterErrorf("invalid link local IP address: %v", llIPNet.IP)
}
}
if opt, ok := ep.generic[netlabel.MacAddress]; ok {
if mac, ok := opt.(net.HardwareAddr); ok {
ep.iface.mac = mac
}
}
ipam, capability, err := n.getController().getIPAMDriver(n.ipamType)
if err != nil {
return nil, err
}
ep.ipamOptions = map[string]string{netlabel.EndpointName: name}
if capability.RequiresMACAddress {
if ep.iface.mac == nil {
ep.iface.mac = netutils.GenerateRandomMAC()
}
ep.ipamOptions[netlabel.MacAddress] = ep.iface.mac.String()
}
wantIPv6 := n.enableIPv6 && !ep.disableIPv6
if err = ep.assignAddress(ipam, n.enableIPv4, wantIPv6); err != nil {
return nil, err
}
defer func() {
if err != nil {
ep.releaseIPAddresses()
}
}()
if err = n.addEndpoint(ctx, ep); err != nil {
return nil, err
}
defer func() {
if err != nil {
if e := ep.deleteEndpoint(false); e != nil {
log.G(ctx).Warnf("cleaning up endpoint failed %s : %v", name, e)
}
}
}()
// We should perform storeEndpoint call right after addEndpoint
// in order to have iface properly configured
if err = n.getController().storeEndpoint(ctx, ep); err != nil {
return nil, err
}
defer func() {
if err != nil {
if e := n.getController().deleteStoredEndpoint(ep); e != nil {
log.G(ctx).Warnf("error rolling back endpoint %s from store: %v", name, e)
}
}
}()
return ep, nil
}
// Endpoints returns the list of Endpoint(s) in this network.
func (n *Network) Endpoints() []*Endpoint {
endpoints, err := n.getEndpointsFromStore()
if err != nil {
log.G(context.TODO()).Error(err)
}
return endpoints
}
// HasContainerAttachments returns true when len(n.Endpoints()) > 0.
func (n *Network) HasContainerAttachments() bool {
return len(n.Endpoints()) > 0
}
// WalkEndpoints uses the provided function to walk the Endpoints.
func (n *Network) WalkEndpoints(walker EndpointWalker) {
for _, e := range n.Endpoints() {
if walker(e) {
return
}
}
}
// EndpointByName returns the Endpoint which has the passed name. If not found,
// an [errdefs.ErrNotFound] is returned.
func (n *Network) EndpointByName(name string) (*Endpoint, error) {
if name == "" {
return nil, types.InvalidParameterErrorf("invalid name: name is empty")
}
var e *Endpoint
s := func(current *Endpoint) bool {
if current.Name() == name {
e = current
return true
}
return false
}
n.WalkEndpoints(s)
if e == nil {
return nil, errdefs.NotFound(fmt.Errorf("endpoint %s not found", name))
}
return e, nil
}
// updateSvcRecord adds or deletes local DNS records for a given Endpoint.
func (n *Network) updateSvcRecord(ctx context.Context, ep *Endpoint, isAdd bool) {
ctx, span := otel.Tracer("").Start(ctx, "libnetwork.updateSvcRecord", trace.WithAttributes(
attribute.String("ep.name", ep.name),
attribute.Bool("isAdd", isAdd)))
defer span.End()
iface := ep.Iface()
if iface == nil {
return
}
var ipv4, ipv6 net.IP
if iface.Address() != nil {
ipv4 = iface.Address().IP
}
if iface.AddressIPv6() != nil {
ipv6 = iface.AddressIPv6().IP
}
serviceID := ep.svcID
if serviceID == "" {
serviceID = ep.ID()
}
dnsNames := ep.getDNSNames()
if isAdd {
for i, dnsName := range dnsNames {
ipMapUpdate := i == 0 // ipMapUpdate indicates whether PTR records should be updated.
n.addSvcRecords(ep.ID(), dnsName, serviceID, ipv4, ipv6, ipMapUpdate, "updateSvcRecord")
}
} else {
for i, dnsName := range dnsNames {
ipMapUpdate := i == 0 // ipMapUpdate indicates whether PTR records should be updated.
n.deleteSvcRecords(ep.ID(), dnsName, serviceID, ipv4, ipv6, ipMapUpdate, "updateSvcRecord")
}
}
}
func addIPToName(ipMap *setmatrix.SetMatrix[string, ipInfo], name, serviceID string, ip net.IP) {
reverseIP := netutils.ReverseIP(ip.String())
ipMap.Insert(reverseIP, ipInfo{
name: name,
serviceID: serviceID,
})
}
func delIPToName(ipMap *setmatrix.SetMatrix[string, ipInfo], name, serviceID string, ip net.IP) {
reverseIP := netutils.ReverseIP(ip.String())
ipMap.Remove(reverseIP, ipInfo{
name: name,
serviceID: serviceID,
})
}
func addNameToIP(svcMap *setmatrix.SetMatrix[string, svcMapEntry], name, serviceID string, epIP net.IP) {
// Since DNS name resolution is case-insensitive, Use the lower-case form
// of the name as the key into svcMap
lowerCaseName := strings.ToLower(name)
svcMap.Insert(lowerCaseName, svcMapEntry{
ip: epIP.String(),
serviceID: serviceID,
})
}
func delNameToIP(svcMap *setmatrix.SetMatrix[string, svcMapEntry], name, serviceID string, epIP net.IP) {
lowerCaseName := strings.ToLower(name)
svcMap.Remove(lowerCaseName, svcMapEntry{
ip: epIP.String(),
serviceID: serviceID,
})
}
// TODO(aker): remove ipMapUpdate param and add a proper method dedicated to update PTR records.
func (n *Network) addSvcRecords(eID, name, serviceID string, epIPv4, epIPv6 net.IP, ipMapUpdate bool, method string) {
// Do not add service names for ingress network as this is a
// routing only network
if n.ingress {
return
}
networkID := n.ID()
log.G(context.TODO()).Debugf("%s (%.7s).addSvcRecords(%s, %s, %s, %t) %s sid:%s", eID, networkID, name, epIPv4, epIPv6, ipMapUpdate, method, serviceID)
c := n.getController()
c.mu.Lock()
defer c.mu.Unlock()
sr, ok := c.svcRecords[networkID]
if !ok {
sr = &svcInfo{}
c.svcRecords[networkID] = sr
}
if ipMapUpdate {
if epIPv4 != nil {
addIPToName(&sr.ipMap, name, serviceID, epIPv4)
}
if epIPv6 != nil {
addIPToName(&sr.ipMap, name, serviceID, epIPv6)
}
}
if epIPv4 != nil {
addNameToIP(&sr.svcMap, name, serviceID, epIPv4)
}
if epIPv6 != nil {
addNameToIP(&sr.svcIPv6Map, name, serviceID, epIPv6)
}
}
func (n *Network) deleteSvcRecords(eID, name, serviceID string, epIPv4, epIPv6 net.IP, ipMapUpdate bool, method string) {
// Do not delete service names from ingress network as this is a
// routing only network
if n.ingress {
return
}
networkID := n.ID()
log.G(context.TODO()).Debugf("%s (%.7s).deleteSvcRecords(%s, %s, %s, %t) %s sid:%s ", eID, networkID, name, epIPv4, epIPv6, ipMapUpdate, method, serviceID)
c := n.getController()
c.mu.Lock()
defer c.mu.Unlock()
sr, ok := c.svcRecords[networkID]
if !ok {
return
}
if ipMapUpdate {
if epIPv4 != nil {
delIPToName(&sr.ipMap, name, serviceID, epIPv4)
}
if epIPv6 != nil {
delIPToName(&sr.ipMap, name, serviceID, epIPv6)
}
}
if epIPv4 != nil {
delNameToIP(&sr.svcMap, name, serviceID, epIPv4)
}
if epIPv6 != nil {
delNameToIP(&sr.svcIPv6Map, name, serviceID, epIPv6)
}
}
func (n *Network) getController() *Controller {
n.mu.Lock()
defer n.mu.Unlock()
return n.ctrlr
}
func (n *Network) ipamAllocate() (retErr error) {
if n.hasSpecialDriver() {
return nil
}
ipam, _, err := n.getController().getIPAMDriver(n.ipamType)
if err != nil {
return err
}
if n.addrSpace == "" {
if n.addrSpace, err = n.deriveAddressSpace(); err != nil {
return err
}
}
if n.enableIPv4 {
if err := n.ipamAllocateVersion(4, ipam); err != nil {
return err
}
defer func() {
if retErr != nil {
n.ipamReleaseVersion(4, ipam)
}
}()
}
if n.enableIPv6 {
if err := n.ipamAllocateVersion(6, ipam); err != nil {
return err
}
}
return nil
}
func (n *Network) ipamAllocateVersion(ipVer int, ipam ipamapi.Ipam) error {
var (
cfgList *[]*IpamConf
infoList *[]*IpamInfo
skipGwAlloc bool
err error
)
switch ipVer {
case 4:
cfgList = &n.ipamV4Config
infoList = &n.ipamV4Info
skipGwAlloc = n.skipGwAllocIPv4
case 6:
cfgList = &n.ipamV6Config
infoList = &n.ipamV6Info
skipGwAlloc = n.skipGwAllocIPv6
default:
return types.InternalErrorf("incorrect ip version passed to ipam allocate: %d", ipVer)
}
if len(*cfgList) == 0 {
*cfgList = []*IpamConf{{}}
}
*infoList = make([]*IpamInfo, len(*cfgList))
log.G(context.TODO()).Debugf("Allocating IPv%d pools for network %s (%s)", ipVer, n.Name(), n.ID())
for i, cfg := range *cfgList {
if err = cfg.Validate(); err != nil {
return err
}
d := &IpamInfo{}
(*infoList)[i] = d
d.AddressSpace = n.addrSpace
var reserved []netip.Prefix
if n.Scope() != scope.Global {
reserved = netutils.InferReservedNetworks(ipVer == 6)
}
alloc, err := ipam.RequestPool(ipamapi.PoolRequest{
AddressSpace: n.addrSpace,
Pool: cfg.PreferredPool,
SubPool: cfg.SubPool,
Options: n.ipamOptions,
Exclude: reserved,
V6: ipVer == 6,
})
if err != nil {
return err
}
d.PoolID = alloc.PoolID
d.Pool = netiputil.ToIPNet(alloc.Pool)
d.Meta = alloc.Meta
defer func() {
if err != nil {
if err := ipam.ReleasePool(d.PoolID); err != nil {
log.G(context.TODO()).Warnf("Failed to release address pool %s after failure to create network %s (%s)", d.PoolID, n.Name(), n.ID())
}
}
}()
// If there's no user-configured gateway address but the IPAM driver returned a gw when it
// set up the pool, use it. (It doesn't need to be requested/reserved in IPAM.)
if cfg.Gateway == "" {
if gws, ok := d.Meta[netlabel.Gateway]; ok {
if d.Gateway, err = types.ParseCIDR(gws); err != nil {
return types.InvalidParameterErrorf("failed to parse gateway address (%v) returned by ipam driver: %v", gws, err)
}
}
}
// If there's still no gateway, reserve cfg.Gateway if the user specified it. Else,
// if the driver wants a gateway, let the IPAM driver select an address.
if d.Gateway == nil && (cfg.Gateway != "" || !skipGwAlloc) {
gatewayOpts := map[string]string{
ipamapi.RequestAddressType: netlabel.Gateway,
}
if d.Gateway, _, err = ipam.RequestAddress(d.PoolID, net.ParseIP(cfg.Gateway), gatewayOpts); err != nil {
return types.InternalErrorf("failed to allocate gateway (%v): %v", cfg.Gateway, err)
}
}
// Auxiliary addresses must be part of the master address pool
// If they fall into the container addressable pool, libnetwork will reserve them
if cfg.AuxAddresses != nil {
var ip net.IP
d.IPAMData.AuxAddresses = make(map[string]*net.IPNet, len(cfg.AuxAddresses))
for k, v := range cfg.AuxAddresses {
if ip = net.ParseIP(v); ip == nil {
return types.InvalidParameterErrorf("non parsable secondary ip address (%s:%s) passed for network %s", k, v, n.Name())
}
if !d.Pool.Contains(ip) {
return types.ForbiddenErrorf("auxiliary address: (%s:%s) must belong to the master pool: %s", k, v, d.Pool)
}
// Attempt reservation in the container addressable pool, silent the error if address does not belong to that pool
if d.IPAMData.AuxAddresses[k], _, err = ipam.RequestAddress(d.PoolID, ip, nil); err != nil && !errors.Is(err, ipamapi.ErrIPOutOfRange) {
return types.InternalErrorf("failed to allocate secondary ip address (%s:%s): %v", k, v, err)
}
}
}
}
return nil
}
func (n *Network) ipamRelease() {
if n.hasSpecialDriver() {
return
}
ipam, _, err := n.getController().getIPAMDriver(n.ipamType)
if err != nil {
log.G(context.TODO()).Warnf("Failed to retrieve ipam driver to release address pool(s) on delete of network %s (%s): %v", n.Name(), n.ID(), err)
return
}
n.ipamReleaseVersion(4, ipam)
n.ipamReleaseVersion(6, ipam)
}
func (n *Network) ipamReleaseVersion(ipVer int, ipam ipamapi.Ipam) {
var infoList *[]*IpamInfo
switch ipVer {
case 4:
infoList = &n.ipamV4Info
case 6:
infoList = &n.ipamV6Info
default:
log.G(context.TODO()).Warnf("incorrect ip version passed to ipam release: %d", ipVer)
return
}
if len(*infoList) == 0 {
return
}
log.G(context.TODO()).Debugf("releasing IPv%d pools from network %s (%s)", ipVer, n.Name(), n.ID())
for _, d := range *infoList {
if d.Gateway != nil {
// FIXME(robmry) - if an IPAM driver returned a gateway in Meta[netlabel.Gateway], and
// no user config overrode that address, it wasn't explicitly allocated so it shouldn't
// be released here?
if err := ipam.ReleaseAddress(d.PoolID, d.Gateway.IP); err != nil {
log.G(context.TODO()).Warnf("Failed to release gateway ip address %s on delete of network %s (%s): %v", d.Gateway.IP, n.Name(), n.ID(), err)
}
}
if d.IPAMData.AuxAddresses != nil {
for k, nw := range d.IPAMData.AuxAddresses {
if d.Pool.Contains(nw.IP) {
if err := ipam.ReleaseAddress(d.PoolID, nw.IP); err != nil && !errors.Is(err, ipamapi.ErrIPOutOfRange) {
log.G(context.TODO()).Warnf("Failed to release secondary ip address %s (%v) on delete of network %s (%s): %v", k, nw.IP, n.Name(), n.ID(), err)
}
}
}
}
if err := ipam.ReleasePool(d.PoolID); err != nil {
log.G(context.TODO()).Warnf("Failed to release address pool %s on delete of network %s (%s): %v", d.PoolID, n.Name(), n.ID(), err)
}
}
*infoList = nil
}
func (n *Network) getIPInfo(ipVer int) []*IpamInfo {
var info []*IpamInfo
switch ipVer {
case 4:
info = n.ipamV4Info
case 6:
info = n.ipamV6Info
default:
return nil
}
l := make([]*IpamInfo, 0, len(info))
n.mu.Lock()
l = append(l, info...)
n.mu.Unlock()
return l
}
func (n *Network) getIPData(ipVer int) []driverapi.IPAMData {
var info []*IpamInfo
switch ipVer {
case 4:
info = n.ipamV4Info
case 6:
info = n.ipamV6Info
default:
return nil
}
l := make([]driverapi.IPAMData, 0, len(info))
n.mu.Lock()
for _, d := range info {
l = append(l, d.IPAMData)
}
n.mu.Unlock()
return l
}
func (n *Network) deriveAddressSpace() (string, error) {
ipam, _ := n.getController().ipamRegistry.IPAM(n.ipamType)
if ipam == nil {
return "", types.NotFoundErrorf("failed to get default address space: unknown ipam type %q", n.ipamType)
}
local, global, err := ipam.GetDefaultAddressSpaces()
if err != nil {
return "", types.NotFoundErrorf("failed to get default address space: %v", err)
}
if n.Scope() == scope.Global {
return global, nil
}
return local, nil
}
// Peers returns a slice of PeerInfo structures which has the information about the peer
// nodes participating in the same overlay network. This is currently the per-network
// gossip cluster. For non-dynamic overlay networks and bridge networks it returns an
// empty slice
func (n *Network) Peers() []networkdb.PeerInfo {
if !n.Dynamic() {
return []networkdb.PeerInfo{}
}
a := n.getController().getAgent()
if a == nil {
return []networkdb.PeerInfo{}
}
return a.networkDB.Peers(n.ID())
}
func (n *Network) DriverOptions() map[string]string {
n.mu.Lock()
defer n.mu.Unlock()
if n.generic != nil {
if m, ok := n.generic[netlabel.GenericData]; ok {
return m.(map[string]string)
}
}
return map[string]string{}
}
func (n *Network) Scope() string {
n.mu.Lock()
defer n.mu.Unlock()
return n.scope
}
func (n *Network) IpamConfig() (ipamType string, ipamOptions map[string]string, ipamV4Config []*IpamConf, ipamV6Config []*IpamConf) {
n.mu.Lock()
defer n.mu.Unlock()
ipamV4Config = make([]*IpamConf, len(n.ipamV4Config))
for i, c := range n.ipamV4Config {
ipamV4Config[i] = c.Copy()
}
ipamV6Config = make([]*IpamConf, len(n.ipamV6Config))
for i, c := range n.ipamV6Config {
ipamV6Config[i] = c.Copy()
}
return n.ipamType, n.ipamOptions, ipamV4Config, ipamV6Config
}
func (n *Network) IpamInfo() (ipamV4Info []*IpamInfo, ipamV6Info []*IpamInfo) {
n.mu.Lock()
defer n.mu.Unlock()
ipamV4Info = make([]*IpamInfo, len(n.ipamV4Info))
for i, info := range n.ipamV4Info {
ipamV4Info[i] = info.Copy()
}
ipamV6Info = make([]*IpamInfo, len(n.ipamV6Info))
for i, info := range n.ipamV6Info {
ipamV6Info[i] = info.Copy()
}
return ipamV4Info, ipamV6Info
}
func (n *Network) Internal() bool {
n.mu.Lock()
defer n.mu.Unlock()
return n.internal
}
func (n *Network) Attachable() bool {
n.mu.Lock()
defer n.mu.Unlock()
return n.attachable
}
func (n *Network) Ingress() bool {
n.mu.Lock()
defer n.mu.Unlock()
return n.ingress
}
func (n *Network) Dynamic() bool {
n.mu.Lock()
defer n.mu.Unlock()
return n.dynamic
}
func (n *Network) IPv4Enabled() bool {
n.mu.Lock()
defer n.mu.Unlock()
return n.enableIPv4
}
func (n *Network) IPv6Enabled() bool {
n.mu.Lock()
defer n.mu.Unlock()
return n.enableIPv6
}
func (n *Network) ConfigFrom() string {
n.mu.Lock()
defer n.mu.Unlock()
return n.configFrom
}
func (n *Network) ConfigOnly() bool {
n.mu.Lock()
defer n.mu.Unlock()
return n.configOnly
}
func (n *Network) Labels() map[string]string {
n.mu.Lock()
defer n.mu.Unlock()
lbls := make(map[string]string, len(n.labels))
for k, v := range n.labels {
lbls[k] = v
}
return lbls
}
func (n *Network) TableEventRegister(tableName string, objType driverapi.ObjectType) error {
if !driverapi.IsValidType(objType) {
return fmt.Errorf("invalid object type %v in registering table, %s", objType, tableName)
}
t := networkDBTable{
name: tableName,
objType: objType,
}
n.mu.Lock()
defer n.mu.Unlock()
n.driverTables = append(n.driverTables, t)
return nil
}
func (n *Network) UpdateIpamConfig(ipV4Data []driverapi.IPAMData) {
ipamV4Config := make([]*IpamConf, len(ipV4Data))
for i, data := range ipV4Data {
ic := &IpamConf{}
ic.PreferredPool = data.Pool.String()
ic.Gateway = data.Gateway.IP.String()
ipamV4Config[i] = ic
}
n.mu.Lock()
defer n.mu.Unlock()
n.ipamV4Config = ipamV4Config
}
// Special drivers are ones which do not need to perform any Network plumbing
func (n *Network) hasSpecialDriver() bool {
return n.Type() == "host" || n.Type() == "null"
}
func (n *Network) hasLoadBalancerEndpoint() bool {
return len(n.loadBalancerIP) != 0
}
// ResolveName looks up addresses of ipType for name req.
// Returns (addresses, true) if req is found, but len(addresses) may be 0 if
// there are no addresses of ipType. If the name is not found, the bool return
// will be false.
func (n *Network) ResolveName(ctx context.Context, req string, ipType types.IPFamily) ([]net.IP, bool) {
c := n.getController()
networkID := n.ID()
_, span := otel.Tracer("").Start(ctx, "Network.ResolveName", trace.WithAttributes(
attribute.String("libnet.network.name", n.Name()),
attribute.String("libnet.network.id", networkID),
))
defer span.End()
c.mu.Lock()
// TODO(aker): release the lock earlier
defer c.mu.Unlock()
sr, ok := c.svcRecords[networkID]
if !ok {
return nil, false
}
req = strings.TrimSuffix(req, ".")
req = strings.ToLower(req)
ipSet, ok4 := sr.svcMap.Get(req)
ipSet6, ok6 := sr.svcIPv6Map.Get(req)
if !ok4 && !ok6 {
// No result for v4 or v6, the name doesn't exist.
return nil, false
}
if ipType == types.IPv6 {
ipSet = ipSet6
}
// this map is to avoid IP duplicates, this can happen during a transition period where 2 services are using the same IP
noDup := make(map[string]bool)
var ipLocal []net.IP
for _, ip := range ipSet {
if _, dup := noDup[ip.ip]; !dup {
noDup[ip.ip] = true
ipLocal = append(ipLocal, net.ParseIP(ip.ip))
}
}
return ipLocal, true
}
func (n *Network) HandleQueryResp(name string, ip net.IP) {
networkID := n.ID()
c := n.getController()
c.mu.Lock()
defer c.mu.Unlock()
sr, ok := c.svcRecords[networkID]
if !ok {
return
}
ipStr := netutils.ReverseIP(ip.String())
// If an object with extResolver == true is already in the set this call will fail
// but anyway it means that has already been inserted before
if ok, _ := sr.ipMap.Contains(ipStr, ipInfo{name: name}); ok {
sr.ipMap.Remove(ipStr, ipInfo{name: name})
sr.ipMap.Insert(ipStr, ipInfo{name: name, extResolver: true})
}
}
func (n *Network) ResolveIP(_ context.Context, ip string) string {
networkID := n.ID()
c := n.getController()
c.mu.Lock()
defer c.mu.Unlock()
sr, ok := c.svcRecords[networkID]
if !ok {
return ""
}
nwName := n.Name()
elemSet, ok := sr.ipMap.Get(ip)
if !ok || len(elemSet) == 0 {
return ""
}
// NOTE it is possible to have more than one element in the Set, this will happen
// because of interleave of different events from different sources (local container create vs
// network db notifications)
// In such cases the resolution will be based on the first element of the set, and can vary
// during the system stabilization
elem := elemSet[0]
if elem.extResolver {
return ""
}
return elem.name + "." + nwName
}
func (n *Network) ResolveService(ctx context.Context, name string) ([]*net.SRV, []net.IP) {
c := n.getController()
srv := []*net.SRV{}
ip := []net.IP{}
log.G(ctx).Debugf("Service name To resolve: %v", name)
// There are DNS implementations that allow SRV queries for names not in
// the format defined by RFC 2782. Hence specific validations checks are
// not done
parts := strings.Split(name, ".")
if len(parts) < 3 {
return nil, nil
}
portName := parts[0]
proto := parts[1]
svcName := strings.Join(parts[2:], ".")
networkID := n.ID()
c.mu.Lock()
defer c.mu.Unlock()
sr, ok := c.svcRecords[networkID]
if !ok {
return nil, nil
}
svcs, ok := sr.service[svcName]
if !ok {
return nil, nil
}
for _, svc := range svcs {
if svc.portName != portName {
continue
}
if svc.proto != proto {
continue
}
for _, t := range svc.target {
srv = append(srv,
&net.SRV{
Target: t.name,
Port: t.port,
})
ip = append(ip, t.ip)
}
}
return srv, ip
}
func (n *Network) NdotsSet() bool {
return false
}
// config-only network is looked up by name
func (c *Controller) getConfigNetwork(name string) (*Network, error) {
var n *Network
c.WalkNetworks(func(current *Network) bool {
if current.ConfigOnly() && current.Name() == name {
n = current
return true
}
return false
})
if n == nil {
return nil, types.NotFoundErrorf("configuration network %q not found", name)
}
return n, nil
}
func (n *Network) lbSandboxName() string {
name := "lb-" + n.name
if n.ingress {
name = n.name + "-sbox"
}
return name
}
func (n *Network) lbEndpointName() string {
return n.name + "-endpoint"
}
func (n *Network) createLoadBalancerSandbox() (retErr error) {
sandboxName := n.lbSandboxName()
// Mark the sandbox to be a load balancer
sbOptions := []SandboxOption{OptionLoadBalancer(n.id)}
if n.ingress {
sbOptions = append(sbOptions, OptionIngress())
}
sb, err := n.ctrlr.NewSandbox(context.TODO(), sandboxName, sbOptions...)
if err != nil {
return err
}
defer func() {
if retErr != nil {
if e := n.ctrlr.SandboxDestroy(context.WithoutCancel(context.TODO()), sandboxName); e != nil {
log.G(context.TODO()).Warnf("could not delete sandbox %s on failure on failure (%v): %v", sandboxName, retErr, e)
}
}
}()
endpointName := n.lbEndpointName()
epOptions := []EndpointOption{
CreateOptionIPAM(n.loadBalancerIP, nil, nil),
CreateOptionLoadBalancer(),
}
ep, err := n.createEndpoint(context.TODO(), endpointName, epOptions...)
if err != nil {
return err
}
defer func() {
if retErr != nil {
if e := ep.Delete(context.WithoutCancel(context.TODO()), true); e != nil {
log.G(context.TODO()).Warnf("could not delete endpoint %s on failure on failure (%v): %v", endpointName, retErr, e)
}
}
}()
if err := ep.Join(context.TODO(), sb, nil); err != nil {
return err
}
return sb.EnableService()
}
func (n *Network) deleteLoadBalancerSandbox() error {
n.mu.Lock()
c := n.ctrlr
name := n.name
n.mu.Unlock()
sandboxName := n.lbSandboxName()
endpointName := n.lbEndpointName()
endpoint, err := n.EndpointByName(endpointName)
if err != nil {
log.G(context.TODO()).Warnf("Failed to find load balancer endpoint %s on network %s: %v", endpointName, name, err)
} else {
info := endpoint.Info()
if info != nil {
sb := info.Sandbox()
if sb != nil {
if err := sb.DisableService(); err != nil {
log.G(context.TODO()).Warnf("Failed to disable service on sandbox %s: %v", sandboxName, err)
// Ignore error and attempt to delete the load balancer endpoint
}
}
}
if err := endpoint.Delete(context.TODO(), true); err != nil {
log.G(context.TODO()).Warnf("Failed to delete endpoint %s (%s) in %s: %v", endpoint.Name(), endpoint.ID(), sandboxName, err)
// Ignore error and attempt to delete the sandbox.
}
}
if err := c.SandboxDestroy(context.TODO(), sandboxName); err != nil {
return fmt.Errorf("Failed to delete %s sandbox: %v", sandboxName, err)
}
return nil
}
func (n *Network) IPAMStatus(ctx context.Context) (network.IPAMStatus, error) {
status := network.IPAMStatus{
Subnets: make(map[netip.Prefix]network.SubnetStatus),
}
if n.hasSpecialDriver() {
// Special drivers do not assign addresses from IPAM
return status, nil
}
ipamdriver, _, err := n.getController().getIPAMDriver(n.ipamType)
if err != nil {
return status, err
}
ipam, ok := ipamdriver.(ipamapi.PoolStatuser)
if !ok {
return status, nil
}
var errs []error
info4, info6 := n.IpamInfo()
for info := range iterutil.Chain(slices.Values(info4), slices.Values(info6)) {
pstat, err := ipam.PoolStatus(info.PoolID)
if err != nil {
errs = append(errs, fmt.Errorf("failed to retrieve pool %s status: %w", info.PoolID, err))
continue
}
prefix, _ := netiputil.ToPrefix(info.Pool)
status.Subnets[prefix] = pstat
}
return status, errors.Join(errs...)
}