libnetwork/controller.go
/*
Package libnetwork provides the basic functionality and extension points to
create network namespaces and allocate interfaces for containers to use.
networkType := "bridge"
// Create a new controller instance
driverOptions := options.Generic{}
genericOption := make(map[string]interface{})
genericOption[netlabel.GenericData] = driverOptions
controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption))
if err != nil {
return
}
// Create a network for containers to join.
// NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can make use of
network, err := controller.NewNetwork(networkType, "network1", "")
if err != nil {
return
}
// For each new container: allocate IP and interfaces. The returned network
// settings will be used for container infos (inspect and such), as well as
// iptables rules for port publishing. This info is contained or accessible
// from the returned endpoint.
ep, err := network.CreateEndpoint(context.TODO(), "Endpoint1")
if err != nil {
return
}
// Create the sandbox for the container.
// NewSandbox accepts Variadic optional arguments which libnetwork can use.
sbx, err := controller.NewSandbox("container1",
libnetwork.OptionHostname("test"),
libnetwork.OptionDomainname("example.com"))
// A sandbox can join the endpoint via the join api.
err = ep.Join(sbx)
if err != nil {
return
}
*/
package libnetwork
import (
"context"
"fmt"
"net"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/containerd/log"
"github.com/docker/docker/libnetwork/cluster"
"github.com/docker/docker/libnetwork/config"
"github.com/docker/docker/libnetwork/datastore"
"github.com/docker/docker/libnetwork/diagnostic"
"github.com/docker/docker/libnetwork/discoverapi"
"github.com/docker/docker/libnetwork/driverapi"
remotedriver "github.com/docker/docker/libnetwork/drivers/remote"
"github.com/docker/docker/libnetwork/drvregistry"
"github.com/docker/docker/libnetwork/ipamapi"
"github.com/docker/docker/libnetwork/ipams"
"github.com/docker/docker/libnetwork/netlabel"
"github.com/docker/docker/libnetwork/osl"
"github.com/docker/docker/libnetwork/scope"
"github.com/docker/docker/libnetwork/types"
"github.com/docker/docker/pkg/plugingetter"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/pkg/stringid"
"github.com/moby/locker"
"github.com/pkg/errors"
"go.opentelemetry.io/otel"
)
// NetworkWalker is a client provided function which will be used to walk the Networks.
// When the function returns true, the walk will stop.
type NetworkWalker func(nw *Network) bool
// Controller manages networks.
type Controller struct {
id string
drvRegistry drvregistry.Networks
ipamRegistry drvregistry.IPAMs
sandboxes map[string]*Sandbox
cfg *config.Config
store *datastore.Store
extKeyListener net.Listener
svcRecords map[string]*svcInfo
serviceBindings map[serviceKey]*service
ingressSandbox *Sandbox
agent *nwAgent
networkLocker *locker.Locker
agentInitDone chan struct{}
agentStopDone chan struct{}
keys []*types.EncryptionKey
DiagnosticServer *diagnostic.Server
mu sync.Mutex
// FIXME(thaJeztah): defOsSbox is always nil on non-Linux: move these fields to Linux-only files.
defOsSboxOnce sync.Once
defOsSbox *osl.Namespace
}
// New creates a new instance of network controller.
func New(cfgOptions ...config.Option) (*Controller, error) {
cfg := config.New(cfgOptions...)
store, err := datastore.New(cfg.DataDir, cfg.DatastoreBucket)
if err != nil {
return nil, fmt.Errorf("libnet controller initialization: %w", err)
}
c := &Controller{
id: stringid.GenerateRandomID(),
cfg: cfg,
store: store,
sandboxes: map[string]*Sandbox{},
svcRecords: make(map[string]*svcInfo),
serviceBindings: make(map[serviceKey]*service),
agentInitDone: make(chan struct{}),
networkLocker: locker.New(),
DiagnosticServer: diagnostic.New(),
}
c.drvRegistry.Notify = c
// External plugins don't need config passed through daemon. They can
// bootstrap themselves.
if err := remotedriver.Register(&c.drvRegistry, c.cfg.PluginGetter); err != nil {
return nil, err
}
if err := registerNetworkDrivers(&c.drvRegistry, c.makeDriverConfig); err != nil {
return nil, err
}
if err := ipams.Register(&c.ipamRegistry, c.cfg.PluginGetter, c.cfg.DefaultAddressPool, nil); err != nil {
return nil, err
}
c.WalkNetworks(func(nw *Network) bool {
if n := nw; n.hasSpecialDriver() && !n.ConfigOnly() {
if err := n.getController().addNetwork(n); err != nil {
log.G(context.TODO()).Warnf("Failed to populate network %q with driver %q", nw.Name(), nw.Type())
}
}
return false
})
// Reserve pools first before doing cleanup. Otherwise the
// cleanups of endpoint/network and sandbox below will
// generate many unnecessary warnings
c.reservePools()
// Cleanup resources
if err := c.sandboxCleanup(c.cfg.ActiveSandboxes); err != nil {
log.G(context.TODO()).WithError(err).Error("error during sandbox cleanup")
}
if err := c.cleanupLocalEndpoints(); err != nil {
log.G(context.TODO()).WithError(err).Warnf("error during endpoint cleanup")
}
c.networkCleanup()
if err := c.startExternalKeyListener(); err != nil {
return nil, err
}
setupArrangeUserFilterRule(c)
return c, nil
}
// SetClusterProvider sets the cluster provider.
func (c *Controller) SetClusterProvider(provider cluster.Provider) {
var sameProvider bool
c.mu.Lock()
// Avoids to spawn multiple goroutine for the same cluster provider
if c.cfg.ClusterProvider == provider {
// If the cluster provider is already set, there is already a go routine spawned
// that is listening for events, so nothing to do here
sameProvider = true
} else {
c.cfg.ClusterProvider = provider
}
c.mu.Unlock()
if provider == nil || sameProvider {
return
}
// We don't want to spawn a new go routine if the previous one did not exit yet
c.AgentStopWait()
go c.clusterAgentInit()
}
// SetKeys configures the encryption key for gossip and overlay data path.
func (c *Controller) SetKeys(keys []*types.EncryptionKey) error {
// libnetwork side of agent depends on the keys. On the first receipt of
// keys setup the agent. For subsequent key set handle the key change
subsysKeys := make(map[string]int)
for _, key := range keys {
if key.Subsystem != subsysGossip &&
key.Subsystem != subsysIPSec {
return fmt.Errorf("key received for unrecognized subsystem")
}
subsysKeys[key.Subsystem]++
}
for s, count := range subsysKeys {
if count != keyringSize {
return fmt.Errorf("incorrect number of keys for subsystem %v", s)
}
}
if c.getAgent() == nil {
c.mu.Lock()
c.keys = keys
c.mu.Unlock()
return nil
}
return c.handleKeyChange(keys)
}
func (c *Controller) getAgent() *nwAgent {
c.mu.Lock()
defer c.mu.Unlock()
return c.agent
}
func (c *Controller) clusterAgentInit() {
clusterProvider := c.cfg.ClusterProvider
var keysAvailable bool
for {
eventType := <-clusterProvider.ListenClusterEvents()
// The events: EventSocketChange, EventNodeReady and EventNetworkKeysAvailable are not ordered
// when all the condition for the agent initialization are met then proceed with it
switch eventType {
case cluster.EventNetworkKeysAvailable:
// Validates that the keys are actually available before starting the initialization
// This will handle old spurious messages left on the channel
c.mu.Lock()
keysAvailable = c.keys != nil
c.mu.Unlock()
fallthrough
case cluster.EventSocketChange, cluster.EventNodeReady:
if keysAvailable && c.isSwarmNode() {
c.agentOperationStart()
if err := c.agentSetup(clusterProvider); err != nil {
c.agentStopComplete()
} else {
c.agentInitComplete()
}
}
case cluster.EventNodeLeave:
c.agentOperationStart()
c.mu.Lock()
c.keys = nil
c.mu.Unlock()
// We are leaving the cluster. Make sure we
// close the gossip so that we stop all
// incoming gossip updates before cleaning up
// any remaining service bindings. But before
// deleting the networks since the networks
// should still be present when cleaning up
// service bindings
c.agentClose()
c.cleanupServiceDiscovery("")
c.cleanupServiceBindings("")
c.agentStopComplete()
return
}
}
}
// AgentInitWait waits for agent initialization to be completed in the controller.
func (c *Controller) AgentInitWait() {
c.mu.Lock()
agentInitDone := c.agentInitDone
c.mu.Unlock()
if agentInitDone != nil {
<-agentInitDone
}
}
// AgentStopWait waits for the Agent stop to be completed in the controller.
func (c *Controller) AgentStopWait() {
c.mu.Lock()
agentStopDone := c.agentStopDone
c.mu.Unlock()
if agentStopDone != nil {
<-agentStopDone
}
}
// agentOperationStart marks the start of an Agent Init or Agent Stop
func (c *Controller) agentOperationStart() {
c.mu.Lock()
if c.agentInitDone == nil {
c.agentInitDone = make(chan struct{})
}
if c.agentStopDone == nil {
c.agentStopDone = make(chan struct{})
}
c.mu.Unlock()
}
// agentInitComplete notifies the successful completion of the Agent initialization
func (c *Controller) agentInitComplete() {
c.mu.Lock()
if c.agentInitDone != nil {
close(c.agentInitDone)
c.agentInitDone = nil
}
c.mu.Unlock()
}
// agentStopComplete notifies the successful completion of the Agent stop
func (c *Controller) agentStopComplete() {
c.mu.Lock()
if c.agentStopDone != nil {
close(c.agentStopDone)
c.agentStopDone = nil
}
c.mu.Unlock()
}
func (c *Controller) makeDriverConfig(ntype string) map[string]interface{} {
if c.cfg == nil {
return nil
}
cfg := map[string]interface{}{
netlabel.LocalKVClient: c.store,
}
for _, label := range c.cfg.Labels {
key, val, _ := strings.Cut(label, "=")
if !strings.HasPrefix(key, netlabel.DriverPrefix+"."+ntype) {
continue
}
cfg[key] = val
}
// Merge in the existing config for this driver.
for k, v := range c.cfg.DriverConfig(ntype) {
cfg[k] = v
}
return cfg
}
// ID returns the controller's unique identity.
func (c *Controller) ID() string {
return c.id
}
// BuiltinDrivers returns the list of builtin network drivers.
func (c *Controller) BuiltinDrivers() []string {
drivers := []string{}
c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
if driver.IsBuiltIn() {
drivers = append(drivers, name)
}
return false
})
return drivers
}
// BuiltinIPAMDrivers returns the list of builtin ipam drivers.
func (c *Controller) BuiltinIPAMDrivers() []string {
drivers := []string{}
c.ipamRegistry.WalkIPAMs(func(name string, driver ipamapi.Ipam, _ *ipamapi.Capability) bool {
if driver.IsBuiltIn() {
drivers = append(drivers, name)
}
return false
})
return drivers
}
func (c *Controller) processNodeDiscovery(nodes []net.IP, add bool) {
c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
if d, ok := driver.(discoverapi.Discover); ok {
c.pushNodeDiscovery(d, capability, nodes, add)
}
return false
})
}
func (c *Controller) pushNodeDiscovery(d discoverapi.Discover, capability driverapi.Capability, nodes []net.IP, add bool) {
var self net.IP
// try swarm-mode config
if agent := c.getAgent(); agent != nil {
self = net.ParseIP(agent.advertiseAddr)
}
if d == nil || capability.ConnectivityScope != scope.Global || nodes == nil {
return
}
for _, node := range nodes {
nodeData := discoverapi.NodeDiscoveryData{Address: node.String(), Self: node.Equal(self)}
var err error
if add {
err = d.DiscoverNew(discoverapi.NodeDiscovery, nodeData)
} else {
err = d.DiscoverDelete(discoverapi.NodeDiscovery, nodeData)
}
if err != nil {
log.G(context.TODO()).Debugf("discovery notification error: %v", err)
}
}
}
// Config returns the bootup configuration for the controller.
func (c *Controller) Config() config.Config {
c.mu.Lock()
defer c.mu.Unlock()
if c.cfg == nil {
return config.Config{}
}
return *c.cfg
}
func (c *Controller) isManager() bool {
c.mu.Lock()
defer c.mu.Unlock()
if c.cfg == nil || c.cfg.ClusterProvider == nil {
return false
}
return c.cfg.ClusterProvider.IsManager()
}
func (c *Controller) isAgent() bool {
c.mu.Lock()
defer c.mu.Unlock()
if c.cfg == nil || c.cfg.ClusterProvider == nil {
return false
}
return c.cfg.ClusterProvider.IsAgent()
}
func (c *Controller) isSwarmNode() bool {
return c.isManager() || c.isAgent()
}
func (c *Controller) GetPluginGetter() plugingetter.PluginGetter {
return c.cfg.PluginGetter
}
func (c *Controller) RegisterDriver(networkType string, driver driverapi.Driver, capability driverapi.Capability) error {
if d, ok := driver.(discoverapi.Discover); ok {
c.agentDriverNotify(d)
}
return nil
}
// XXX This should be made driver agnostic. See comment below.
const overlayDSROptionString = "dsr"
// NewNetwork creates a new network of the specified network type. The options
// are network specific and modeled in a generic way.
func (c *Controller) NewNetwork(networkType, name string, id string, options ...NetworkOption) (_ *Network, retErr error) {
if id != "" {
c.networkLocker.Lock(id)
defer c.networkLocker.Unlock(id) //nolint:errcheck
if _, err := c.NetworkByID(id); err == nil {
return nil, NetworkNameError(id)
}
}
if strings.TrimSpace(name) == "" {
return nil, ErrInvalidName(name)
}
// Make sure two concurrent calls to this method won't create conflicting
// networks, otherwise libnetwork will end up in an invalid state.
if name != "" {
c.networkLocker.Lock(name)
defer c.networkLocker.Unlock(name)
if _, err := c.NetworkByName(name); err == nil {
return nil, NetworkNameError(name)
}
}
if id == "" {
id = stringid.GenerateRandomID()
}
defaultIpam := defaultIpamForNetworkType(networkType)
// Construct the network object
nw := &Network{
name: name,
networkType: networkType,
generic: map[string]interface{}{netlabel.GenericData: make(map[string]string)},
ipamType: defaultIpam,
id: id,
created: time.Now(),
ctrlr: c,
persist: true,
drvOnce: &sync.Once{},
loadBalancerMode: loadBalancerModeDefault,
}
nw.processOptions(options...)
if err := nw.validateConfiguration(); err != nil {
return nil, err
}
// These variables must be defined here, as declaration would otherwise
// be skipped by the "goto addToStore"
var (
caps driverapi.Capability
err error
skipCfgEpCount bool
)
// Reset network types, force local scope and skip allocation and
// plumbing for configuration networks. Reset of the config-only
// network drivers is needed so that this special network is not
// usable by old engine versions.
if nw.configOnly {
nw.scope = scope.Local
nw.networkType = "null"
goto addToStore
}
_, caps, err = nw.resolveDriver(nw.networkType, true)
if err != nil {
return nil, err
}
if nw.scope == scope.Local && caps.DataScope == scope.Global {
return nil, types.ForbiddenErrorf("cannot downgrade network scope for %s networks", networkType)
}
if nw.ingress && caps.DataScope != scope.Global {
return nil, types.ForbiddenErrorf("Ingress network can only be global scope network")
}
// At this point the network scope is still unknown if not set by user
if (caps.DataScope == scope.Global || nw.scope == scope.Swarm) &&
c.isSwarmNode() && !nw.dynamic {
if c.isManager() {
if !nw.enableIPv4 {
return nil, types.InvalidParameterErrorf("IPv4 cannot be disabled in a Swarm scoped network")
}
// For non-distributed controlled environment, globalscoped non-dynamic networks are redirected to Manager
return nil, ManagerRedirectError(name)
}
return nil, types.ForbiddenErrorf("Cannot create a multi-host network from a worker node. Please create the network from a manager node.")
}
if nw.scope == scope.Swarm && !c.isSwarmNode() {
return nil, types.ForbiddenErrorf("cannot create a swarm scoped network when swarm is not active")
}
// Make sure we have a driver available for this network type
// before we allocate anything.
if _, err := nw.driver(true); err != nil {
return nil, err
}
// From this point on, we need the network specific configuration,
// which may come from a configuration-only network
if nw.configFrom != "" {
configNetwork, err := c.getConfigNetwork(nw.configFrom)
if err != nil {
return nil, types.NotFoundErrorf("configuration network %q does not exist", nw.configFrom)
}
if err := configNetwork.applyConfigurationTo(nw); err != nil {
return nil, types.InternalErrorf("Failed to apply configuration: %v", err)
}
nw.generic[netlabel.Internal] = nw.internal
defer func() {
if retErr == nil && !skipCfgEpCount {
if err := configNetwork.getEpCnt().IncEndpointCnt(); err != nil {
log.G(context.TODO()).Warnf("Failed to update reference count for configuration network %q on creation of network %q: %v", configNetwork.Name(), nw.name, err)
}
}
}()
}
if err := nw.ipamAllocate(); err != nil {
return nil, err
}
defer func() {
if retErr != nil {
nw.ipamRelease()
}
}()
// Note from thaJeztah to future code visitors, or "future self".
//
// This code was previously assigning the error to the global "err"
// variable (before it was renamed to "retErr"), but in case of a
// "MaskableError" did not *return* the error:
// https://github.com/moby/moby/blob/b325dcbff60a04cedbe40eb627465fc7379d05bf/libnetwork/controller.go#L566-L573
//
// Depending on code paths further down, that meant that this error
// was either overwritten by other errors (and thus not handled in
// defer statements) or handled (if no other code was overwriting it.
//
// I suspect this was a bug (but possible without effect), but it could
// have been intentional. This logic is confusing at least, and even
// more so combined with the handling in defer statements that check for
// both the "err" return AND "skipCfgEpCount":
// https://github.com/moby/moby/blob/b325dcbff60a04cedbe40eb627465fc7379d05bf/libnetwork/controller.go#L586-L602
//
// To save future visitors some time to dig up history:
//
// - config-only networks were added in 25082206df465d1c11dd1276a65b4a1dc701bd43
// - the special error-handling and "skipCfgEpcoung" was added in ddd22a819867faa0cd7d12b0c3fad1099ac3eb26
// - and updated in 87b082f3659f9ec245ab15d781e6bfffced0af83 to don't use string-matching
//
// To cut a long story short: if this broke anything, you know who to blame :)
if err := c.addNetwork(nw); err != nil {
if _, ok := err.(types.MaskableError); ok { //nolint:gosimple
// This error can be ignored and set this boolean
// value to skip a refcount increment for configOnly networks
skipCfgEpCount = true
} else {
return nil, err
}
}
defer func() {
if retErr != nil {
if err := nw.deleteNetwork(); err != nil {
log.G(context.TODO()).Warnf("couldn't roll back driver network on network %s creation failure: %v", nw.name, retErr)
}
}
}()
// XXX If the driver type is "overlay" check the options for DSR
// being set. If so, set the network's load balancing mode to DSR.
// This should really be done in a network option, but due to
// time pressure to get this in without adding changes to moby,
// swarm and CLI, it is being implemented as a driver-specific
// option. Unfortunately, drivers can't influence the core
// "libnetwork.Network" data type. Hence we need this hack code
// to implement in this manner.
if gval, ok := nw.generic[netlabel.GenericData]; ok && nw.networkType == "overlay" {
optMap := gval.(map[string]string)
if _, ok := optMap[overlayDSROptionString]; ok {
nw.loadBalancerMode = loadBalancerModeDSR
}
}
addToStore:
// First store the endpoint count, then the network. To avoid to
// end up with a datastore containing a network and not an epCnt,
// in case of an ungraceful shutdown during this function call.
epCnt := &endpointCnt{n: nw}
if err := c.updateToStore(context.TODO(), epCnt); err != nil {
return nil, err
}
defer func() {
if retErr != nil {
if err := c.deleteFromStore(epCnt); err != nil {
log.G(context.TODO()).Warnf("could not rollback from store, epCnt %v on failure (%v): %v", epCnt, retErr, err)
}
}
}()
nw.epCnt = epCnt
if err := c.updateToStore(context.TODO(), nw); err != nil {
return nil, err
}
defer func() {
if retErr != nil {
if err := c.deleteFromStore(nw); err != nil {
log.G(context.TODO()).Warnf("could not rollback from store, network %v on failure (%v): %v", nw, retErr, err)
}
}
}()
if nw.configOnly {
return nw, nil
}
joinCluster(nw)
defer func() {
if retErr != nil {
nw.cancelDriverWatches()
if err := nw.leaveCluster(); err != nil {
log.G(context.TODO()).Warnf("Failed to leave agent cluster on network %s on failure (%v): %v", nw.name, retErr, err)
}
}
}()
if nw.hasLoadBalancerEndpoint() {
if err := nw.createLoadBalancerSandbox(); err != nil {
return nil, err
}
}
if c.isSwarmNode() {
c.mu.Lock()
arrangeIngressFilterRule()
c.mu.Unlock()
}
if err := c.SetupUserChains(); err != nil {
log.G(context.TODO()).WithError(err).Warnf("Controller.NewNetwork %s:", name)
}
return nw, nil
}
// Sets up the DOCKER-USER chain for each iptables version (IPv4, IPv6) that's
// enabled in the controller's configuration.
func (c *Controller) SetupUserChains() error {
for _, ipVersion := range c.enabledIptablesVersions() {
if err := setupUserChain(ipVersion); err != nil {
return err
}
}
return nil
}
var joinCluster NetworkWalker = func(nw *Network) bool {
if nw.configOnly {
return false
}
if err := nw.joinCluster(); err != nil {
log.G(context.TODO()).Errorf("Failed to join network %s (%s) into agent cluster: %v", nw.Name(), nw.ID(), err)
}
nw.addDriverWatches()
return false
}
func (c *Controller) reservePools() {
networks, err := c.getNetworks()
if err != nil {
log.G(context.TODO()).Warnf("Could not retrieve networks from local store during ipam allocation for existing networks: %v", err)
return
}
for _, n := range networks {
if n.configOnly {
continue
}
if !doReplayPoolReserve(n) {
continue
}
// Construct pseudo configs for the auto IP case
autoIPv4 := (len(n.ipamV4Config) == 0 || (len(n.ipamV4Config) == 1 && n.ipamV4Config[0].PreferredPool == "")) && len(n.ipamV4Info) > 0
autoIPv6 := (len(n.ipamV6Config) == 0 || (len(n.ipamV6Config) == 1 && n.ipamV6Config[0].PreferredPool == "")) && len(n.ipamV6Info) > 0
if n.enableIPv4 && autoIPv4 {
n.ipamV4Config = []*IpamConf{{PreferredPool: n.ipamV4Info[0].Pool.String()}}
}
if n.enableIPv6 && autoIPv6 {
n.ipamV6Config = []*IpamConf{{PreferredPool: n.ipamV6Info[0].Pool.String()}}
}
// Account current network gateways
if n.enableIPv4 {
for i, cfg := range n.ipamV4Config {
if cfg.Gateway == "" && n.ipamV4Info[i].Gateway != nil {
cfg.Gateway = n.ipamV4Info[i].Gateway.IP.String()
}
}
}
if n.enableIPv6 {
for i, cfg := range n.ipamV6Config {
if cfg.Gateway == "" && n.ipamV6Info[i].Gateway != nil {
cfg.Gateway = n.ipamV6Info[i].Gateway.IP.String()
}
}
}
// Reserve pools
if err := n.ipamAllocate(); err != nil {
log.G(context.TODO()).Warnf("Failed to allocate ipam pool(s) for network %q (%s): %v", n.Name(), n.ID(), err)
}
// Reserve existing endpoints' addresses
ipam, _, err := n.getController().getIPAMDriver(n.ipamType)
if err != nil {
log.G(context.TODO()).Warnf("Failed to retrieve ipam driver for network %q (%s) during address reservation", n.Name(), n.ID())
continue
}
epl, err := n.getEndpointsFromStore()
if err != nil {
log.G(context.TODO()).Warnf("Failed to retrieve list of current endpoints on network %q (%s)", n.Name(), n.ID())
continue
}
for _, ep := range epl {
if ep.Iface() == nil {
log.G(context.TODO()).Warnf("endpoint interface is empty for %q (%s)", ep.Name(), ep.ID())
continue
}
if err := ep.assignAddress(ipam, ep.Iface().Address() != nil, ep.Iface().AddressIPv6() != nil); err != nil {
log.G(context.TODO()).Warnf("Failed to reserve current address for endpoint %q (%s) on network %q (%s)",
ep.Name(), ep.ID(), n.Name(), n.ID())
}
}
}
}
func doReplayPoolReserve(n *Network) bool {
_, caps, err := n.getController().getIPAMDriver(n.ipamType)
if err != nil {
log.G(context.TODO()).Warnf("Failed to retrieve ipam driver for network %q (%s): %v", n.Name(), n.ID(), err)
return false
}
return caps.RequiresRequestReplay
}
func (c *Controller) addNetwork(n *Network) error {
d, err := n.driver(true)
if err != nil {
return err
}
// Create the network
if err := d.CreateNetwork(n.id, n.generic, n, n.getIPData(4), n.getIPData(6)); err != nil {
return err
}
n.startResolver()
return nil
}
// Networks returns the list of Network(s) managed by this controller.
func (c *Controller) Networks(ctx context.Context) []*Network {
var list []*Network
for _, n := range c.getNetworksFromStore(ctx) {
if n.inDelete {
continue
}
list = append(list, n)
}
return list
}
// WalkNetworks uses the provided function to walk the Network(s) managed by this controller.
func (c *Controller) WalkNetworks(walker NetworkWalker) {
for _, n := range c.Networks(context.TODO()) {
if walker(n) {
return
}
}
}
// NetworkByName returns the Network which has the passed name.
// If not found, the error [ErrNoSuchNetwork] is returned.
func (c *Controller) NetworkByName(name string) (*Network, error) {
if name == "" {
return nil, ErrInvalidName(name)
}
var n *Network
c.WalkNetworks(func(current *Network) bool {
if current.Name() == name {
n = current
return true
}
return false
})
if n == nil {
return nil, ErrNoSuchNetwork(name)
}
return n, nil
}
// NetworkByID returns the Network which has the passed id.
// If not found, the error [ErrNoSuchNetwork] is returned.
func (c *Controller) NetworkByID(id string) (*Network, error) {
if id == "" {
return nil, ErrInvalidID(id)
}
return c.getNetworkFromStore(id)
}
// NewSandbox creates a new sandbox for containerID.
func (c *Controller) NewSandbox(ctx context.Context, containerID string, options ...SandboxOption) (_ *Sandbox, retErr error) {
if containerID == "" {
return nil, types.InvalidParameterErrorf("invalid container ID")
}
ctx, span := otel.Tracer("").Start(ctx, "libnetwork.Controller.NewSandbox")
defer span.End()
var sb *Sandbox
c.mu.Lock()
for _, s := range c.sandboxes {
if s.containerID == containerID {
// If not a stub, then we already have a complete sandbox.
if !s.isStub {
sbID := s.ID()
c.mu.Unlock()
return nil, types.ForbiddenErrorf("container %s is already present in sandbox %s", containerID, sbID)
}
// We already have a stub sandbox from the
// store. Make use of it so that we don't lose
// the endpoints from store but reset the
// isStub flag.
sb = s
sb.isStub = false
break
}
}
c.mu.Unlock()
// Create sandbox and process options first. Key generation depends on an option
if sb == nil {
// TODO(thaJeztah): given that a "containerID" must be unique in the list of sandboxes, is there any reason we're not using containerID as sandbox ID on non-Windows?
sandboxID := containerID
if runtime.GOOS != "windows" {
sandboxID = stringid.GenerateRandomID()
}
sb = &Sandbox{
id: sandboxID,
containerID: containerID,
endpoints: []*Endpoint{},
epPriority: map[string]int{},
populatedEndpoints: map[string]struct{}{},
config: containerConfig{},
controller: c,
extDNS: []extDNSEntry{},
}
}
sb.processOptions(options...)
c.mu.Lock()
if sb.ingress && c.ingressSandbox != nil {
c.mu.Unlock()
return nil, types.ForbiddenErrorf("ingress sandbox already present")
}
if sb.ingress {
c.ingressSandbox = sb
sb.config.hostsPath = filepath.Join(c.cfg.DataDir, "/network/files/hosts")
sb.config.resolvConfPath = filepath.Join(c.cfg.DataDir, "/network/files/resolv.conf")
sb.id = "ingress_sbox"
} else if sb.loadBalancerNID != "" {
sb.id = "lb_" + sb.loadBalancerNID
}
c.mu.Unlock()
defer func() {
if retErr != nil {
c.mu.Lock()
if sb.ingress {
c.ingressSandbox = nil
}
c.mu.Unlock()
}
}()
if err := sb.setupResolutionFiles(ctx); err != nil {
return nil, err
}
if err := c.setupOSLSandbox(sb); err != nil {
return nil, err
}
c.mu.Lock()
c.sandboxes[sb.id] = sb
c.mu.Unlock()
defer func() {
if retErr != nil {
c.mu.Lock()
delete(c.sandboxes, sb.id)
c.mu.Unlock()
}
}()
if err := sb.storeUpdate(ctx); err != nil {
return nil, fmt.Errorf("failed to update the store state of sandbox: %v", err)
}
return sb, nil
}
// GetSandbox returns the Sandbox which has the passed id.
//
// It returns an [ErrInvalidID] when passing an invalid ID, or an
// [types.NotFoundError] if no Sandbox was found for the container.
func (c *Controller) GetSandbox(containerID string) (*Sandbox, error) {
if containerID == "" {
return nil, ErrInvalidID("id is empty")
}
c.mu.Lock()
defer c.mu.Unlock()
if runtime.GOOS == "windows" {
// fast-path for Windows, which uses the container ID as sandbox ID.
if sb := c.sandboxes[containerID]; sb != nil && !sb.isStub {
return sb, nil
}
} else {
for _, sb := range c.sandboxes {
if sb.containerID == containerID && !sb.isStub {
return sb, nil
}
}
}
return nil, types.NotFoundErrorf("network sandbox for container %s not found", containerID)
}
// SandboxByID returns the Sandbox which has the passed id.
// If not found, a [types.NotFoundError] is returned.
func (c *Controller) SandboxByID(id string) (*Sandbox, error) {
if id == "" {
return nil, ErrInvalidID(id)
}
c.mu.Lock()
s, ok := c.sandboxes[id]
c.mu.Unlock()
if !ok {
return nil, types.NotFoundErrorf("sandbox %s not found", id)
}
return s, nil
}
// SandboxDestroy destroys a sandbox given a container ID.
func (c *Controller) SandboxDestroy(ctx context.Context, id string) error {
var sb *Sandbox
c.mu.Lock()
for _, s := range c.sandboxes {
if s.containerID == id {
sb = s
break
}
}
c.mu.Unlock()
// It is not an error if sandbox is not available
if sb == nil {
return nil
}
return sb.Delete(ctx)
}
func (c *Controller) loadDriver(networkType string) error {
var err error
if pg := c.GetPluginGetter(); pg != nil {
_, err = pg.Get(networkType, driverapi.NetworkPluginEndpointType, plugingetter.Lookup)
} else {
_, err = plugins.Get(networkType, driverapi.NetworkPluginEndpointType)
}
if err != nil {
if errors.Cause(err) == plugins.ErrNotFound {
return types.NotFoundErrorf("%v", err)
}
return err
}
return nil
}
func (c *Controller) loadIPAMDriver(name string) error {
var err error
if pg := c.GetPluginGetter(); pg != nil {
_, err = pg.Get(name, ipamapi.PluginEndpointType, plugingetter.Lookup)
} else {
_, err = plugins.Get(name, ipamapi.PluginEndpointType)
}
if err != nil {
if errors.Cause(err) == plugins.ErrNotFound {
return types.NotFoundErrorf("%v", err)
}
return err
}
return nil
}
func (c *Controller) getIPAMDriver(name string) (ipamapi.Ipam, *ipamapi.Capability, error) {
id, caps := c.ipamRegistry.IPAM(name)
if id == nil {
// Might be a plugin name. Try loading it
if err := c.loadIPAMDriver(name); err != nil {
return nil, nil, err
}
// Now that we resolved the plugin, try again looking up the registry
id, caps = c.ipamRegistry.IPAM(name)
if id == nil {
return nil, nil, types.InvalidParameterErrorf("invalid ipam driver: %q", name)
}
}
return id, caps, nil
}
// Stop stops the network controller.
func (c *Controller) Stop() {
c.store.Close()
c.stopExternalKeyListener()
osl.GC()
}
// StartDiagnostic starts the network diagnostic server listening on port.
func (c *Controller) StartDiagnostic(port int) {
c.mu.Lock()
if !c.DiagnosticServer.IsDiagnosticEnabled() {
c.DiagnosticServer.EnableDiagnostic("127.0.0.1", port)
}
c.mu.Unlock()
}
// StopDiagnostic stops the network diagnostic server.
func (c *Controller) StopDiagnostic() {
c.mu.Lock()
if c.DiagnosticServer.IsDiagnosticEnabled() {
c.DiagnosticServer.DisableDiagnostic()
}
c.mu.Unlock()
}
// IsDiagnosticEnabled returns true if the diagnostic server is running.
func (c *Controller) IsDiagnosticEnabled() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.DiagnosticServer.IsDiagnosticEnabled()
}