Skip to main content
RunBook Academy

VyOSXXXVIII · NAT FundamentalsSNAT vs DNAT concept

SNAT vs DNAT concept — source vs destination NAT, the kernel nftables primitives

Advanced⏱ ~22 minvyosconfigureset nat source ruleset nat destination rulecommitsaveshow natconntrack -Lconntrack -Etcpdump

What you'll learn

  • Distinguish source NAT from destination NAT and the direction of translation
  • Trace a packet through the NAT hooks (PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING)
  • Configure SNAT and DNAT on VyOS 1.5 LTS and validate with conntrack
  • Recognise the production failure modes where the NAT direction is wrong
  • Reason about conntrack and NAT asymmetry

Prerequisites

Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling) · 2026-08-15

Not yet marked complete on this device.

Network Address Translation (NAT) is the mechanism that allows a router to rewrite the source or destination address of a packet as it traverses the router. The two flavours are:

  • Source NAT (SNAT) — rewrites the source address of egress traffic. The classic example: a private host (192.168.1.100) sends a packet to an Internet server; the router rewrites the source to the public address (203.0.113.10) before the packet leaves.
  • Destination NAT (DNAT) — rewrites the destination address of ingress traffic. The classic example: an Internet client sends a packet to the public address (203.0.113.10:443); the router rewrites the destination to the private host (192.168.1.100:443) before the packet is forwarded.

On VyOS 1.5 LTS both flavours are implemented on top of the Linux kernel’s nftables NAT primitives. The kernel hooks (PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING) determine where the translation happens; the conntrack table records the translation so the return packets are correctly reversed.

This lesson introduces the NAT primitives, the kernel hooks, the conntrack interaction, and the production failure modes where the NAT direction is wrong or the conntrack entry is stale.

The five NAT hooks

The Linux kernel processes a packet through five NAT hooks. Each hook has a specific role:

  • PREROUTING — DNAT for inbound traffic. The packet arrives on the ingress interface; the kernel consults the PREROUTING chain to determine the destination address. If the destination matches a DNAT rule, the address is rewritten before the routing decision.
  • INPUT — DNAT for traffic destined for the router itself. The packet has been routed to the router’s IP; the INPUT chain can rewrite the destination before the local socket receives it.
  • FORWARD — DNAT and SNAT for traffic being forwarded. The packet has been routed and is being forwarded through the router.
  • OUTPUT — DNAT for traffic originating from the router. The packet was generated locally; the OUTPUT chain can rewrite the destination.
  • POSTROUTING — SNAT for outbound traffic. The packet has been routed and is about to leave on the egress interface; the POSTROUTING chain can rewrite the source.
flowchart LR
  P["Packet arrives on eth0 (WAN)"]
  PR["PREROUTING<br/>DNAT for inbound<br/>(e.g., port forward)"]
  R["Routing decision"]
  I["INPUT<br/>DNAT for local<br/>(e.g., DNAT to router)"]
  F["FORWARD<br/>DNAT/SNAT for transit"]
  OUT["OUTPUT<br/>DNAT for local-originated"]
  PO["POSTROUTING<br/>SNAT for outbound<br/>(e.g., masquerade)"]
  E["Packet leaves on eth1 (LAN) or vice versa"]

  P --> PR
  PR --> R
  R -->|"destination is local"| I
  R -->|"destination is forwarded"| F
  I --> OUT
  F --> PO
  OUT --> PO
  PO --> E

The two NAT-relevant hooks are PREROUTING (DNAT for inbound) and POSTROUTING (SNAT for outbound). The INPUT, FORWARD, and OUTPUT hooks are involved in specific cases but the typical SNAT/DNAT traffic passes through PREROUTING and POSTROUTING.

Source NAT (SNAT)

SNAT rewrites the source address of egress traffic. The typical use case is allowing internal hosts to access the Internet through a single public IP address.

