dotcloud/docker

View on GitHub
libnetwork/network.go

Summary

Maintainability
A
0 mins
Test Coverage
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.21

package libnetwork

import (
    "context"
    "encoding/json"
    "fmt"
    "net"
    "net/netip"
    "runtime"
    "strings"
    "sync"
    "time"

    "github.com/containerd/log"
    "github.com/docker/docker/libnetwork/datastore"
    "github.com/docker/docker/libnetwork/driverapi"
    "github.com/docker/docker/libnetwork/etchosts"
    "github.com/docker/docker/libnetwork/internal/netiputil"
    "github.com/docker/docker/libnetwork/internal/setmatrix"
    "github.com/docker/docker/libnetwork/ipamapi"
    "github.com/docker/docker/libnetwork/ipams/defaultipam"
    "github.com/docker/docker/libnetwork/netlabel"
    "github.com/docker/docker/libnetwork/netutils"
    "github.com/docker/docker/libnetwork/networkdb"
    "github.com/docker/docker/libnetwork/options"
    "github.com/docker/docker/libnetwork/scope"
    "github.com/docker/docker/libnetwork/types"
    "github.com/docker/docker/pkg/stringid"
    "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[svcMapEntry]
    svcIPv6Map setmatrix.SetMatrix[svcMapEntry]
    ipMap      setmatrix.SetMatrix[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 != "" && nil == net.ParseIP(c.Gateway) {
        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 net.IP) bool {
    if c == nil {
        return false
    }
    if c.PreferredPool == "" {
        return false
    }

    _, allowedRange, _ := net.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 != ""
}

// 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]interface{}{
        "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]interface{}
        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)
        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
    postIPv6         bool
    epCnt            *endpointCnt
    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
    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
}

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,
    }
}

// CopyTo deep copies to the destination IpamConfig
func (c *IpamConf) CopyTo(dstC *IpamConf) error {
    dstC.PreferredPool = c.PreferredPool
    dstC.SubPool = c.SubPool
    dstC.Gateway = c.Gateway
    if c.AuxAddresses != nil {
        dstC.AuxAddresses = make(map[string]string, len(c.AuxAddresses))
        for k, v := range c.AuxAddresses {
            dstC.AuxAddresses[k] = v
        }
    }
    return nil
}

// CopyTo deep copies to the destination IpamInfo
func (i *IpamInfo) CopyTo(dstI *IpamInfo) error {
    dstI.PoolID = i.PoolID
    if i.Meta != nil {
        dstI.Meta = make(map[string]string)
        for k, v := range i.Meta {
            dstI.Meta[k] = v
        }
    }

    dstI.AddressSpace = i.AddressSpace
    dstI.Pool = types.GetIPNetCopy(i.Pool)
    dstI.Gateway = types.GetIPNetCopy(i.Gateway)

    if i.AuxAddresses != nil {
        dstI.AuxAddresses = make(map[string]*net.IPNet)
        for k, v := range i.AuxAddresses {
            dstI.AuxAddresses[k] = types.GetIPNetCopy(v)
        }
    }

    return nil
}

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 n.configOnly {
            return types.ForbiddenErrorf("a configuration network cannot depend on another configuration network")
        }
        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          interface{}
                )
                switch t := data.(type) {
                case map[string]interface{}, 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 len(n.ipamType) != 0 {
        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
        }
    }
    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.postIPv6 = n.postIPv6
    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

    // 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 _, v4conf := range n.ipamV4Config {
        dstV4Conf := &IpamConf{}
        if err := v4conf.CopyTo(dstV4Conf); err != nil {
            return err
        }
        dstN.ipamV4Config = append(dstN.ipamV4Config, dstV4Conf)
    }

    for _, v4info := range n.ipamV4Info {
        dstV4Info := &IpamInfo{}
        if err := v4info.CopyTo(dstV4Info); err != nil {
            return err
        }
        dstN.ipamV4Info = append(dstN.ipamV4Info, dstV4Info)
    }

    for _, v6conf := range n.ipamV6Config {
        dstV6Conf := &IpamConf{}
        if err := v6conf.CopyTo(dstV6Conf); err != nil {
            return err
        }
        dstN.ipamV6Config = append(dstN.ipamV6Config, dstV6Conf)
    }

    for _, v6info := range n.ipamV6Info {
        dstV6Info := &IpamInfo{}
        if err := v6info.CopyTo(dstV6Info); err != nil {
            return err
        }
        dstN.ipamV6Info = append(dstN.ipamV6Info, dstV6Info)
    }

    dstN.generic = options.Generic{}
    for k, v := range n.generic {
        dstN.generic[k] = v
    }

    return nil
}

func (n *Network) getEpCnt() *endpointCnt {
    n.mu.Lock()
    defer n.mu.Unlock()

    return n.epCnt
}

