#!/usr/sbin/nft -f define out_if = "enp4s0" define out_ip = 11.22.33.44/32 define vm_if = "br0" define vm_net = 192.168.122.0/24 flush ruleset table ip filter { chain INPUT { type filter hook input priority filter; policy drop; iifname $out_if tcp dport 22 counter jump f2b-sshd ct state related,established counter accept iifname "lo" counter accept meta l4proto icmp accept tcp dport 22 counter accept iifname $out_if udp sport 67-68 udp dport 67-68 counter accept iifname $vm_if udp dport 53 counter accept iifname $vm_if udp sport 67-68 udp dport 67-68 counter accept counter reject with icmp type admin-prohibited } chain FORWARD { type filter hook forward priority filter; policy drop; ct state related,established counter accept # portforwards are implicitly accepted ct status dnat counter accept iifname $vm_if ip saddr $vm_net counter accept counter reject with icmp type admin-prohibited } chain OUTPUT { type filter hook output priority filter; policy drop; counter accept } chain f2b-sshd { counter return } } table ip nat { map portforwards { type inet_service : ipv4_addr . inet_service elements = { 16085 : 192.168.122.2 . 22 } } chain PREROUTING { type nat hook prerouting priority dstnat; policy accept; # portforward (+ hairpinning) iif {$out_if, $vm_if} ip protocol tcp ip daddr $out_ip dnat ip addr . port to tcp dport map @portforwards; } chain INPUT { type nat hook input priority 100; policy accept; } chain OUTPUT { type nat hook output priority -100; policy accept; # portforwards for local connections ip protocol tcp ip daddr $out_ip dnat ip addr . port to tcp dport map @portforwards; } chain POSTROUTING { type nat hook postrouting priority srcnat; policy accept; # masquerade outgoing connections iifname $vm_if oifname $out_if counter masquerade # masquerade hairpinned connections iifname $vm_if oifname $vm_if ct status dnat counter masquerade } }