From 0d618361f97745fc9a3b2e9e583de30e053b78cc Mon Sep 17 00:00:00 2001 From: Hayato Kiwata Date: Tue, 25 Aug 2026 01:34:20 +0900 Subject: [PATCH] fix(pasta): assign a fixed IPv6 address, gateway, and DNS to the netns Currently, `curl -fsSL http://[${parent_ipv6}]:8080` fails when running `hack/integration-ipv6.sh` with `--net=pasta`. This is because pasta copies the destination address `${parent_ipv6}` into the isolated netns and the destination becomes a local address inside the netns. To begin with, the only interface on the host with an available IPv6 route is the dummy interface (`dummy42`), so it is selected as the template, and its address and route are duplicated into the netns by `nl_addr_dup()` and `nl_route_dup()` [1]. As a result, the route to `${parent_ipv6}` becomes `local`, the packets never leave the netns, and they do not reach the httpd running on the host. To resolve this error, pass `--address`, `--gateway`, and `--dns-forward` when running pasta, so that a fixed IPv6 configuration is assigned to tap0 inside the netns. child: fd00::100 gateway: fd00::2 DNS: fd00::3 With these options, pasta executes `nl_addr_set()` and `nl_route_set_def()` [1] in `pasta.c` instead of `nl_addr_dup()` and `nl_route_dup()`, and assigns the specified address and route to tap0 inside the netns. As a result, `${parent_ipv6}` is no longer duplicated into the netns, and when running `curl -fsSL http://[${parent_ipv6}]:8080` inside the netns, packets leave the netns through tap0. On the host side, pasta relays the connection to the httpd, so the curl now succeeds. [1] https://passt.top/passt/tree/pasta.c Signed-off-by: Hayato Kiwata --- hack/integration-ipv6.sh | 20 +++++++---- pkg/network/iputils/iputils.go | 16 +++++++++ pkg/network/iputils/iputils_test.go | 55 +++++++++++++++++++++++++++++ pkg/network/pasta/pasta.go | 24 ++++++++++++- 4 files changed, 107 insertions(+), 8 deletions(-) diff --git a/hack/integration-ipv6.sh b/hack/integration-ipv6.sh index 4123b2fe..ebd9f578 100755 --- a/hack/integration-ipv6.sh +++ b/hack/integration-ipv6.sh @@ -15,11 +15,17 @@ echo "hello ipv6" >${tmp}/index.html busybox httpd -f -p "[${parent_ipv6}]:8080" -h "${tmp}" & pid=$! -$ROOTLESSKIT \ - --net=slirp4netns \ - --ipv6 \ - sh -euc "sleep 3; exec curl -fsSL http://[${parent_ipv6}]:8080" +cleanup() { + kill -9 $pid || true + sudo ip link del ${parent_dummy} || true + rm -rf ${tmp} +} +trap cleanup EXIT -kill -9 $pid || true -sudo ip link del ${parent_dummy} -rm -rf ${tmp} +for net in 'slirp4netns' 'pasta' +do + $ROOTLESSKIT \ + --net=${net} \ + --ipv6 \ + sh -euc "sleep 3; exec curl -fsSL http://[${parent_ipv6}]:8080" +done diff --git a/pkg/network/iputils/iputils.go b/pkg/network/iputils/iputils.go index 7a615c43..d200018d 100644 --- a/pkg/network/iputils/iputils.go +++ b/pkg/network/iputils/iputils.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "fmt" "math" + "math/big" "net" ) @@ -21,3 +22,18 @@ func AddIPInt(ip net.IP, i int) (net.IP, error) { binary.BigEndian.PutUint32(res, uint32(resInt64)) return res, nil } + +func AddIPInt6(ip net.IP, i int) (net.IP, error) { + ip6 := ip.To16() + if ip.To4() != nil || ip6 == nil { + return nil, fmt.Errorf("expected IPv6 address, got %s", ip.String()) + } + b := new(big.Int).SetBytes(ip6) + b.Add(b, big.NewInt(int64(i))) + if b.Sign() < 0 || b.BitLen() > 128 { + return nil, fmt.Errorf("%s + %d overflows", ip.String(), i) + } + res := make(net.IP, net.IPv6len) + b.FillBytes(res) + return res, nil +} diff --git a/pkg/network/iputils/iputils_test.go b/pkg/network/iputils/iputils_test.go index 9cf6d122..8d6ae79d 100644 --- a/pkg/network/iputils/iputils_test.go +++ b/pkg/network/iputils/iputils_test.go @@ -49,3 +49,58 @@ func TestAddIPInt(t *testing.T) { } } } + +func TestAddIPInt6(t *testing.T) { + type testCase struct { + s string + i int + expected string + } + testCases := []testCase{ + { + "fd00::", + 0x2, + "fd00::2", + }, + { + "fd00::", + 0x100, + "fd00::100", + }, + { + "fd00::ffff", + 0x1, + "fd00::1:0", + }, + { + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + 0x1, + "", + }, + { + "10.0.2.0", + 100, + "", + }, + } + for i, tc := range testCases { + ip := net.ParseIP(tc.s) + if ip == nil { + t.Fatalf("invalid IP: %q", tc.s) + } + gotIP, err := AddIPInt6(ip, tc.i) + if tc.expected == "" { + if err == nil { + t.Fatalf("#%d: expected error, got no error", i) + } + } else { + if err != nil { + t.Fatalf("#%d: expected no error, got %q", i, err) + } + got := gotIP.String() + if got != tc.expected { + t.Fatalf("#%d: expected %q, got %q", i, tc.expected, got) + } + } + } +} diff --git a/pkg/network/pasta/pasta.go b/pkg/network/pasta/pasta.go index dcb1b407..3bb3a8c3 100644 --- a/pkg/network/pasta/pasta.go +++ b/pkg/network/pasta/pasta.go @@ -76,6 +76,10 @@ func NewParentDriver(logWriter io.Writer, binary string, mtu int, ipnet *net.IPN return nil, err } } + _, ipnet6, err := net.ParseCIDR("fd00::/64") + if err != nil { + return nil, err + } if ifname == "" { ifname = "tap0" @@ -95,6 +99,7 @@ func NewParentDriver(logWriter io.Writer, binary string, mtu int, ipnet *net.IPN binary: binary, mtu: mtu, ipnet: ipnet, + ipnet6: ipnet6, disableHostLoopback: disableHostLoopback, enableIPv6: enableIPv6, ifname: ifname, @@ -109,6 +114,7 @@ type parentDriver struct { binary string mtu int ipnet *net.IPNet + ipnet6 *net.IPNet disableHostLoopback bool enableIPv6 bool ifname string @@ -170,7 +176,23 @@ func (d *parentDriver) ConfigureNetwork(childPID int, stateDir, detachedNetNSPat if d.disableHostLoopback { opts = append(opts, "--no-map-gw", "--tcp-ns=none", "--udp-ns=none") } - if !d.enableIPv6 { + if d.enableIPv6 { + // 0x100, so that the address is rendered as fd00::100 + address, err := iputils.AddIPInt6(d.ipnet6.IP, 0x100) + if err != nil { + return nil, common.Seq(cleanups), err + } + gateway, err := iputils.AddIPInt6(d.ipnet6.IP, 2) + if err != nil { + return nil, common.Seq(cleanups), err + } + dns, err := iputils.AddIPInt6(d.ipnet6.IP, 3) + if err != nil { + return nil, common.Seq(cleanups), err + } + opts = append(opts, "--address="+address.String(), "--gateway="+gateway.String(), + "--dns-forward="+dns.String()) + } else { opts = append(opts, "--ipv4-only") } if d.implicitPortForwarding {