Skip to content

Commit

Permalink
new network driver: pasta (with port driver implicit)
Browse files Browse the repository at this point in the history
Pasta: https://passt.top/passt/
Usage: `rootlesskit --net=pasta --port-driver=implicit`

- No support for explicit port forwarding (`rootlessctl add-ports`),
  as pasta doesn't support it yet.
  Use `--port-driver=implicit` to let pasta forward ports implicitly.
  The forwarded ports are not visible in `rootlessctl list-ports`.

- Tested with pasta 2023_06_25.32660ce on Ubuntu 23.04.
  Doesn't work with 2023_06_03.429e1a7: `Option --no-copy-routes needs --config-net`
  (This is printed despite that `--no-copy-routes` is not specified)

- Doesn't work with Ubuntu 23.04's dpkg (passt_0.0~git20230216.4663ccc-1_amd64.deb):
  `Couldn't open user namespace /proc/51813/ns/user: Permission denied`
  Likely to be related to AppArmor.

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
  • Loading branch information
AkihiroSuda committed Jun 29, 2023
1 parent 99b2c78 commit 5e91aa6
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 29 deletions.
37 changes: 33 additions & 4 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ jobs:
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-net.sh vpnkit 1500 --detach-netns
- name: "Benchmark: Network (MTU=1500, network driver=pasta)"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-net.sh pasta 1500
- name: "Benchmark: Network (MTU=1500, network driver=pasta) with detach-netns"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-net.sh pasta 1500 --detach-netns
- name: "Benchmark: Network (MTU=1500, network driver=lxc-user-nic)"
run: |
docker run --rm --privileged \
Expand All @@ -79,6 +87,10 @@ jobs:
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-net.sh slirp4netns 65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto
- name: "Benchmark: Network (MTU=65520, network driver=pasta)"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-net.sh pasta 65520
- name: "Benchmark: Network (MTU=65520, network driver=lxc-user-nic)"
run: |
docker run --rm --privileged \
Expand All @@ -88,22 +100,38 @@ jobs:
docker run --rm --privileged \
rootlesskit:test-integration ./benchmark-iperf3-net.sh rootful_veth 65520
# ===== Benchmark: TCP Ports =====
- name: "Benchmark: TCP Ports (port driver=slirp4netns)"
- name: "Benchmark: TCP Ports (network driver=slirp4netns, port driver=slirp4netns)"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-port.sh slirp4netns
- name: "Benchmark: TCP Ports (port driver=slirp4netns) with detach-netns"
- name: "Benchmark: TCP Ports (network driver=slirp4netns, port driver=slirp4netns) with detach-netns"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-port.sh slirp4netns --detach-netns
- name: "Benchmark: TCP Ports (port driver=builtin)"
- name: "Benchmark: TCP Ports (network driver=slirp4netns, port driver=builtin)"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-port.sh builtin
- name: "Benchmark: TCP Ports (network driver=slirp4netns, port driver=builtin) with detach-netns"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-port.sh builtin --detach-netns
- name: "Benchmark: TCP Ports (network driver=slirp4netns, port driver=builtin)"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-port.sh builtin
- name: "Benchmark: TCP Ports (port driver=builtin) with detach-netns"
- name: "Benchmark: TCP Ports (network driver=slirp4netns, port driver=builtin) with detach-netns"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-port.sh builtin --detach-netns
- name: "Benchmark: TCP Ports (network driver=pasta, port driver=implicit)"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-port.sh implicit --net=pasta
- name: "Benchmark: TCP Ports (network driver=pasta, port driver=implicit) with detach-netns"
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-port.sh implicit --net=pasta --detach-netns
# ===== Benchmark: UDP Ports =====
- name: "Benchmark: UDP Ports (port driver=slirp4netns)"
run: |
Expand All @@ -121,6 +149,7 @@ jobs:
run: |
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
rootlesskit:test-integration ./benchmark-iperf3-port-udp.sh builtin --detach-netns
# pasta+builtin does not work with UDP yet
test-integration-docker:
name: "Integration test (Docker)"
runs-on: ubuntu-latest
Expand Down
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ ARG UBUNTU_VERSION=22.04
ARG SHADOW_VERSION=4.13
ARG SLIRP4NETNS_VERSION=v1.2.0
ARG VPNKIT_VERSION=0.5.0
ARG PASST_VERSION=2023_06_27.289301b
ARG DOCKER_VERSION=24.0.2
ARG DOCKER_CHANNEL=stable

Expand Down Expand Up @@ -45,6 +46,15 @@ RUN ./autogen.sh --disable-nls --disable-man --without-audit --without-selinux -

