github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/network/slirp4netns/slirp4netns.go (about)

     1  package slirp4netns
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"os"
    10  	"os/exec"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"syscall"
    15  	"time"
    16  
    17  	"github.com/sirupsen/logrus"
    18  	"golang.org/x/sys/unix"
    19  
    20  	"github.com/rootless-containers/rootlesskit/v2/pkg/api"
    21  	"github.com/rootless-containers/rootlesskit/v2/pkg/common"
    22  	"github.com/rootless-containers/rootlesskit/v2/pkg/messages"
    23  	"github.com/rootless-containers/rootlesskit/v2/pkg/network"
    24  	"github.com/rootless-containers/rootlesskit/v2/pkg/network/iputils"
    25  	"github.com/rootless-containers/rootlesskit/v2/pkg/network/parentutils"
    26  )
    27  
    28  type Features struct {
    29  	// SupportsEnableIPv6 --enable-ipv6 (v0.2.0)
    30  	SupportsEnableIPv6 bool
    31  	// SupportsCIDR --cidr (v0.3.0)
    32  	SupportsCIDR bool
    33  	// SupportsDisableHostLoopback --disable-host-loopback (v0.3.0)
    34  	SupportsDisableHostLoopback bool
    35  	// SupportsAPISocket --api-socket (v0.3.0)
    36  	SupportsAPISocket bool
    37  	// SupportsEnableSandbox --enable-sandbox (v0.4.0)
    38  	SupportsEnableSandbox bool
    39  	// SupportsEnableSeccomp --enable-seccomp (v0.4.0)
    40  	SupportsEnableSeccomp bool
    41  	// KernelSupportsSeccomp whether the kernel supports slirp4netns --enable-seccomp
    42  	KernelSupportsEnableSeccomp bool
    43  }
    44  
    45  func DetectFeatures(binary string) (*Features, error) {
    46  	if binary == "" {
    47  		return nil, errors.New("got empty slirp4netns binary")
    48  	}
    49  	realBinary, err := exec.LookPath(binary)
    50  	if err != nil {
    51  		return nil, fmt.Errorf("slirp4netns binary %q is not installed: %w", binary, err)
    52  	}
    53  	cmd := exec.Command(realBinary, "--help")
    54  	cmd.Env = os.Environ()
    55  	b, err := cmd.CombinedOutput()
    56  	s := string(b)
    57  	if err != nil {
    58  		return nil, fmt.Errorf(
    59  			"command \"%s --help\" failed, make sure slirp4netns v0.4.0+ is installed: %q: %w",
    60  			realBinary, s, err,
    61  		)
    62  	}
    63  	if !strings.Contains(s, "--netns-type") {
    64  		// We don't use --netns-type, but we check the presence of --netns-type to
    65  		// ensure slirp4netns >= v0.4.0: https://github.com/rootless-containers/rootlesskit/issues/143
    66  		return nil, errors.New("slirp4netns seems older than v0.4.0")
    67  	}
    68  	kernelSupportsEnableSeccomp := false
    69  	if unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0) != unix.EINVAL {
    70  		kernelSupportsEnableSeccomp = unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0) != unix.EINVAL
    71  	}
    72  	f := Features{
    73  		SupportsEnableIPv6:          strings.Contains(s, "--enable-ipv6"),
    74  		SupportsCIDR:                strings.Contains(s, "--cidr"),
    75  		SupportsDisableHostLoopback: strings.Contains(s, "--disable-host-loopback"),
    76  		SupportsAPISocket:           strings.Contains(s, "--api-socket"),
    77  		SupportsEnableSandbox:       strings.Contains(s, "--enable-sandbox"),
    78  		SupportsEnableSeccomp:       strings.Contains(s, "--enable-seccomp"),
    79  		KernelSupportsEnableSeccomp: kernelSupportsEnableSeccomp,
    80  	}
    81  	return &f, nil
    82  }
    83  
    84  // NewParentDriver instantiates new parent driver.
    85  // Requires slirp4netns v0.4.0 or later.
    86  func NewParentDriver(logWriter io.Writer, binary string, mtu int, ipnet *net.IPNet, ifname string, disableHostLoopback bool, apiSocketPath string,
    87  	enableSandbox, enableSeccomp, enableIPv6 bool) (network.ParentDriver, error) {
    88  	if binary == "" {
    89  		return nil, errors.New("got empty slirp4netns binary")
    90  	}
    91  	if mtu < 0 {
    92  		return nil, errors.New("got negative mtu")
    93  	}
    94  	if mtu == 0 {
    95  		mtu = 65520
    96  	}
    97  
    98  	if ifname == "" {
    99  		ifname = "tap0"
   100  	}
   101  
   102  	features, err := DetectFeatures(binary)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	if enableIPv6 && !features.SupportsEnableIPv6 {
   107  		return nil, errors.New("this version of slirp4netns does not support --enable-ipv6")
   108  	}
   109  	if ipnet != nil && !features.SupportsCIDR {
   110  		return nil, errors.New("this version of slirp4netns does not support --cidr")
   111  	}
   112  	if disableHostLoopback && !features.SupportsDisableHostLoopback {
   113  		return nil, errors.New("this version of slirp4netns does not support --disable-host-loopback")
   114  	}
   115  	if apiSocketPath != "" && !features.SupportsAPISocket {
   116  		return nil, errors.New("this version of slirp4netns does not support --api-socket")
   117  	}
   118  	if enableSandbox && !features.SupportsEnableSandbox {
   119  		return nil, errors.New("this version of slirp4netns does not support --enable-sandbox")
   120  	}
   121  	if enableSeccomp && !features.SupportsEnableSeccomp {
   122  		return nil, errors.New("this version of slirp4netns does not support --enable-seccomp")
   123  	}
   124  	if enableSeccomp && !features.KernelSupportsEnableSeccomp {
   125  		return nil, errors.New("kernel does not support seccomp")
   126  	}
   127  
   128  	return &parentDriver{
   129  		logWriter:           logWriter,
   130  		binary:              binary,
   131  		mtu:                 mtu,
   132  		ipnet:               ipnet,
   133  		disableHostLoopback: disableHostLoopback,
   134  		apiSocketPath:       apiSocketPath,
   135  		enableSandbox:       enableSandbox,
   136  		enableSeccomp:       enableSeccomp,
   137  		enableIPv6:          enableIPv6,
   138  		ifname:              ifname,
   139  	}, nil
   140  }
   141  
   142  type parentDriver struct {
   143  	logWriter           io.Writer
   144  	binary              string
   145  	mtu                 int
   146  	ipnet               *net.IPNet
   147  	disableHostLoopback bool
   148  	apiSocketPath       string
   149  	enableSandbox       bool
   150  	enableSeccomp       bool
   151  	enableIPv6          bool
   152  	ifname              string
   153  	infoMu              sync.RWMutex
   154  	info                func() *api.NetworkDriverInfo
   155  }
   156  
   157  const DriverName = "slirp4netns"
   158  
   159  func (d *parentDriver) Info(ctx context.Context) (*api.NetworkDriverInfo, error) {
   160  	d.infoMu.RLock()
   161  	infoFn := d.info
   162  	d.infoMu.RUnlock()
   163  	if infoFn == nil {
   164  		return &api.NetworkDriverInfo{
   165  			Driver: DriverName,
   166  		}, nil
   167  	}
   168  
   169  	return infoFn(), nil
   170  }
   171  
   172  func (d *parentDriver) MTU() int {
   173  	return d.mtu
   174  }
   175  
   176  func (d *parentDriver) ConfigureNetwork(childPID int, stateDir, detachedNetNSPath string) (*messages.ParentInitNetworkDriverCompleted, func() error, error) {
   177  	if detachedNetNSPath != "" && d.enableSandbox {
   178  		return nil, nil, errors.New("slirp4netns sandbox is not compatible with detach-netns (https://github.com/rootless-containers/slirp4netns/issues/317)")
   179  	}
   180  	tap := d.ifname
   181  	var cleanups []func() error
   182  	if err := parentutils.PrepareTap(childPID, detachedNetNSPath, tap); err != nil {
   183  		return nil, common.Seq(cleanups), fmt.Errorf("setting up tap %s: %w", tap, err)
   184  	}
   185  	readyR, readyW, err := os.Pipe()
   186  	if err != nil {
   187  		return nil, common.Seq(cleanups), err
   188  	}
   189  	defer readyR.Close()
   190  	defer readyW.Close()
   191  	// -r: readyFD (requires slirp4netns >= v0.4.0: https://github.com/rootless-containers/rootlesskit/issues/143)
   192  	opts := []string{"--mtu", strconv.Itoa(d.mtu), "-r", "3"}
   193  	if d.disableHostLoopback {
   194  		opts = append(opts, "--disable-host-loopback")
   195  	}
   196  	if d.ipnet != nil {
   197  		opts = append(opts, "--cidr", d.ipnet.String())
   198  	}
   199  	if d.apiSocketPath != "" {
   200  		opts = append(opts, "--api-socket", d.apiSocketPath)
   201  	}
   202  	if d.enableSandbox {
   203  		opts = append(opts, "--enable-sandbox")
   204  	}
   205  	if d.enableSeccomp {
   206  		opts = append(opts, "--enable-seccomp")
   207  	}
   208  	if d.enableIPv6 {
   209  		opts = append(opts, "--enable-ipv6")
   210  	}
   211  	if detachedNetNSPath == "" {
   212  		opts = append(opts, strconv.Itoa(childPID))
   213  	} else {
   214  		opts = append(opts,
   215  			fmt.Sprintf("--userns-path=/proc/%d/ns/user", childPID),
   216  			"--netns-type=path",
   217  			detachedNetNSPath)
   218  	}
   219  	opts = append(opts, tap)
   220  	cmd := exec.Command(d.binary, opts...)
   221  	// FIXME: Stdout doen't seem captured
   222  	cmd.Stdout = d.logWriter
   223  	cmd.Stderr = d.logWriter
   224  	cmd.SysProcAttr = &syscall.SysProcAttr{
   225  		Pdeathsig: syscall.SIGKILL,
   226  	}
   227  	cmd.ExtraFiles = append(cmd.ExtraFiles, readyW)
   228  	cleanups = append(cleanups, func() error {
   229  		logrus.Debugf("killing slirp4netns")
   230  		if cmd.Process != nil {
   231  			_ = cmd.Process.Kill()
   232  		}
   233  		wErr := cmd.Wait()
   234  		logrus.Debugf("killed slirp4netns: %v", wErr)
   235  		return nil
   236  	})
   237  	if err := cmd.Start(); err != nil {
   238  		return nil, common.Seq(cleanups), fmt.Errorf("executing %v: %w", cmd, err)
   239  	}
   240  
   241  	if err := waitForReadyFD(cmd.Process.Pid, readyR); err != nil {
   242  		return nil, common.Seq(cleanups), fmt.Errorf("waiting for ready fd (%v): %w", cmd, err)
   243  	}
   244  	netmsg := messages.ParentInitNetworkDriverCompleted{
   245  		Dev: tap,
   246  		DNS: make([]string, 0, 2),
   247  		MTU: d.mtu,
   248  	}
   249  	if d.ipnet != nil {
   250  		// TODO: get the actual configuration via slirp4netns API?
   251  		x, err := iputils.AddIPInt(d.ipnet.IP, 100)
   252  		if err != nil {
   253  			return nil, common.Seq(cleanups), err
   254  		}
   255  		netmsg.IP = x.String()
   256  		netmsg.Netmask, _ = d.ipnet.Mask.Size()
   257  		x, err = iputils.AddIPInt(d.ipnet.IP, 2)
   258  		if err != nil {
   259  			return nil, common.Seq(cleanups), err
   260  		}
   261  		netmsg.Gateway = x.String()
   262  		x, err = iputils.AddIPInt(d.ipnet.IP, 3)
   263  		if err != nil {
   264  			return nil, common.Seq(cleanups), err
   265  		}
   266  		netmsg.DNS = append(netmsg.DNS, x.String())
   267  	} else {
   268  		netmsg.IP = "10.0.2.100"
   269  		netmsg.Netmask = 24
   270  		netmsg.Gateway = "10.0.2.2"
   271  		netmsg.DNS = append(netmsg.DNS, "10.0.2.3")
   272  	}
   273  
   274  	if d.enableIPv6 {
   275  		// for now slirp4netns only supports fd00::3 as v6 nameserver
   276  		// https://github.com/rootless-containers/slirp4netns/blob/ee1542e1532e6a7f266b8b6118973ab3b10a8bb5/slirp4netns.c#L272
   277  		netmsg.DNS = append(netmsg.DNS, "fd00::3")
   278  	}
   279  
   280  	apiDNS := make([]net.IP, 0, cap(netmsg.DNS))
   281  	for _, nameserver := range netmsg.DNS {
   282  		apiDNS = append(apiDNS, net.ParseIP(nameserver))
   283  	}
   284  
   285  	d.infoMu.Lock()
   286  	d.info = func() *api.NetworkDriverInfo {
   287  		return &api.NetworkDriverInfo{
   288  			Driver:         DriverName,
   289  			DNS:            apiDNS,
   290  			ChildIP:        net.ParseIP(netmsg.IP),
   291  			DynamicChildIP: false,
   292  		}
   293  	}
   294  	d.infoMu.Unlock()
   295  	return &netmsg, common.Seq(cleanups), nil
   296  }
   297  
   298  // waitForReady is from libpod
   299  // https://github.com/containers/libpod/blob/e6b843312b93ddaf99d0ef94a7e60ff66bc0eac8/libpod/networking_linux.go#L272-L308
   300  func waitForReadyFD(cmdPid int, r *os.File) error {
   301  	b := make([]byte, 16)
   302  	for {
   303  		if err := r.SetDeadline(time.Now().Add(1 * time.Second)); err != nil {
   304  			return fmt.Errorf("error setting slirp4netns pipe timeout: %w", err)
   305  		}
   306  		if _, err := r.Read(b); err == nil {
   307  			break
   308  		} else {
   309  			if os.IsTimeout(err) {
   310  				// Check if the process is still running.
   311  				var status syscall.WaitStatus
   312  				pid, err := syscall.Wait4(cmdPid, &status, syscall.WNOHANG, nil)
   313  				if err != nil {
   314  					return fmt.Errorf("failed to read slirp4netns process status: %w", err)
   315  				}
   316  				if pid != cmdPid {
   317  					continue
   318  				}
   319  				if status.Exited() {
   320  					return errors.New("slirp4netns failed")
   321  				}
   322  				if status.Signaled() {
   323  					return errors.New("slirp4netns killed by signal")
   324  				}
   325  				continue
   326  			}
   327  			return fmt.Errorf("failed to read from slirp4netns sync pipe: %w", err)
   328  		}
   329  	}
   330  	return nil
   331  }
   332  
   333  func NewChildDriver() network.ChildDriver {
   334  	return &childDriver{}
   335  }
   336  
   337  type childDriver struct {
   338  }
   339  
   340  func (d *childDriver) ChildDriverInfo() (*network.ChildDriverInfo, error) {
   341  	return &network.ChildDriverInfo{
   342  		ConfiguresInterface: false,
   343  	}, nil
   344  }
   345  
   346  func (d *childDriver) ConfigureNetworkChild(netmsg *messages.ParentInitNetworkDriverCompleted, detachedNetNSPath string) (string, error) {
   347  	tap := netmsg.Dev
   348  	if tap == "" {
   349  		return "", errors.New("could not determine the preconfigured tap")
   350  	}
   351  	// tap is created and "up".
   352  	// IP stuff and MTU are not configured by the parent here,
   353  	// and they are up to the child.
   354  	return tap, nil
   355  }