github.com/containers/podman/v4@v4.9.4/libpod/networking_common.go (about)

     1  //go:build !remote && (linux || freebsd)
     2  // +build !remote
     3  // +build linux freebsd
     4  
     5  package libpod
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"regexp"
    11  	"sort"
    12  
    13  	"github.com/containers/common/libnetwork/etchosts"
    14  	"github.com/containers/common/libnetwork/types"
    15  	"github.com/containers/common/pkg/config"
    16  	"github.com/containers/common/pkg/machine"
    17  	"github.com/containers/common/pkg/util"
    18  	"github.com/containers/podman/v4/libpod/define"
    19  	"github.com/containers/podman/v4/libpod/events"
    20  	"github.com/containers/podman/v4/pkg/namespaces"
    21  	"github.com/containers/podman/v4/pkg/rootless"
    22  	"github.com/containers/storage/pkg/lockfile"
    23  	"github.com/sirupsen/logrus"
    24  )
    25  
    26  // convertPortMappings will remove the HostIP part from the ports when running inside podman machine.
    27  // This is needed because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports.
    28  // For machine the HostIP must only be used by gvproxy and never in the VM.
    29  func (c *Container) convertPortMappings() []types.PortMapping {
    30  	if !machine.IsGvProxyBased() || len(c.config.PortMappings) == 0 {
    31  		return c.config.PortMappings
    32  	}
    33  	// if we run in a machine VM we have to ignore the host IP part
    34  	newPorts := make([]types.PortMapping, 0, len(c.config.PortMappings))
    35  	for _, port := range c.config.PortMappings {
    36  		port.HostIP = ""
    37  		newPorts = append(newPorts, port)
    38  	}
    39  	return newPorts
    40  }
    41  
    42  func (c *Container) getNetworkOptions(networkOpts map[string]types.PerNetworkOptions) types.NetworkOptions {
    43  	nameservers := make([]string, 0, len(c.runtime.config.Containers.DNSServers.Get())+len(c.config.DNSServer))
    44  	nameservers = append(nameservers, c.runtime.config.Containers.DNSServers.Get()...)
    45  	for _, ip := range c.config.DNSServer {
    46  		nameservers = append(nameservers, ip.String())
    47  	}
    48  	opts := types.NetworkOptions{
    49  		ContainerID:   c.config.ID,
    50  		ContainerName: getNetworkPodName(c),
    51  		DNSServers:    nameservers,
    52  	}
    53  	opts.PortMappings = c.convertPortMappings()
    54  
    55  	// If the container requested special network options use this instead of the config.
    56  	// This is the case for container restore or network reload.
    57  	if c.perNetworkOpts != nil {
    58  		opts.Networks = c.perNetworkOpts
    59  	} else {
    60  		opts.Networks = networkOpts
    61  	}
    62  	return opts
    63  }
    64  
    65  // setUpNetwork will set up the networks, on error it will also tear down the cni
    66  // networks. If rootless it will join/create the rootless network namespace.
    67  func (r *Runtime) setUpNetwork(ns string, opts types.NetworkOptions) (map[string]types.StatusBlock, error) {
    68  	rootlessNetNS, err := r.GetRootlessNetNs(true)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	var results map[string]types.StatusBlock
    73  	setUpPod := func() error {
    74  		results, err = r.network.Setup(ns, types.SetupOptions{NetworkOptions: opts})
    75  		return err
    76  	}
    77  	// rootlessNetNS is nil if we are root
    78  	if rootlessNetNS != nil {
    79  		// execute the setup in the rootless net ns
    80  		err = rootlessNetNS.Do(setUpPod)
    81  		rootlessNetNS.Lock.Unlock()
    82  	} else {
    83  		err = setUpPod()
    84  	}
    85  	return results, err
    86  }
    87  
    88  // getNetworkPodName return the pod name (hostname) used by dns backend.
    89  // If we are in the pod network namespace use the pod name otherwise the container name
    90  func getNetworkPodName(c *Container) string {
    91  	if c.config.NetMode.IsPod() || c.IsInfra() {
    92  		pod, err := c.runtime.state.Pod(c.PodID())
    93  		if err == nil {
    94  			return pod.Name()
    95  		}
    96  	}
    97  	return c.Name()
    98  }
    99  
   100  // Tear down a container's network configuration and joins the
   101  // rootless net ns as rootless user
   102  func (r *Runtime) teardownNetworkBackend(ns string, opts types.NetworkOptions) error {
   103  	rootlessNetNS, err := r.GetRootlessNetNs(false)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	tearDownPod := func() error {
   108  		if err := r.network.Teardown(ns, types.TeardownOptions{NetworkOptions: opts}); err != nil {
   109  			return fmt.Errorf("tearing down network namespace configuration for container %s: %w", opts.ContainerID, err)
   110  		}
   111  		return nil
   112  	}
   113  
   114  	// rootlessNetNS is nil if we are root
   115  	if rootlessNetNS != nil {
   116  		// execute the network setup in the rootless net ns
   117  		err = rootlessNetNS.Do(tearDownPod)
   118  		if cerr := rootlessNetNS.Cleanup(r); cerr != nil {
   119  			logrus.WithError(cerr).Error("failed to clean up rootless netns")
   120  		}
   121  		rootlessNetNS.Lock.Unlock()
   122  	} else {
   123  		err = tearDownPod()
   124  	}
   125  	return err
   126  }
   127  
   128  // Tear down a container's network backend configuration, but do not tear down the
   129  // namespace itself.
   130  func (r *Runtime) teardownNetwork(ctr *Container) error {
   131  	if ctr.state.NetNS == "" {
   132  		// The container has no network namespace, we're set
   133  		return nil
   134  	}
   135  
   136  	logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS, ctr.ID())
   137  
   138  	networks, err := ctr.networks()
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	if !ctr.config.NetMode.IsSlirp4netns() &&
   144  		!ctr.config.NetMode.IsPasta() && len(networks) > 0 {
   145  		netOpts := ctr.getNetworkOptions(networks)
   146  		return r.teardownNetworkBackend(ctr.state.NetNS, netOpts)
   147  	}
   148  	return nil
   149  }
   150  
   151  // isBridgeNetMode checks if the given network mode is bridge.
   152  // It returns nil when it is set to bridge and an error otherwise.
   153  func isBridgeNetMode(n namespaces.NetworkMode) error {
   154  	if !n.IsBridge() {
   155  		return fmt.Errorf("%q is not supported: %w", n, define.ErrNetworkModeInvalid)
   156  	}
   157  	return nil
   158  }
   159  
   160  // Reload only works with containers with a configured network.
   161  // It will tear down, and then reconfigure, the network of the container.
   162  // This is mainly used when a reload of firewall rules wipes out existing
   163  // firewall configuration.
   164  // Efforts will be made to preserve MAC and IP addresses.
   165  // Only works on containers with bridge networking at present, though in the future we could
   166  // extend this to stop + restart slirp4netns
   167  func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.StatusBlock, error) {
   168  	if ctr.state.NetNS == "" {
   169  		return nil, fmt.Errorf("container %s network is not configured, refusing to reload: %w", ctr.ID(), define.ErrCtrStateInvalid)
   170  	}
   171  	if err := isBridgeNetMode(ctr.config.NetMode); err != nil {
   172  		return nil, err
   173  	}
   174  	logrus.Infof("Going to reload container %s network", ctr.ID())
   175  
   176  	err := r.teardownNetwork(ctr)
   177  	if err != nil {
   178  		// teardownNetwork will error if the iptables rules do not exist and this is the case after
   179  		// a firewall reload. The purpose of network reload is to recreate the rules if they do
   180  		// not exists so we should not log this specific error as error. This would confuse users otherwise.
   181  		// iptables-legacy and iptables-nft will create different errors. Make sure to match both.
   182  		b, rerr := regexp.MatchString("Couldn't load target `CNI-[a-f0-9]{24}':No such file or directory|Chain 'CNI-[a-f0-9]{24}' does not exist", err.Error())
   183  		if rerr == nil && !b {
   184  			logrus.Error(err)
   185  		} else {
   186  			logrus.Info(err)
   187  		}
   188  	}
   189  
   190  	networkOpts, err := ctr.networks()
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	// Set the same network settings as before..
   196  	netStatus := ctr.getNetworkStatus()
   197  	for network, perNetOpts := range networkOpts {
   198  		for name, netInt := range netStatus[network].Interfaces {
   199  			perNetOpts.InterfaceName = name
   200  			perNetOpts.StaticMAC = netInt.MacAddress
   201  			for _, netAddress := range netInt.Subnets {
   202  				perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.IPNet.IP)
   203  			}
   204  			// Normally interfaces have a length of 1, only for some special cni configs we could get more.
   205  			// For now just use the first interface to get the ips this should be good enough for most cases.
   206  			break
   207  		}
   208  		networkOpts[network] = perNetOpts
   209  	}
   210  	ctr.perNetworkOpts = networkOpts
   211  
   212  	return r.configureNetNS(ctr, ctr.state.NetNS)
   213  }
   214  
   215  // Produce an InspectNetworkSettings containing information on the container
   216  // network.
   217  func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, error) {
   218  	if c.config.NetNsCtr != "" {
   219  		netNsCtr, err := c.runtime.GetContainer(c.config.NetNsCtr)
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  		// see https://github.com/containers/podman/issues/10090
   224  		// the container has to be locked for syncContainer()
   225  		netNsCtr.lock.Lock()
   226  		defer netNsCtr.lock.Unlock()
   227  		// Have to sync to ensure that state is populated
   228  		if err := netNsCtr.syncContainer(); err != nil {
   229  			return nil, err
   230  		}
   231  		logrus.Debugf("Container %s shares network namespace, retrieving network info of container %s", c.ID(), c.config.NetNsCtr)
   232  
   233  		return netNsCtr.getContainerNetworkInfo()
   234  	}
   235  
   236  	settings := new(define.InspectNetworkSettings)
   237  	settings.Ports = makeInspectPorts(c.config.PortMappings, c.config.ExposedPorts)
   238  
   239  	networks, err := c.networks()
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  
   244  	setDefaultNetworks := func() {
   245  		settings.Networks = make(map[string]*define.InspectAdditionalNetwork, 1)
   246  		name := c.NetworkMode()
   247  		addedNet := new(define.InspectAdditionalNetwork)
   248  		addedNet.NetworkID = name
   249  		settings.Networks[name] = addedNet
   250  	}
   251  
   252  	if c.state.NetNS == "" {
   253  		if networkNSPath, set := c.joinedNetworkNSPath(); networkNSPath != "" {
   254  			if result, err := c.inspectJoinedNetworkNS(networkNSPath); err == nil {
   255  				// fallback to dummy configuration
   256  				settings.InspectBasicNetworkConfig = resultToBasicNetworkConfig(result)
   257  			} else {
   258  				// do not propagate error inspecting a joined network ns
   259  				logrus.Errorf("Inspecting network namespace: %s of container %s: %v", networkNSPath, c.ID(), err)
   260  			}
   261  			return settings, nil
   262  		} else if set {
   263  			// network none case, if running allow user to join netns via sandbox key
   264  			// https://github.com/containers/podman/issues/16716
   265  			if c.state.PID > 0 {
   266  				settings.SandboxKey = fmt.Sprintf("/proc/%d/ns/net", c.state.PID)
   267  			}
   268  		}
   269  		// We can't do more if the network is down.
   270  		// We still want to make dummy configurations for each network
   271  		// the container joined.
   272  		if len(networks) > 0 {
   273  			settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks))
   274  			for net, opts := range networks {
   275  				cniNet := new(define.InspectAdditionalNetwork)
   276  				cniNet.NetworkID = net
   277  				cniNet.Aliases = opts.Aliases
   278  				settings.Networks[net] = cniNet
   279  			}
   280  		} else {
   281  			setDefaultNetworks()
   282  		}
   283  
   284  		return settings, nil
   285  	}
   286  
   287  	// Set network namespace path
   288  	settings.SandboxKey = c.state.NetNS
   289  
   290  	netStatus := c.getNetworkStatus()
   291  	// If this is empty, we're probably slirp4netns
   292  	if len(netStatus) == 0 {
   293  		return settings, nil
   294  	}
   295  
   296  	// If we have networks - handle that here
   297  	if len(networks) > 0 {
   298  		if len(networks) != len(netStatus) {
   299  			return nil, fmt.Errorf("network inspection mismatch: asked to join %d network(s) %v, but have information on %d network(s): %w", len(networks), networks, len(netStatus), define.ErrInternal)
   300  		}
   301  
   302  		settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks))
   303  
   304  		for name, opts := range networks {
   305  			result := netStatus[name]
   306  			addedNet := new(define.InspectAdditionalNetwork)
   307  			addedNet.NetworkID = name
   308  			addedNet.Aliases = opts.Aliases
   309  			addedNet.InspectBasicNetworkConfig = resultToBasicNetworkConfig(result)
   310  
   311  			settings.Networks[name] = addedNet
   312  		}
   313  
   314  		// if not only the default network is connected we can return here
   315  		// otherwise we have to populate the InspectBasicNetworkConfig settings
   316  		_, isDefaultNet := networks[c.runtime.config.Network.DefaultNetwork]
   317  		if !(len(networks) == 1 && isDefaultNet) {
   318  			return settings, nil
   319  		}
   320  	} else {
   321  		setDefaultNetworks()
   322  	}
   323  
   324  	// If not joining networks, we should have at most 1 result
   325  	if len(netStatus) > 1 {
   326  		return nil, fmt.Errorf("should have at most 1 network status result if not joining networks, instead got %d: %w", len(netStatus), define.ErrInternal)
   327  	}
   328  
   329  	if len(netStatus) == 1 {
   330  		for _, status := range netStatus {
   331  			settings.InspectBasicNetworkConfig = resultToBasicNetworkConfig(status)
   332  		}
   333  	}
   334  	return settings, nil
   335  }
   336  
   337  // resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI
   338  // result
   339  func resultToBasicNetworkConfig(result types.StatusBlock) define.InspectBasicNetworkConfig {
   340  	config := define.InspectBasicNetworkConfig{}
   341  	interfaceNames := make([]string, 0, len(result.Interfaces))
   342  	for interfaceName := range result.Interfaces {
   343  		interfaceNames = append(interfaceNames, interfaceName)
   344  	}
   345  	// ensure consistent inspect results by sorting
   346  	sort.Strings(interfaceNames)
   347  	for _, interfaceName := range interfaceNames {
   348  		netInt := result.Interfaces[interfaceName]
   349  		for _, netAddress := range netInt.Subnets {
   350  			size, _ := netAddress.IPNet.Mask.Size()
   351  			if netAddress.IPNet.IP.To4() != nil {
   352  				// ipv4
   353  				if config.IPAddress == "" {
   354  					config.IPAddress = netAddress.IPNet.IP.String()
   355  					config.IPPrefixLen = size
   356  					config.Gateway = netAddress.Gateway.String()
   357  				} else {
   358  					config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, define.Address{Addr: netAddress.IPNet.IP.String(), PrefixLength: size})
   359  				}
   360  			} else {
   361  				// ipv6
   362  				if config.GlobalIPv6Address == "" {
   363  					config.GlobalIPv6Address = netAddress.IPNet.IP.String()
   364  					config.GlobalIPv6PrefixLen = size
   365  					config.IPv6Gateway = netAddress.Gateway.String()
   366  				} else {
   367  					config.SecondaryIPv6Addresses = append(config.SecondaryIPv6Addresses, define.Address{Addr: netAddress.IPNet.IP.String(), PrefixLength: size})
   368  				}
   369  			}
   370  		}
   371  		if config.MacAddress == "" {
   372  			config.MacAddress = netInt.MacAddress.String()
   373  		} else {
   374  			config.AdditionalMacAddresses = append(config.AdditionalMacAddresses, netInt.MacAddress.String())
   375  		}
   376  	}
   377  	return config
   378  }
   379  
   380  // NetworkDisconnect removes a container from the network
   381  func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error {
   382  	// only the bridge mode supports cni networks
   383  	if err := isBridgeNetMode(c.config.NetMode); err != nil {
   384  		return err
   385  	}
   386  
   387  	c.lock.Lock()
   388  	defer c.lock.Unlock()
   389  
   390  	networks, err := c.networks()
   391  	if err != nil {
   392  		return err
   393  	}
   394  
   395  	// check if network exists and if the input is an ID we get the name
   396  	// CNI and netavark and the libpod db only uses names so it is important that we only use the name
   397  	netName, err = c.runtime.normalizeNetworkName(netName)
   398  	if err != nil {
   399  		return err
   400  	}
   401  
   402  	_, nameExists := networks[netName]
   403  	if !nameExists && len(networks) > 0 {
   404  		return fmt.Errorf("container %s is not connected to network %s", nameOrID, netName)
   405  	}
   406  
   407  	if err := c.syncContainer(); err != nil {
   408  		return err
   409  	}
   410  	// get network status before we disconnect
   411  	networkStatus := c.getNetworkStatus()
   412  
   413  	if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil {
   414  		return err
   415  	}
   416  
   417  	c.newNetworkEvent(events.NetworkDisconnect, netName)
   418  	if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
   419  		return nil
   420  	}
   421  
   422  	if c.state.NetNS == "" {
   423  		return fmt.Errorf("unable to disconnect %s from %s: %w", nameOrID, netName, define.ErrNoNetwork)
   424  	}
   425  
   426  	opts := types.NetworkOptions{
   427  		ContainerID:   c.config.ID,
   428  		ContainerName: getNetworkPodName(c),
   429  	}
   430  	opts.PortMappings = c.convertPortMappings()
   431  	opts.Networks = map[string]types.PerNetworkOptions{
   432  		netName: networks[netName],
   433  	}
   434  
   435  	if err := c.runtime.teardownNetworkBackend(c.state.NetNS, opts); err != nil {
   436  		return err
   437  	}
   438  
   439  	// update network status if container is running
   440  	oldStatus, statusExist := networkStatus[netName]
   441  	delete(networkStatus, netName)
   442  	c.state.NetworkStatus = networkStatus
   443  	err = c.save()
   444  	if err != nil {
   445  		return err
   446  	}
   447  
   448  	// Reload ports when there are still connected networks, maybe we removed the network interface with the child ip.
   449  	// Reloading without connected networks does not make sense, so we can skip this step.
   450  	if rootless.IsRootless() && len(networkStatus) > 0 {
   451  		if err := c.reloadRootlessRLKPortMapping(); err != nil {
   452  			return err
   453  		}
   454  	}
   455  
   456  	// Update resolv.conf if required
   457  	if statusExist {
   458  		stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs))
   459  		for _, ip := range oldStatus.DNSServerIPs {
   460  			stringIPs = append(stringIPs, ip.String())
   461  		}
   462  		if len(stringIPs) > 0 {
   463  			logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs)
   464  			if err := c.removeNameserver(stringIPs); err != nil {
   465  				return err
   466  			}
   467  		}
   468  
   469  		// update /etc/hosts file
   470  		if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
   471  			// sync the names with c.getHostsEntries()
   472  			names := []string{c.Hostname(), c.config.Name}
   473  			rm := etchosts.GetNetworkHostEntries(map[string]types.StatusBlock{netName: oldStatus}, names...)
   474  			if len(rm) > 0 {
   475  				// make sure to lock this file to prevent concurrent writes when
   476  				// this is used a net dependency container
   477  				lock, err := lockfile.GetLockFile(file)
   478  				if err != nil {
   479  					return fmt.Errorf("failed to lock hosts file: %w", err)
   480  				}
   481  				logrus.Debugf("Remove /etc/hosts entries %v", rm)
   482  				lock.Lock()
   483  				err = etchosts.Remove(file, rm)
   484  				lock.Unlock()
   485  				if err != nil {
   486  					return err
   487  				}
   488  			}
   489  		}
   490  	}
   491  	return nil
   492  }
   493  
   494  // ConnectNetwork connects a container to a given network
   495  func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
   496  	// only the bridge mode supports networks
   497  	if err := isBridgeNetMode(c.config.NetMode); err != nil {
   498  		return err
   499  	}
   500  
   501  	c.lock.Lock()
   502  	defer c.lock.Unlock()
   503  
   504  	networks, err := c.networks()
   505  	if err != nil {
   506  		return err
   507  	}
   508  
   509  	// check if network exists and if the input is an ID we get the name
   510  	// CNI and netavark and the libpod db only uses names so it is important that we only use the name
   511  	netName, err = c.runtime.normalizeNetworkName(netName)
   512  	if err != nil {
   513  		return err
   514  	}
   515  
   516  	if err := c.syncContainer(); err != nil {
   517  		return err
   518  	}
   519  
   520  	// get network status before we connect
   521  	networkStatus := c.getNetworkStatus()
   522  
   523  	netOpts.Aliases = append(netOpts.Aliases, getExtraNetworkAliases(c)...)
   524  
   525  	if netOpts.InterfaceName == "" {
   526  		netOpts.InterfaceName = getFreeInterfaceName(networks)
   527  		if netOpts.InterfaceName == "" {
   528  			return errors.New("could not find free network interface name")
   529  		}
   530  	}
   531  
   532  	if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil {
   533  		// Docker compat: treat requests to attach already attached networks as a no-op, ignoring opts
   534  		if errors.Is(err, define.ErrNetworkConnected) && !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
   535  			return nil
   536  		}
   537  
   538  		return err
   539  	}
   540  	c.newNetworkEvent(events.NetworkConnect, netName)
   541  	if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) {
   542  		return nil
   543  	}
   544  	if c.state.NetNS == "" {
   545  		return fmt.Errorf("unable to connect %s to %s: %w", nameOrID, netName, define.ErrNoNetwork)
   546  	}
   547  
   548  	opts := types.NetworkOptions{
   549  		ContainerID:   c.config.ID,
   550  		ContainerName: getNetworkPodName(c),
   551  	}
   552  	opts.PortMappings = c.convertPortMappings()
   553  	opts.Networks = map[string]types.PerNetworkOptions{
   554  		netName: netOpts,
   555  	}
   556  
   557  	results, err := c.runtime.setUpNetwork(c.state.NetNS, opts)
   558  	if err != nil {
   559  		return err
   560  	}
   561  	if len(results) != 1 {
   562  		return errors.New("when adding aliases, results must be of length 1")
   563  	}
   564  
   565  	// we need to get the old host entries before we add the new one to the status
   566  	// if we do not add do it here we will get the wrong existing entries which will throw of the logic
   567  	// we could also copy the map but this does not seem worth it
   568  	// sync the hostNames with c.getHostsEntries()
   569  	hostNames := []string{c.Hostname(), c.config.Name}
   570  	oldHostEntries := etchosts.GetNetworkHostEntries(networkStatus, hostNames...)
   571  
   572  	// update network status
   573  	if networkStatus == nil {
   574  		networkStatus = make(map[string]types.StatusBlock, 1)
   575  	}
   576  	networkStatus[netName] = results[netName]
   577  	c.state.NetworkStatus = networkStatus
   578  
   579  	err = c.save()
   580  	if err != nil {
   581  		return err
   582  	}
   583  
   584  	// The first network needs a port reload to set the correct child ip for the rootlessport process.
   585  	// Adding a second network does not require a port reload because the child ip is still valid.
   586  	if rootless.IsRootless() && len(networks) == 0 {
   587  		if err := c.reloadRootlessRLKPortMapping(); err != nil {
   588  			return err
   589  		}
   590  	}
   591  
   592  	ipv6 := c.checkForIPv6(networkStatus)
   593  
   594  	// Update resolv.conf if required
   595  	stringIPs := make([]string, 0, len(results[netName].DNSServerIPs))
   596  	for _, ip := range results[netName].DNSServerIPs {
   597  		if (ip.To4() == nil) && !ipv6 {
   598  			continue
   599  		}
   600  		stringIPs = append(stringIPs, ip.String())
   601  	}
   602  	if len(stringIPs) > 0 {
   603  		logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs)
   604  		if err := c.addNameserver(stringIPs); err != nil {
   605  			return err
   606  		}
   607  	}
   608  
   609  	// update /etc/hosts file
   610  	if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok {
   611  		// make sure to lock this file to prevent concurrent writes when
   612  		// this is used a net dependency container
   613  		lock, err := lockfile.GetLockFile(file)
   614  		if err != nil {
   615  			return fmt.Errorf("failed to lock hosts file: %w", err)
   616  		}
   617  		new := etchosts.GetNetworkHostEntries(results, hostNames...)
   618  		logrus.Debugf("Add /etc/hosts entries %v", new)
   619  		// use special AddIfExists API to make sure we only add new entries if an old one exists
   620  		// see the AddIfExists() comment for more information
   621  		lock.Lock()
   622  		err = etchosts.AddIfExists(file, oldHostEntries, new)
   623  		lock.Unlock()
   624  		if err != nil {
   625  			return err
   626  		}
   627  	}
   628  
   629  	return nil
   630  }
   631  
   632  // get a free interface name for a new network
   633  // return an empty string if no free name was found
   634  func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string {
   635  	ifNames := make([]string, 0, len(networks))
   636  	for _, opts := range networks {
   637  		ifNames = append(ifNames, opts.InterfaceName)
   638  	}
   639  	for i := 0; i < 100000; i++ {
   640  		ifName := fmt.Sprintf("eth%d", i)
   641  		if !util.StringInSlice(ifName, ifNames) {
   642  			return ifName
   643  		}
   644  	}
   645  	return ""
   646  }
   647  
   648  func getExtraNetworkAliases(c *Container) []string {
   649  	// always add the short id as alias for docker compat
   650  	alias := []string{c.config.ID[:12]}
   651  	// if an explicit hostname was set add it as well
   652  	if c.config.Spec.Hostname != "" {
   653  		alias = append(alias, c.config.Spec.Hostname)
   654  	}
   655  	return alias
   656  }
   657  
   658  // DisconnectContainerFromNetwork removes a container from its network
   659  func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
   660  	ctr, err := r.LookupContainer(nameOrID)
   661  	if err != nil {
   662  		return err
   663  	}
   664  	return ctr.NetworkDisconnect(nameOrID, netName, force)
   665  }
   666  
   667  // ConnectContainerToNetwork connects a container to a network
   668  func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error {
   669  	ctr, err := r.LookupContainer(nameOrID)
   670  	if err != nil {
   671  		return err
   672  	}
   673  	return ctr.NetworkConnect(nameOrID, netName, netOpts)
   674  }
   675  
   676  // normalizeNetworkName takes a network name, a partial or a full network ID and returns the network name.
   677  // If the network is not found an error is returned.
   678  func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) {
   679  	net, err := r.network.NetworkInspect(nameOrID)
   680  	if err != nil {
   681  		return "", err
   682  	}
   683  	return net.Name, nil
   684  }
   685  
   686  // ocicniPortsToNetTypesPorts convert the old port format to the new one
   687  // while deduplicating ports into ranges
   688  func ocicniPortsToNetTypesPorts(ports []types.OCICNIPortMapping) []types.PortMapping {
   689  	if len(ports) == 0 {
   690  		return nil
   691  	}
   692  
   693  	newPorts := make([]types.PortMapping, 0, len(ports))
   694  
   695  	// first sort the ports
   696  	sort.Slice(ports, func(i, j int) bool {
   697  		return compareOCICNIPorts(ports[i], ports[j])
   698  	})
   699  
   700  	// we already check if the slice is empty so we can use the first element
   701  	currentPort := types.PortMapping{
   702  		HostIP:        ports[0].HostIP,
   703  		HostPort:      uint16(ports[0].HostPort),
   704  		ContainerPort: uint16(ports[0].ContainerPort),
   705  		Protocol:      ports[0].Protocol,
   706  		Range:         1,
   707  	}
   708  
   709  	for i := 1; i < len(ports); i++ {
   710  		if ports[i].HostIP == currentPort.HostIP &&
   711  			ports[i].Protocol == currentPort.Protocol &&
   712  			ports[i].HostPort-int32(currentPort.Range) == int32(currentPort.HostPort) &&
   713  			ports[i].ContainerPort-int32(currentPort.Range) == int32(currentPort.ContainerPort) {
   714  			currentPort.Range++
   715  		} else {
   716  			newPorts = append(newPorts, currentPort)
   717  			currentPort = types.PortMapping{
   718  				HostIP:        ports[i].HostIP,
   719  				HostPort:      uint16(ports[i].HostPort),
   720  				ContainerPort: uint16(ports[i].ContainerPort),
   721  				Protocol:      ports[i].Protocol,
   722  				Range:         1,
   723  			}
   724  		}
   725  	}
   726  	newPorts = append(newPorts, currentPort)
   727  	return newPorts
   728  }
   729  
   730  // compareOCICNIPorts will sort the ocicni ports by
   731  // 1) host ip
   732  // 2) protocol
   733  // 3) hostPort
   734  // 4) container port
   735  func compareOCICNIPorts(i, j types.OCICNIPortMapping) bool {
   736  	if i.HostIP != j.HostIP {
   737  		return i.HostIP < j.HostIP
   738  	}
   739  
   740  	if i.Protocol != j.Protocol {
   741  		return i.Protocol < j.Protocol
   742  	}
   743  
   744  	if i.HostPort != j.HostPort {
   745  		return i.HostPort < j.HostPort
   746  	}
   747  
   748  	return i.ContainerPort < j.ContainerPort
   749  }