Public routes to VMs

By default, all incoming traffic to the VMs must originate from your workstation (or another VM on your workstation) - no traffic is routed to your VMs from any other interface.

If you want to break this rule, and allow public routes into these VMs (DNAT port forwarding), you will need to install the libvirt hook that sets up the iptables forwarding rules:

Download the port-forwarding hook

[bash]: Run this on your workstation:
sudo mkdir -p /usr/local/src/
sudo su -c "cd /usr/local/src && git clone https://github.com/EnigmaCurry/libvirt-hook-qemu.git"
CREDITS

EnigmaCurry/libvirt-hook-qemu is my own fork of saschpe/libvirt-hook-qemu which has been slightly customized for this configuration. Thank you to Sascha Peilicke for creating this hook!

Install the hook files

[bash]: Run this on your workstation:
sudo mkdir -p /etc/libvirt-dnat-hook
sudo cp /usr/local/src/libvirt-hook-qemu/hooks.schema.json /etc/libvirt-dnat-hook

Set config variables

Set some temporary variables the same as from your config:

[bash]: Set temporary environment variables
NAME=debian-dev
IP_ADDRESS=192.168.122.2

Customize the port-forwarding hook

Use the example and schema as a reference, then setup the port mapping you want for each VM:

[bash]: Run this on your workstation:
NAME=${NAME:-debian-dev}
IP_ADDRESS=${IP_ADDRESS:-192.168.122.2}
cat << EOF | jq | sudo tee /etc/libvirt-dnat-hook/hooks.json
{
  "${NAME}": {
    "interface": "virbr0",
    "private_ip": "${IP_ADDRESS}",
    "port_map": {
        "tcp": [
            [2222, 22],
            [80, 80],
            [443, 443]
        ]
    }
  }
}
EOF
Tip

This example opens the following public ports:

  • Public TCP port 2222 forwards to the VM’s port 22.
  • Public TCP port 80 forwards to the VM’s port 80.
  • Public TCP port 443 forwards to the VM’s port 443.

UDP ports need to be in their own section, a sibling of TCP. Each VM needs its own config, mapped at the top level by the VM’s unique name.

Autostart port-forwarding script on boot

Create DNAT service template

[bash]: Run this on your workstation:
VM_ADMIN=${VM_ADMIN:-libvirt-admin}
cat << EOF | sudo tee /etc/systemd/system/libvirt-DNAT@.service
[Unit]
Description=${VM_ADMIN} VM: %i - DNAT port forwarding
Requires=libvirt@%i.service
Requires=network-online.target
After=libvirt@%i.service
After=network-online.target

[Service]
Type=oneshot
RemainAfterExit=true
Environment="XDG_RUNTIME_DIR=/run/user/$(id -u ${VM_ADMIN})"
Environment="CONFIG_PATH=/etc/libvirt-dnat-hook"
ExecStart=/usr/bin/python /usr/local/src/libvirt-hook-qemu/hooks %i start
ExecStop=/usr/bin/python /usr/local/src/libvirt-hook-qemu/hooks %i stopped

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload

Enable DNAT service once per VM you want to expose

[bash]: Run this on your workstation:
NAME=${NAME:-debian-dev}
sudo systemctl enable --now libvirt-DNAT@${NAME}.service
sudo systemctl status libvirt-DNAT@${NAME}.service
Stopping and/or Disabling the service

If you want to disable the port mapping, run:

[bash]: Run this on your workstation:
NAME=${NAME:-debian-dev}
sudo systemctl disable --now libvirt-DNAT@${NAME}.service

Or to temporarily stop the port mapping (until you run start or reboot):

[bash]: Run this on your workstation:
NAME=${NAME:-debian-dev}
sudo systemctl stop libvirt-DNAT@${NAME}.service

Reboot workstation

Once rebooted, test that your port forward rule exists in iptables rules:

[bash]: Run this on your workstation:
sudo iptables-save | grep 2222
(stdout)
-A DNAT-debian-dev -d 10.13.13.227/32 -p tcp -m tcp --dport 2222 -j DNAT --to-destination 192.168.122.2:22
-A SNAT-debian-dev -s 192.168.122.2/32 -d 192.168.122.2/32 -p tcp -m tcp --dport 2222 -j MASQUERADE