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

     1  package pasta
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"os/exec"
    10  	"strconv"
    11  	"sync"
    12  
    13  	"github.com/sirupsen/logrus"
    14  
    15  	"github.com/rootless-containers/rootlesskit/v2/pkg/api"
    16  	"github.com/rootless-containers/rootlesskit/v2/pkg/common"
    17  	"github.com/rootless-containers/rootlesskit/v2/pkg/messages"
    18  	"github.com/rootless-containers/rootlesskit/v2/pkg/network"
    19  	"github.com/rootless-containers/rootlesskit/v2/pkg/network/iputils"
    20  )
    21  
    22  type Features struct {
    23  	// Has `--host-lo-to-ns-lo` (introduced in passt 2024_10_30.ee7d0b6)
    24  	// https://passt.top/passt/commit/?id=b4dace8f462b346ae2135af1f8d681a99a849a5f
    25  	HasHostLoToNsLo bool
    26  }
    27  
    28  func DetectFeatures(binary string) (*Features, error) {
    29  	if binary == "" {
    30  		return nil, errors.New("got empty pasta binary")
    31  	}
    32  	realBinary, err := exec.LookPath(binary)
    33  	if err != nil {
    34  		return nil, fmt.Errorf("pasta binary %q is not installed: %w", binary, err)
    35  	}
    36  	cmd := exec.Command(realBinary, "--version")
    37  	b, err := cmd.CombinedOutput()
    38  	if err != nil {
    39  		return nil, fmt.Errorf(`command "%s --version" failed, make sure pasta is installed: %q: %w`,
    40  			realBinary, string(b), err)
    41  	}
    42  	f := Features{
    43  		HasHostLoToNsLo: false,
    44  	}
    45  	cmd = exec.Command(realBinary, "--host-lo-to-ns-lo", "--version")
    46  	if cmd.Run() == nil {
    47  		f.HasHostLoToNsLo = true
    48  	}
    49  	return &f, nil
    50  }
    51  
    52  // NewParentDriver instantiates new parent driver.
    53  func NewParentDriver(logWriter io.Writer, binary string, mtu int, ipnet *net.IPNet, ifname string,
    54  	disableHostLoopback, enableIPv6, implicitPortForwarding bool) (network.ParentDriver, error) {
    55  	if binary == "" {
    56  		return nil, errors.New("got empty slirp4netns binary")
    57  	}
    58  	if mtu < 0 {
    59  		return nil, errors.New("got negative mtu")
    60  	}
    61  	if mtu == 0 {
    62  		mtu = 65520
    63  	}
    64  
    65  	if ipnet == nil {
    66  		var err error
    67  		_, ipnet, err = net.ParseCIDR("10.0.2.0/24")
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  	}
    72  
    73  	if ifname == "" {
    74  		ifname = "tap0"
    75  	}
    76  
    77  	feat, err := DetectFeatures(binary)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	return &parentDriver{
    83  		logWriter:              logWriter,
    84  		binary:                 binary,
    85  		mtu:                    mtu,
    86  		ipnet:                  ipnet,
    87  		disableHostLoopback:    disableHostLoopback,
    88  		enableIPv6:             enableIPv6,
    89  		ifname:                 ifname,
    90  		implicitPortForwarding: implicitPortForwarding,
    91  		feat:                   feat,
    92  	}, nil
    93  }
    94  
    95  type parentDriver struct {
    96  	logWriter              io.Writer
    97  	binary                 string
    98  	mtu                    int
    99  	ipnet                  *net.IPNet
   100  	disableHostLoopback    bool
   101  	enableIPv6             bool
   102  	ifname                 string
   103  	infoMu                 sync.RWMutex
   104  	implicitPortForwarding bool
   105  	info                   func() *api.NetworkDriverInfo
   106  	feat                   *Features
   107  }
   108  
   109  const DriverName = "pasta"
   110  
   111  func (d *parentDriver) Info(ctx context.Context) (*api.NetworkDriverInfo, error) {
   112  	d.infoMu.RLock()
   113  	infoFn := d.info
   114  	d.infoMu.RUnlock()
   115  	if infoFn == nil {
   116  		return &api.NetworkDriverInfo{
   117  			Driver: DriverName,
   118  		}, nil
   119  	}
   120  
   121  	return infoFn(), nil
   122  }
   123  
   124  func (d *parentDriver) MTU() int {
   125  	return d.mtu
   126  }
   127  
   128  func (d *parentDriver) ConfigureNetwork(childPID int, stateDir, detachedNetNSPath string) (*messages.ParentInitNetworkDriverCompleted, func() error, error) {
   129  	tap := d.ifname
   130  	var cleanups []func() error
   131  
   132  	address, err := iputils.AddIPInt(d.ipnet.IP, 100)
   133  	if err != nil {
   134  		return nil, common.Seq(cleanups), err
   135  	}
   136  	netmask, _ := d.ipnet.Mask.Size()
   137  	gateway, err := iputils.AddIPInt(d.ipnet.IP, 2)
   138  	if err != nil {
   139  		return nil, common.Seq(cleanups), err
   140  	}
   141  	dns, err := iputils.AddIPInt(d.ipnet.IP, 3)
   142  	if err != nil {
   143  		return nil, common.Seq(cleanups), err
   144  	}
   145  
   146  	opts := []string{
   147  		"--stderr",
   148  		"--ns-ifname=" + d.ifname,
   149  		"--mtu=" + strconv.Itoa(d.mtu),
   150  		"--config-net",
   151  		"--address=" + address.String(),
   152  		"--netmask=" + strconv.Itoa(netmask),
   153  		"--gateway=" + gateway.String(),
   154  		"--dns-forward=" + dns.String(),
   155  	}
   156  	if d.disableHostLoopback {
   157  		opts = append(opts, "--no-map-gw")
   158  	}
   159  	if !d.enableIPv6 {
   160  		opts = append(opts, "--ipv4-only")
   161  	}
   162  	if d.implicitPortForwarding {
   163  		opts = append(opts, "--tcp-ports=auto",
   164  			"--udp-ports=auto")
   165  	} else {
   166  		opts = append(opts, "--tcp-ports=none",
   167  			"--udp-ports=none")
   168  	}
   169  	if d.feat != nil {
   170  		if d.feat.HasHostLoToNsLo {
   171  			// Needed to keep `docker run -p 127.0.0.1:8080:80` functional with
   172  			// passt >= 2024_10_30.ee7d0b6
   173  			//
   174  			// https://github.com/rootless-containers/rootlesskit/pull/482#issuecomment-2591798590
   175  			opts = append(opts, "--host-lo-to-ns-lo")
   176  		}
   177  	}
   178  	if detachedNetNSPath == "" {
   179  		opts = append(opts, strconv.Itoa(childPID))
   180  	} else {
   181  		opts = append(opts,
   182  			fmt.Sprintf("--userns=/proc/%d/ns/user", childPID),
   183  			"--netns="+detachedNetNSPath)
   184  	}
   185  
   186  	// FIXME: Doesn't work with:
   187  	// - passt-0.0~git20230627.289301b-1 (Ubuntu 23.10)
   188  	// - passt-0.0~git20240220.1e6f92b-1 (Ubuntu 24.04)
   189  	// see https://bugs.launchpad.net/ubuntu/+source/passt/+bug/2077158
   190  	//
   191  	// Workaround: set the kernel.apparmor_restrict_unprivileged_userns
   192  	// sysctl to 0, or (preferred) add the AppArmor profile from upstream,
   193  	// or from Debian packages, or from Ubuntu > 24.10.
   194  	cmd := exec.Command(d.binary, opts...)
   195  	logrus.Debugf("Executing %v", cmd.Args)
   196  	out, err := cmd.CombinedOutput()
   197  	if err != nil {
   198  		exitErr := &exec.ExitError{}
   199  		if errors.As(err, &exitErr) {
   200  			return nil, common.Seq(cleanups),
   201  				fmt.Errorf("pasta failed with exit code %d:\n%s",
   202  					exitErr.ExitCode(), string(out))
   203  		}
   204  		return nil, common.Seq(cleanups), fmt.Errorf("executing %v: %w", cmd, err)
   205  	}
   206  
   207  	netmsg := messages.ParentInitNetworkDriverCompleted{
   208  		Dev: tap,
   209  		MTU: d.mtu,
   210  	}
   211  	netmsg.IP = address.String()
   212  	netmsg.Netmask = netmask
   213  	netmsg.Gateway = gateway.String()
   214  	netmsg.DNS = []string{dns.String()}
   215  
   216  	d.infoMu.Lock()
   217  	d.info = func() *api.NetworkDriverInfo {
   218  		return &api.NetworkDriverInfo{
   219  			Driver:         DriverName,
   220  			DNS:            []net.IP{net.ParseIP(netmsg.DNS[0])},
   221  			ChildIP:        net.ParseIP(netmsg.IP),
   222  			DynamicChildIP: false,
   223  		}
   224  	}
   225  	d.infoMu.Unlock()
   226  	return &netmsg, common.Seq(cleanups), nil
   227  }
   228  
   229  func NewChildDriver() network.ChildDriver {
   230  	return &childDriver{}
   231  }
   232  
   233  type childDriver struct {
   234  }
   235  
   236  func (d *childDriver) ChildDriverInfo() (*network.ChildDriverInfo, error) {
   237  	return &network.ChildDriverInfo{
   238  		ConfiguresInterface: true,
   239  	}, nil
   240  }
   241  
   242  func (d *childDriver) ConfigureNetworkChild(netmsg *messages.ParentInitNetworkDriverCompleted, detachedNetNSPath string) (string, error) {
   243  	// NOP
   244  	return netmsg.Dev, nil
   245  }