Skip to content

Commit ecfe592

Browse files
authored
Merge pull request #358 from AkihiroSuda/pasta
new network driver: `pasta` (with port driver `implicit`)
2 parents 99b2c78 + 7766f34 commit ecfe592

File tree

12 files changed

+381
-34
lines changed

12 files changed

+381
-34
lines changed

.github/workflows/main.yaml

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ jobs:
6262
run: |
6363
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
6464
rootlesskit:test-integration ./benchmark-iperf3-net.sh vpnkit 1500 --detach-netns
65+
- name: "Benchmark: Network (MTU=1500, network driver=pasta)"
66+
run: |
67+
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
68+
rootlesskit:test-integration ./benchmark-iperf3-net.sh pasta 1500
69+
- name: "Benchmark: Network (MTU=1500, network driver=pasta) with detach-netns"
70+
run: |
71+
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
72+
rootlesskit:test-integration ./benchmark-iperf3-net.sh pasta 1500 --detach-netns
6573
- name: "Benchmark: Network (MTU=1500, network driver=lxc-user-nic)"
6674
run: |
6775
docker run --rm --privileged \
@@ -79,6 +87,10 @@ jobs:
7987
run: |
8088
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
8189
rootlesskit:test-integration ./benchmark-iperf3-net.sh slirp4netns 65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto
90+
- name: "Benchmark: Network (MTU=65520, network driver=pasta)"
91+
run: |
92+
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
93+
rootlesskit:test-integration ./benchmark-iperf3-net.sh pasta 65520
8294
- name: "Benchmark: Network (MTU=65520, network driver=lxc-user-nic)"
8395
run: |
8496
docker run --rm --privileged \
@@ -88,22 +100,30 @@ jobs:
88100
docker run --rm --privileged \
89101
rootlesskit:test-integration ./benchmark-iperf3-net.sh rootful_veth 65520
90102
# ===== Benchmark: TCP Ports =====
91-
- name: "Benchmark: TCP Ports (port driver=slirp4netns)"
103+
- name: "Benchmark: TCP Ports (network driver=slirp4netns, port driver=slirp4netns)"
92104
run: |
93105
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
94106
rootlesskit:test-integration ./benchmark-iperf3-port.sh slirp4netns
95-
- name: "Benchmark: TCP Ports (port driver=slirp4netns) with detach-netns"
107+
- name: "Benchmark: TCP Ports (network driver=slirp4netns, port driver=slirp4netns) with detach-netns"
96108
run: |
97109
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
98110
rootlesskit:test-integration ./benchmark-iperf3-port.sh slirp4netns --detach-netns
99-
- name: "Benchmark: TCP Ports (port driver=builtin)"
111+
- name: "Benchmark: TCP Ports (network driver=slirp4netns, port driver=builtin)"
100112
run: |
101113
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
102114
rootlesskit:test-integration ./benchmark-iperf3-port.sh builtin
103-
- name: "Benchmark: TCP Ports (port driver=builtin) with detach-netns"
115+
- name: "Benchmark: TCP Ports (network driver=slirp4netns, port driver=builtin) with detach-netns"
104116
run: |
105117
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
106118
rootlesskit:test-integration ./benchmark-iperf3-port.sh builtin --detach-netns
119+
- name: "Benchmark: TCP Ports (network driver=pasta, port driver=implicit)"
120+
run: |
121+
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
122+
rootlesskit:test-integration ./benchmark-iperf3-port.sh implicit --net=pasta
123+
- name: "Benchmark: TCP Ports (network driver=pasta, port driver=implicit) with detach-netns"
124+
run: |
125+
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
126+
rootlesskit:test-integration ./benchmark-iperf3-port.sh implicit --net=pasta --detach-netns
107127
# ===== Benchmark: UDP Ports =====
108128
- name: "Benchmark: UDP Ports (port driver=slirp4netns)"
109129
run: |
@@ -121,6 +141,7 @@ jobs:
121141
run: |
122142
docker run --rm --security-opt seccomp=unconfined --security-opt apparmor=unconfined --device /dev/net/tun \
123143
rootlesskit:test-integration ./benchmark-iperf3-port-udp.sh builtin --detach-netns
144+
# pasta+builtin does not work with UDP yet
124145
test-integration-docker:
125146
name: "Integration test (Docker)"
126147
runs-on: ubuntu-latest
@@ -152,3 +173,10 @@ jobs:
152173
docker exec test docker info
153174
docker exec test ./integration-docker.sh
154175
docker rm -f test
176+
- name: "Docker Integration test: net=pasta, port-driver=implicit"
177+
run: |
178+
docker run -d --name test --network custom --privileged -e DOCKERD_ROOTLESS_ROOTLESSKIT_NET=pasta -e DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=implicit rootlesskit:test-integration-docker
179+
sleep 2
180+
docker exec test docker info
181+
docker exec test ./integration-docker.sh
182+
docker rm -f test

Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ ARG UBUNTU_VERSION=22.04
33
ARG SHADOW_VERSION=4.13
44
ARG SLIRP4NETNS_VERSION=v1.2.0
55
ARG VPNKIT_VERSION=0.5.0
6+
ARG PASST_VERSION=2023_06_27.289301b
67
ARG DOCKER_VERSION=24.0.2
78
ARG DOCKER_CHANNEL=stable
89

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

