Jul 20
2019

Bridged networking with qemu on Linux

This document describes setting up qemu system emulation with an IP address which is visible on the host's network using systemd-networkd and qemu-bridge-helper. This makes the system running inside qemu accessible externally. This procedure was developed on a Debian 10 (Buster) system but should work with any system capable of using systemd for network configuration.

The hardest part of this process was digging through the layers of HOWTOs on the Internet which recommended things which were either deprecated in modern Linux systems or simply don't work. As such, I expect the details to go out of date quickly, but the high level shouldn't change quite so fast:

  • You'll need to create a network bridge device and bind your default-route NIC to it.
  • You can then create a tap device attached to the bridge for qemu to use. Qemu can set up the necessary tap device using qemu-bridge-helper if you have configured it appropriately.
  • Having done this, you will still need to configure the host's firewall to allow packets to move across the bridge.

Create your bridge using systemd-networkd. This follows the Arch instructions. To summarise:

  1. Define the bridge itself by creating /etc/systemd/network/br0.netdev:
    [NetDev]
    Name=br0
    Kind=bridge
    
  2. Bind your Ethernet connection to the bridge by creating /etc/systemd/network/br0-bind.network:
    [Match]
    Name=en*
    
    [Network]
    Bridge=br0
    
  3. Specify the bridge IP configuration by creating /etc/systemd/network/br0ip.network:
    [Match]
    Name=br0
    
    [Network]
    DHCP=ipv4
    IPForward=true
    

Enable systemd-networkd: sudo systemctl enable systemd-networkd.service

Start or restart systemd-networkd: sudo sudo systemctl start systemd-networkd.service

This will change the IP address of your default route interface (it will now be the bridge).

Make qemu-bridge-helper setuid: to set up a bridge as a normal user, qemu-bridge-helper must be run as root. Do this by adding the setuid bit, sudo chmod +s /usr/lib/qemu/qemu-bridge-helper (understand the security implications, which, as far as I can make out, are that unprivileged users could create arbitrary bridges bypassing firewall rules (in the best case), or effectively become root (which is the worst case for any setuid binary, because it may have security bugs)).

Allow user access to the bridge from qemu-bridge-helper: Create or modify /etc/qemu/bridge.conf:

allow br0

Allow packet forwarding across the bridge: Actually you don't need to do anything here, because you added IPForward=true above. If you hadn't, you would only be able to access the host IP from inside the Qemu-hosted machine.

Fix name resolution: If you were previously using NetworkManager for your networking, you will need to switch to systemd-resolved instead, because systemd-networkd expects it. sudo systemctl enable systemd-resolved.service, sudo systemctl start systemd-resolved.service, and cd etc; sudo rm resolv.conf; sudo ln -s /run/systemd/resolve/resolv.conf resolv.conf. More information; also search "resolv" here.)

Configure the qemu run by adding -nic bridge to your qemu command line.

Why do we need a bridge anyway?: From the Arch wiki (which is a great resource): a bridge is a network switch, but implemented in software. Above, we created a bridge and added the default-route NIC to it. When qemu starts and runs qemu-bridge-helper, it creates a new tap device, which is effectively a virtual NIC with its own MAC address, and connects that device to the bridge. With our iptables configuration, that's all we need to do: routing to the right network segment is done by the kernel.