Ad-hoc router using OpenWrt in a VM

/images/openwrt_crop.png

During writing of the last post I actually bricked my home router after installing a custom image. At the same time I didn't manage to get TFTP recovery working [1] so I started searching for ways to restore my home network for now to worry about fixing the router later.

The choice fell on using an ARM board to run OpenWrt, which required some creative workarounds. This post documents them.

What I want to replace:

  • router-switch combo: 1x WAN, 5x LAN, no WiFi

  • OS: OpenWrt

What I have:

  • 64-bit ARM SBC, capable of hardware virtualization

  • USB 3.0 Ethernet adapter (if your SBC has 2x LAN natively that works too)

  • 6-port Ethernet switch

Network setup

eth0 will be the LAN, eth1 the WAN. The LAN bridge also gets an IP address for management (pick one outside your DHCP pool) so you can SSH to the host even if the OpenWrt VM is not in operation.

/etc/systemd/network/br0.netdev:

[NetDev]
Name=br0
Kind=bridge

/etc/systemd/network/br1.netdev:

[NetDev]
Name=br1
Kind=bridge

/etc/systemd/network/br0.network:

[Match]
Name=br0
[Bridge]
MulticastRouter=permanent
[Network]
Address=192.168.2.254/24
Gateway=192.168.2.1
DNS=192.168.2.1
IPv6AcceptRA=no
LinkLocalAddressing=no

/etc/systemd/network/br1.netdev:

[Match]
Name=br1
[Bridge]
MulticastRouter=permanent
[Network]
DHCP=no
IPv6AcceptRA=no
LinkLocalAddressing=no

/etc/systemd/network/eth0.network:

[Match]
Name=eth0
[Network]
Bridge=br0

/etc/systemd/network/eth1.network:

[Match]
Name=eth1
[Network]
Bridge=br1

Virtual Machine

I had to virtualize OpenWrt because in terms of SBC it supports a few select platforms (eg. Raspberry Pi), but not any I own.

Installation:

cd /root
wget "https://downloads.openwrt.org/snapshots/targets/armvirt/64/openwrt-armvirt-64-"{rootfs-ext4.img.gz,Image}
gunzip openwrt-armvirt-64-rootfs-ext4.img.gz
echo 'allow all' >/etc/qemu/bridge.conf
systemctl enable guest.service

/root/run.sh:

#!/bin/bash
cd /root
exec qemu-system-aarch64 -enable-kvm -cpu host \
        -nographic -M virt,highmem=off -m 128 -smp 4 \
        -kernel openwrt-armvirt-64-Image -append "root=fe00" \
        -drive file=openwrt-armvirt-64-rootfs-ext4.img,format=raw,if=none,id=hd0 \
        -device virtio-blk-pci,drive=hd0 \
        -nic bridge,br=br0,model=virtio -nic bridge,br=br1,model=virtio

/etc/systemd/system/guest.service:

[Unit]
Description=QEMU guest
After=network.target
[Service]
ExecStart=/root/run.sh
Restart=always
RestartSec=10s
[Install]
WantedBy=multi-user.target

OpenWrt

If the default OpenWrt configuration has address conflicts with the rest of your network, you can adjust it ahead of time like so:

ip l add dev br0 type bridge
ip l add dev br1 type bridge
./run.sh
# log into openwrt
  uci set network.eth0.ipaddr=192.168.7.1
  uci commit
  poweroff

Now suppose you want to import the configuration of your old OpenWrt install, except: it has totally different interface names all over! Not a problem either as you can rename them by adding the following lines to /etc/rc.local:

ip l set eth0 name lan1
ip l set eth1 name wan

If you did everything right up until this point you can shut down the SBC, plug in all the cables, let it reboot and an OpenWrt router should appear in your network at http://192.168.1.1 just as if it was a real device.

Future thoughts

  • Setting net.core.default_qdisc = pfifo_fast on the host can save some CPU time, not sure whether this breaks QoS

  • If you have a WiFi USB adapter you could pull it into the VM and enjoy wireless too! See here for an example

  • When running this setup long term it would make sense to strip down the host OS

  • The QEMU serial console could be bound to a socket or PTS to interact with it even when guest.service is running