Skip to main content
RunBook Academy

OPNsenseXII · NAT Fundamentals and Outbound NATNAT fundamentals

NAT fundamentals — SNAT, DNAT, and where in the packet flow each one happens

Foundation⏱ ~12 minpfctltcpdump

What you'll learn

  • Distinguish source NAT (SNAT) from destination NAT (DNAT)
  • Identify where in the PF packet flow each translation occurs
  • Trace a packet from source to destination through both NAT stages
  • Recognise the relationship between NAT and PF state

Prerequisites

Verified against OPNsense 25.x · FreeBSD 14.x · PF (FreeBSD packet filter) FreeBSD 14.x · Unbound 1.20+ · Kea DHCP OPNsense 25.x plugin · WireGuard in-kernel + OPNsense plugin · strongSwan (IPsec plugin) OPNsense 25.x plugin · OpenVPN 2.6.x · Suricata 7.x · 2026-08-14

Not yet marked complete on this device.

Every Internet-connected firewall performs translation. A host on the LAN has a private address; the firewall rewrites the source to the public WAN address before sending the packet out. A server on the LAN is reachable from the Internet; the firewall rewrites the destination from the WAN address to the server’s private address before forwarding the packet in. Both are NAT, but they are different operations, happen in different directions, and live in different parts of the packet flow.

This lesson covers the two flavours of NAT — source and destination — and where in the PF pipeline each one runs. Every other NAT concept in the course builds on this.

Two flavours of NAT

Source NAT (SNAT) rewrites the source of the packet. The typical use case is outbound traffic: a LAN client sends a packet to the Internet, and the firewall changes the source IP from the client’s private address to the firewall’s public WAN address. The remote server sees traffic from the WAN IP; the reply comes back to the WAN IP; the firewall reverses the translation for the return path.

Destination NAT (DNAT) rewrites the destination of the packet. The typical use case is inbound traffic: an Internet client sends a packet to the firewall’s WAN IP on a forwarded port, and the firewall changes the destination IP to the internal server’s private address. The internal server sees traffic as if it came directly from the Internet client; the reply goes back through the firewall, which reverses the translation.

PF uses the term rdr for DNAT (redirect) and the term nat for SNAT. The two are evaluated in different places in the packet flow.

Read-only / Safepfctl -s nat
$ pfctl -s nat
No NAT rules loaded.

nat-anchor "nat/*" {
}
rdr-anchor "rdr/*" {
}

Illustrative output

Where in the packet flow each translation runs

PF processes packets in a fixed order, and translation happens at specific points:

  1. Inbound hook on the arriving interface. The packet arrives.
  2. DNAT (rdr). If the packet matches an rdr rule bound to this interface, the destination address is rewritten.
  3. Filter rules, inbound. PF evaluates the (post-DNAT) ruleset for this hook. If a rule matches, PF creates state and decides to pass or block.
  4. Routing. The kernel picks the egress interface and next hop for the (now possibly-rewritten) packet.
  5. SNAT (nat). If the packet matches a nat rule bound to the egress interface, the source address is rewritten.
  6. Filter rules, outbound. Normally skipped: the packet matches the state step 3 created and pf passes a state match without evaluating any rules. Where the ruleset is walked here, it sees the post-SNAT source.
  7. Outgoing interface. The packet is sent out.

The order matters, and the rule is the same on both hooks: translation first, filtering second. DNAT therefore runs before the inbound filter rules, which must reference the post-DNAT destination; SNAT runs before the outbound filter rules, so an out rule on the WAN sees the translated source. The reason an operator’s LAN rule matches the client’s real address is not that SNAT is late — it is that the LAN inbound hook is a different hook from the one the nat rule is bound to.

The packet flow with NAT, end-to-end

Consider an outbound flow: LAN client 192.0.2.50 wants to reach 203.0.113.50 on TCP/443. The WAN IP is 198.51.100.1.

  1. Packet arrives on LAN (igb0) with source 192.0.2.50:51234, destination 203.0.113.50:443.
  2. No rdr rule matches (DNAT is inbound). Filter evaluates the LAN rules: the pass rule for outbound HTTPS matches.
  3. PF routes the packet toward 203.0.113.50 via WAN (igb1).
  4. NAT rule on WAN matches: source 192.0.2.50 is rewritten to 198.51.100.1.
  5. Packet leaves WAN with source 198.51.100.1:51234, destination 203.0.113.50:443.
  6. State entry records the translation: external source 198.51.100.1:51234 corresponds to internal source 192.0.2.50:51234.

