| /* | |
| 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.Scope) | |
| 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{}{} | |
| 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 | |
| } | |
| if c.cfg.Scope.IsValid() { | |
| cfg[netlabel.LocalKVClient] = c.store | |
| } | |
| 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() { | |
| // 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 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 | |
| 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, true, 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() | |
| } |