# Source NAT for outbound traffic
set nat source rule 10 outbound-interface name eth0
set nat source rule 10 source address 192.168.1.0/24
set nat source rule 10 translation address 203.0.113.10
set nat source rule 10 protocol tcp_udp

# Apply the SNAT (commit triggers the configuration)
commit

The rule says: any packet from 192.168.1.0/24 leaving on eth0 has its source address rewritten to 203.0.113.10.

The kernel implementation: the packet walks the POSTROUTING chain (after routing has decided the egress interface is eth0). The rule matches (source in 192.168.1.0/24, egress interface eth0); the source address is rewritten. The conntrack entry records the translation:

tcp 6 300 ESTABLISHED src=192.168.1.100 dst=198.51.100.5
  sport=52012 dport=443 packets=12 bytes=1844 [ASSURED]
  src=198.51.100.5 dst=203.0.113.10 sport=443 dport=52012
  packets=8 bytes=4096

The conntrack entry has two directions:

  • Original directionsrc=192.168.1.100 (the original source), dst=198.51.100.5 (the original destination).
  • Reply directionsrc=198.51.100.5 (the original destination), dst=203.0.113.10 (the rewritten source).

When the reply packet arrives, the kernel uses the reply direction to look up the conntrack entry, then rewrites the destination back to the original (192.168.1.100) before forwarding.

Destination NAT (DNAT)

DNAT rewrites the destination address of ingress traffic. The typical use case is port forwarding: an Internet client connects to the public IP; the router rewrites the destination to the private host.

# Destination NAT for inbound traffic
set nat destination rule 10 inbound-interface name eth0
set nat destination rule 10 destination port 443
set nat destination rule 10 protocol tcp
set nat destination rule 10 translation address 192.168.1.100
set nat destination rule 10 translation port 443

commit

The rule says: any TCP packet arriving on eth0 with destination port 443 has its destination rewritten to 192.168.1.100:443.

The kernel implementation: the packet walks the PREROUTING chain (before routing has decided where to forward). The rule matches (destination port 443, protocol tcp); the destination address is rewritten. The conntrack entry records the translation:

tcp 6 300 ESTABLISHED src=198.51.100.5 dst=203.0.113.10
  sport=52012 dport=443 packets=12 bytes=1844 [ASSURED]
  src=192.168.1.100 dst=198.51.100.5 sport=443 dport=52012
  packets=8 bytes=4096

The reply direction has the original destination (the public IP) and the rewritten source (the private IP). When the reply packet arrives, the kernel uses the reply direction to look up the conntrack entry, then rewrites the source back to the public IP (203.0.113.10) before forwarding.

The conntrack interaction

The conntrack table is the bridge between SNAT and DNAT. The SNAT rule creates a conntrack entry; the DNAT rule creates a conntrack entry; the reply packets use the conntrack entry to reverse the translation.

The conntrack entry is the source of truth for the firewall. The firewall sees the post-NAT address, not the pre-NAT address. A firewall rule that permits the pre-NAT address does not match the post-NAT packet.

sequenceDiagram
  participant C as Client (198.51.100.5)
  participant R as Router
  participant H as Host (192.168.1.100)

  C->>R: SYN dst=203.0.113.10:443
  Note over R: PREROUTING DNAT<br/>dst becomes 192.168.1.100:443<br/>conntrack: 198.51.100.5 ↔ 203.0.113.10 � 192.168.1.100
  R->>H: SYN dst=192.168.1.100:443
  H->>R: SYN/ACK src=192.168.1.100:443
  Note over R: POSTROUTING SNAT (hairpin)<br/>src becomes 203.0.113.10:443
  R->>C: SYN/ACK src=203.0.113.10:443
  C->>R: ACK dst=203.0.113.10:443
  Note over R: conntrack lookup<br/>dst becomes 192.168.1.100:443
  R->>H: ACK dst=192.168.1.100:443

