Port Forwarding to a VM

Hi,

I’m attempting to get port forwarding to a virtual machine working, for testing purposes its just the ssh port for now. I’ve tried lots of variations, but based on:

my current configuration is:

{ config, pkgs, ... }:
{
  virtualisation.libvirtd = {
    enable = true;
    qemu = {
      package = pkgs.qemu_kvm;
      runAsRoot = true;
      swtpm.enable = true;
      ovmf = {
        enable = true;
        packages = [ (pkgs.OVMF.override {
          secureBoot = true;
          tpmSupport = true;
        }).fd ];
      };
    };
  };
  programs.virt-manager.enable = true;
  environment.systemPackages = with pkgs; [
    virtiofsd
    xorriso
  ];
  users.users.alistair = {
    extraGroups = [ "libvirtd" ];
  };
  networking = {
    networkmanager.enable = true;  #  temp
#    nftables = {
#      enable = true;
#      ruleset = ''
#        table ip nat {
#          chain PREROUTING {
#           type nat hook prerouting priority dstnat; policy accept;
#           iifname "enp0s20u1u1" tcp dport 22 dnat to 192.168.122.9:22
#          }
#        }
#      '';
#    };
    firewall.allowedTCPPorts = [ 7022 ];
    nat = {
     enable = true;
     internalInterfaces = [ "enp0s20u1u1" ];
     externalInterface = "virbr0";
     forwardPorts = [
       {
         sourcePort = 7022;
         proto = "tcp";
         destination = "192.168.122.9:22";
       }
     ];
    };
  };
}

This starts up the network in the VM fine, and I can ssh in from the host, but the port forwarding doesn’t work.

If I uncomment networking.nftables = { ... }, attempting to start the network results in:

$ sudo virsh net-start default
[sudo] password: 
error: Failed to start network default
error: internal error: Failed to apply firewall rules /nix/store/isqrn21fmha9dxf1bbl4mm7j6irpw1x8-iptables-1.8.10/bin/iptables -w --table nat --list-rules: iptables v1.8.10 (nf_tables): table `nat' is incompatible, use 'nft' tool.

and there is no virbr0 device.

Changing the table name as suggested by the error and in another page I found on the web, e.g. table ip natty { allows the network to start, but then the guest OS isn’t assigned an ip (v4) address.

What am I missing?

Thanks,
Alistair

I finally got this working… the configuration below port forwards to two different VMs, Ubunt22.04 and zmtest01 (this is on my test machine):

kvm.nix:

{ config, pkgs, ... }:
{
  virtualisation.libvirtd = {
    enable = true;
    qemu = {
      package = pkgs.qemu_kvm;
      runAsRoot = true;
      swtpm.enable = true;
      ovmf = {
        enable = true;
        packages = [ (pkgs.OVMF.override {
          secureBoot = true;
          tpmSupport = true;
        }).fd ];
      };
    };
    # Remember that the hooks and networking.nat.forwardPorts have to agree
    hooks.qemu = {
      ub2204 = ../files/qemu-hook.sh;
    };
  };
  programs.virt-manager.enable = true;
  environment.systemPackages = with pkgs; [
    virtiofsd
    xorriso
  ];
  users.users.alistair = {
    extraGroups = [ "libvirtd" ];
  };
  networking = {
    # Ubuntu22.04 VM: 8022 (SSH), 5901 (VNC)
    # zmtest01 VM: 9022 (SSH), 9080 (HTTP), 9440 (HTTPS)
    firewall.allowedTCPPorts = [ 8022 5901 9022 9080 9443 ];
    nat = {
     enable = true;
     internalInterfaces = [ "enp0s20u1u1" ];
     externalInterface = "virbr0";
     forwardPorts = [
       {
         sourcePort = 8022;
         proto = "tcp";
         destination = "192.168.122.9:22";
       }
       {
         sourcePort = 5901;
         proto = "tcp";
         destination = "192.168.122.9:5901";
       }
       {
         sourcePort = 9022;
         proto = "tcp";
         destination = "192.168.122.4:22";
       }
       {
         sourcePort = 9080;
         proto = "tcp";
         destination = "192.168.122.4:80";
       }
       {
         sourcePort = 9443;
         proto = "tcp";
         destination = "192.168.122.4:443";
       }
     ];
    };
  };
}

../files/qemu-hook.sh:

#!/run/current-system/sw/bin/bash

echo "qemu-hook: ${1} ${2}" >> /tmp/qemu-hook.log

if [ "${1}" = "zmtest01" ]; then
   # SSH on 9022
   GUEST_IP="192.168.122.4"
   GUEST_PORT="22"
   HOST_PORT="9022"
   if [ "${2}" = "stopped" ] || [ "${2}" = "reconnect" ]; then
    iptables -D FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
    iptables -t nat -D PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
   fi
   if [ "${2}" = "start" ] || [ "${2}" = "reconnect" ]; then
    iptables -I FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
    iptables -t nat -I PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
   fi

   # HTTP on 9080
   GUEST_IP="192.168.122.4"
   GUEST_PORT="80"
   HOST_PORT="9080"
   if [ "${2}" = "stopped" ] || [ "${2}" = "reconnect" ]; then
    iptables -D FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
    iptables -t nat -D PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
   fi
   if [ "${2}" = "start" ] || [ "${2}" = "reconnect" ]; then
    iptables -I FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
    iptables -t nat -I PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
   fi

   # HTTPS on 9443
   GUEST_IP="192.168.122.4"
   GUEST_PORT="443"
   HOST_PORT="9443"
   if [ "${2}" = "stopped" ] || [ "${2}" = "reconnect" ]; then
    iptables -D FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
    iptables -t nat -D PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
   fi
   if [ "${2}" = "start" ] || [ "${2}" = "reconnect" ]; then
    iptables -I FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
    iptables -t nat -I PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
   fi
fi

if [ "${1}" = "ubuntu22.04" ]; then
   # SSH on 8022
   GUEST_IP="192.168.122.9"
   GUEST_PORT="22"
   HOST_PORT="8022"
   if [ "${2}" = "stopped" ] || [ "${2}" = "reconnect" ]; then
    iptables -D FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
    iptables -t nat -D PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
   fi
   if [ "${2}" = "start" ] || [ "${2}" = "reconnect" ]; then
    iptables -I FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
    iptables -t nat -I PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
   fi

   # VNC stays on 5901
   GUEST_IP="192.168.122.9"
   GUEST_PORT="5901"
   HOST_PORT="5901"
   if [ "${2}" = "stopped" ] || [ "${2}" = "reconnect" ]; then
    iptables -D FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
    iptables -t nat -D PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
   fi
   if [ "${2}" = "start" ] || [ "${2}" = "reconnect" ]; then
    iptables -I FORWARD -o virbr0 -p tcp -d $GUEST_IP --dport $GUEST_PORT -j ACCEPT
    iptables -t nat -I PREROUTING -p tcp --dport $HOST_PORT -j DNAT --to $GUEST_IP:$GUEST_PORT
   fi
fi

Hope this helps someone else.

1 Like