github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/networking_linux.go (about)

     1  // +build linux
     2  
     3  package libpod
     4  
     5  import (
     6  	"bytes"
     7  	"crypto/rand"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strings"
    16  	"syscall"
    17  	"time"
    18  
    19  	cnitypes "github.com/containernetworking/cni/pkg/types/current"
    20  	"github.com/containernetworking/plugins/pkg/ns"
    21  	"github.com/containers/libpod/libpod/define"
    22  	"github.com/containers/libpod/pkg/errorhandling"
    23  	"github.com/containers/libpod/pkg/netns"
    24  	"github.com/containers/libpod/pkg/rootless"
    25  	"github.com/containers/libpod/pkg/rootlessport"
    26  	"github.com/cri-o/ocicni/pkg/ocicni"
    27  	"github.com/pkg/errors"
    28  	"github.com/sirupsen/logrus"
    29  	"github.com/vishvananda/netlink"
    30  	"golang.org/x/sys/unix"
    31  )
    32  
    33  // Get an OCICNI network config
    34  func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP, staticMAC net.HardwareAddr) ocicni.PodNetwork {
    35  	var networkKey string
    36  	if len(networks) > 0 {
    37  		// This is inconsistent for >1 network, but it's probably the
    38  		// best we can do.
    39  		networkKey = networks[0]
    40  	} else {
    41  		networkKey = r.netPlugin.GetDefaultNetworkName()
    42  	}
    43  	network := ocicni.PodNetwork{
    44  		Name:      name,
    45  		Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces
    46  		ID:        id,
    47  		NetNS:     nsPath,
    48  		RuntimeConfig: map[string]ocicni.RuntimeConfig{
    49  			networkKey: {PortMappings: ports},
    50  		},
    51  	}
    52  
    53  	// If we have extra networks, add them
    54  	if len(networks) > 0 {
    55  		network.Networks = make([]ocicni.NetAttachment, len(networks))
    56  		for i, netName := range networks {
    57  			network.Networks[i].Name = netName
    58  		}
    59  	}
    60  
    61  	if staticIP != nil || staticMAC != nil {
    62  		// For static IP or MAC, we need to populate networks even if
    63  		// it's just the default.
    64  		if len(networks) == 0 {
    65  			// If len(networks) == 0 this is guaranteed to be the
    66  			// default network.
    67  			network.Networks = []ocicni.NetAttachment{{Name: networkKey}}
    68  		}
    69  		var rt ocicni.RuntimeConfig = ocicni.RuntimeConfig{PortMappings: ports}
    70  		if staticIP != nil {
    71  			rt.IP = staticIP.String()
    72  		}
    73  		if staticMAC != nil {
    74  			rt.MAC = staticMAC.String()
    75  		}
    76  		network.RuntimeConfig = map[string]ocicni.RuntimeConfig{
    77  			networkKey: rt,
    78  		}
    79  	}
    80  
    81  	return network
    82  }
    83  
    84  // Create and configure a new network namespace for a container
    85  func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Result, error) {
    86  	var requestedIP net.IP
    87  	if ctr.requestedIP != nil {
    88  		requestedIP = ctr.requestedIP
    89  		// cancel request for a specific IP in case the container is reused later
    90  		ctr.requestedIP = nil
    91  	} else {
    92  		requestedIP = ctr.config.StaticIP
    93  	}
    94  
    95  	var requestedMAC net.HardwareAddr
    96  	if ctr.requestedMAC != nil {
    97  		requestedMAC = ctr.requestedMAC
    98  		// cancel request for a specific MAC in case the container is reused later
    99  		ctr.requestedMAC = nil
   100  	} else {
   101  		requestedMAC = ctr.config.StaticMAC
   102  	}
   103  
   104  	podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC)
   105  
   106  	results, err := r.netPlugin.SetUpPod(podNetwork)
   107  	if err != nil {
   108  		return nil, errors.Wrapf(err, "error configuring network namespace for container %s", ctr.ID())
   109  	}
   110  	defer func() {
   111  		if err != nil {
   112  			if err2 := r.netPlugin.TearDownPod(podNetwork); err2 != nil {
   113  				logrus.Errorf("Error tearing down partially created network namespace for container %s: %v", ctr.ID(), err2)
   114  			}
   115  		}
   116  	}()
   117  
   118  	networkStatus := make([]*cnitypes.Result, 0)
   119  	for idx, r := range results {
   120  		logrus.Debugf("[%d] CNI result: %v", idx, r.Result)
   121  		resultCurrent, err := cnitypes.GetResult(r.Result)
   122  		if err != nil {
   123  			return nil, errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.Result, err)
   124  		}
   125  		networkStatus = append(networkStatus, resultCurrent)
   126  	}
   127  
   128  	return networkStatus, nil
   129  }
   130  
   131  // Create and configure a new network namespace for a container
   132  func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, err error) {
   133  	ctrNS, err := netns.NewNS()
   134  	if err != nil {
   135  		return nil, nil, errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID())
   136  	}
   137  	defer func() {
   138  		if err != nil {
   139  			if err2 := netns.UnmountNS(ctrNS); err2 != nil {
   140  				logrus.Errorf("Error unmounting partially created network namespace for container %s: %v", ctr.ID(), err2)
   141  			}
   142  			if err2 := ctrNS.Close(); err2 != nil {
   143  				logrus.Errorf("Error closing partially created network namespace for container %s: %v", ctr.ID(), err2)
   144  			}
   145  		}
   146  	}()
   147  
   148  	logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID())
   149  
   150  	networkStatus := []*cnitypes.Result{}
   151  	if !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() {
   152  		networkStatus, err = r.configureNetNS(ctr, ctrNS)
   153  	}
   154  	return ctrNS, networkStatus, err
   155  }
   156  
   157  type slirpFeatures struct {
   158  	HasDisableHostLoopback bool
   159  	HasMTU                 bool
   160  	HasEnableSandbox       bool
   161  	HasEnableSeccomp       bool
   162  }
   163  
   164  func checkSlirpFlags(path string) (*slirpFeatures, error) {
   165  	cmd := exec.Command(path, "--help")
   166  	out, err := cmd.CombinedOutput()
   167  	if err != nil {
   168  		return nil, errors.Wrapf(err, "slirp4netns %q", out)
   169  	}
   170  	return &slirpFeatures{
   171  		HasDisableHostLoopback: strings.Contains(string(out), "--disable-host-loopback"),
   172  		HasMTU:                 strings.Contains(string(out), "--mtu"),
   173  		HasEnableSandbox:       strings.Contains(string(out), "--enable-sandbox"),
   174  		HasEnableSeccomp:       strings.Contains(string(out), "--enable-seccomp"),
   175  	}, nil
   176  }
   177  
   178  // Configure the network namespace for a rootless container
   179  func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
   180  	path := r.config.Engine.NetworkCmdPath
   181  
   182  	if path == "" {
   183  		var err error
   184  		path, err = exec.LookPath("slirp4netns")
   185  		if err != nil {
   186  			logrus.Errorf("could not find slirp4netns, the network namespace won't be configured: %v", err)
   187  			return nil
   188  		}
   189  	}
   190  
   191  	syncR, syncW, err := os.Pipe()
   192  	if err != nil {
   193  		return errors.Wrapf(err, "failed to open pipe")
   194  	}
   195  	defer errorhandling.CloseQuiet(syncR)
   196  	defer errorhandling.CloseQuiet(syncW)
   197  
   198  	havePortMapping := len(ctr.Config().PortMappings) > 0
   199  	logPath := filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("slirp4netns-%s.log", ctr.config.ID))
   200  
   201  	cmdArgs := []string{}
   202  	slirpFeatures, err := checkSlirpFlags(path)
   203  	if err != nil {
   204  		return errors.Wrapf(err, "error checking slirp4netns binary %s: %q", path, err)
   205  	}
   206  	if slirpFeatures.HasDisableHostLoopback {
   207  		cmdArgs = append(cmdArgs, "--disable-host-loopback")
   208  	}
   209  	if slirpFeatures.HasMTU {
   210  		cmdArgs = append(cmdArgs, "--mtu", "65520")
   211  	}
   212  	if slirpFeatures.HasEnableSandbox {
   213  		cmdArgs = append(cmdArgs, "--enable-sandbox")
   214  	}
   215  	if slirpFeatures.HasEnableSeccomp {
   216  		cmdArgs = append(cmdArgs, "--enable-seccomp")
   217  	}
   218  
   219  	// the slirp4netns arguments being passed are describes as follows:
   220  	// from the slirp4netns documentation: https://github.com/rootless-containers/slirp4netns
   221  	// -c, --configure Brings up the tap interface
   222  	// -e, --exit-fd=FD specify the FD for terminating slirp4netns
   223  	// -r, --ready-fd=FD specify the FD to write to when the initialization steps are finished
   224  	cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4")
   225  	netnsPath := ""
   226  	if !ctr.config.PostConfigureNetNS {
   227  		ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
   228  		if err != nil {
   229  			return errors.Wrapf(err, "failed to create rootless network sync pipe")
   230  		}
   231  		netnsPath = ctr.state.NetNS.Path()
   232  		cmdArgs = append(cmdArgs, "--netns-type=path", netnsPath, "tap0")
   233  	} else {
   234  		defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR)
   235  		defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW)
   236  		netnsPath = fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID)
   237  		// we don't use --netns-path here (unavailable for slirp4netns < v0.4)
   238  		cmdArgs = append(cmdArgs, fmt.Sprintf("%d", ctr.state.PID), "tap0")
   239  	}
   240  
   241  	cmd := exec.Command(path, cmdArgs...)
   242  	logrus.Debugf("slirp4netns command: %s", strings.Join(cmd.Args, " "))
   243  	cmd.SysProcAttr = &syscall.SysProcAttr{
   244  		Setpgid: true,
   245  	}
   246  
   247  	// workaround for https://github.com/rootless-containers/slirp4netns/pull/153
   248  	if slirpFeatures.HasEnableSandbox {
   249  		cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWNS
   250  		cmd.SysProcAttr.Unshareflags = syscall.CLONE_NEWNS
   251  	}
   252  
   253  	// Leak one end of the pipe in slirp4netns, the other will be sent to conmon
   254  	cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncR, syncW)
   255  
   256  	logFile, err := os.Create(logPath)
   257  	if err != nil {
   258  		return errors.Wrapf(err, "failed to open slirp4netns log file %s", logPath)
   259  	}
   260  	defer logFile.Close()
   261  	// Unlink immediately the file so we won't need to worry about cleaning it up later.
   262  	// It is still accessible through the open fd logFile.
   263  	if err := os.Remove(logPath); err != nil {
   264  		return errors.Wrapf(err, "delete file %s", logPath)
   265  	}
   266  	cmd.Stdout = logFile
   267  	cmd.Stderr = logFile
   268  	if err := cmd.Start(); err != nil {
   269  		return errors.Wrapf(err, "failed to start slirp4netns process")
   270  	}
   271  	defer func() {
   272  		if err := cmd.Process.Release(); err != nil {
   273  			logrus.Errorf("unable to release command process: %q", err)
   274  		}
   275  	}()
   276  
   277  	if err := waitForSync(syncR, cmd, logFile, 1*time.Second); err != nil {
   278  		return err
   279  	}
   280  
   281  	if havePortMapping {
   282  		return r.setupRootlessPortMapping(ctr, netnsPath)
   283  	}
   284  	return nil
   285  }
   286  
   287  func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout time.Duration) error {
   288  	prog := filepath.Base(cmd.Path)
   289  	if len(cmd.Args) > 0 {
   290  		prog = cmd.Args[0]
   291  	}
   292  	b := make([]byte, 16)
   293  	for {
   294  		if err := syncR.SetDeadline(time.Now().Add(timeout)); err != nil {
   295  			return errors.Wrapf(err, "error setting %s pipe timeout", prog)
   296  		}
   297  		// FIXME: return err as soon as proc exits, without waiting for timeout
   298  		if _, err := syncR.Read(b); err == nil {
   299  			break
   300  		} else {
   301  			if os.IsTimeout(err) {
   302  				// Check if the process is still running.
   303  				var status syscall.WaitStatus
   304  				pid, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil)
   305  				if err != nil {
   306  					return errors.Wrapf(err, "failed to read %s process status", prog)
   307  				}
   308  				if pid != cmd.Process.Pid {
   309  					continue
   310  				}
   311  				if status.Exited() {
   312  					// Seek at the beginning of the file and read all its content
   313  					if _, err := logFile.Seek(0, 0); err != nil {
   314  						logrus.Errorf("could not seek log file: %q", err)
   315  					}
   316  					logContent, err := ioutil.ReadAll(logFile)
   317  					if err != nil {
   318  						return errors.Wrapf(err, "%s failed", prog)
   319  					}
   320  					return errors.Errorf("%s failed: %q", prog, logContent)
   321  				}
   322  				if status.Signaled() {
   323  					return errors.Errorf("%s killed by signal", prog)
   324  				}
   325  				continue
   326  			}
   327  			return errors.Wrapf(err, "failed to read from %s sync pipe", prog)
   328  		}
   329  	}
   330  	return nil
   331  }
   332  
   333  func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) (err error) {
   334  	syncR, syncW, err := os.Pipe()
   335  	if err != nil {
   336  		return errors.Wrapf(err, "failed to open pipe")
   337  	}
   338  	defer errorhandling.CloseQuiet(syncR)
   339  	defer errorhandling.CloseQuiet(syncW)
   340  
   341  	logPath := filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("rootlessport-%s.log", ctr.config.ID))
   342  	logFile, err := os.Create(logPath)
   343  	if err != nil {
   344  		return errors.Wrapf(err, "failed to open rootlessport log file %s", logPath)
   345  	}
   346  	defer logFile.Close()
   347  	// Unlink immediately the file so we won't need to worry about cleaning it up later.
   348  	// It is still accessible through the open fd logFile.
   349  	if err := os.Remove(logPath); err != nil {
   350  		return errors.Wrapf(err, "delete file %s", logPath)
   351  	}
   352  
   353  	if !ctr.config.PostConfigureNetNS {
   354  		ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe()
   355  		if err != nil {
   356  			return errors.Wrapf(err, "failed to create rootless port sync pipe")
   357  		}
   358  	}
   359  
   360  	cfg := rootlessport.Config{
   361  		Mappings:  ctr.config.PortMappings,
   362  		NetNSPath: netnsPath,
   363  		ExitFD:    3,
   364  		ReadyFD:   4,
   365  		TmpDir:    ctr.runtime.config.Engine.TmpDir,
   366  	}
   367  	cfgJSON, err := json.Marshal(cfg)
   368  	if err != nil {
   369  		return err
   370  	}
   371  	cfgR := bytes.NewReader(cfgJSON)
   372  	var stdout bytes.Buffer
   373  	cmd := exec.Command(fmt.Sprintf("/proc/%d/exe", os.Getpid()))
   374  	cmd.Args = []string{rootlessport.ReexecKey}
   375  	// Leak one end of the pipe in rootlessport process, the other will be sent to conmon
   376  
   377  	if ctr.rootlessPortSyncR != nil {
   378  		defer errorhandling.CloseQuiet(ctr.rootlessPortSyncR)
   379  	}
   380  
   381  	cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncR, syncW)
   382  	cmd.Stdin = cfgR
   383  	// stdout is for human-readable error, stderr is for debug log
   384  	cmd.Stdout = &stdout
   385  	cmd.Stderr = io.MultiWriter(logFile, &logrusDebugWriter{"rootlessport: "})
   386  	cmd.SysProcAttr = &syscall.SysProcAttr{
   387  		Setpgid: true,
   388  	}
   389  	if err := cmd.Start(); err != nil {
   390  		return errors.Wrapf(err, "failed to start rootlessport process")
   391  	}
   392  	defer func() {
   393  		if err := cmd.Process.Release(); err != nil {
   394  			logrus.Errorf("unable to release rootlessport process: %q", err)
   395  		}
   396  	}()
   397  	if err := waitForSync(syncR, cmd, logFile, 3*time.Second); err != nil {
   398  		stdoutStr := stdout.String()
   399  		if stdoutStr != "" {
   400  			// err contains full debug log and too verbose, so return stdoutStr
   401  			logrus.Debug(err)
   402  			return errors.Errorf("failed to expose ports via rootlessport: %q", stdoutStr)
   403  		}
   404  		return err
   405  	}
   406  	logrus.Debug("rootlessport is ready")
   407  	return nil
   408  }
   409  
   410  // Configure the network namespace using the container process
   411  func (r *Runtime) setupNetNS(ctr *Container) (err error) {
   412  	nsProcess := fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID)
   413  
   414  	b := make([]byte, 16)
   415  
   416  	if _, err := rand.Reader.Read(b); err != nil {
   417  		return errors.Wrapf(err, "failed to generate random netns name")
   418  	}
   419  
   420  	nsPath := fmt.Sprintf("/var/run/netns/cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
   421  
   422  	if err := os.MkdirAll(filepath.Dir(nsPath), 0711); err != nil {
   423  		return errors.Wrapf(err, "cannot create %s", filepath.Dir(nsPath))
   424  	}
   425  
   426  	mountPointFd, err := os.Create(nsPath)
   427  	if err != nil {
   428  		return errors.Wrapf(err, "cannot open %s", nsPath)
   429  	}
   430  	if err := mountPointFd.Close(); err != nil {
   431  		return err
   432  	}
   433  
   434  	if err := unix.Mount(nsProcess, nsPath, "none", unix.MS_BIND, ""); err != nil {
   435  		return errors.Wrapf(err, "cannot mount %s", nsPath)
   436  	}
   437  
   438  	netNS, err := ns.GetNS(nsPath)
   439  	if err != nil {
   440  		return err
   441  	}
   442  	networkStatus, err := r.configureNetNS(ctr, netNS)
   443  
   444  	// Assign NetNS attributes to container
   445  	ctr.state.NetNS = netNS
   446  	ctr.state.NetworkStatus = networkStatus
   447  	return err
   448  }
   449  
   450  // Join an existing network namespace
   451  func joinNetNS(path string) (ns.NetNS, error) {
   452  	netNS, err := ns.GetNS(path)
   453  	if err != nil {
   454  		return nil, errors.Wrapf(err, "error retrieving network namespace at %s", path)
   455  	}
   456  
   457  	return netNS, nil
   458  }
   459  
   460  // Close a network namespace.
   461  // Differs from teardownNetNS() in that it will not attempt to undo the setup of
   462  // the namespace, but will instead only close the open file descriptor
   463  func (r *Runtime) closeNetNS(ctr *Container) error {
   464  	if ctr.state.NetNS == nil {
   465  		// The container has no network namespace, we're set
   466  		return nil
   467  	}
   468  
   469  	if err := ctr.state.NetNS.Close(); err != nil {
   470  		return errors.Wrapf(err, "error closing network namespace for container %s", ctr.ID())
   471  	}
   472  
   473  	ctr.state.NetNS = nil
   474  
   475  	return nil
   476  }
   477  
   478  // Tear down a network namespace, undoing all state associated with it.
   479  func (r *Runtime) teardownNetNS(ctr *Container) error {
   480  	if ctr.state.NetNS == nil {
   481  		// The container has no network namespace, we're set
   482  		return nil
   483  	}
   484  
   485  	logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID())
   486  
   487  	// rootless containers do not use the CNI plugin
   488  	if !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() {
   489  		var requestedIP net.IP
   490  		if ctr.requestedIP != nil {
   491  			requestedIP = ctr.requestedIP
   492  			// cancel request for a specific IP in case the container is reused later
   493  			ctr.requestedIP = nil
   494  		} else {
   495  			requestedIP = ctr.config.StaticIP
   496  		}
   497  
   498  		var requestedMAC net.HardwareAddr
   499  		if ctr.requestedMAC != nil {
   500  			requestedMAC = ctr.requestedMAC
   501  			// cancel request for a specific MAC in case the container is reused later
   502  			ctr.requestedMAC = nil
   503  		} else {
   504  			requestedMAC = ctr.config.StaticMAC
   505  		}
   506  
   507  		podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC)
   508  
   509  		if err := r.netPlugin.TearDownPod(podNetwork); err != nil {
   510  			return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID())
   511  		}
   512  	}
   513  
   514  	// First unmount the namespace
   515  	if err := netns.UnmountNS(ctr.state.NetNS); err != nil {
   516  		return errors.Wrapf(err, "error unmounting network namespace for container %s", ctr.ID())
   517  	}
   518  
   519  	// Now close the open file descriptor
   520  	if err := ctr.state.NetNS.Close(); err != nil {
   521  		return errors.Wrapf(err, "error closing network namespace for container %s", ctr.ID())
   522  	}
   523  
   524  	ctr.state.NetNS = nil
   525  
   526  	return nil
   527  }
   528  
   529  func getContainerNetNS(ctr *Container) (string, error) {
   530  	if ctr.state.NetNS != nil {
   531  		return ctr.state.NetNS.Path(), nil
   532  	}
   533  	if ctr.config.NetNsCtr != "" {
   534  		c, err := ctr.runtime.GetContainer(ctr.config.NetNsCtr)
   535  		if err != nil {
   536  			return "", err
   537  		}
   538  		if err = c.syncContainer(); err != nil {
   539  			return "", err
   540  		}
   541  		return c.state.NetNS.Path(), nil
   542  	}
   543  	return "", nil
   544  }
   545  
   546  func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) {
   547  	var netStats *netlink.LinkStatistics
   548  	// rootless v2 cannot seem to resolve its network connection to
   549  	// collect statistics.  For now, we allow stats to at least run
   550  	// by returning nil
   551  	if rootless.IsRootless() {
   552  		return netStats, nil
   553  	}
   554  	netNSPath, netPathErr := getContainerNetNS(ctr)
   555  	if netPathErr != nil {
   556  		return nil, netPathErr
   557  	}
   558  	if netNSPath == "" {
   559  		// If netNSPath is empty, it was set as none, and no netNS was set up
   560  		// this is a valid state and thus return no error, nor any statistics
   561  		return nil, nil
   562  	}
   563  	err := ns.WithNetNSPath(netNSPath, func(_ ns.NetNS) error {
   564  		link, err := netlink.LinkByName(ocicni.DefaultInterfaceName)
   565  		if err != nil {
   566  			return err
   567  		}
   568  		netStats = link.Attrs().Statistics
   569  		return nil
   570  	})
   571  	return netStats, err
   572  }
   573  
   574  // Produce an InspectNetworkSettings containing information on the container
   575  // network.
   576  func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, error) {
   577  	settings := new(define.InspectNetworkSettings)
   578  	settings.Ports = []ocicni.PortMapping{}
   579  	if c.config.PortMappings != nil {
   580  		// TODO: This may not be safe.
   581  		settings.Ports = c.config.PortMappings
   582  	}
   583  
   584  	// We can't do more if the network is down.
   585  	if c.state.NetNS == nil {
   586  		return settings, nil
   587  	}
   588  
   589  	// Set network namespace path
   590  	settings.SandboxKey = c.state.NetNS.Path()
   591  
   592  	// If this is empty, we're probably slirp4netns
   593  	if len(c.state.NetworkStatus) == 0 {
   594  		return settings, nil
   595  	}
   596  
   597  	// If we have CNI networks - handle that here
   598  	if len(c.config.Networks) > 0 {
   599  		if len(c.config.Networks) != len(c.state.NetworkStatus) {
   600  			return nil, errors.Wrapf(define.ErrInternal, "network inspection mismatch: asked to join %d CNI networks but have information on %d networks", len(c.config.Networks), len(c.state.NetworkStatus))
   601  		}
   602  
   603  		settings.Networks = make(map[string]*define.InspectAdditionalNetwork)
   604  
   605  		// CNI results should be in the same order as the list of
   606  		// networks we pass into CNI.
   607  		for index, name := range c.config.Networks {
   608  			cniResult := c.state.NetworkStatus[index]
   609  			addedNet := new(define.InspectAdditionalNetwork)
   610  			addedNet.NetworkID = name
   611  
   612  			basicConfig, err := resultToBasicNetworkConfig(cniResult)
   613  			if err != nil {
   614  				return nil, err
   615  			}
   616  			addedNet.InspectBasicNetworkConfig = basicConfig
   617  
   618  			settings.Networks[name] = addedNet
   619  		}
   620  
   621  		return settings, nil
   622  	}
   623  
   624  	// If not joining networks, we should have at most 1 result
   625  	if len(c.state.NetworkStatus) > 1 {
   626  		return nil, errors.Wrapf(define.ErrInternal, "should have at most 1 CNI result if not joining networks, instead got %d", len(c.state.NetworkStatus))
   627  	}
   628  
   629  	if len(c.state.NetworkStatus) == 1 {
   630  		basicConfig, err := resultToBasicNetworkConfig(c.state.NetworkStatus[0])
   631  		if err != nil {
   632  			return nil, err
   633  		}
   634  
   635  		settings.InspectBasicNetworkConfig = basicConfig
   636  	}
   637  
   638  	return settings, nil
   639  }
   640  
   641  // resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI
   642  // result
   643  func resultToBasicNetworkConfig(result *cnitypes.Result) (define.InspectBasicNetworkConfig, error) {
   644  	config := define.InspectBasicNetworkConfig{}
   645  
   646  	for _, ctrIP := range result.IPs {
   647  		size, _ := ctrIP.Address.Mask.Size()
   648  		switch {
   649  		case ctrIP.Version == "4" && config.IPAddress == "":
   650  			config.IPAddress = ctrIP.Address.IP.String()
   651  			config.IPPrefixLen = size
   652  			config.Gateway = ctrIP.Gateway.String()
   653  			if ctrIP.Interface != nil && *ctrIP.Interface < len(result.Interfaces) && *ctrIP.Interface > 0 {
   654  				config.MacAddress = result.Interfaces[*ctrIP.Interface].Mac
   655  			}
   656  		case ctrIP.Version == "4" && config.IPAddress != "":
   657  			config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, ctrIP.Address.String())
   658  			if ctrIP.Interface != nil && *ctrIP.Interface < len(result.Interfaces) && *ctrIP.Interface > 0 {
   659  				config.AdditionalMacAddresses = append(config.AdditionalMacAddresses, result.Interfaces[*ctrIP.Interface].Mac)
   660  			}
   661  		case ctrIP.Version == "6" && config.IPAddress == "":
   662  			config.GlobalIPv6Address = ctrIP.Address.IP.String()
   663  			config.GlobalIPv6PrefixLen = size
   664  			config.IPv6Gateway = ctrIP.Gateway.String()
   665  		case ctrIP.Version == "6" && config.IPAddress != "":
   666  			config.SecondaryIPv6Addresses = append(config.SecondaryIPv6Addresses, ctrIP.Address.String())
   667  		default:
   668  			return config, errors.Wrapf(define.ErrInternal, "unrecognized IP version %q", ctrIP.Version)
   669  		}
   670  	}
   671  
   672  	return config, nil
   673  }
   674  
   675  // This is a horrible hack, necessary because CNI does not properly clean up
   676  // after itself on an unclean reboot. Return what we're pretty sure is the path
   677  // to CNI's internal files (it's not really exposed to us).
   678  func getCNINetworksDir() (string, error) {
   679  	return "/var/lib/cni/networks", nil
   680  }
   681  
   682  type logrusDebugWriter struct {
   683  	prefix string
   684  }
   685  
   686  func (w *logrusDebugWriter) Write(p []byte) (int, error) {
   687  	logrus.Debugf("%s%s", w.prefix, string(p))
   688  	return len(p), nil
   689  }