The conntrack entry survives until the flow times out or is explicitly deleted. A conntrack -D command can clear the entry; the next packet of the flow is judged as NEW.

Production failure modes

The five production failure modes the operator must recognise:

  1. Wrong NAT direction — the operator writes a DNAT rule for outbound traffic. The packet is rewritten before routing; the routing decision does not match the rewritten destination. The packet is dropped.
  2. Missing conntrack entry — the SNAT/DNAT rule is in place but conntrack cannot create an entry (table full). The packet is dropped without translation.
  3. Asymmetric routing — the forward path goes through the router but the return path does not. The conntrack entry is created on the forward path; the return packet has no conntrack entry on its path. The translation is not reversed; the return packet is dropped.
  4. Hairpin failure — a host on the LAN tries to reach the public IP of another LAN host. The DNAT rule rewrites the destination to the LAN host, but the LAN host replies with its own IP as the source (no hairpin). The client receives a SYN/ACK from an unexpected source; the TCP handshake fails.
  5. Stale conntrack entry — a SNAT/DNAT rule is changed or removed, but existing flows still have the old conntrack entry. The new packets are translated by the new rule; the existing flows use the old translation. The operator must flush the conntrack table after a NAT change.

Operational commands

The NAT state is exercised through the standard VyOS operational commands plus direct access to the conntrack table:

# Show the NAT rules
show nat source
show nat destination

# Show a specific rule
show nat source rule 10

# Show the conntrack entries (with NAT)
sudo conntrack -L
sudo conntrack -L -n  # numeric addresses

# Live event stream
sudo conntrack -E

# Flush the conntrack table (use after NAT changes)
sudo conntrack -F

# Show the rendered nftables NAT rules
sudo nft list chain inet nat prerouting
sudo nft list chain inet nat postrouting

# Live packet capture
sudo tcpdump -ni eth0 'port 443'

Rollback

The NAT changes are rolled back the same way as any VyOS configuration change:

# Show the candidate diff
compare

# Revert to a previous revision
rollback 1
commit
save

# Or delete the rule and re-commit
delete nat source rule 10
commit
save

# Flush the conntrack table after the rollback
sudo conntrack -F

The discipline: every NAT rollback is followed by a conntrack flush. The flush forces existing flows to re-create their conntrack entries; without the flush, the existing flows continue to use the old translation.

Production discipline

Cross-course references

  • Part XXXVII-01 (XXXVII-VyOS-Firewall / stateful vs stateless) covers the conntrack layer.
  • Part XXXVIII-02 (XXXVIII-VyOS-NAT / masquerade) covers the dynamic SNAT pattern.
  • Part XXXVIII-03 (XXXVIII-VyOS-NAT / port forwarding) covers the DNAT pattern.
  • Part XXXVIII-06 (XXXVIII-VyOS-NAT / NAT troubleshoot) covers the diagnostic method.

Quiz

Knowledge check · 4 questions

  1. Q1. Which kernel hook applies SNAT to outbound traffic?

  2. Q2. A NAT change automatically flushes the existing conntrack entries; existing flows immediately use the new translation.

  3. Q3. An operator configures DNAT to forward inbound HTTPS (203.0.113.10:443) to a host (192.168.1.100:443). The operator does not configure SNAT on the return path. The host's default gateway is the router. An Internet client connects to 203.0.113.10:443. The connection works. Why?

    DNAT rule: inbound-interface eth0, destination port 443, translation address 192.168.1.100:443. No SNAT rule for the return. The host 192.168.1.100 has a default gateway of 192.168.1.1 (the router's LAN IP).

  4. Q4. An operator configures `set nat destination rule 10 translation address 192.168.1.100` (a DNAT rule) but the intent is to allow internal hosts to access the Internet. What happens?

    The operator wrote a DNAT rule (destination translation) for outbound traffic. The intent is to SNAT the internal hosts so they appear to come from 203.0.113.10. The rule is wrong: it rewrites the destination, not the source.

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