{ config, pkgs, ... }: let # kittykat public NIC publicInterface = "enp1s0"; # SSH tunnel IPs overlordTunIp = "10.0.0.1"; kittykatTunIp = "10.0.0.2"; # Zomboid ports: # 16261 = main game port # 16262+ = player ports, using 16262-16272 as a sane test range # 52015 = extra UDP port your server is listening on zomboidUdpPorts = [ 16261 16262 16263 16264 16265 16266 16267 16268 16269 16270 16271 16272 52015 ]; in { boot.kernelModules = ["tun"]; boot.kernel.sysctl = { "net.ipv4.ip_forward" = 1; }; services.openssh = { enable = true; settings = { PermitTunnel = "yes"; # Strongly prefer key-only root login if you're using root for the tunnel. PasswordAuthentication = false; }; }; # Declaratively create kittykat's tun0. networking.interfaces.tun0 = { virtual = true; virtualType = "tun"; ipv4.addresses = [ { address = kittykatTunIp; prefixLength = 30; } ]; }; networking.firewall = { enable = true; # Public Zomboid UDP ports on kittykat. allowedUDPPorts = zomboidUdpPorts; # Allow tunnel-side packets too. interfaces.tun0.allowedUDPPorts = zomboidUdpPorts; }; networking.nftables = { enable = true; ruleset = '' table ip zomboid_tunnel { chain prerouting { type nat hook prerouting priority dstnat; policy accept; # Public players -> kittykat public IP -> overlord over tun0 iifname "${publicInterface}" udp dport 16261-16272 dnat to ${overlordTunIp} iifname "${publicInterface}" udp dport 52015 dnat to ${overlordTunIp}:52015 } chain postrouting { type nat hook postrouting priority srcnat; policy accept; # Important: # Make overlord see traffic as coming from kittykat's tun IP, # so replies go back through the tunnel instead of overlord's normal internet route. oifname "tun0" ip daddr ${overlordTunIp} udp dport 16261-16272 snat to ${kittykatTunIp} oifname "tun0" ip daddr ${overlordTunIp} udp dport 52015 snat to ${kittykatTunIp} } chain forward { type filter hook forward priority filter; policy accept; # Public -> tunnel iifname "${publicInterface}" oifname "tun0" ip daddr ${overlordTunIp} udp dport 16261-16272 accept iifname "${publicInterface}" oifname "tun0" ip daddr ${overlordTunIp} udp dport 52015 accept # Tunnel replies -> public iifname "tun0" oifname "${publicInterface}" ip saddr ${overlordTunIp} accept } } ''; }; }