IPv6 NAT on libvirt virtual machine

librtvirt supports IPv6 for virtual machines, but that is usually a bridge solution or a subnet of the /64 the libvirt host is connected to. But I'm in the situation working on a network without IPv6, so I get my IPv6 address from a Wireguard interface. To get IPv6 working in a virtual machine on my host, I do not have a range available on my host machine without extensive changes to the wireguard VPN.

The solution is to use NAT with IPv6.

First, create a new libvirt NAT network with IPv6 enabled and the ranges you want. For the ipv6 range I chose fd00::/64 because that is a private range like 10.x.x.x.

<network connections="1">
  <name>virbr1</name>
  <uuid>a592c6e0-b9ed-463a-a0a3-3feef8c01b7d</uuid>
  <forward mode="nat">
    <nat>
      <port start="1024" end="65535"/>
    </nat>
  </forward>
  <bridge name="virbr1" stp="on" delay="0"/>
  <mac address="52:54:00:74:c6:cf"/>
  <domain name="virbr1"/>
  <ip address="192.168.100.1" netmask="255.255.255.0">
    <dhcp>
      <range start="192.168.100.128" end="192.168.100.254"/>
    </dhcp>
  </ip>
  <ip family="ipv6" address="fd00::1" prefix="64">
    <dhcp>
      <range start="fd00::100" end="fd00::1ff"/>
    </dhcp>
  </ip>
</network>

Because we want to do nat and I could not find a libvirt network filter rule for doing nat, I used the hook mechanism of libvirt, which means putting a script called network in /etc/libvirt/hooks:

#!/bin/bash
VIRT_NET_MATCH="virbr1"
VIRT_BRIDGE_MATCH="virbr1"
IPV6_DEFAULT_INTERFACE="vpn-thuis"
if [ "$1" == "$VIRT_NET_MATCH" ]; then
  VIRT_BRIDGE=$VIRT_BRIDGE_MATCH
  if [ "$2" == "started" ]; then
    echo "Adding NAT MASQUERADING entries for $VIRT_BRIDGE"
    ip6tables -t nat -A POSTROUTING -s fd00::/64 -o $IPV6_DEFAULT_INTERFACE -j MASQUERADE
  elif [ "$2" == "stopped" ]; then
    echo "Stopping $1 ($VIRT_BRIDGE) ..."
    ip6tables -t nat -D POSTROUTING -s fd00::/64 -o $IPV6_DEFAULT_INTERFACE -j MASQUERADE
  fi
fi

Make sure this file is executable. IPV6_DEFAULT_INTERFACE is the mail IPv6 interface over which the natting should take place. The rest is self explanatory I think. When libvirt brings the network interface online, it calls the /etc/libvirt/hooks/network script, and this handled the creation or deleten of the NAT rule in the firewall of the host.

Links