FROM djs55/vpnkit:${VPNKIT_VERSION} AS vpnkit

FROM ubuntu:${UBUNTU_VERSION} AS passt
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y git gcc libtool make
RUN git clone https://passt.top/passt
WORKDIR /passt
ARG PASST_VERSION
RUN git pull && git checkout $PASST_VERSION
RUN make && make install

FROM ubuntu:${UBUNTU_VERSION} AS test-integration
# iproute2: for `ip` command that rootlesskit needs to exec
# liblxc-common and lxc-utils: for `lxc-user-nic` binary required for --net=lxc-user-nic
Expand All @@ -67,6 +77,7 @@ ARG SLIRP4NETNS_VERSION
RUN curl -sSL -o /home/user/bin/slirp4netns https://github.com/rootless-containers/slirp4netns/releases/download/${SLIRP4NETNS_VERSION}/slirp4netns-x86_64 && \
chmod +x /home/user/bin/slirp4netns
COPY --from=vpnkit /vpnkit /home/user/bin/vpnkit
COPY --from=passt /usr/local /usr/local
ADD ./hack /home/user/hack
RUN chown -R user:user /run/user/1000 /home/user
USER user
Expand Down
5 changes: 5 additions & 0 deletions cmd/rootlesskit-docker-proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ func xmain(f *os.File) error {
return fmt.Errorf("failed to call info API, probably RootlessKit binary is too old (needs to be v0.14.0 or later): %w", err)
}

// info.PortDriver is currently nil for "none" and "implicit", but this may change in future
if info.PortDriver == nil || info.PortDriver.Driver == "none" || info.PortDriver.Driver == "implicit" {
return syscall.Exec(realProxy, append([]string{realProxy}, os.Args[1:]...), os.Environ())
}

