Fedora + Wifi + KVM + OPNsense: How to route all traffic through VM?

Started by uname, April 09, 2025, 12:45:54 PM

Previous topic - Next topic
April 09, 2025, 12:45:54 PM Last Edit: April 09, 2025, 08:43:24 PM by uname Reason: more detailes provided
I have a laptop with Fedora Linux 41 installed. The laptop is always connected to WiFi, there is no wired connection. I'm trying to set up OPNsense in a virtual machine and route all traffic through this VM. I aim to keep my setup as minimal as possible (no libvirt, no Proxmox).

At the moment, I have a VM with OPNsense installed. This machine can connect to the internet (when WAN is in user mode: -netdev user,id=wan  -device virtio-net-pci,netdev=wan), and the web interface is accessible from the host environment. I'm almost there, but I can't figure out how to route the traffic through the VM. I've tried many methods: bridge, NAT, macvtap, and various combinations - nothing works.

I have little experience with KVM and network interfaces. Please help me figure out what I'm doing wrong. I'm losing my mind here.

In my current setup, the OPNsense VM's WAN interface fails to obtain an IP address via DHCP:
https://github.com/ruslanbay/opnsense-kvm/blob/main/deploy-opnsense.sh
#!/bin/bash
set -e

# Configuration
OPNSENSE_VER="25.1"
IMG_PATH="~/VMs/images/OPNsense-${OPNSENSE_VER}-serial-amd64.img"
VM_NAME="opnsense"
RAM_MB=1024
CPUS=2
WLAN_IF="wlp0s20f3"
LAN_BRIDGE="br-lan"
LAN_IP="192.168.100.1"
LAN_SUBNET="192.168.100.0/24"
DISK_SIZE="5G"
DISK_PATH="~/VMs/images/${VM_NAME}.qcow2"

# Ensure the user is running as root
if [[ $EUID -ne 0 ]]; then
    echo -e "This script must be run as root! Use sudo."
    exit 1
fi

# Check dependencies
check_dependencies() {
    # Check if QEMU/KVM is installed
    if ! command -v qemu-system-x86_64 &> /dev/null; then
        echo "Installing QEMU/KVM core packages..."
        sudo dnf install -y --setopt=install_weak_deps=FALSE qemu-kvm-core qemu-img

        # Load KVM kernel modules (Intel example)
        sudo modprobe kvm kvm_intel 2>/dev/null || true

        # Add user to kvm group
        sudo usermod -aG kvm "$USER"
        echo "Please logout/login or reboot to apply group changes and ensure KVM is ready!"
        exit 1  # Exit to force user to re-login
    fi

    # Verify /dev/kvm exists and is accessible
    if [ ! -c /dev/kvm ]; then
        echo "ERROR: /dev/kvm not found. Ensure KVM is enabled in BIOS and kernel modules are loaded."
        exit 1
    fi
}

# Verify ISO path
verify_iso() {
    if [ ! -f "$IMG_PATH" ]; then
        echo "OPNsense ISO not found at: $IMG_PATH"

        mkdir -p "$( dirname $IMG_PATH )"
       
        IMG_PATH="$( dirname $IMG_PATH )/OPNsense-${OPNSENSE_VER}-serial-amd64.img"

        curl -o "${IMG_PATH}.bz2" "https://mirror.ams1.nl.leaseweb.net/opnsense/releases/${OPNSENSE_VER}/OPNsense-${OPNSENSE_VER}-serial-amd64.img.bz2"
       
        bzip2 -d "${IMG_PATH}.bz2"
    fi
}

# Create VM disk
create_disk() {
    if [ ! -f "$DISK_PATH" ]; then
        echo "Creating VM disk at $DISK_PATH..."
        mkdir -p "$(dirname "$DISK_PATH")"
        sudo qemu-img create -f qcow2 "$DISK_PATH" "$DISK_SIZE"
    fi
}

# Setup network interfaces
setup_network_interfaces() {
    if ! ip link show "$LAN_BRIDGE" &> /dev/null; then
        # Create bridge for LAN interface
        sudo ip link add name $LAN_BRIDGE type bridge
        sudo ip link set $LAN_BRIDGE up
        sudo ip addr add $LAN_IP/24 dev $LAN_BRIDGE
       
        # Create tap interface for LAN
        sudo ip tuntap add tap0 mode tap
        sudo ip link set tap0 up
        sudo ip link set tap0 master $LAN_BRIDGE
       
        # Create macvtap interface for WAN (in passthrough mode)
        sudo ip link add link $WLAN_IF name macvtap0 type macvtap mode passthru
        sudo ip link set macvtap0 up

        # Get the tap device name created by macvtap
        TAP_NUM=$(< /sys/class/net/macvtap0/ifindex)
        TAP_DEV="tap${TAP_NUM}"
       
        # Wait for tap device to be created
        sleep 2
       
        # Configure NAT for the LAN network
        sudo sysctl -w net.ipv4.ip_forward=1
        sudo iptables -t nat -A POSTROUTING -o $WLAN_IF -j MASQUERADE
        sudo iptables -A FORWARD -i br-lan -o $WLAN_IF -j ACCEPT
        sudo iptables -A FORWARD -i $WLAN_IF -o br-lan -m state --state RELATED,ESTABLISHED -j ACCEPT
       
        # Allow custom bridge
        # echo "allow $LAN_BRIDGE" | sudo tee -a /etc/qemu/bridge.conf
    fi
}

run_vm() {
    echo "Start the VM"
    sudo qemu-system-x86_64 \
        -enable-kvm \
        -name "$VM_NAME" \
        -m "$RAM_MB" \
        -smp "$CPUS" \
        -drive id=hd0,file="$DISK_PATH",format=qcow2,if=none \
        -device virtio-blk-pci,drive=hd0 \
        # -drive id=hd1,file="$IMG_PATH",format=raw,if=none \
        # -device virtio-blk-pci,drive=hd1,bootindex=1 \
        -netdev tap,id=lan,ifname=tap0,script=no \
        -device virtio-net-pci,netdev=lan,mac=52:54:00:12:34:56 \
        -netdev tap,id=wan,ifname=$TAP_DEV,script=no,downscript=no  \
        -device virtio-net-pci,netdev=wan,mac=52:54:00:12:34:57 \
        -nographic
}

# Route all traffic through the VM
route_traffic() {
    sudo ip route del default
    sudo ip route add default via ${LAN_IP%.*}.2 dev $LAN_BRIDGE  # LAN_IP="192.168.100.1"; ${LAN_IP%.*}.2 == 192.168.100.2
}

# Main function
main() {
    check_dependencies
    verify_iso
    create_disk
    setup_network_interfaces
    install_vm
    route_traffic
}

# Run the script
main

Did you disable hardware checksum offload?
(Interfaces: Settings)

Thank you for the reply. I should have clarified that I am using the existing OPNsense configuration from my Hyper-V installation. I expected it to work without any modifications. I will try to disable hardware checksum offload. Are there any other settings specific to KVM?


I spent a few days migrating my OPNsense setup from Hyper-V to KVM, unfortunately, it didn't quite work out.

As an alternative, I created a browser extension that blocks web requests based on GeoIP and ASN. The extension is compatible with both desktop and Android devices. You can find the source code on GitHub: https://github.com/veto-firewall/veto.

Although the extension is still awaiting review on the Mozilla Add-on Store (a process that can take some time), you're welcome to try out a developer preview version by following the installation guide. I'd love to receive your feedback, reviews, and pull requests on GitHub.