Posted on 2 minutes read

Qemu support rootless mode routing of network packets through the host using Slirp, you only need to give the right options to qemu to set this up, without having to tinker with tap interfaces or iptables.

However, using it for IPv6 can be a bit more challenging, especially since the documentation lacks IPv6 examples.

The Qemu options look like this -device virtio-net-pci,netdev=n0,mac=52:54:12:34:56:00 -netdev user,id=n0,ipv4=off,ipv6=on,ipv6-net=??. The only unknown is what to put for ipv6-net.

One would guess that using a Unique Local Address would work, but Qemu will only route packets out of the guest for IPs in the specified network.

The trick is to give ::/0 for the network, this way Qemu will route everything out of the guest.

Test

We download an Ubuntu cloud image, mask systemd-networkd-wait-online and change the root password with libguestfs[1]:

virt-customize -a jammy-server-cloudimg-amd64-disk-kvm.img --run-command 'systemctl mask systemd-networkd-wait-online.service'
virt-customize -a jammy-server-cloudimg-amd64-disk-kvm.img --root-password password:root

Then we run:

qemu-system-${HOSTTYPE} -m 2G -nographic -serial mon:stdio -nodefaults -enable-kvm -drive file=jammy-server-cloudimg-amd64-disk-kvm.img,format=qcow2 -device virtio-net-pci,netdev=n0,mac=52:54:12:34:56:00 -netdev user,id=n0,ipv4=off,ipv6=on,ipv6-net=::/0 # Press CTRL+a CTRL+x to quit qemu

Setup network in guest:

root@ubuntu:~# ip link set ens2 up                                       # Bring iface up
root@ubuntu:~# echo 'nameserver 2606:4700:4700::1111' > /etc/resolv.conf # use cloudflare DNS

We get an IPv6 and route are configured:

root@ubuntu:~# ip -6 --brief a                                           # we get an ipv6
lo               UNKNOWN        ::1/128
ens2             UP             ::5054:12ff:fe34:5600/64 fe80::5054:12ff:fe34:5600/64
root@ubuntu:~# ip -6 r
::1 dev lo proto kernel metric 256 pref medium
::/64 dev ens2 proto kernel metric 256 expires 86274sec pref medium
fe80::/64 dev ens2 proto kernel metric 256 pref medium
default via fe80::2 dev ens2 proto ra metric 1024 expires 1674sec hoplimit 64 pref medium

Ping doesn't work[2]:

root@ubuntu:~# ping google.com
PING google.com(par21s23-in-x0e.1e100.net (2a00:1450:4007:81a::200e)) 56 data bytes
qemu-system-x86_64: Slirp: external icmpv6 not supported yet

But curl does:

root@ubuntu:~# curl -v ifconfig.me
*   Trying 2600:1901:0:bbc3:::80...
* Connected to ifconfig.me (2600:1901:0:bbc3::) port 80 (#0)
> GET / HTTP/1.1
> Host: ifconfig.me
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< server: fasthttp
< date: Thu, 11 Apr 2024 08:16:58 GMT
< content-type: text/plain
< Content-Length: 38
< access-control-allow-origin: *
< via: 1.1 google
<
<redacted-ip6>                                                   # Slirp translated host ip6

After bringing up the iface, we get an ip on ::/0, we use cloudflare nameserver and can use curl to get our IPv6.

We end up with the host IPv6 because it goes trough Slirp.

Debug

You can also add -object filter-dump,id=d0,netdev=n0,file=dump.pcap to get a pcap file of the VM network.

That's all for today, have fun with IPv6.


  1. Despite my best efforts, I couldn't make cloud-init work. Maybe it could have its own blog post, but today we're only interested in the Qemu IPv6 Slirp stack. ↩

  2. https://github.com/qemu/libslirp/blob/v4.7.0/src/ip6_icmp.c#L419. ↩