4647
FROM djs55/vpnkit:${VPNKIT_VERSION} AS vpnkit
4748

49+
FROM ubuntu:${UBUNTU_VERSION} AS passt
50+
ENV DEBIAN_FRONTEND=noninteractive
51+
RUN apt-get update && apt-get install -y git gcc libtool make
52+
RUN git clone https://passt.top/passt
53+
WORKDIR /passt
54+
ARG PASST_VERSION
55+
RUN git pull && git checkout $PASST_VERSION
56+
RUN make && make install
57+
4858
FROM ubuntu:${UBUNTU_VERSION} AS test-integration
4959
# iproute2: for `ip` command that rootlesskit needs to exec
5060
# liblxc-common and lxc-utils: for `lxc-user-nic` binary required for --net=lxc-user-nic
@@ -67,6 +77,7 @@ ARG SLIRP4NETNS_VERSION
6777
RUN curl -sSL -o /home/user/bin/slirp4netns https://github.com/rootless-containers/slirp4netns/releases/download/${SLIRP4NETNS_VERSION}/slirp4netns-x86_64 && \
6878
chmod +x /home/user/bin/slirp4netns
6979
COPY --from=vpnkit /vpnkit /home/user/bin/vpnkit
80+
COPY --from=passt /usr/local /usr/local
7081
ADD ./hack /home/user/hack
7182
RUN chown -R user:user /run/user/1000 /home/user
7283
USER user

cmd/rootlesskit-docker-proxy/main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,15 @@ func xmain(f *os.File) error {
152152
return fmt.Errorf("failed to call info API, probably RootlessKit binary is too old (needs to be v0.14.0 or later): %w", err)
153153
}
154154

