mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
There have been numerous reports of the "has active endpoints" error over the years. Historically, there were some faulty code paths that could lead to this error, but we believe they all have been fixed by now. However, users are still facing this error from time to time. Either because they forgot that some containers are still running, or because we still have bugs lying around. To help users figure whether this error is legitimate, and what triggers it, add endpoint names (which are just container names) to the error message. Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2180 lines
57 KiB
Go
2180 lines
57 KiB
Go
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
|
//go:build go1.22
|
|
|
|
package libnetwork
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/containerd/log"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/docker/docker/internal/sliceutil"
|
|
"github.com/docker/docker/libnetwork/datastore"
|
|
"github.com/docker/docker/libnetwork/driverapi"
|
|
"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]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
|
|
}
|
|
|
|
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 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 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
|
|
}
|
|
}
|
|
|
|
// 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 _, 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) 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
|
|
}
|
|
|
|
// 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]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)
|
|
}
|
|
|
|
// 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]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 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["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(¶ms)
|
|
}
|
|
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 ep.name }),
|
|
}
|
|
}
|
|
|
|
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:
|
|
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 _, 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, 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
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
wantIPv6 := n.enableIPv6 && !ep.disableIPv6
|
|
|
|
if err = ep.assignAddress(ipam, n.enableIPv4, wantIPv6); 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 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)
|
|
}
|
|
}
|
|
}()
|
|
|
|
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)
|
|
}
|
|
}()
|
|
}
|
|
|
|
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,
|
|
// 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[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) 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 && 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 && 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
|
|
}
|
|
|
|
// 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 int) ([]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, 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
|
|
}
|