The reply follows the reverse path. PF matches the state entry and reverses the translation: source 203.0.113.50:443 back to 192.0.2.50:51234 (unchanged on the reply’s source — only the destination is rewritten back). The reply reaches the LAN client without the client ever knowing the translation happened.

Now consider an inbound flow: Internet client 203.0.113.99 wants to reach a web server on TCP/80, forwarded to 192.0.2.10. The WAN IP is 198.51.100.1.

  1. Packet arrives on WAN (igb1) with source 203.0.113.99:51820, destination 198.51.100.1:80.
  2. RDR rule matches: destination 198.51.100.1:80 is rewritten to 192.0.2.10:80.
  3. Filter evaluates the WAN rules: the auto-generated associated rule for the port forward permits the traffic.
  4. PF routes the packet toward 192.0.2.10 via LAN (igb0).
  5. NAT rule on LAN does not match (no SNAT for inbound forwarded traffic). Source remains 203.0.113.99:51820.
  6. Packet leaves LAN with source 203.0.113.99:51820, destination 192.0.2.10:80.
Read-only / Safepfctl state with translation
$ pfctl -ss | grep -A 1 '203.0.113.99'
all tcp 203.0.113.99:51820 <- 192.0.2.10:80       ESTABLISHED:ESTABLISHED
[ 1723123456 + 86400 ] age 00:00:03, 203.0.113.99:51820 -> 198.51.100.1:80

Illustrative output

Translation and state are inseparable

A translation is not useful without state. PF must remember that the outbound packet with source 198.51.100.1:51234 corresponds to the internal 192.0.2.50:51234 so that the reply — which arrives with destination 198.51.100.1:51234 and source 203.0.113.50:443 — can be reverse-translated to destination 192.0.2.50:51234 and source 203.0.113.50:443 for delivery to the LAN client.

Without state, the reply would be undeliverable: the firewall would not know which internal host sent the original packet, and the internal host would not recognise the reply (its source would be 198.51.100.1:51234, not 192.0.2.50:51234). This is why every NAT rule in PF implicitly creates state for matched flows.

Why this matters for the rest of the course

Every lesson in Parts XII and XIII builds on this single fact: translation always precedes filtering on the hook it is bound to — DNAT on the ingress hook, SNAT on the egress hook — and state ties the two views together.

The outbound NAT lessons (68–72) focus on SNAT — the rules that change the source on outbound traffic, how to choose automatic vs manual modes, and the production traps of broad translations.

The port-forwarding lessons (73–78) focus on DNAT — the rules that change the destination on inbound traffic, the associated firewall rule that permits the traffic after DNAT, and the hairpin/return paths that are the source of most “port forward works from the Internet but not from the LAN” tickets.

Summary

  • SNAT rewrites the source address (typical case: outbound traffic leaving the LAN).
  • DNAT rewrites the destination address (typical case: inbound traffic to a forwarded port).
  • Translation runs before filtering on the hook it is bound to: DNAT before the inbound filter rules, SNAT before the outbound ones.
  • Inbound rules — the ones OPNsense generates and the ones operators write — therefore see pre-SNAT sources and post-DNAT destinations, the local view of the flow. An out rule on the egress interface sees the post-SNAT source.
  • A translation is recorded in PF state; without state, return traffic cannot be reverse-translated.
  • NAT is not a security boundary. Filter rules are.

Knowledge check · 4 questions

  1. Q1. A packet flows from LAN client 192.0.2.50 to an Internet host, with the firewall performing outbound NAT. Which address does the filter stage see as the source?

  2. Q2. In PF, translation is evaluated before the filter rules on the same hook — rdr before the inbound rules on the ingress interface, nat before the outbound rules on the egress interface.

  3. Q3. Which of the following are true about NAT and PF state? Select all that apply.

  4. Q4. A forwarded port from the WAN IP 198.51.100.1:443 to internal server 192.0.2.10:443. An inbound packet arrives at the WAN. After PF processes the rdr rule and the filter stage, what destination address does the packet carry as it is forwarded to the LAN?

Passing score: 75%. Answers are checked in this browser.