155+
// info.PortDriver is currently nil for "none" and "implicit", but this may change in future
156+
if info.PortDriver == nil || info.PortDriver.Driver == "none" || info.PortDriver.Driver == "implicit" {
157+
realProxyExe, err := exec.LookPath(realProxy)
158+
if err != nil {
159+
return err
160+
}
161+
return syscall.Exec(realProxyExe, append([]string{realProxy}, os.Args[1:]...), os.Environ())
162+
}
163+
155164
// use loopback IP as the child IP, when port-driver="builtin"
156165
childIP := "127.0.0.1"
157166
if isIPv6(*hostIP) {

cmd/rootlesskit/category.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
const (
1111
CategoryState = "State"
1212
CategoryNetwork = "Network"
13+
CategoryPasta = "Network [pasta]"
1314
CategorySlirp4netns = "Network [slirp4netns]"
1415
CategoryVPNKit = "Network [vpnkit]"
1516
CategoryLXCUserNic = "Network [lxc-user-nic]"

cmd/rootlesskit/main.go

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/rootless-containers/rootlesskit/pkg/common"
1818
"github.com/rootless-containers/rootlesskit/pkg/copyup/tmpfssymlink"
1919
"github.com/rootless-containers/rootlesskit/pkg/network/lxcusernic"
20+
"github.com/rootless-containers/rootlesskit/pkg/network/pasta"
2021
"github.com/rootless-containers/rootlesskit/pkg/network/slirp4netns"
2122
"github.com/rootless-containers/rootlesskit/pkg/network/vpnkit"
2223
"github.com/rootless-containers/rootlesskit/pkg/parent"
@@ -77,9 +78,14 @@ See https://rootlesscontaine.rs/getting-started/common/ .
7778
}, CategoryState),
7879
Categorize(&cli.StringFlag{
7980
Name: "net",
80-
Usage: "network driver [host, slirp4netns, vpnkit, lxc-user-nic(experimental)]",
81+
Usage: "network driver [host, pasta(experimental), slirp4netns, vpnkit, lxc-user-nic(experimental)]",
8182
Value: "host",
8283
}, CategoryNetwork),
84+
Categorize(&cli.StringFlag{
85+
Name: "pasta-binary",
86+
Usage: "path of pasta binary for --net=pasta",
87+
Value: "pasta",
88+
}, CategoryPasta),
8389
Categorize(&cli.StringFlag{
8490
Name: "slirp4netns-binary",
8591
Usage: "path of slirp4netns binary for --net=slirp4netns",
@@ -112,24 +118,24 @@ See https://rootlesscontaine.rs/getting-started/common/ .
112118
}, CategoryLXCUserNic),
113119
Categorize(&cli.IntFlag{
114120
Name: "mtu",
115-
Usage: "MTU for non-host network (default: 65520 for slirp4netns, 1500 for others)",
121+
Usage: "MTU for non-host network (default: 65520 for pasta and slirp4netns, 1500 for others)",
116122
Value: 0, // resolved into 65520 for slirp4netns, 1500 for others
117123
}, CategoryNetwork),
118124
Categorize(&cli.StringFlag{
119125
Name: "cidr",
120-
Usage: "CIDR for slirp4netns network (default: 10.0.2.0/24)",
126+
Usage: "CIDR for pasta and slirp4netns networks (default: 10.0.2.0/24)",
121127
}, CategoryNetwork),
122128
Categorize(&cli.StringFlag{
123129
Name: "ifname",
124-
Usage: "Network interface name (default: tap0 for slirp4netns and vpnkit, eth0 for lxc-user-nic)",
130+
Usage: "Network interface name (default: tap0 for pasta, slirp4netns, and vpnkit; eth0 for lxc-user-nic)",
125131
}, CategoryNetwork),
126132
Categorize(&cli.BoolFlag{
127133
Name: "disable-host-loopback",
128134
Usage: "prohibit connecting to 127.0.0.1:* on the host namespace",
129135
}, CategoryNetwork),
130136
Categorize(&cli.BoolFlag{
131137
Name: "ipv6",
132-
Usage: "enable IPv6 routing. Unrelated to port forwarding. Only supported for slirp4netns. (experimental)",
138+
Usage: "enable IPv6 routing. Unrelated to port forwarding. Only supported for pasta and slirp4netns. (experimental)",
133139
}, CategoryNetwork),
134140
Categorize(&cli.StringSliceFlag{
135141
Name: "copy-up",
@@ -142,7 +148,7 @@ See https://rootlesscontaine.rs/getting-started/common/ .
142148
}, CategoryMount),
143149
Categorize(&cli.StringFlag{
144150
Name: "port-driver",
145-
Usage: "port driver for non-host network. [none, builtin, slirp4netns]",
151+
Usage: "port driver for non-host network. [none, implicit (for pasta), builtin, slirp4netns]",
146152
Value: "none",
147153
}, CategoryPort),
148154
Categorize(&cli.StringSliceFlag{
@@ -333,14 +339,14 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
333339
ipv6 := clicontext.Bool("ipv6")
334340
if ipv6 {
335341
logrus.Warn("ipv6 is experimental")
336-
if s := clicontext.String("net"); s != "slirp4netns" {
342+
if s := clicontext.String("net"); s != "pasta" && s != "slirp4netns" {
337343
logrus.Warnf("--ipv6 is discarded for --net=%s", s)
338344
}
339345
}
340346

341347
disableHostLoopback := clicontext.Bool("disable-host-loopback")
342348
if !disableHostLoopback && clicontext.String("net") != "host" {
343-
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)")
349+
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)")
344350
}
345351

346352
slirp4netnsAPISocketPath := ""
@@ -354,11 +360,30 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
354360
logrus.Warnf("unsupported mtu for --net=host: %d", mtu)
355361
}
356362
if ipnet != nil {
357-
return opt, errors.New("custom cidr is supported only for --net=slirp4netns")
363+
return opt, errors.New("custom cidr is not supported for --net=host")
358364
}
359365
if ifname != "" {
360366
return opt, errors.New("ifname cannot be specified for --net=host")
361367
}
368+
case "pasta":
369+
logrus.Warn("\"pasta\" network driver is experimental. Needs very recent version of pasta (see docs/network.md). No support for forwarding UDP ports (yet).")
370+
binary := clicontext.String("pasta-binary")
371+
if _, err := exec.LookPath(binary); err != nil {
372+
return opt, err
373+
}
374+
var implicitPortForward bool
375+
switch portDriver := clicontext.String("port-driver"); portDriver {
376+
case "none":
377+
implicitPortForward = false
378+
case "implicit":
379+
implicitPortForward = true
380+
default:
381+
return opt, errors.New("network \"pasta\" requires port driver \"none\" or \"implicit\"")
382+
}
383+
opt.NetworkDriver, err = pasta.NewParentDriver(&logrusDebugWriter{label: "network/pasta"}, binary, mtu, ipnet, ifname, disableHostLoopback, ipv6, implicitPortForward)
384+
if err != nil {
385+
return opt, err
386+
}
362387
case "slirp4netns":
363388
binary := clicontext.String("slirp4netns-binary")
364389
if _, err := exec.LookPath(binary); err != nil {
@@ -424,7 +449,7 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
424449
}
425450
case "vpnkit":
426451
if ipnet != nil {
427-
return opt, errors.New("custom cidr is supported only for --net=slirp4netns")
452+
return opt, errors.New("custom cidr is not supported for --net=vpnkit")
428453
}
429454
binary := clicontext.String("vpnkit-binary")
430455
if _, err := exec.LookPath(binary); err != nil {
@@ -434,7 +459,7 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
434459
case "lxc-user-nic":
435460
logrus.Warn("\"lxc-user-nic\" network driver is experimental")
436461
if ipnet != nil {
437-
return opt, errors.New("custom cidr is supported only for --net=slirp4netns")
462+
return opt, errors.New("custom cidr is not supported for --net=lxc-user-nic")
438463
}
439464
if !disableHostLoopback {
440465
logrus.Warn("--disable-host-loopback is implicitly set for lxc-user-nic")
@@ -456,6 +481,11 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
456481
if len(clicontext.StringSlice("publish")) != 0 {
457482
return opt, fmt.Errorf("port driver %q does not support publishing ports", s)
458483
}
484+
case "implicit":
485+
if clicontext.String("net") != "pasta" {
486+
return opt, errors.New("port driver requires pasta network")
487+
}
488+
// NOP
459489
case "slirp4netns":
460490
if clicontext.String("net") != "slirp4netns" {
461491
return opt, errors.New("port driver requires slirp4netns network")
@@ -529,6 +559,8 @@ func createChildOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey string
529559
switch s := clicontext.String("net"); s {
530560
case "host":
531561
// NOP
562+
case "pasta":
563+
opt.NetworkDriver = pasta.NewChildDriver()
532564
case "slirp4netns":
533565
opt.NetworkDriver = slirp4netns.NewChildDriver()
534566
case "vpnkit":
@@ -549,7 +581,7 @@ func createChildOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey string
549581
return opt, fmt.Errorf("unknown copy-up mode: %s", s)
550582
}
551583
switch s := clicontext.String("port-driver"); s {
552-
case "none":
584+
case "none", "implicit":
553585
// NOP
554586
case "slirp4netns":
555587
opt.PortDriver = slirp4netns_port.NewChildDriver()

docs/network.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
RootlessKit provides several drivers for providing network connectivity:
44

55
* `--net=host`: use host network namespace (default)
6+
* `--net=pasta`: use [pasta](https://passt.top/passt/) (experimental)
67
* `--net=slirp4netns`: use [slirp4netns](https://github.com/rootless-containers/slirp4netns) (recommended)
78
* `--net=vpnkit`: use [VPNKit](https://github.com/moby/vpnkit)
89
* `--net=lxc-user-nic`: use `lxc-user-nic` (experimental)
@@ -138,6 +139,29 @@ The network is configured as follows by default:
138139
As in `--net=slirp4netns`, specifying `--copy-up=/etc` and `--disable-host-loopback` is highly recommended.
139140
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.
140141

142+
### `--net=pasta` (experimental)
143+
144+
`--net=pasta` (since RootlessKit v2.0, EXPERIMENTAL) uses [pasta (passt)](https://passt.top/passt/).
145+
`--net=pasta` is expected to be used in conjunction with `--port-driver=implicit`.
146+
147+
> **Note**
148+
> `--net=pasta` needs [pasta (passt)](https://passt.top/passt/) `2023_06_25.32660ce` or later.
149+
>
150+
> Depending on the version of pasta and the host operating system,
151+
> running `sudo apparmor_parser -R /etc/apparmor.d/usr.bin.passt` might be needed too.
152+
153+
Pros:
154+
* Possible to perform network-namespaced operations, e.g. creating iptables rules, running `tcpdump`
155+
* Supports ICMP Echo (`ping`) when `/proc/sys/net/ipv4/ping_group_range` is configured
156+
* TCP port forwarding (`--port-driver=implicit`) is very fast
157+
* TCP port forwarding (`--port-driver=implicit`) can retain source IP addresses
158+
159+
Cons:
160+
* UDP port forwarding is not supported yet
161+
162+
The network configuration for pasta is similar to slirp4netns.
163+
As in `--net=slirp4netns`, specifying `--copy-up=/etc` and `--disable-host-loopback` is highly recommended.
164+
141165
### `--net=lxc-user-nic` (experimental)
142166

143167
`--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.

docs/port.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ The default value is `none` (do not expose ports).
1212

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

15-
The `builtin` driver is fastest, but be aware that the source IP is not propagated and always set to 127.0.0.1.
15+
The `builtin` driver is fast, but be aware that the source IP is not propagated and always set to 127.0.0.1.
16+
17+
For [`pasta`](./network.md) networks, the `implicit` port driver is the best choice.
18+
19+
* To be documented: [`bypass4netns`](https://github.com/rootless-containers/bypass4netns) for native performance.
1620

1721
### Exposing ports
1822
For example, to expose 80 in the child as 8080 in the parent:

hack/benchmark-iperf3-net.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
#!/bin/bash
22
source $(realpath $(dirname $0))/common.inc.sh
3+
function benchmark::iperf3::pasta() {
4+
INFO "[benchmark:iperf3] slirp4netns ($@)"
5+
statedir=$(mktemp -d)
6+
if echo "$@" | grep -q -- --detach-netns; then
7+
IPERF3C="nsenter -n${statedir}/netns $IPERF3C"
8+
fi
9+
set -x
10+
$ROOTLESSKIT --state-dir=$statedir --net=slirp4netns $@ -- $IPERF3C 10.0.2.2
11+
set +x
12+
}
13+
314
function benchmark::iperf3::slirp4netns() {
415
INFO "[benchmark:iperf3] slirp4netns ($@)"
516
statedir=$(mktemp -d)

0 commit comments

Comments
 (0)