// TODO : Can be made much more generic with the help of reflection (but has some golang limitations)
func (n *Network) MarshalJSON() ([]byte, error) {
    netMap := make(map[string]interface{})
    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
    netMap["postIPv6"] = n.postIPv6
    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
    return json.Marshal(netMap)
}

// TODO : Can be made much more generic with the help of reflection (but has some golang limitations)
func (n *Network) UnmarshalJSON(b []byte) (err error) {
    var netMap map[string]interface{}
    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)
    if v, ok := netMap["enableIPv4"]; ok {
        n.enableIPv4 = v.(bool)
    } else {
        // Set enableIPv4 for IPv4 networks created before the option was added.
        n.enableIPv4 = len(n.ipamV4Info) > 0
    }
    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]interface{}); 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]interface{}); 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]interface{})
        // Restore opts in their map[string]string form
        if v, ok := n.generic[netlabel.GenericData]; ok {
            var lmap map[string]string
            ba, err := json.Marshal(v)
            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["postIPv6"]; ok {
        n.postIPv6 = 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)
    }
    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]interface{}) NetworkOption {
    return func(n *Network) {
        if n.generic == nil {
            n.generic = make(map[string]interface{})
        }
        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]interface{})
        }
        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]interface{})
        }
        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]interface{})
        }
        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]interface{})
        }
        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
    }
}

// NetworkOptionDeferIPv6Alloc instructs the network to defer the IPV6 address allocation until after the endpoint has been created
// It is being provided to support the specific docker daemon flags where user can deterministically assign an IPv6 address
// to a container as combination of fixed-cidr-v6 + mac-address
// TODO: Remove this option setter once we support endpoint ipam options
func NetworkOptionDeferIPv6Alloc(enable bool) NetworkOption {
    return func(n *Network) {
        n.postIPv6 = enable
    }
}

// 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 &UnknownNetworkError{name: name, id: 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}
    }

    // Check that the network is empty
    var emptyCount uint64
    if n.hasLoadBalancerEndpoint() {
        emptyCount = 1
    }
    if !force && n.getEpCnt().EndpointCnt() > emptyCount {
        if n.configOnly {
            return types.ForbiddenErrorf("configuration network %q is in use", n.Name())
        }
        return &ActiveEndpointsError{name: n.name, id: n.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)
        }
        // Reload the network from the store to update the epcnt.
        n, err = c.getNetworkFromStore(id)
        if err != nil {
            return &UnknownNetworkError{name: name, id: id}
        }
    }

    // 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.updateToStore(context.TODO(), n); err != nil {
        return fmt.Errorf("error marking network %s (%s) for deletion: %v", n.Name(), n.ID(), err)
    }

    if n.ConfigFrom() != "" {
        if t, err := c.getConfigNetwork(n.ConfigFrom()); err == nil {
            if err := t.getEpCnt().DecEndpointCnt(); err != nil {
                log.G(context.TODO()).Warnf("Failed to update reference count for configuration network %q on removal of network %q: %v",
                    t.Name(), n.Name(), err)
            }
        } else {
            log.G(context.TODO()).Warnf("Could not find configuration network %q during removal of network %q", n.configFrom, n.Name())
        }
    }

    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:
    // deleteFromStore performs an atomic delete operation and the
    // Network.epCnt will help prevent any possible
    // race between endpoint join and network delete
    if err = c.deleteFromStore(n.getEpCnt()); err != nil {
        if !force {
            return fmt.Errorf("error deleting network endpoint count from store: %v", err)
        }
        log.G(context.TODO()).Debugf("Error deleting endpoint count from store for stale network %s (%s) for deletion: %v", n.Name(), n.ID(), err)
    }

    if err = c.deleteFromStore(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 _, ok := err.(types.ForbiddenError); ok {
            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, ErrInvalidName(name)
    }

    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]interface{}), 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
    }

    if capability.RequiresMACAddress {
        if ep.iface.mac == nil {
            ep.iface.mac = netutils.GenerateRandomMAC()
        }
        if ep.ipamOptions == nil {
            ep.ipamOptions = make(map[string]string)
        }
        ep.ipamOptions[netlabel.MacAddress] = ep.iface.mac.String()
    }

    if err = ep.assignAddress(ipam, n.enableIPv4, n.enableIPv6 && !n.postIPv6); err != nil {
        return nil, err
    }
    defer func() {
        if err != nil {
            ep.releaseAddress()
        }
    }()

    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 updateToStore call right after addEndpoint
    // in order to have iface properly configured
    if err = n.getController().updateToStore(ctx, ep); err != nil {
        return nil, err
    }
    defer func() {
        if err != nil {
            if e := n.getController().deleteFromStore(ep); e != nil {
                log.G(ctx).Warnf("error rolling back endpoint %s from store: %v", name, e)
            }
        }
    }()

    if err = ep.assignAddress(ipam, false, n.enableIPv6 && n.postIPv6); err != nil {
        return nil, err
    }

    if !n.getController().isSwarmNode() || n.Scope() != scope.Swarm || !n.driverIsMultihost() {
        n.updateSvcRecord(context.WithoutCancel(ctx), ep, true)
        defer func() {
            if err != nil {
                n.updateSvcRecord(context.WithoutCancel(ctx), ep, false)
            }
        }()
    }

    // Increment endpoint count to indicate completion of endpoint addition
    if err = n.getEpCnt().IncEndpointCnt(); err != nil {
        return nil, err
    }

    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
}

