Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rootlesskit for openSUSE:Factory checked in at 2026-08-11 17:09:33 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rootlesskit (Old) and /work/SRC/openSUSE:Factory/.rootlesskit.new.17972 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rootlesskit" Tue Aug 11 17:09:33 2026 rev:19 rq:1370455 version:3.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/rootlesskit/rootlesskit.changes 2026-07-20 09:56:52.495152984 +0200 +++ /work/SRC/openSUSE:Factory/.rootlesskit.new.17972/rootlesskit.changes 2026-08-11 17:09:49.751472622 +0200 @@ -1,0 +2,14 @@ +Mon Aug 10 05:05:25 UTC 2026 - Yash Maurya <[email protected]> + +- Update to version 3.1.0: + * v3.1.0 + * feat: support --port-driver=pesto for IPv4 + * docs: refresh network driver benchmark table + * fix: benchmark pasta, not slirp4netns + * fix: correct field names in ParsePortSpec error messages + * fix(pasta): block host loopback access from the namespace when `--disable-host-loopback` is set + * Build(deps): Bump actions/setup-go from 6 to 7 + * chore: update error message when the pasta binary dosen't exist + * v3.0.2+dev + +------------------------------------------------------------------- Old: ---- rootlesskit-3.0.2.tar.gz New: ---- rootlesskit-3.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rootlesskit.spec ++++++ --- /var/tmp/diff_new_pack.w0Pmui/_old 2026-08-11 17:09:51.783558751 +0200 +++ /var/tmp/diff_new_pack.w0Pmui/_new 2026-08-11 17:09:51.787558921 +0200 @@ -17,7 +17,7 @@ Name: rootlesskit -Version: 3.0.2 +Version: 3.1.0 Release: 0 Summary: Linux-native fakeroot using user namespaces License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.w0Pmui/_old 2026-08-11 17:09:51.819560277 +0200 +++ /var/tmp/diff_new_pack.w0Pmui/_new 2026-08-11 17:09:51.827560616 +0200 @@ -4,7 +4,7 @@ <param name="url">https://github.com/rootless-containers/rootlesskit.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v3.0.2</param> + <param name="revision">v3.1.0</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.w0Pmui/_old 2026-08-11 17:09:51.855561803 +0200 +++ /var/tmp/diff_new_pack.w0Pmui/_new 2026-08-11 17:09:51.859561973 +0200 @@ -1,5 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/rootless-containers/rootlesskit.git</param> - <param name="changesrevision">6c019736083e46424b4b657d84ec0db510a8ef40</param></service></servicedata> + <param name="changesrevision">62d2101fbbe4f79bc845a337c4e868d27ff602c9</param></service></servicedata> +(No newline at EOF) ++++++ rootlesskit-3.0.2.tar.gz -> rootlesskit-3.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/Dockerfile new/rootlesskit-3.1.0/Dockerfile --- old/rootlesskit-3.0.2/Dockerfile 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/Dockerfile 2026-08-04 10:50:20.000000000 +0200 @@ -3,7 +3,7 @@ ARG SHADOW_VERSION=4.17.4 ARG SLIRP4NETNS_VERSION=v1.3.4 ARG VPNKIT_VERSION=0.6.0 -ARG PASST_VERSION=2026_01_20.386b5f5 +ARG PASST_VERSION=2026_07_28.f8df3f1 ARG DOCKER_VERSION=29.5.2 ARG DOCKER_CHANNEL=stable diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/README.md new/rootlesskit-3.1.0/README.md --- old/rootlesskit-3.0.2/README.md 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/README.md 2026-08-04 10:50:20.000000000 +0200 @@ -154,7 +154,7 @@ rootlesskit [global options] [arguments...] VERSION: - 3.0.0 + 3.1.0 DESCRIPTION: RootlessKit is a Linux-native implementation of "fake root" using user_namespaces(7). @@ -199,11 +199,12 @@ --detach-netns detach network namespaces (default: false) Network [lxc-user-nic]: - --lxc-user-nic-binary value path of lxc-user-nic binary for --net=lxc-user-nic + --lxc-user-nic-binary value path of lxc-user-nic binary for --net=lxc-user-nic (default: "/usr/libexec/lxc/lxc-user-nic") --lxc-user-nic-bridge value lxc-user-nic bridge name (default: "lxcbr0") Network [pasta]: --pasta-binary value path of pasta binary for --net=pasta (default: "pasta") + --pesto-binary value path of pesto binary for --port-driver=pesto (default: "pesto") Network [slirp4netns]: --slirp4netns-binary value path of slirp4netns binary for --net=slirp4netns (default: "slirp4netns") @@ -214,7 +215,7 @@ --vpnkit-binary value path of VPNKit binary for --net=vpnkit (default: "vpnkit") Port: - --port-driver value port driver for non-host network. [none, implicit (for pasta), builtin, slirp4netns, gvisor-tap-vsock(experimental)] (default: "none") + --port-driver value port driver for non-host network. [none, implicit (for pasta), builtin, slirp4netns, gvisor-tap-vsock(experimental), pesto(experimental)] (default: "none") --publish value, -p value [ --publish value, -p value ] publish ports. e.g. "127.0.0.1:8080:80/tcp" --source-ip-transparent preserve real client source IP using IP_TRANSPARENT (builtin port driver, TCP only) (default: true) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/cmd/rootlesskit/main.go new/rootlesskit-3.1.0/cmd/rootlesskit/main.go --- old/rootlesskit-3.0.2/cmd/rootlesskit/main.go 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/cmd/rootlesskit/main.go 2026-08-04 10:50:20.000000000 +0200 @@ -28,6 +28,7 @@ "github.com/rootless-containers/rootlesskit/v3/pkg/parent" "github.com/rootless-containers/rootlesskit/v3/pkg/port/builtin" gvisortapvsock_port "github.com/rootless-containers/rootlesskit/v3/pkg/port/gvisortapvsock" + pasta_port "github.com/rootless-containers/rootlesskit/v3/pkg/port/pasta" "github.com/rootless-containers/rootlesskit/v3/pkg/port/portutil" slirp4netns_port "github.com/rootless-containers/rootlesskit/v3/pkg/port/slirp4netns" "github.com/rootless-containers/rootlesskit/v3/pkg/systemd/activation" @@ -108,6 +109,9 @@ if gvisortapvsock_port.Available { portDrivers = append(portDrivers, "gvisor-tap-vsock(experimental)") } + if pasta_port.Available { + portDrivers = append(portDrivers, "pesto(experimental)") + } portDriversHelp := strings.Join(portDrivers, ", ") app.Flags = []cli.Flag{ @@ -135,6 +139,11 @@ Value: "pasta", }, CategoryPasta), Categorize(&cli.StringFlag{ + Name: "pesto-binary", + Usage: "path of pesto binary for --port-driver=pesto", + Value: "pesto", + }, CategoryPasta), + Categorize(&cli.StringFlag{ Name: "slirp4netns-binary", Usage: "path of slirp4netns binary for --net=slirp4netns", Value: "slirp4netns", @@ -431,6 +440,14 @@ if clicontext.String("port-driver") == "slirp4netns" { slirp4netnsAPISocketPath = filepath.Join(opt.StateDir, ".s4nn.sock") } + portDriver := clicontext.String("port-driver") + if portDriver == "pasta" { + return opt, errors.New(`unknown port driver "pasta": did you mean "pesto"?`) + } + pestoSocketPath := "" + if portDriver == "pesto" { + pestoSocketPath = filepath.Join(opt.StateDir, ".pasta.sock") + } switch s := clicontext.String("net"); s { case "host": // NOP @@ -471,14 +488,14 @@ } var implicitPortForward bool switch portDriver := clicontext.String("port-driver"); portDriver { - case "none": + case "none", "pesto": implicitPortForward = false case "implicit": implicitPortForward = true default: - return opt, errors.New("network \"pasta\" requires port driver \"none\" or \"implicit\"") + return opt, errors.New("network \"pasta\" requires port driver \"none\" or \"implicit\" or \"pesto\"") } - opt.NetworkDriver, err = pasta.NewParentDriver(&logrusDebugWriter{label: "network/pasta"}, binary, mtu, ipnet, ifname, disableHostLoopback, ipv6, implicitPortForward) + opt.NetworkDriver, err = pasta.NewParentDriver(&logrusDebugWriter{label: "network/pasta"}, binary, mtu, ipnet, ifname, pestoSocketPath, disableHostLoopback, ipv6, implicitPortForward) if err != nil { return opt, err } @@ -610,7 +627,7 @@ } case "implicit": if clicontext.String("net") != "pasta" { - return opt, errors.New("port driver requires pasta network") + return opt, errors.New("port driver \"implicit\" requires --net=pasta") } // NOP case "slirp4netns": @@ -638,6 +655,18 @@ if err != nil { return opt, err } + case "pesto": + if clicontext.String("net") != "pasta" { + return opt, errors.New("port driver \"pesto\" requires --net=pasta") + } + binary := clicontext.String("pesto-binary") + if _, err := exec.LookPath(binary); err != nil { + return opt, err + } + opt.PortDriver, err = pasta_port.NewParentDriver(&logrusDebugWriter{label: "port/pesto"}, binary, pestoSocketPath) + if err != nil { + return opt, err + } default: return opt, fmt.Errorf("unknown port driver: %s", s) } @@ -731,6 +760,8 @@ opt.PortDriver = builtin.NewChildDriver(&logrusDebugWriter{label: "port/builtin"}) case "gvisor-tap-vsock": opt.PortDriver = gvisortapvsock_port.NewChildDriver() + case "pesto": + opt.PortDriver = pasta_port.NewChildDriver() default: return opt, fmt.Errorf("unknown port driver: %s", s) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/docs/network.md new/rootlesskit-3.1.0/docs/network.md --- old/rootlesskit-3.0.2/docs/network.md 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/docs/network.md 2026-08-04 10:50:20.000000000 +0200 @@ -9,17 +9,17 @@ * `--net=lxc-user-nic`: use `lxc-user-nic` (experimental) * `--net=gvisor-tap-vsock`: use [gvisor-tap-vsock](https://github.com/containers/gvisor-tap-vsock) (experimental) -[Benchmark: iperf3 from the child to the parent (Apr 10, 2026)](https://github.com/rootless-containers/rootlesskit/actions/runs/24200485791/job/70642399211): +[Benchmark: iperf3 from the child to the parent (Jul 26, 2026)](https://github.com/rootless-containers/rootlesskit/actions/runs/30191669102/job/89765864664): | Driver | MTU=1500 | MTU=65520 |---------------------------------------|------------|------------- -|`slirp4netns` | 0.84 Gbps | 6.17 Gbps -|`slirp4netns` (with sandbox + seccomp) | 0.80 Gbps | 6.19 Gbps -|`vpnkit` | 0.10 Gbps |(Unsupported) -|`pasta` | 0.87 Gbps | 6.48 Gbps -|`gvisor-tap-vsock` | 1.55 Gbps | 5.40 Gbps -|`lxc-user-nic` | 29.3 Gbps | 30.4 Gbps -|(rootful veth) | (35.0 Gbps)| (36.2 Gbps) +|`slirp4netns` | 1.69 Gbps | 8.11 Gbps +|`slirp4netns` (with sandbox + seccomp) | 1.68 Gbps | 8.32 Gbps +|`vpnkit` | 0.14 Gbps |(Unsupported) +|`pasta` | 0.24 Gbps | 31.9 Gbps +|`gvisor-tap-vsock` | 2.46 Gbps | 8.75 Gbps +|`lxc-user-nic` | 49.1 Gbps | 50.7 Gbps +|(rootful veth) | (49.3 Gbps)| (50.8 Gbps) * To be documented: [`bypass4netns`](https://github.com/rootless-containers/bypass4netns) for native performance. @@ -147,7 +147,7 @@ ### `--net=pasta` (experimental) `--net=pasta` (since RootlessKit v2.0, EXPERIMENTAL) uses [pasta (passt)](https://passt.top/passt/). -`--net=pasta` is expected to be used in conjunction with `--port-driver=implicit`. +`--net=pasta` is expected to be used in conjunction with `--port-driver=pesto` or `--port-driver=implicit`. > **Note** > `--net=pasta` needs [pasta (passt)](https://passt.top/passt/) > `2023_06_25.32660ce` or later. @@ -170,9 +170,7 @@ * Supports ICMP Echo (`ping`) when `/proc/sys/net/ipv4/ping_group_range` is configured * TCP port forwarding (`--port-driver=implicit`) is very fast * TCP port forwarding (`--port-driver=implicit`) can retain source IP addresses - -Cons: -* Lacks API for explicit port forwarding (`rootlessctl (list-ports|add-ports|remove-ports)`) +* Explicit port forwarding via `rootlessctl (list-ports|add-ports|remove-ports)` is supported with `--port-driver=pesto` (experimental, IPv4-only, requires pasta `2026_05_07.1afd4ed` or later) The network configuration for pasta is similar to slirp4netns. As in `--net=slirp4netns`, specifying `--copy-up=/etc` and `--disable-host-loopback` is highly recommended. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/docs/port.md new/rootlesskit-3.1.0/docs/port.md --- old/rootlesskit-3.0.2/docs/port.md 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/docs/port.md 2026-08-04 10:50:20.000000000 +0200 @@ -9,13 +9,20 @@ | `slirp4netns` | 8.03 Gbps | Propagated | | `builtin` | 29.9 Gbps | Propagated for TCP (since v3.0) | Source IP propagation (`--source-ip-transparent`) applies to TCP only; UDP is not propagated. In the case of Rootless Docker, userland-proxy has to be disabled for propagating the source IP. | `implicit` | 37.6 Gbps | Propagated | Requires `pasta` network +| `pesto` (Experimental) | TBD | Propagated | Requires `pasta` network and passt `2026_05_07.1afd4ed` or later. Supports explicit port management through `rootlessctl`. | `gvisor-tap-vsock` (Experimental) | 3.83 Gbps | Not propagated | Throughput is currently limited; see issue link below for improvement ideas. Benchmark: iperf3 from the parent to the child is measured on GitHub Actions ([Apr 10, 2026](https://github.com/rootless-containers/rootlesskit/actions/runs/24200485791/job/70642399211)) The `builtin` driver is fast and should be the best choice for most use cases. -For [`pasta`](./network.md) networks, the `implicit` port driver is the best choice. +For [`pasta`](./network.md) networks, choose the port driver based on how +ports should be exposed: + +* Use `pesto` to expose only explicitly published ports using `rootlessctl list-ports`, `add-ports`, and `remove-ports`. +* Use `implicit` to automatically expose ports listened on in the network namespace. This may expose ports that were not intended to be reachable from the host. + +The `pesto` port driver is experimental and currently supports IPv4 port forwarding only. Both the `pasta` and `pesto` executables must be installed. > [!NOTE] > The `gvisor-tap-vsock` port driver is experimental. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/hack/benchmark-iperf3-net.sh new/rootlesskit-3.1.0/hack/benchmark-iperf3-net.sh --- old/rootlesskit-3.0.2/hack/benchmark-iperf3-net.sh 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/hack/benchmark-iperf3-net.sh 2026-08-04 10:50:20.000000000 +0200 @@ -1,13 +1,13 @@ #!/bin/bash source $(realpath $(dirname $0))/common.inc.sh function benchmark::iperf3::pasta() { - INFO "[benchmark:iperf3] slirp4netns ($@)" + INFO "[benchmark:iperf3] pasta ($@)" 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 + $ROOTLESSKIT --state-dir=$statedir --net=pasta $@ -- $IPERF3C 10.0.2.2 set +x } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/hack/benchmark-iperf3-port.sh new/rootlesskit-3.1.0/hack/benchmark-iperf3-port.sh --- old/rootlesskit-3.0.2/hack/benchmark-iperf3-port.sh 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/hack/benchmark-iperf3-port.sh 2026-08-04 10:50:20.000000000 +0200 @@ -19,6 +19,8 @@ portid=$($rootlessctl add-ports 127.0.0.1:5201:5201/tcp) $rootlessctl list-ports fi + # wait for the port to be bound + sleep 3 $IPERF3C 127.0.0.1 if [ $portdriver != "implicit" ]; then $rootlessctl remove-ports $portid diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/hack/integration-net.sh new/rootlesskit-3.1.0/hack/integration-net.sh --- old/rootlesskit-3.0.2/hack/integration-net.sh 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/hack/integration-net.sh 2026-08-04 10:50:20.000000000 +0200 @@ -19,3 +19,30 @@ sudo /usr/lib/$(uname -m)-linux-gnu/lxc/lxc-net start || sudo /etc/init.d/lxc-net start || true fi $ROOTLESSKIT --net=${net} --copy-up=/etc --copy-up=/run --disable-host-loopback ${flags} -- nslookup example.com + +# Test that a server process listening on the host loopback is not accessible from the isolated namespace +tmp=$(mktemp -d) +echo "hello host loopback" >${tmp}/index.html + +busybox httpd -f -p 127.0.0.1:8080 -h ${tmp} & +pid=$! + +sleep 1 + +statedir=$(mktemp -d) +CURL="timeout 3 curl -fsSL http://127.0.0.1:8080" +if echo "${flags}" | grep -q -- --detach-netns; then + # With --detach-netns, the child command runs in the host's network namespace, + # so it has to enter the detached network namespace explicitly. + CURL="nsenter -n${statedir}/netns ${CURL}" +fi + +if $ROOTLESSKIT --state-dir=${statedir} --net=${net} --copy-up=/etc --copy-up=/run --disable-host-loopback ${flags} -- ${CURL}; then + ERROR "the host loopback should not be accessible from the isolated namespace" + exit 1 +fi + +INFO "the host loopback is not accessible from the isolated namespace, as expected" + +kill -9 $pid || true +rm -rf ${tmp} ${statedir} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/hack/integration-port.sh new/rootlesskit-3.1.0/hack/integration-port.sh --- old/rootlesskit-3.0.2/hack/integration-port.sh 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/hack/integration-port.sh 2026-08-04 10:50:20.000000000 +0200 @@ -1,6 +1,8 @@ #!/bin/bash source $(realpath $(dirname $0))/common.inc.sh +ROOTLESSCTL="rootlessctl" + # test_port PORT_DRIVER CURL_URL EXPECTATION [ROOTLESSKIT ARGS...] function test_port() { args="$@" @@ -12,19 +14,30 @@ shift rootlesskit_args="$@" INFO "Testing port_driver=\"${port_driver}\" curl_url=\"${curl_url}\" expectation=\"${expectation}\" rootlesskit_args=\"${rootlesskit_args}\"" + net=slirp4netns + if [ "${port_driver}" = "pesto" ]; then + # The pesto port driver is specific to the pasta network driver + net=pasta + fi tmp=$(mktemp -d) state_dir=${tmp}/state html_dir=${tmp}/html mkdir -p ${html_dir} echo "test_port ($args)" >${html_dir}/index.html + httpd="busybox httpd -f -v -p 80 -h ${html_dir}" + if echo "${rootlesskit_args}" | grep -q -- --detach-netns; then + # With --detach-netns, the child command runs in the host's network + # namespace, so the server has to enter the detached netns explicitly. + httpd="nsenter -n${state_dir}/netns ${httpd}" + fi $ROOTLESSKIT \ --state-dir=${state_dir} \ - --net=slirp4netns \ + --net=${net} \ --disable-host-loopback \ --copy-up=/etc \ --port-driver=${port_driver} \ ${rootlesskit_args} \ - busybox httpd -f -v -p 80 -h ${html_dir} \ + ${httpd} \ 2>&1 & pid=$! sleep 1 @@ -49,8 +62,108 @@ fi INFO "Test pasing, stopping httpd (\"exit status 255\" is negligible here)" - kill -SIGTERM $(cat ${state_dir}/child_pid) + # child_pid does not exist when rootlesskit itself was expected to fail on startup + if [ -f ${state_dir}/child_pid ]; then + kill -SIGTERM $(cat ${state_dir}/child_pid) || true + fi wait $pid >/dev/null 2>&1 || true + wait_for_pasta_exit + rm -rf $tmp +} + +function wait_for_pasta_exit() { + for i in $(seq 50); do + pgrep -x pasta >/dev/null || break + sleep 0.1 + done +} + +# test_pesto_dynamic [ROOTLESSKIT ARGS...] +function test_pesto_dynamic() { + rootlesskit_args="$@" + INFO "Testing dynamic port management (rootlesskit_args=\"${rootlesskit_args}\")" + tmp=$(mktemp -d) + state_dir=${tmp}/state + html_dir=${tmp}/html + mkdir -p ${html_dir} + echo "test_pesto_dynamic" >${html_dir}/index.html + httpd="busybox httpd -f -v -p 80 -h ${html_dir}" + if echo "${rootlesskit_args}" | grep -q -- --detach-netns; then + # With --detach-netns, the child command runs in the host's network + # namespace, so the server has to enter the detached netns explicitly. + httpd="nsenter -n${state_dir}/netns ${httpd}" + fi + $ROOTLESSKIT \ + --state-dir=${state_dir} \ + --net=pasta \ + --disable-host-loopback \ + --copy-up=/etc \ + --port-driver=pesto \ + ${rootlesskit_args} \ + ${httpd} \ + 2>&1 & + pid=$! + sleep 1 + api_sock=${state_dir}/api.sock + + INFO "= the port must not be reachable before add-ports =" + if curl -fsSL http://127.0.0.1:8080; then + ERROR "curl should not success before add-ports" + exit 1 + fi + + INFO "= add-ports, then the port must be reachable =" + id=$($ROOTLESSCTL --socket=${api_sock} add-ports 127.0.0.1:8080:80/tcp) + curl -fsSL http://127.0.0.1:8080 + $ROOTLESSCTL --socket=${api_sock} list-ports --json | grep -q '"parentPort":8080' + + INFO "= adding a conflicting port must fail =" + if $ROOTLESSCTL --socket=${api_sock} add-ports 127.0.0.1:8080:80/tcp; then + ERROR "add-ports should fail for a conflicting port" + exit 1 + fi + + INFO "= ChildIP matching the namespace address (10.0.2.100) is accepted =" + # 10.0.2.100 is the first address of the default CIDR (10.0.2.0/24) + 100 + $ROOTLESSCTL --socket=${api_sock} add-ports 127.0.0.1:8081:10.0.2.100:80/tcp + curl -fsSL http://127.0.0.1:8081 + + INFO "= any other ChildIP must be rejected (pasta cannot honor it) =" + if $ROOTLESSCTL --socket=${api_sock} add-ports 127.0.0.1:8083:10.9.9.9:80/tcp; then + ERROR "add-ports should fail for an unsupported ChildIP" + exit 1 + fi + + INFO "= unsupported proto (tcp6) must be rejected =" + if $ROOTLESSCTL --socket=${api_sock} add-ports :8082:80/tcp6; then + ERROR "add-ports should fail for tcp6" + exit 1 + fi + + INFO "= udp rules can be added and removed =" + udp_id=$($ROOTLESSCTL --socket=${api_sock} add-ports 127.0.0.1:5353:5353/udp) + $ROOTLESSCTL --socket=${api_sock} list-ports --json | grep -q '"proto":"udp"' + $ROOTLESSCTL --socket=${api_sock} remove-ports ${udp_id} + + INFO "= remove-ports, then new connections must be refused =" + $ROOTLESSCTL --socket=${api_sock} remove-ports ${id} + if curl -fsSL http://127.0.0.1:8080; then + ERROR "curl should not success after remove-ports" + exit 1 + fi + + INFO "= removing an unknown port ID must fail =" + if $ROOTLESSCTL --socket=${api_sock} remove-ports ${id}; then + ERROR "remove-ports should fail for an unknown ID" + exit 1 + fi + + INFO "Test passing, stopping httpd (\"exit status 255\" is negligible here)" + if [ -f ${state_dir}/child_pid ]; then + kill -SIGTERM $(cat ${state_dir}/child_pid) || true + fi + wait $pid >/dev/null 2>&1 || true + wait_for_pasta_exit rm -rf $tmp } @@ -89,4 +202,26 @@ INFO "=== protocol \"tcp4\" is strictly v4-only ===" test_port slirp4netns http://[::1]:8080 "should fail" -p 0.0.0.0:8080:80/tcp4 +INFO "===== Port driver: pesto (--net=pasta) =====" +INFO "=== static publishing via -p ===" +test_port pesto http://127.0.0.1:8080 "should success" -p 0.0.0.0:8080:80/tcp +test_port pesto http://127.0.0.1:8080 "should success" -p 127.0.0.1:8080:80/tcp +test_port pesto http://127.0.0.1:8080 "should success" -p 0.0.0.0:8080:80/tcp4 + +INFO "=== port translation (8080 -> 80) ===" +test_port pesto http://127.0.0.1:8080 "should success" -p :8080:80/tcp + +INFO "=== a port that is not published must not be reachable ===" +test_port pesto http://127.0.0.1:9090 "should fail" -p 0.0.0.0:8080:80/tcp + +INFO "=== unsupported proto (tcp6) makes the startup fail ===" +test_port pesto http://[::1]:8080 "should fail" -p [::]:8080:80/tcp6 + +INFO "=== dynamic port management via rootlessctl ===" +test_pesto_dynamic + +INFO "=== with --detach-netns ===" +test_port pesto http://127.0.0.1:8080 "should success" -p 0.0.0.0:8080:80/tcp --detach-netns +test_pesto_dynamic --detach-netns + INFO "===== PASSING =====" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/pkg/network/pasta/pasta.go new/rootlesskit-3.1.0/pkg/network/pasta/pasta.go --- old/rootlesskit-3.0.2/pkg/network/pasta/pasta.go 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/pkg/network/pasta/pasta.go 2026-08-04 10:50:20.000000000 +0200 @@ -23,6 +23,9 @@ // Has `--host-lo-to-ns-lo` (introduced in passt 2024_10_30.ee7d0b6) // https://passt.top/passt/commit/?id=b4dace8f462b346ae2135af1f8d681a99a849a5f HasHostLoToNsLo bool + // Has `--conf-path` (introduced in passt 2026_05_07.1afd4ed) + // https://passt.top/passt/commit/?id=f1d893ca1926e58ae5a2bf5602b515a883f3f994 + HasConfPath bool } func DetectFeatures(binary string) (*Features, error) { @@ -46,14 +49,18 @@ if cmd.Run() == nil { f.HasHostLoToNsLo = true } + cmd = exec.Command(realBinary, "--conf-path=/dev/null", "--version") + if cmd.Run() == nil { + f.HasConfPath = true + } return &f, nil } // NewParentDriver instantiates new parent driver. -func NewParentDriver(logWriter io.Writer, binary string, mtu int, ipnet *net.IPNet, ifname string, +func NewParentDriver(logWriter io.Writer, binary string, mtu int, ipnet *net.IPNet, ifname, apiSocketPath string, disableHostLoopback, enableIPv6, implicitPortForwarding bool) (network.ParentDriver, error) { if binary == "" { - return nil, errors.New("got empty slirp4netns binary") + return nil, errors.New("got empty pasta binary") } if mtu < 0 { return nil, errors.New("got negative mtu") @@ -79,6 +86,10 @@ return nil, err } + if !feat.HasConfPath && apiSocketPath != "" { + return nil, errors.New("port driver \"pesto\" requires pasta with --conf-path support (passt >= 2026_05_07.1afd4ed)") + } + return &parentDriver{ logWriter: logWriter, binary: binary, @@ -87,6 +98,7 @@ disableHostLoopback: disableHostLoopback, enableIPv6: enableIPv6, ifname: ifname, + apiSocketPath: apiSocketPath, implicitPortForwarding: implicitPortForwarding, feat: feat, }, nil @@ -100,6 +112,7 @@ disableHostLoopback bool enableIPv6 bool ifname string + apiSocketPath string infoMu sync.RWMutex implicitPortForwarding bool info func() *api.NetworkDriverInfo @@ -155,7 +168,7 @@ "--dns-forward=" + dns.String(), } if d.disableHostLoopback { - opts = append(opts, "--no-map-gw") + opts = append(opts, "--no-map-gw", "--tcp-ns=none", "--udp-ns=none") } if !d.enableIPv6 { opts = append(opts, "--ipv4-only") @@ -168,7 +181,10 @@ "--udp-ports=none") } if d.feat != nil { - if d.feat.HasHostLoToNsLo { + // Skipped for the "pesto" port driver: inbound connections must be + // forwarded to the child IP, because that is where the listener in the + // namespace (e.g. docker-proxy) is bound. + if d.feat.HasHostLoToNsLo && d.apiSocketPath == "" { // Needed to keep `docker run -p 127.0.0.1:8080:80` functional with // passt >= 2024_10_30.ee7d0b6 // @@ -176,6 +192,10 @@ opts = append(opts, "--host-lo-to-ns-lo") } } + if d.apiSocketPath != "" { + // passt >= 2026_05_07.1afd4ed + opts = append(opts, "--conf-path="+d.apiSocketPath) + } if detachedNetNSPath == "" { opts = append(opts, strconv.Itoa(childPID)) } else { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/pkg/port/pasta/pesto.go new/rootlesskit-3.1.0/pkg/port/pasta/pesto.go --- old/rootlesskit-3.0.2/pkg/port/pasta/pesto.go 1970-01-01 01:00:00.000000000 +0100 +++ new/rootlesskit-3.1.0/pkg/port/pasta/pesto.go 2026-08-04 10:50:20.000000000 +0200 @@ -0,0 +1,187 @@ +package pasta + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "os/exec" + "sync" + + "github.com/rootless-containers/rootlesskit/v3/pkg/api" + "github.com/rootless-containers/rootlesskit/v3/pkg/port" + "github.com/rootless-containers/rootlesskit/v3/pkg/port/portutil" + "github.com/sirupsen/logrus" +) + +func NewParentDriver(logWriter io.Writer, binary, socketPath string) (port.ParentDriver, error) { + if socketPath == "" { + return nil, errors.New("configuration socket path is not set") + } + + cmd := exec.Command(binary, "--version") + b, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf(`command "%s --version" failed, make sure pesto is installed: %q: %w`, + binary, string(b), err) + } + + d := driver{ + logWriter: logWriter, + ports: make(map[int]*port.Status), + apiSocketPath: socketPath, + binary: binary, + nextID: 1, + } + + return &d, nil +} + +type driver struct { + logWriter io.Writer + apiSocketPath string + mu sync.Mutex + childIP string // can be empty + ports map[int]*port.Status + binary string + nextID int +} + +func (d *driver) Info(ctx context.Context) (*api.PortDriverInfo, error) { + info := &api.PortDriverInfo{ + Driver: "pesto", + // No IPv6 support yet + Protos: []string{"tcp", "tcp4", "udp", "udp4"}, + DisallowLoopbackChildIP: true, + } + return info, nil +} + +func (d *driver) OpaqueForChild() map[string]string { + // NOP, as this driver does not have child-side logic. + return nil +} + +func (d *driver) RunParentDriver(initComplete chan struct{}, quit <-chan struct{}, cctx *port.ChildContext) error { + if cctx != nil && cctx.IP != nil { + if cctx.IP.To4() != nil { + d.childIP = cctx.IP.To4().String() + } + } + initComplete <- struct{}{} + <-quit + return nil +} + +func (d *driver) createArgs(method string, spec port.Spec) ([]string, error) { + opts := []string{method} + o := "" + + switch spec.Proto { + case "tcp", "tcp4": + o += "--tcp-ports=" + case "udp", "udp4": + o += "--udp-ports=" + default: + return nil, fmt.Errorf("unsupported protocol: %s", spec.Proto) + } + parentIP := spec.ParentIP + if parentIP == "" { + parentIP = "0.0.0.0" + } + p := net.ParseIP(parentIP) + if p == nil { + return nil, fmt.Errorf("invalid IP: %q", parentIP) + } + p = p.To4() + if p == nil { + return nil, fmt.Errorf("unsupported IP: %s", parentIP) + } + o += fmt.Sprintf("%s/%d:%d", p.String(), spec.ParentPort, spec.ChildPort) + opts = append(opts, o) + + return append(opts, d.apiSocketPath), nil +} + +func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) { + d.mu.Lock() + defer d.mu.Unlock() + + if err := portutil.ValidatePortSpec(spec, d.ports); err != nil { + return nil, err + } + if spec.ChildIP != "" && spec.ChildIP != d.childIP { + return nil, fmt.Errorf("unsupported ChildIP %q: the pesto port driver only supports the namespace address %q", spec.ChildIP, d.childIP) + } + + opts, err := d.createArgs("-A", spec) + if err != nil { + return nil, err + } + cmd := exec.Command(d.binary, opts...) + logrus.Debugf("Executing %v", cmd.Args) + out, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("pesto failed: %w\noutput: %s", err, out) + } + + id := d.nextID + st := port.Status{ + ID: id, + Spec: spec, + } + d.ports[id] = &st + d.nextID++ + + return &st, nil +} + +func (d *driver) ListPorts(ctx context.Context) ([]port.Status, error) { + var ports []port.Status + d.mu.Lock() + for _, p := range d.ports { + ports = append(ports, *p) + } + d.mu.Unlock() + return ports, nil +} + +func (d *driver) RemovePort(ctx context.Context, id int) error { + d.mu.Lock() + defer d.mu.Unlock() + + st, ok := d.ports[id] + if !ok { + return fmt.Errorf("invalid ID: %d", id) + } + opts, err := d.createArgs("-D", st.Spec) + if err != nil { + return err + } + cmd := exec.Command(d.binary, opts...) + logrus.Debugf("Executing %v", cmd.Args) + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("pesto failed: %w\noutput: %s", err, out) + } + delete(d.ports, id) + + return nil +} + +func NewChildDriver() port.ChildDriver { + return &childDriver{} +} + +type childDriver struct { +} + +func (d *childDriver) RunChildDriver(opaque map[string]string, quit <-chan struct{}, detachedNetNSPath string) error { + // NOP + <-quit + return nil +} + +// Available indicates whether this port driver is compiled in (used for generating help text) +const Available = true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/pkg/port/port.go new/rootlesskit-3.1.0/pkg/port/port.go --- old/rootlesskit-3.0.2/pkg/port/port.go 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/pkg/port/port.go 2026-08-04 10:50:20.000000000 +0200 @@ -20,6 +20,7 @@ // - builtin driver: 127.0.0.1 // - slirp4netns driver: slirp4netns's child IP, e.g., 10.0.2.100 // - gvisor-tap-vsock driver: gvisor-tap-vsock's child IP, e.g., 10.0.2.100 + // - pesto driver: pasta's child IP, e.g., 10.0.2.100 ChildIP string `json:"childIP,omitempty"` } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/pkg/port/portutil/portutil.go new/rootlesskit-3.1.0/pkg/port/portutil/portutil.go --- old/rootlesskit-3.0.2/pkg/port/portutil/portutil.go 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/pkg/port/portutil/portutil.go 2026-08-04 10:50:20.000000000 +0200 @@ -94,7 +94,7 @@ return nil, fmt.Errorf("unexpected ParentIP in PortSpec string: %q", portSpec) } if parts[childIP] != "" && net.ParseIP(parts[childIP]) == nil { - return nil, fmt.Errorf("unexpected ParentIP in PortSpec string: %q", portSpec) + return nil, fmt.Errorf("unexpected ChildIP in PortSpec string: %q", portSpec) } ps := &port.Spec{ @@ -105,12 +105,12 @@ ps.ParentPort, err = strconv.Atoi(parts[parentPort]) if err != nil { - return nil, fmt.Errorf("unexpected ChildPort in PortSpec string: %q: %w", portSpec, err) + return nil, fmt.Errorf("unexpected ParentPort in PortSpec string: %q: %w", portSpec, err) } ps.ChildPort, err = strconv.Atoi(parts[childPort]) if err != nil { - return nil, fmt.Errorf("unexpected ParentPort in PortSpec string: %q: %w", portSpec, err) + return nil, fmt.Errorf("unexpected ChildPort in PortSpec string: %q: %w", portSpec, err) } return ps, nil diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rootlesskit-3.0.2/pkg/version/version.go new/rootlesskit-3.1.0/pkg/version/version.go --- old/rootlesskit-3.0.2/pkg/version/version.go 2026-07-10 17:08:25.000000000 +0200 +++ new/rootlesskit-3.1.0/pkg/version/version.go 2026-08-04 10:50:20.000000000 +0200 @@ -1,3 +1,3 @@ package version -const Version = "3.0.2" +const Version = "3.1.0" ++++++ vendor.tar.gz ++++++
