The most surprising thing about nftables masquerading is that it’s not a separate feature, but rather a specific application of its connection tracking and address translation capabilities.
Let’s see it in action. Imagine you have a private network with IP addresses like 192.168.1.0/24 and you want your machines on this network to access the internet. Your gateway machine, running nftables, has two interfaces: eth0 connected to the internet (with a public IP) and eth1 connected to your private network.
Here’s a basic nftables configuration to achieve this:
table ip nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
oifname "eth0" masquerade
}
}
When a packet originates from a machine on your private network (e.g., 192.168.1.10) destined for the internet (e.g., 8.8.8.8), it arrives at the gateway’s eth1 interface. The postrouting hook in the nat table intercepts this packet after the routing decision has been made but before it leaves the gateway.
The oifname "eth0" rule matches packets that are about to be sent out the eth0 interface. The masquerade keyword is a shorthand for a specific type of Source NAT (SNAT). It tells nftables to:
- Track the connection:
nftablesuses its connection tracking (conntrack) module to remember this outgoing connection. - Translate the source IP: It replaces the private source IP address (
192.168.1.10) with the gateway’s public IP address oneth0. - Translate the source port (if necessary): It also assigns a new source port to the packet, typically a random high port, and records this mapping in the
conntracktable.
When a reply packet comes back from the internet, it will be addressed to the gateway’s public IP and the port assigned by masquerade. The prerouting hook (which we haven’t explicitly defined a chain for here, but nftables handles it implicitly for NAT) or a conntrack lookup will see the destination IP and port, find the corresponding entry in the conntrack table, and translate the destination IP and port back to the original private IP (192.168.1.10) and its original port before forwarding it to the private network.
The beauty of masquerade is its dynamic nature. It automatically uses the IP address of the outgoing interface. This is incredibly useful for dynamic IP addresses, like those assigned by DHCP, because you don’t need to hardcode the public IP. If the gateway’s public IP changes, masquerade will automatically start using the new IP without any configuration changes.
Here’s a slightly more detailed view, showing how you’d typically add this to a more complete nftables setup with basic filtering:
flush ruleset
table ip filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow established/related connections
ct state established,related accept
# Allow loopback
iif lo accept
# Allow ICMP
ip protocol icmp accept
# Allow SSH from trusted IPs (example)
tcp dport 22 ip saddr 192.168.1.0/24 accept
# Drop everything else
reject
}
chain forward {
type filter hook forward priority 0; policy drop;
# Allow established/related connections for forwarding
ct state established,related accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority -150; policy accept;
# For incoming traffic, if you have specific DNAT rules
}
chain postrouting {
type nat hook postrouting priority 100; policy accept;
# Masquerade traffic going out the public interface (eth0)
oifname "eth0" masquerade
}
}
# Example: Add a rule to allow forwarded traffic from the private network
table ip filter {
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
# Allow traffic from private network to anywhere
iifname "eth1" accept
}
}
In this expanded example, the filter table’s forward chain is crucial. masquerade handles the IP translation, but the filter table’s forward chain decides whether packets between interfaces are allowed to pass. The iifname "eth1" accept rule in the forward chain permits traffic originating from the private network (eth1) to be forwarded. Without this, even though the NAT rule would translate the addresses, the firewall would block the packets from traversing the gateway.
The masquerade target implicitly uses the conntrack module and applies SNAT. It’s a powerful shortcut when the external IP is dynamic or when you simply want to avoid specifying an explicit source IP address. The postrouting hook is chosen because the NAT decision needs to happen after the routing table lookup determines the outgoing interface, but before the packet is sent.
One significant aspect often overlooked is how masquerade interacts with multiple external interfaces. If your gateway had two internet connections and you wanted to masquerade traffic out either of them, you’d typically define separate masquerade rules for each outgoing interface (oifname "eth0" masquerade and oifname "eth1" masquerade). However, if you wanted to bind masquerading to a specific external IP address rather than the interface’s current IP, you would use the snat target instead. For example, oifname "eth0" snat to 1.2.3.4.
The next logical step after setting up basic NAT is often to configure specific port forwarding (Destination NAT or DNAT) for services running on your private network that need to be accessible from the internet.