// use loopback IP as the child IP, when port-driver="builtin"
childIP := "127.0.0.1"
if isIPv6(*hostIP) {
Expand Down
1 change: 1 addition & 0 deletions cmd/rootlesskit/category.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
const (
CategoryState = "State"
CategoryNetwork = "Network"
CategoryPasta = "Network [pasta]"
CategorySlirp4netns = "Network [slirp4netns]"
CategoryVPNKit = "Network [vpnkit]"
CategoryLXCUserNic = "Network [lxc-user-nic]"
Expand Down
55 changes: 43 additions & 12 deletions cmd/rootlesskit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/rootless-containers/rootlesskit/pkg/common"
"github.com/rootless-containers/rootlesskit/pkg/copyup/tmpfssymlink"
"github.com/rootless-containers/rootlesskit/pkg/network/lxcusernic"
"github.com/rootless-containers/rootlesskit/pkg/network/pasta"
"github.com/rootless-containers/rootlesskit/pkg/network/slirp4netns"
"github.com/rootless-containers/rootlesskit/pkg/network/vpnkit"
"github.com/rootless-containers/rootlesskit/pkg/parent"
Expand Down Expand Up @@ -77,9 +78,14 @@ See https://rootlesscontaine.rs/getting-started/common/ .
}, CategoryState),
Categorize(&cli.StringFlag{
Name: "net",
Usage: "network driver [host, slirp4netns, vpnkit, lxc-user-nic(experimental)]",
Usage: "network driver [host, pasta, slirp4netns, vpnkit, lxc-user-nic(experimental)]",
Value: "host",
}, CategoryNetwork),
Categorize(&cli.StringFlag{
Name: "pasta-binary",
Usage: "path of pasta binary for --net=pasta",
Value: "pasta",
}, CategoryPasta),
Categorize(&cli.StringFlag{
Name: "slirp4netns-binary",
Usage: "path of slirp4netns binary for --net=slirp4netns",
Expand Down Expand Up @@ -112,24 +118,24 @@ See https://rootlesscontaine.rs/getting-started/common/ .
}, CategoryLXCUserNic),
Categorize(&cli.IntFlag{
Name: "mtu",
Usage: "MTU for non-host network (default: 65520 for slirp4netns, 1500 for others)",
Usage: "MTU for non-host network (default: 65520 for pasta and slirp4netns, 1500 for others)",
Value: 0, // resolved into 65520 for slirp4netns, 1500 for others
}, CategoryNetwork),
Categorize(&cli.StringFlag{
Name: "cidr",
Usage: "CIDR for slirp4netns network (default: 10.0.2.0/24)",
Usage: "CIDR for pasta and slirp4netns networks (default: 10.0.2.0/24)",
}, CategoryNetwork),
Categorize(&cli.StringFlag{
Name: "ifname",
Usage: "Network interface name (default: tap0 for slirp4netns and vpnkit, eth0 for lxc-user-nic)",
Usage: "Network interface name (default: tap0 for pasta, slirp4netns, and vpnkit; eth0 for lxc-user-nic)",
}, CategoryNetwork),
Categorize(&cli.BoolFlag{
Name: "disable-host-loopback",
Usage: "prohibit connecting to 127.0.0.1:* on the host namespace",
}, CategoryNetwork),
Categorize(&cli.BoolFlag{
Name: "ipv6",
Usage: "enable IPv6 routing. Unrelated to port forwarding. Only supported for slirp4netns. (experimental)",
Usage: "enable IPv6 routing. Unrelated to port forwarding. Only supported for pasta and slirp4netns. (experimental)",
}, CategoryNetwork),
Categorize(&cli.StringSliceFlag{
Name: "copy-up",
Expand All @@ -142,7 +148,7 @@ See https://rootlesscontaine.rs/getting-started/common/ .
}, CategoryMount),
Categorize(&cli.StringFlag{
Name: "port-driver",
Usage: "port driver for non-host network. [none, builtin, slirp4netns]",
Usage: "port driver for non-host network. [none, implicit (for pasta), builtin, slirp4netns]",
Value: "none",
}, CategoryPort),
Categorize(&cli.StringSliceFlag{
Expand Down Expand Up @@ -333,14 +339,14 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
ipv6 := clicontext.Bool("ipv6")
if ipv6 {
logrus.Warn("ipv6 is experimental")
if s := clicontext.String("net"); s != "slirp4netns" {
if s := clicontext.String("net"); s != "pasta" && s != "slirp4netns" {
logrus.Warnf("--ipv6 is discarded for --net=%s", s)
}
}

disableHostLoopback := clicontext.Bool("disable-host-loopback")
if !disableHostLoopback && clicontext.String("net") != "host" {
logrus.Warn("specifying --disable-host-loopback is highly recommended to prohibit connecting to 127.0.0.1:* on the host namespace (requires slirp4netns or VPNKit)")
logrus.Warn("specifying --disable-host-loopback is highly recommended to prohibit connecting to 127.0.0.1:* on the host namespace (requires pasta, slirp4netns, or VPNKit)")
}

slirp4netnsAPISocketPath := ""
Expand All @@ -354,11 +360,29 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
logrus.Warnf("unsupported mtu for --net=host: %d", mtu)
}
if ipnet != nil {
return opt, errors.New("custom cidr is supported only for --net=slirp4netns")
return opt, errors.New("custom cidr is not supported for --net=host")
}
if ifname != "" {
return opt, errors.New("ifname cannot be specified for --net=host")
}
case "pasta":
binary := clicontext.String("pasta-binary")
if _, err := exec.LookPath(binary); err != nil {
return opt, err
}
var implicitPortForward bool
switch portDriver := clicontext.String("port-driver"); portDriver {
case "none":
implicitPortForward = false
case "implicit":
implicitPortForward = true
default:
return opt, errors.New("network \"pasta\" requires port driver \"none\" or \"implicit\"")
}
opt.NetworkDriver, err = pasta.NewParentDriver(&logrusDebugWriter{label: "network/pasta"}, binary, mtu, ipnet, ifname, disableHostLoopback, ipv6, implicitPortForward)
if err != nil {
return opt, err
}
case "slirp4netns":
binary := clicontext.String("slirp4netns-binary")
if _, err := exec.LookPath(binary); err != nil {
Expand Down Expand Up @@ -424,7 +448,7 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
}
case "vpnkit":
if ipnet != nil {
return opt, errors.New("custom cidr is supported only for --net=slirp4netns")
return opt, errors.New("custom cidr is not supported for --net=vpnkit")
}
binary := clicontext.String("vpnkit-binary")
if _, err := exec.LookPath(binary); err != nil {
Expand All @@ -434,7 +458,7 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
case "lxc-user-nic":
logrus.Warn("\"lxc-user-nic\" network driver is experimental")
if ipnet != nil {
return opt, errors.New("custom cidr is supported only for --net=slirp4netns")
return opt, errors.New("custom cidr is not supported for --net=lxc-user-nic")
}
if !disableHostLoopback {
logrus.Warn("--disable-host-loopback is implicitly set for lxc-user-nic")
Expand All @@ -456,6 +480,11 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
if len(clicontext.StringSlice("publish")) != 0 {
return opt, fmt.Errorf("port driver %q does not support publishing ports", s)
}
case "implicit":
if clicontext.String("net") != "pasta" {
return opt, errors.New("port driver requires pasta network")
}
// NOP
case "slirp4netns":
if clicontext.String("net") != "slirp4netns" {
return opt, errors.New("port driver requires slirp4netns network")
Expand Down Expand Up @@ -529,6 +558,8 @@ func createChildOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey string
switch s := clicontext.String("net"); s {
case "host":
// NOP
case "pasta":
opt.NetworkDriver = pasta.NewChildDriver()
case "slirp4netns":
opt.NetworkDriver = slirp4netns.NewChildDriver()
case "vpnkit":
Expand All @@ -549,7 +580,7 @@ func createChildOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey string
return opt, fmt.Errorf("unknown copy-up mode: %s", s)
}
switch s := clicontext.String("port-driver"); s {
case "none":
case "none", "implicit":
// NOP
case "slirp4netns":
opt.PortDriver = slirp4netns_port.NewChildDriver()
Expand Down
22 changes: 22 additions & 0 deletions docs/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
RootlessKit provides several drivers for providing network connectivity:

* `--net=host`: use host network namespace (default)
* `--net=pasta`: use [pasta](https://passt.top/passt/) (experimental)
* `--net=slirp4netns`: use [slirp4netns](https://github.com/rootless-containers/slirp4netns) (recommended)
* `--net=vpnkit`: use [VPNKit](https://github.com/moby/vpnkit)
* `--net=lxc-user-nic`: use `lxc-user-nic` (experimental)
Expand Down Expand Up @@ -138,6 +139,27 @@ The network is configured as follows by default:
As in `--net=slirp4netns`, specifying `--copy-up=/etc` and `--disable-host-loopback` is highly recommended.
If `--disable-host-loopback` is not specified, ports listening on 127.0.0.1 in the host are accessible as 192.168.65.2 in the RootlessKit's network namespace.

### `--net=pasta` (experimental)

`--net=pasta` (since RootlessKit v2.0, EXPERIMENTAL) uses [pasta](https://passt.top/passt/).
`--net=pasta` is expected to be used in conjunction with `--port-driver=implicit`.

> **Note**
> Needs [pasta](https://passt.top/passt/) `2023_06_25.32660ce` or later.
> Maybe need `sudo apparmor_parser -R /etc/apparmor.d/usr.bin.passt` too.
Pros:
* Possible to perform network-namespaced operations, e.g. creating iptables rules, running `tcpdump`
* Supports ICMP Echo (`ping`) when `/proc/sys/net/ipv4/ping_group_range` is configured
* Port forwarding (`--port-driver=implicit`) is very fast
* Port forwarding (`--port-driver=implicit`) can retain source IP addresses

Cons:
* UDP support seems currently broken

The network configuration for pasta is similar to slirp4netns.
As in `--net=slirp4netns`, specifying `--copy-up=/etc` and `--disable-host-loopback` is highly recommended.

### `--net=lxc-user-nic` (experimental)

`--net=lxc-user-nic` isolates the network namespace from the host and launch [`lxc-user-nic(1)`](https://linuxcontainers.org/lxc/manpages/man1/lxc-user-nic.1.html) SUID binary for providing kernel-mode NAT.
Expand Down
6 changes: 5 additions & 1 deletion docs/port.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ The default value is `none` (do not expose ports).

([Benchmark: iperf3 from the parent to the child (Mar 8, 2020)](https://github.com/rootless-containers/rootlesskit/runs/492498728))

The `builtin` driver is fastest, but be aware that the source IP is not propagated and always set to 127.0.0.1.
The `builtin` driver is fast, but be aware that the source IP is not propagated and always set to 127.0.0.1.

For [`pasta`](./network.md) networks, the `implicit` port driver is the best choice.

* To be documented: [`bypass4netns`](https://github.com/rootless-containers/bypass4netns) for native performance.

### Exposing ports
For example, to expose 80 in the child as 8080 in the parent:
Expand Down
11 changes: 11 additions & 0 deletions hack/benchmark-iperf3-net.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
#!/bin/bash
source $(realpath $(dirname $0))/common.inc.sh
function benchmark::iperf3::pasta() {
INFO "[benchmark:iperf3] slirp4netns ($@)"
statedir=$(mktemp -d)
if echo "$@" | grep -q -- --detach-netns; then
IPERF3C="nsenter -n${statedir}/netns $IPERF3C"
fi
set -x
$ROOTLESSKIT --state-dir=$statedir --net=slirp4netns $@ -- $IPERF3C 10.0.2.2
set +x
}

function benchmark::iperf3::slirp4netns() {
INFO "[benchmark:iperf3] slirp4netns ($@)"
statedir=$(mktemp -d)
Expand Down
Loading

0 comments on commit 5e91aa6

Please sign in to comment.