// 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,
// the error ErrNoSuchEndpoint is returned.
func (n *Network) EndpointByName(name string) (*Endpoint, error) {
    if name == "" {
        return nil, ErrInvalidName(name)
    }
    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, ErrNoSuchEndpoint(name)
    }

    return e, nil
}

// EndpointByID should *never* be called as it's going to create a 2nd instance of an Endpoint. The first one lives in
// the Sandbox the endpoint is attached to. Instead, the endpoint should be retrieved by calling [Sandbox.Endpoints()].
func (n *Network) EndpointByID(id string) (*Endpoint, error) {
    if id == "" {
        return nil, ErrInvalidID(id)
    }

    ep, err := n.getEndpointFromStore(id)
    if err != nil {
        return nil, ErrNoSuchEndpoint(id)
    }

    return ep, 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[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[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[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[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) getSvcRecords(ep *Endpoint) []etchosts.Record {
    n.mu.Lock()
    defer n.mu.Unlock()

    if ep == nil {
        return nil
    }

    var recs []etchosts.Record

    epName := ep.Name()

    n.ctrlr.mu.Lock()
    defer n.ctrlr.mu.Unlock()
    sr, ok := n.ctrlr.svcRecords[n.id]
    if !ok {
        return nil
    }

    for _, svcMap := range []*setmatrix.SetMatrix[svcMapEntry]{&sr.svcMap, &sr.svcIPv6Map} {
        svcMapKeys := svcMap.Keys()
        // Loop on service names on this network
        for _, k := range svcMapKeys {
            if strings.Split(k, ".")[0] == epName {
                continue
            }
            // Get all the IPs associated to this service
            mapEntryList, ok := svcMap.Get(k)
            if !ok {
                // The key got deleted
                continue
            }
            if len(mapEntryList) == 0 {
                log.G(context.TODO()).Warnf("Found empty list of IP addresses for service %s on network %s (%s)", k, n.name, n.id)
                continue
            }

            recs = append(recs, etchosts.Record{
                Hosts: k,
                IP:    mapEntryList[0].ip,
            })
        }
    }

    return recs
}

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
        err      error
    )

    switch ipVer {
    case 4:
        cfgList = &n.ipamV4Config
        infoList = &n.ipamV4Info
    case 6:
        cfgList = &n.ipamV6Config
        infoList = &n.ipamV6Info
    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 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 user requested a specific gateway, libnetwork will allocate it
        // irrespective of whether ipam driver returned a gateway already.
        // If none of the above is true, libnetwork will allocate one.
        if cfg.Gateway != "" || d.Gateway == nil {
            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 && 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 {
            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 && 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 {
        cc := &IpamConf{}
        if err := c.CopyTo(cc); err != nil {
            log.G(context.TODO()).WithError(err).Error("Error copying ipam ipv4 config")
        }
        ipamV4Config[i] = cc
    }

    ipamV6Config = make([]*IpamConf, len(n.ipamV6Config))
    for i, c := range n.ipamV6Config {
        cc := &IpamConf{}
        if err := c.CopyTo(cc); err != nil {
            log.G(context.TODO()).WithError(err).Debug("Error copying ipam ipv6 config")
        }
        ipamV6Config[i] = cc
    }

    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 {
        ic := &IpamInfo{}
        if err := info.CopyTo(ic); err != nil {
            log.G(context.TODO()).WithError(err).Error("Error copying IPv4 IPAM config")
        }
        ipamV4Info[i] = ic
    }

    ipamV6Info = make([]*IpamInfo, len(n.ipamV6Info))
    for i, info := range n.ipamV6Info {
        ic := &IpamInfo{}
        if err := info.CopyTo(ic); err != nil {
            log.G(context.TODO()).WithError(err).Error("Error copying IPv6 IPAM config")
        }
        ipamV6Info[i] = ic
    }

    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
}

func (n *Network) ResolveName(ctx context.Context, req string, ipType int) ([]net.IP, bool) {
    var ipv6Miss 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, ok := sr.svcMap.Get(req)

    if ipType == types.IPv6 {
        // If the name resolved to v4 address then its a valid name in
        // the docker network domain. If the network is not v6 enabled
        // set ipv6Miss to filter the DNS query from going to external
        // resolvers.
        if ok && !n.enableIPv6 {
            ipv6Miss = true
        }
        ipSet, ok = sr.svcIPv6Map.Get(req)
    }

    if ok && len(ipSet) > 0 {
        // 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, ok
    }

    return nil, ipv6Miss
}

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 stabilitation
    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, 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
}