nftables NAT is more flexible than iptables, allowing you to define sophisticated network address translation rules with finer control, especially when dealing with multiple interfaces and complex routing scenarios.
Let’s see nftables in action. Imagine a simple gateway machine with two network interfaces: eth0 connected to the internet (public IP) and eth1 connected to a private network (private IPs). We want to allow machines on the private network to access the internet, and also allow incoming connections to a specific server on the private network.
Here’s how you’d set that up with nftables:
# Flush existing rules and tables (use with caution!)
nft flush ruleset
# Create a table for NAT
nft add table ip nat
nft add chain ip nat prerouting { type nat hook prerouting priority -100 \; }
nft add chain ip nat postrouting { type nat hook postrouting priority 100 \; }
# Masquerade (SNAT) for outgoing traffic from the private network
# This automatically uses the IP of the outgoing interface (eth0)
nft add rule ip nat postrouting oifname "eth0" masquerade
# DNAT for incoming traffic to a specific private server
# Forwarding TCP port 80 from public IP 192.0.2.1 to private IP 10.0.0.100 on port 80
nft add rule ip nat prerouting ip daddr 192.0.2.1 tcp dport 80 dnat to 10.0.0.100:80
# DNAT for incoming traffic to another private server
# Forwarding TCP port 443 from public IP 192.0.2.1 to private IP 10.0.0.101 on port 443
nft add rule ip nat prerouting ip daddr 192.0.2.1 tcp dport 443 dnat to 10.0.0.101:443
# Enable IP forwarding (this is a sysctl setting, not an nftables rule itself)
echo 1 > /proc/sys/net/ipv4/ip_forward
This setup achieves two primary goals:
-
Masquerading (SNAT): When a device on the private network (e.g.,
10.0.0.5) tries to reach a website on the internet, its source IP (10.0.0.5) needs to be translated to the gateway’s public IP address (192.0.2.1) so the internet server knows where to send the response. Themasqueraderule in thepostroutingchain handles this. It automatically picks up the IP of the outgoing interface (eth0) and applies the source NAT. This is particularly useful when the outgoing interface’s IP is dynamic (like DHCP). -
Destination NAT (DNAT): When an external user tries to access a web server running on
10.0.0.100on the private network via the gateway’s public IP (192.0.2.1) on port80, the incoming packet needs its destination IP and/or port translated. Thednatrule in thepreroutingchain intercepts these incoming packets before routing decisions are made, changing the destination IP to10.0.0.100and the destination port to80.
The prerouting hook is used for DNAT because it’s the first point where packets arrive, allowing us to modify destination addresses before the system decides where to send them. The postrouting hook is used for SNAT because it’s the last point before packets leave the system, allowing us to modify source addresses based on the chosen outgoing interface. The priority values (-100 for prerouting, 100 for postrouting) ensure NAT rules are processed before other filtering rules that might be attached to the same hooks.
The most surprising true thing about nftables NAT is that you can define SNAT rules not just based on the outgoing interface, but also based on the original source IP address of the packet. This allows for very granular control, for instance, if you had multiple private subnets behind a single gateway and wanted to ensure traffic from each subnet used a specific public IP for its SNAT. You could achieve this with a rule like nft add rule ip nat postrouting ip saddr 10.0.0.0/24 snat to 192.0.2.1.
The next concept you’ll likely explore is how to combine NAT with nftables’ powerful filtering capabilities, such as allowing specific incoming connections through your DNAT rules while blocking others.