Network topology: Virtual private server (VPS) hosts wireguard server and performs NAT over ports 80 and 443 to a home machine which hosts my webserver.
I’ve been successfully using iptables to perform port forwarding for a while but during a revamp of my DNS setup I wanted to finally migrate to NFTables as it is the modern replacement for IPtables as I understand it.
From what I can tell the firewall rules that nix generates for each should accomplish the same thing but NFTables doesn’t seem to be forwarding the traffic.
IPTables Nix Config:
{
networking.firewall = {
enable = true;
allowedUDPPorts = [ 80 443 ];
allowedTCPPorts = [80 443 ];
};
networking.nat = {
enable = true;
externalInterface = "eth0";
internalInterfaces = [ "wg0" ];
externalIP = "<bastion IP>";
extraCommands = ''
iptables -t nat -A POSTROUTING -d 10.100.0.4 -p tcp -m tcp --dport 80 -j MASQUERADE;
iptables -t nat -A POSTROUTING -d 10.100.0.4 -p tcp -m tcp --dport 443 -j MASQUERADE;
iptables -t nat -A POSTROUTING -d 10.100.0.4 -p udp -m udp --dport 80 -j MASQUERADE;
iptables -t nat -A POSTROUTING -d 10.100.0.4 -p udp -m udp --dport 443 -j MASQUERADE;
'';
forwardPorts = [
{
destination = "10.100.0.4:80";
proto = "tcp";
sourcePort = 80;
}
{
destination = "10.100.0.4:443";
proto = "tcp";
sourcePort = 443;
}
{
destination = "10.100.0.4:80";
proto = "udp";
sourcePort = 80;
}
{
destination = "10.100.0.4:443";
proto = "udp";
sourcePort = 443;
}
];
};
}
Output from sudo iptables -L -t nat
:
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
nixos-nat-pre all -- anywhere anywhere
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
nixos-nat-out all -- anywhere anywhere
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE tcp -- anywhere <home webserver hostname> tcp dpt:http
MASQUERADE tcp -- anywhere <home webserver hostname> tcp dpt:https
MASQUERADE udp -- anywhere <home webserver hostname> udp dpt:http
MASQUERADE udp -- anywhere <home webserver hostname> udp dpt:https
nixos-nat-post all -- anywhere anywhere
Chain nixos-nat-out (1 references)
target prot opt source destination
Chain nixos-nat-post (1 references)
target prot opt source destination
SNAT all -- anywhere anywhere mark match 0x1 to:192.155.91.91
Chain nixos-nat-pre (1 references)
target prot opt source destination
MARK all -- anywhere anywhere MARK set 0x1
DNAT tcp -- anywhere anywhere tcp dpt:http to:10.100.0.4:80
DNAT udp -- anywhere anywhere udp dpt:http to:10.100.0.4:80
DNAT tcp -- anywhere anywhere tcp dpt:https to:10.100.0.4:443
DNAT udp -- anywhere anywhere udp dpt:https to:10.100.0.4:443
NFTables Nix Config:
{
networking.nftables.enable = true;
networking.firewall = {
enable = true;
allowedUDPPorts = [ 80 443 ];
allowedTCPPorts = [80 443 ];
};
networking.nat = {
enable = true;
externalInterface = "eth0";
internalInterfaces = [ "wg0" ];
externalIP = "<bastion IP>";
# It looks like nix handles building these rules for me?
#extraCommands = ''
# iptables -t nat -A POSTROUTING -d 10.100.0.4 -p tcp -m tcp --dport 80 -j MASQUERADE;
# iptables -t nat -A POSTROUTING -d 10.100.0.4 -p tcp -m tcp --dport 443 -j MASQUERADE;
# iptables -t nat -A POSTROUTING -d 10.100.0.4 -p udp -m udp --dport 80 -j MASQUERADE;
# iptables -t nat -A POSTROUTING -d 10.100.0.4 -p udp -m udp --dport 443 -j MASQUERADE;
'';
forwardPorts = [
{
destination = "10.100.0.4:80";
proto = "tcp";
sourcePort = 80;
}
{
destination = "10.100.0.4:443";
proto = "tcp";
sourcePort = 443;
}
{
destination = "10.100.0.4:80";
proto = "udp";
sourcePort = 80;
}
{
destination = "10.100.0.4:443";
proto = "udp";
sourcePort = 443;
}
];
};
}
Output from sudo nft list ruleset
:
table inet nixos-fw {
chain rpfilter {
type filter hook prerouting priority mangle + 10; policy drop;
meta nfproto ipv4 udp sport . udp dport { 68 . 67, 67 . 68 } accept comment "DHCPv4 client/server"
meta ipsec exists accept comment "decrypted packets from an IPsec VPN"
fib saddr . mark . iif oif exists accept
jump rpfilter-allow
}
chain rpfilter-allow {
}
chain input {
type filter hook input priority filter; policy drop;
iifname "lo" accept comment "trusted interfaces"
ct state vmap { invalid : drop, established : accept, related : accept, new : jump input-allow, untracked : jump input-allow }
tcp flags syn / fin,syn,rst,ack log prefix "refused connection: " level info
}
chain input-allow {
tcp dport { 22, 80, 443} accept
udp dport { 80, 443, 51820 } accept
iifname "wg0" tcp dport { 9154, 9166 } accept
icmp type echo-request accept comment "allow ping"
icmpv6 type != { nd-redirect, 139 } accept comment "Accept all ICMPv6 messages except redirects and node information queries (type 139). See RFC 4890, section 4.4."
ip6 daddr fe80::/64 udp dport 546 accept comment "DHCPv6 client"
}
}
table ip nixos-nat {
chain pre {
type nat hook prerouting priority dstnat; policy accept;
iifname "eth0" meta l4proto { tcp, udp } dnat ip to meta l4proto . th dport map { tcp . 80 : 10.100.0.4 . 80, udp . 80 : 10.100.0.4 . 80, tcp . 443 : 10.100.0.4 . 443, udp . 443 : 10.100.0.4 . 443 } comment "port forward"
}
chain post {
type nat hook postrouting priority srcnat; policy accept;
iifname "wg0" oifname "eth0" masquerade comment "from internal interfaces"
iifname != "eth0" ip daddr . meta l4proto . th dport { 10.100.0.4 . tcp . 80, 10.100.0.4 . udp . 80, 10.100.0.4 . tcp . 443, 10.100.0.4 . udp . 443 } masquerade comment "port forward loopback snat"
}
chain out {
type nat hook output priority mangle; policy accept;
}
}
Is nix’s expression of port forwarding not handling something?