aboutsummaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
authorDoug Rabson <dfr@rabson.org>2022-09-09 13:33:35 +0100
committerDoug Rabson <dfr@rabson.org>2022-09-12 16:28:47 +0100
commit221c4d5f0692f84b3c9de7cf820d4879a996c23f (patch)
tree6a42573cadfa64427112001b77ffc5083f6f9a57 /libpod
parentf939f3fdfccaeeb976085e5082e56efe6eb6df62 (diff)
downloadpodman-221c4d5f0692f84b3c9de7cf820d4879a996c23f.tar.gz
podman-221c4d5f0692f84b3c9de7cf820d4879a996c23f.tar.bz2
podman-221c4d5f0692f84b3c9de7cf820d4879a996c23f.zip
libpod: Move NetworkDisconnect and NetworkConnect to networking_common.go
This also moves Runtime methods ConnectContainerToNetwork and DisconnectContainerFromNetwork as well as support functions getFreeInterfaceName and normalizeNetworkName. [NO NEW TESTS NEEDED] Signed-off-by: Doug Rabson <dfr@rabson.org> libpod: Move (Connect|Disconnect)Container(To|From)Network and normalizeNetworkName to networking_common.go [NO NEW TESTS NEEDED] Signed-off-by: Doug Rabson <dfr@rabson.org>
Diffstat (limited to 'libpod')
-rw-r--r--libpod/networking_common.go307
-rw-r--r--libpod/networking_freebsd.go231
-rw-r--r--libpod/networking_linux.go304
3 files changed, 309 insertions, 533 deletions
diff --git a/libpod/networking_common.go b/libpod/networking_common.go
index e6509e66b..4f1947489 100644
--- a/libpod/networking_common.go
+++ b/libpod/networking_common.go
@@ -4,14 +4,21 @@
package libpod
import (
+ "errors"
"fmt"
"regexp"
"sort"
+ "github.com/containers/common/libnetwork/etchosts"
"github.com/containers/common/libnetwork/types"
+ "github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/machine"
+ "github.com/containers/common/pkg/util"
"github.com/containers/podman/v4/libpod/define"
+ "github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/namespaces"
+ "github.com/containers/podman/v4/pkg/rootless"
+ "github.com/containers/storage/pkg/lockfile"
"github.com/sirupsen/logrus"
)
@@ -345,3 +352,303 @@ func resultToBasicNetworkConfig(result types.StatusBlock) define.InspectBasicNet
}
return config
}
+
+// NetworkDisconnect removes a container from the network
+func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error {
+ // only the bridge mode supports cni networks
+ if err := isBridgeNetMode(c.config.NetMode); err != nil {
+ return err
+ }
+
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ networks, err := c.networks()
+ if err != nil {
+ return err
+ }
+
+ // check if network exists and if the input is a ID we get the name
+ // CNI only uses names so it is important that we only use the name
+ netName, err = c.runtime.normalizeNetworkName(netName)
+ if err != nil {
+ return err
+ }
+
+ _, nameExists := networks[netName]
+ if !nameExists && len(networks) > 0 {
+ return fmt.Errorf("container %s is not connected to network %s", nameOrID, netName)
+ }
+
+ if err := c.syncContainer(); err != nil {
+ return err
+ }
+ // get network status before we disconnect
+ networkStatus := c.getNetworkStatus()
+
+ if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil {
+ return err
+ }
+
+ c.newNetworkEvent(events.NetworkDisconnect, netName)
+ if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
+ return nil
+ }
+
+ if c.state.NetNS == nil {
+ return fmt.Errorf("unable to disconnect %s from %s: %w", nameOrID, netName, define.ErrNoNetwork)
+ }
+
+ opts := types.NetworkOptions{
+ ContainerID: c.config.ID,
+ ContainerName: getCNIPodName(c),
+ }
+ opts.PortMappings = c.convertPortMappings()
+ opts.Networks = map[string]types.PerNetworkOptions{
+ netName: networks[netName],
+ }
+
+ if err := c.runtime.teardownNetwork(c.state.NetNS.Path(), opts); err != nil {
+ return err
+ }
+
+ // update network status if container is running
+ oldStatus, statusExist := networkStatus[netName]
+ delete(networkStatus, netName)
+ c.state.NetworkStatus = networkStatus
+ err = c.save()
+ if err != nil {
+ return err
+ }
+
+ // Reload ports when there are still connected networks, maybe we removed the network interface with the child ip.
+ // Reloading without connected networks does not make sense, so we can skip this step.
+ if rootless.IsRootless() && len(networkStatus) > 0 {
+ if err := c.reloadRootlessRLKPortMapping(); err != nil {
+ return err
+ }
+ }
+
+ // Update resolv.conf if required
+ if statusExist {
+ stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs))
+ for _, ip := range oldStatus.DNSServerIPs {
+ stringIPs = append(stringIPs, ip.String())
+ }
+ if len(stringIPs) > 0 {
+ logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs)
+ if err := c.removeNameserver(stringIPs); err != nil {
+ return err
+ }
+ }
+
+ // update /etc/hosts file
+ if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
+ // sync the names with c.getHostsEntries()
+ names := []string{c.Hostname(), c.config.Name}
+ rm := etchosts.GetNetworkHostEntries(map[string]types.StatusBlock{netName: oldStatus}, names...)
+ if len(rm) > 0 {
+ // make sure to lock this file to prevent concurrent writes when
+ // this is used a net dependency container
+ lock, err := lockfile.GetLockfile(file)
+ if err != nil {
+ return fmt.Errorf("failed to lock hosts file: %w", err)
+ }
+ logrus.Debugf("Remove /etc/hosts entries %v", rm)
+ lock.Lock()
+ err = etchosts.Remove(file, rm)
+ lock.Unlock()
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+ return nil
+}
+
+// ConnectNetwork connects a container to a given network
+func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
+ // only the bridge mode supports cni networks
+ if err := isBridgeNetMode(c.config.NetMode); err != nil {
+ return err
+ }
+
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ networks, err := c.networks()
+ if err != nil {
+ return err
+ }
+
+ // check if network exists and if the input is a ID we get the name
+ // CNI only uses names so it is important that we only use the name
+ netName, err = c.runtime.normalizeNetworkName(netName)
+ if err != nil {
+ return err
+ }
+
+ if err := c.syncContainer(); err != nil {
+ return err
+ }
+
+ // get network status before we connect
+ networkStatus := c.getNetworkStatus()
+
+ // always add the short id as alias for docker compat
+ netOpts.Aliases = append(netOpts.Aliases, c.config.ID[:12])
+
+ if netOpts.InterfaceName == "" {
+ netOpts.InterfaceName = getFreeInterfaceName(networks)
+ if netOpts.InterfaceName == "" {
+ return errors.New("could not find free network interface name")
+ }
+ }
+
+ if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil {
+ // Docker compat: treat requests to attach already attached networks as a no-op, ignoring opts
+ if errors.Is(err, define.ErrNetworkConnected) && c.ensureState(define.ContainerStateConfigured) {
+ return nil
+ }
+
+ return err
+ }
+ c.newNetworkEvent(events.NetworkConnect, netName)
+ if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
+ return nil
+ }
+ if c.state.NetNS == nil {
+ return fmt.Errorf("unable to connect %s to %s: %w", nameOrID, netName, define.ErrNoNetwork)
+ }
+
+ opts := types.NetworkOptions{
+ ContainerID: c.config.ID,
+ ContainerName: getCNIPodName(c),
+ }
+ opts.PortMappings = c.convertPortMappings()
+ opts.Networks = map[string]types.PerNetworkOptions{
+ netName: netOpts,
+ }
+
+ results, err := c.runtime.setUpNetwork(c.state.NetNS.Path(), opts)
+ if err != nil {
+ return err
+ }
+ if len(results) != 1 {
+ return errors.New("when adding aliases, results must be of length 1")
+ }
+
+ // we need to get the old host entries before we add the new one to the status
+ // if we do not add do it here we will get the wrong existing entries which will throw of the logic
+ // we could also copy the map but this does not seem worth it
+ // sync the hostNames with c.getHostsEntries()
+ hostNames := []string{c.Hostname(), c.config.Name}
+ oldHostEntries := etchosts.GetNetworkHostEntries(networkStatus, hostNames...)
+
+ // update network status
+ if networkStatus == nil {
+ networkStatus = make(map[string]types.StatusBlock, 1)
+ }
+ networkStatus[netName] = results[netName]
+ c.state.NetworkStatus = networkStatus
+
+ err = c.save()
+ if err != nil {
+ return err
+ }
+
+ // The first network needs a port reload to set the correct child ip for the rootlessport process.
+ // Adding a second network does not require a port reload because the child ip is still valid.
+ if rootless.IsRootless() && len(networks) == 0 {
+ if err := c.reloadRootlessRLKPortMapping(); err != nil {
+ return err
+ }
+ }
+
+ ipv6, err := c.checkForIPv6(networkStatus)
+ if err != nil {
+ return err
+ }
+
+ // Update resolv.conf if required
+ stringIPs := make([]string, 0, len(results[netName].DNSServerIPs))
+ for _, ip := range results[netName].DNSServerIPs {
+ if (ip.To4() == nil) && !ipv6 {
+ continue
+ }
+ stringIPs = append(stringIPs, ip.String())
+ }
+ if len(stringIPs) > 0 {
+ logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs)
+ if err := c.addNameserver(stringIPs); err != nil {
+ return err
+ }
+ }
+
+ // update /etc/hosts file
+ if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
+ // make sure to lock this file to prevent concurrent writes when
+ // this is used a net dependency container
+ lock, err := lockfile.GetLockfile(file)
+ if err != nil {
+ return fmt.Errorf("failed to lock hosts file: %w", err)
+ }
+ new := etchosts.GetNetworkHostEntries(results, hostNames...)
+ logrus.Debugf("Add /etc/hosts entries %v", new)
+ // use special AddIfExists API to make sure we only add new entries if an old one exists
+ // see the AddIfExists() comment for more information
+ lock.Lock()
+ err = etchosts.AddIfExists(file, oldHostEntries, new)
+ lock.Unlock()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// get a free interface name for a new network
+// return an empty string if no free name was found
+func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string {
+ ifNames := make([]string, 0, len(networks))
+ for _, opts := range networks {
+ ifNames = append(ifNames, opts.InterfaceName)
+ }
+ for i := 0; i < 100000; i++ {
+ ifName := fmt.Sprintf("eth%d", i)
+ if !util.StringInSlice(ifName, ifNames) {
+ return ifName
+ }
+ }
+ return ""
+}
+
+// DisconnectContainerFromNetwork removes a container from its CNI network
+func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
+ ctr, err := r.LookupContainer(nameOrID)
+ if err != nil {
+ return err
+ }
+ return ctr.NetworkDisconnect(nameOrID, netName, force)
+}
+
+// ConnectContainerToNetwork connects a container to a CNI network
+func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
+ ctr, err := r.LookupContainer(nameOrID)
+ if err != nil {
+ return err
+ }
+ return ctr.NetworkConnect(nameOrID, netName, netOpts)
+}
+
+// normalizeNetworkName takes a network name, a partial or a full network ID and returns the network name.
+// If the network is not found a errors is returned.
+func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) {
+ net, err := r.network.NetworkInspect(nameOrID)
+ if err != nil {
+ return "", err
+ }
+ return net.Name, nil
+}
diff --git a/libpod/networking_freebsd.go b/libpod/networking_freebsd.go
index b6bcb3f71..3023bcac9 100644
--- a/libpod/networking_freebsd.go
+++ b/libpod/networking_freebsd.go
@@ -15,9 +15,6 @@ import (
"github.com/containers/buildah/pkg/jail"
"github.com/containers/common/libnetwork/types"
- "github.com/containers/podman/v4/libpod/define"
- "github.com/containers/podman/v4/libpod/events"
- "github.com/containers/podman/v4/pkg/util"
"github.com/containers/storage/pkg/lockfile"
"github.com/sirupsen/logrus"
)
@@ -263,232 +260,8 @@ func (c *Container) inspectJoinedNetworkNS(networkns string) (q types.StatusBloc
}
-// NetworkDisconnect removes a container from the network
-func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error {
- // only the bridge mode supports cni networks
- if err := isBridgeNetMode(c.config.NetMode); err != nil {
- return err
- }
-
- c.lock.Lock()
- defer c.lock.Unlock()
-
- networks, err := c.networks()
- if err != nil {
- return err
- }
-
- // check if network exists and if the input is a ID we get the name
- // CNI only uses names so it is important that we only use the name
- netName, err = c.runtime.normalizeNetworkName(netName)
- if err != nil {
- return err
- }
-
- _, nameExists := networks[netName]
- if !nameExists && len(networks) > 0 {
- return fmt.Errorf("container %s is not connected to network %s", nameOrID, netName)
- }
-
- if err := c.syncContainer(); err != nil {
- return err
- }
- // get network status before we disconnect
- networkStatus := c.getNetworkStatus()
-
- if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil {
- return err
- }
-
- c.newNetworkEvent(events.NetworkDisconnect, netName)
- if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
- return nil
- }
-
- opts := types.NetworkOptions{
- ContainerID: c.config.ID,
- ContainerName: getCNIPodName(c),
- }
- opts.PortMappings = c.convertPortMappings()
- opts.Networks = map[string]types.PerNetworkOptions{
- netName: networks[netName],
- }
-
- // update network status if container is running
- oldStatus, statusExist := networkStatus[netName]
- delete(networkStatus, netName)
- c.state.NetworkStatus = networkStatus
- err = c.save()
- if err != nil {
- return err
- }
-
- // Update resolv.conf if required
- if statusExist {
- stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs))
- for _, ip := range oldStatus.DNSServerIPs {
- stringIPs = append(stringIPs, ip.String())
- }
- if len(stringIPs) == 0 {
- return nil
- }
- logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs)
- if err := c.removeNameserver(stringIPs); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// ConnectNetwork connects a container to a given network
-func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
- // only the bridge mode supports cni networks
- if err := isBridgeNetMode(c.config.NetMode); err != nil {
- return err
- }
-
- c.lock.Lock()
- defer c.lock.Unlock()
-
- networks, err := c.networks()
- if err != nil {
- return err
- }
-
- // check if network exists and if the input is a ID we get the name
- // CNI only uses names so it is important that we only use the name
- netName, err = c.runtime.normalizeNetworkName(netName)
- if err != nil {
- return err
- }
-
- if err := c.syncContainer(); err != nil {
- return err
- }
-
- // get network status before we connect
- networkStatus := c.getNetworkStatus()
-
- // always add the short id as alias for docker compat
- netOpts.Aliases = append(netOpts.Aliases, c.config.ID[:12])
-
- if netOpts.InterfaceName == "" {
- netOpts.InterfaceName = getFreeInterfaceName(networks)
- if netOpts.InterfaceName == "" {
- return errors.New("could not find free network interface name")
- }
- }
-
- if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil {
- return err
- }
- c.newNetworkEvent(events.NetworkConnect, netName)
- if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
- return nil
- }
-
- opts := types.NetworkOptions{
- ContainerID: c.config.ID,
- ContainerName: getCNIPodName(c),
- }
- opts.PortMappings = c.convertPortMappings()
- opts.Networks = map[string]types.PerNetworkOptions{
- netName: netOpts,
- }
-
- /*
- results, err := c.runtime.setUpNetwork(c.state.NetNS.Path(), opts)
- if err != nil {
- return err
- }
- if len(results) != 1 {
- return errors.New("when adding aliases, results must be of length 1")
- }
- */
- var results map[string]types.StatusBlock
-
- // update network status
- if networkStatus == nil {
- networkStatus = make(map[string]types.StatusBlock, 1)
- }
- networkStatus[netName] = results[netName]
- c.state.NetworkStatus = networkStatus
-
- err = c.save()
- if err != nil {
- return err
- }
-
- // The first network needs a port reload to set the correct child ip for the rootlessport process.
- // Adding a second network does not require a port reload because the child ip is still valid.
-
- ipv6, err := c.checkForIPv6(networkStatus)
- if err != nil {
- return err
- }
-
- // Update resolv.conf if required
- stringIPs := make([]string, 0, len(results[netName].DNSServerIPs))
- for _, ip := range results[netName].DNSServerIPs {
- if (ip.To4() == nil) && !ipv6 {
- continue
- }
- stringIPs = append(stringIPs, ip.String())
- }
- if len(stringIPs) == 0 {
- return nil
- }
- logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs)
- if err := c.addNameserver(stringIPs); err != nil {
- return err
- }
-
- return nil
-}
-
-// get a free interface name for a new network
-// return an empty string if no free name was found
-func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string {
- ifNames := make([]string, 0, len(networks))
- for _, opts := range networks {
- ifNames = append(ifNames, opts.InterfaceName)
- }
- for i := 0; i < 100000; i++ {
- ifName := fmt.Sprintf("eth%d", i)
- if !util.StringInSlice(ifName, ifNames) {
- return ifName
- }
- }
- return ""
-}
-
-// DisconnectContainerFromNetwork removes a container from its CNI network
-func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
- ctr, err := r.LookupContainer(nameOrID)
- if err != nil {
- return err
- }
- return ctr.NetworkDisconnect(nameOrID, netName, force)
-}
-
-// ConnectContainerToNetwork connects a container to a CNI network
-func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
- ctr, err := r.LookupContainer(nameOrID)
- if err != nil {
- return err
- }
- return ctr.NetworkConnect(nameOrID, netName, netOpts)
-}
-
-// normalizeNetworkName takes a network name, a partial or a full network ID and returns the network name.
-// If the network is not found a errors is returned.
-func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) {
- net, err := r.network.NetworkInspect(nameOrID)
- if err != nil {
- return "", err
- }
- return net.Name, nil
+func (c *Container) reloadRootlessRLKPortMapping() error {
+ return errors.New("unsupported (*Container).reloadRootlessRLKPortMapping")
}
// ocicniPortsToNetTypesPorts convert the old port format to the new one
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index 7eae5c396..2052ac0c0 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -20,14 +20,10 @@ import (
"time"
"github.com/containernetworking/plugins/pkg/ns"
- "github.com/containers/common/libnetwork/etchosts"
"github.com/containers/common/libnetwork/resolvconf"
"github.com/containers/common/libnetwork/types"
- "github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/netns"
"github.com/containers/common/pkg/util"
- "github.com/containers/podman/v4/libpod/define"
- "github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/errorhandling"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/containers/podman/v4/utils"
@@ -869,306 +865,6 @@ func (w *logrusDebugWriter) Write(p []byte) (int, error) {
return len(p), nil
}
-// NetworkDisconnect removes a container from the network
-func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error {
- // only the bridge mode supports cni networks
- if err := isBridgeNetMode(c.config.NetMode); err != nil {
- return err
- }
-
- c.lock.Lock()
- defer c.lock.Unlock()
-
- networks, err := c.networks()
- if err != nil {
- return err
- }
-
- // check if network exists and if the input is a ID we get the name
- // CNI only uses names so it is important that we only use the name
- netName, err = c.runtime.normalizeNetworkName(netName)
- if err != nil {
- return err
- }
-
- _, nameExists := networks[netName]
- if !nameExists && len(networks) > 0 {
- return fmt.Errorf("container %s is not connected to network %s", nameOrID, netName)
- }
-
- if err := c.syncContainer(); err != nil {
- return err
- }
- // get network status before we disconnect
- networkStatus := c.getNetworkStatus()
-
- if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil {
- return err
- }
-
- c.newNetworkEvent(events.NetworkDisconnect, netName)
- if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
- return nil
- }
-
- if c.state.NetNS == nil {
- return fmt.Errorf("unable to disconnect %s from %s: %w", nameOrID, netName, define.ErrNoNetwork)
- }
-
- opts := types.NetworkOptions{
- ContainerID: c.config.ID,
- ContainerName: getCNIPodName(c),
- }
- opts.PortMappings = c.convertPortMappings()
- opts.Networks = map[string]types.PerNetworkOptions{
- netName: networks[netName],
- }
-
- if err := c.runtime.teardownNetwork(c.state.NetNS.Path(), opts); err != nil {
- return err
- }
-
- // update network status if container is running
- oldStatus, statusExist := networkStatus[netName]
- delete(networkStatus, netName)
- c.state.NetworkStatus = networkStatus
- err = c.save()
- if err != nil {
- return err
- }
-
- // Reload ports when there are still connected networks, maybe we removed the network interface with the child ip.
- // Reloading without connected networks does not make sense, so we can skip this step.
- if rootless.IsRootless() && len(networkStatus) > 0 {
- if err := c.reloadRootlessRLKPortMapping(); err != nil {
- return err
- }
- }
-
- // Update resolv.conf if required
- if statusExist {
- stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs))
- for _, ip := range oldStatus.DNSServerIPs {
- stringIPs = append(stringIPs, ip.String())
- }
- if len(stringIPs) > 0 {
- logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs)
- if err := c.removeNameserver(stringIPs); err != nil {
- return err
- }
- }
-
- // update /etc/hosts file
- if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
- // sync the names with c.getHostsEntries()
- names := []string{c.Hostname(), c.config.Name}
- rm := etchosts.GetNetworkHostEntries(map[string]types.StatusBlock{netName: oldStatus}, names...)
- if len(rm) > 0 {
- // make sure to lock this file to prevent concurrent writes when
- // this is used a net dependency container
- lock, err := lockfile.GetLockfile(file)
- if err != nil {
- return fmt.Errorf("failed to lock hosts file: %w", err)
- }
- logrus.Debugf("Remove /etc/hosts entries %v", rm)
- lock.Lock()
- err = etchosts.Remove(file, rm)
- lock.Unlock()
- if err != nil {
- return err
- }
- }
- }
- }
- return nil
-}
-
-// ConnectNetwork connects a container to a given network
-func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
- // only the bridge mode supports cni networks
- if err := isBridgeNetMode(c.config.NetMode); err != nil {
- return err
- }
-
- c.lock.Lock()
- defer c.lock.Unlock()
-
- networks, err := c.networks()
- if err != nil {
- return err
- }
-
- // check if network exists and if the input is a ID we get the name
- // CNI only uses names so it is important that we only use the name
- netName, err = c.runtime.normalizeNetworkName(netName)
- if err != nil {
- return err
- }
-
- if err := c.syncContainer(); err != nil {
- return err
- }
-
- // get network status before we connect
- networkStatus := c.getNetworkStatus()
-
- // always add the short id as alias for docker compat
- netOpts.Aliases = append(netOpts.Aliases, c.config.ID[:12])
-
- if netOpts.InterfaceName == "" {
- netOpts.InterfaceName = getFreeInterfaceName(networks)
- if netOpts.InterfaceName == "" {
- return errors.New("could not find free network interface name")
- }
- }
-
- if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil {
- // Docker compat: treat requests to attach already attached networks as a no-op, ignoring opts
- if errors.Is(err, define.ErrNetworkConnected) && c.ensureState(define.ContainerStateConfigured) {
- return nil
- }
-
- return err
- }
- c.newNetworkEvent(events.NetworkConnect, netName)
- if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
- return nil
- }
- if c.state.NetNS == nil {
- return fmt.Errorf("unable to connect %s to %s: %w", nameOrID, netName, define.ErrNoNetwork)
- }
-
- opts := types.NetworkOptions{
- ContainerID: c.config.ID,
- ContainerName: getCNIPodName(c),
- }
- opts.PortMappings = c.convertPortMappings()
- opts.Networks = map[string]types.PerNetworkOptions{
- netName: netOpts,
- }
-
- results, err := c.runtime.setUpNetwork(c.state.NetNS.Path(), opts)
- if err != nil {
- return err
- }
- if len(results) != 1 {
- return errors.New("when adding aliases, results must be of length 1")
- }
-
- // we need to get the old host entries before we add the new one to the status
- // if we do not add do it here we will get the wrong existing entries which will throw of the logic
- // we could also copy the map but this does not seem worth it
- // sync the hostNames with c.getHostsEntries()
- hostNames := []string{c.Hostname(), c.config.Name}
- oldHostEntries := etchosts.GetNetworkHostEntries(networkStatus, hostNames...)
-
- // update network status
- if networkStatus == nil {
- networkStatus = make(map[string]types.StatusBlock, 1)
- }
- networkStatus[netName] = results[netName]
- c.state.NetworkStatus = networkStatus
-
- err = c.save()
- if err != nil {
- return err
- }
-
- // The first network needs a port reload to set the correct child ip for the rootlessport process.
- // Adding a second network does not require a port reload because the child ip is still valid.
- if rootless.IsRootless() && len(networks) == 0 {
- if err := c.reloadRootlessRLKPortMapping(); err != nil {
- return err
- }
- }
-
- ipv6, err := c.checkForIPv6(networkStatus)
- if err != nil {
- return err
- }
-
- // Update resolv.conf if required
- stringIPs := make([]string, 0, len(results[netName].DNSServerIPs))
- for _, ip := range results[netName].DNSServerIPs {
- if (ip.To4() == nil) && !ipv6 {
- continue
- }
- stringIPs = append(stringIPs, ip.String())
- }
- if len(stringIPs) > 0 {
- logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs)
- if err := c.addNameserver(stringIPs); err != nil {
- return err
- }
- }
-
- // update /etc/hosts file
- if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
- // make sure to lock this file to prevent concurrent writes when
- // this is used a net dependency container
- lock, err := lockfile.GetLockfile(file)
- if err != nil {
- return fmt.Errorf("failed to lock hosts file: %w", err)
- }
- new := etchosts.GetNetworkHostEntries(results, hostNames...)
- logrus.Debugf("Add /etc/hosts entries %v", new)
- // use special AddIfExists API to make sure we only add new entries if an old one exists
- // see the AddIfExists() comment for more information
- lock.Lock()
- err = etchosts.AddIfExists(file, oldHostEntries, new)
- lock.Unlock()
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// get a free interface name for a new network
-// return an empty string if no free name was found
-func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string {
- ifNames := make([]string, 0, len(networks))
- for _, opts := range networks {
- ifNames = append(ifNames, opts.InterfaceName)
- }
- for i := 0; i < 100000; i++ {
- ifName := fmt.Sprintf("eth%d", i)
- if !util.StringInSlice(ifName, ifNames) {
- return ifName
- }
- }
- return ""
-}
-
-// DisconnectContainerFromNetwork removes a container from its CNI network
-func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
- ctr, err := r.LookupContainer(nameOrID)
- if err != nil {
- return err
- }
- return ctr.NetworkDisconnect(nameOrID, netName, force)
-}
-
-// ConnectContainerToNetwork connects a container to a CNI network
-func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
- ctr, err := r.LookupContainer(nameOrID)
- if err != nil {
- return err
- }
- return ctr.NetworkConnect(nameOrID, netName, netOpts)
-}
-
-// normalizeNetworkName takes a network name, a partial or a full network ID and returns the network name.
-// If the network is not found a errors is returned.
-func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) {
- net, err := r.network.NetworkInspect(nameOrID)
- if err != nil {
- return "", err
- }
- return net.Name, nil
-}
-
// ocicniPortsToNetTypesPorts convert the old port format to the new one
// while deduplicating ports into ranges
func ocicniPortsToNetTypesPorts(ports []types.OCICNIPortMapping) []types.PortMapping {