Hello!
So, one of the disadvantages of KVM as listed on the Wiki in Why use VirtualBox over KVM? is that it doesn’t work well with host firewalls or VPNs.
The root cause of this issue lies in how Libvirt’s Virtual Networking is implemented. The relevant detail here is that libvirt inserts iptables rules to make its virtual networks work, and these tend to clash with any existing rules.
Finally, after literally months of googling, learning and experimenting, I believe I’ve solved this problem! It’s not a particularly pleasant solution, but it works.
The solution
The basic idea is to manually set up the virtual networks, as documented in this guide:
Custom NAT-based network — libvirt Networking Handbook — Jamie Nguyen
It’s a bit out-of-date (a few steps are not required anymore), but the core idea remains the same.
The really tricky part was getting the specific firewall rules right. Debugging network issues without a proper understanding of Whonix’s network stack was hard enough; doing that while also trying to understand iptables for the first time was a challenge straight from hell
I’ll list the steps required to get it working below. This will be a very minimal demonstration; I won’t implement any systemd services or scripts, I’ll just give you a set of commands that should make things work. Hopefully someone can polish this up and get it onto the Wiki - sorry, but after all the effort I put in here, I’m too tired to work on that as well
Steps
I will use nftables for these steps, because it’s what I preferred to learn; in theory this can be adapted to iptables.
0. Destroy existing Whonix-*
networks
Make sure you have a backup of Whonix_{internal,external}_network.xml
(from the downloaded .tar.xz) for reference.
sudo virsh -c qemu:///system net-destroy Whonix-External
sudo virsh -c qemu:///system net-destroy Whonix-Internal
sudo virsh -c qemu:///system net-undefine Whonix-External
sudo virsh -c qemu:///system net-undefine Whonix-Internal
sudo nft flush ruleset # Clear all rules
1. Add the virtual bridges
sudo brctl addbr virbr1 # Whonix-External
sudo brctl addbr virbr2 # Whonix-Internal
sudo brctl set virbr1 on
sudo brctl set virbr2 on
sudo ip addr add 10.0.2.2/24 dev virbr1 # The address from Whonix_external_network.xml
sudo ip link set up virbr1
sudo ip link set up virbr2
2. Run a dnsmasq instance for virbr1 (Whonix-External)
sudo dnsmasq --except-interface=lo --interface=virbr1 --bind-dynamic --dhcp-range=10.0.2.2,10.0.2.254
3. Apply nftables rules
This is a template for a basic filter-by-default firewall; the goal is to demonstrate the minimum needed to make the virtual bridges work. You should be able to adapt this according to your needs.
Save the following rules in nftables.conf
.
# delete table if exists
table inet filter
delete table inet filter
table inet filter {
chain forward {
type filter hook forward priority filter
###### change to 'accept' to accept everything #####
policy drop
###### add your own rules here to allow types of traffic you want ######
iifname "virbr1" accept comment "outgoing traffic from virbr1"
oifname "virbr1" ct state established,related accept comment "established traffic to virbr1"
}
}
table inet nat
delete table inet nat
table inet nat {
chain postrouting {
type nat hook postrouting priority srcnat
policy accept
##### add any nat rules you already have here #####
iifname virbr1 ip daddr { 224.0.0.0/24, 255.255.255.255/32 } return comment "no masquerading to reserved blocks"
iifname virbr1 oifname != virbr1 masquerade comment "masquerade all outgoing traffic on virbr1"
}
}
Now apply these rules with sudo nft -f nftables.conf
.
At this stage you may also need to enable ipv4 forwarding if that isn’t already enabled:
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
4. Modify the VM configuration
Change the network source from ‘Whonix-External’ to the bridge ‘virbr1’, and from ‘Whonix-Internal’ to the bridge ‘virbr2’. You can do this by manually editing the XML (virsh edit Whonix-*
), or via the virt-manager GUI.
And that’s it! Hopefully I didn’t forget anything.
Further information
Here’s how I figured out the nftables rules:
- Imported and activated the networks as described in the Wiki
sudo nft list ruleset > whonix-libvirt.nft
From this output, I was able to painstakingly assemble a sensible-seeming nftables configuration.
Anyway, I hope this is helpful!
Open topics this answers: