VyOSXXXVIII · NAT FundamentalsNAT with firewall
NAT with firewall — rule placement, established match, conntrack interaction
What you'll learn
- Configure NAT and firewall rules in the correct order on VyOS 1.5 LTS, with the 1.4+ hook-and-jump firewall model
- Predict whether the firewall sees the pre-NAT or post-NAT address for each rule
- Use the established match on the LAN side to permit NAT'd return traffic
- Diagnose the production failure where the firewall sees the wrong address
- Apply the NAT-firewall rule placement discipline
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)
NAT and firewall are two independent subsystems that process the same packet at different stages of the pipeline. The order matters:
- PREROUTING (DNAT) — destination rewritten.
- Routing decision — egress interface chosen.
- INPUT / FORWARD — firewall consulted.
- POSTROUTING (SNAT) — source rewritten.
The firewall sees the post-DNAT, pre-SNAT packet. That is, the destination has been rewritten (for inbound traffic) but the source has not been rewritten (for outbound traffic). The firewall rule must match against the post-NAT address.
This lesson covers the rule placement discipline, the established match on the LAN side, and the production failure modes where the firewall sees the wrong address.
The packet pipeline
The packet pipeline on VyOS 1.5 LTS is a fixed sequence of hooks. The NAT and firewall rules are placed at specific hooks:
flowchart TB
P["Packet arrives on eth0 (WAN)"]
PR["PREROUTING<br/>DNAT (inbound)<br/>conntrack IN"]
R["Routing decision"]
I_IN["INPUT<br/>firewall for local"]
F["FORWARD<br/>firewall for transit<br/>conntrack FORWARD"]
O["OUTPUT<br/>firewall for local-originated"]
PO["POSTROUTING<br/>SNAT (outbound)<br/>conntrack OUT"]
E["Packet leaves on eth1 (LAN)"]
P --> PR
PR --> R
R -->|"destination is local"| I_IN
R -->|"destination is forwarded"| F
I_IN --> O
F --> PO
O --> PO
PO --> E
The NAT and firewall rules are placed as follows:
- DNAT rules — PREROUTING chain of the
nattable. - SNAT rules — POSTROUTING chain of the
nattable. - Firewall rules — INPUT, FORWARD, OUTPUT chains of
the
filtertable.
The kernel processes the nat table first (in PREROUTING
and POSTROUTING), then the filter table (in INPUT,
FORWARD, OUTPUT). The firewall sees the post-DNAT,
pre-SNAT packet.
The firewall sees the post-NAT address
For inbound traffic (Internet client to private host):
- The packet arrives with destination = public IP (203.0.113.10).
- PREROUTING DNAT rewrites the destination to private IP (192.168.1.100).
- The routing decision chooses the egress interface (eth1, the LAN).
- The FORWARD firewall sees destination = 192.168.1.100 (post-NAT).
- The firewall rule must match against 192.168.1.100.
The DNAT rule that sets this up:
set nat destination rule 10 inbound-interface name 'eth0'
set nat destination rule 10 protocol 'tcp'
set nat destination rule 10 destination port '443'
set nat destination rule 10 translation address '192.168.1.100'
The interface is inbound-interface name eth0, not a bare
inbound-interface eth0: on 1.4 and 1.5 the interface matcher
grew a level, so it takes either name for a literal interface
or group for an interface group.
The firewall rule that admits the translated packet must be written against the private IP:
set firewall ipv4 name WAN-IN rule 30 action 'accept'
set firewall ipv4 name WAN-IN rule 30 protocol 'tcp'
set firewall ipv4 name WAN-IN rule 30 destination address '192.168.1.100'
set firewall ipv4 name WAN-IN rule 30 destination port '443'
If the operator writes the firewall rule against the
public IP (destination address 203.0.113.10), the
rule never matches because the firewall sees the private
IP. The traffic is silently dropped.
The established match on the LAN side
For outbound traffic (private host to Internet), the SNAT rule rewrites the source. The firewall sees the post-NAT packet on the WAN interface. The firewall rule on the WAN side is typically permissive (it accepts all outbound traffic).
For the return traffic, the firewall sees the packet on the LAN interface. The destination has been rewritten back to the private IP (192.168.1.100). The firewall rule must match the established connection.
# Accepts established/related return traffic
set firewall ipv4 name LAN-FW rule 10 action 'accept'
set firewall ipv4 name LAN-FW rule 10 description 'Established/related return'
set firewall ipv4 name LAN-FW rule 10 state 'established'
set firewall ipv4 name LAN-FW rule 10 state 'related'
Note the shape of the state match. On 1.3 each state was a node
with an enable leaf (state established enable); on 1.4 and
1.5 state is a multi-value leaf and the states are its values,
so it is state 'established' on its own. The old form does not
commit, which at least fails loudly — unlike most of the traps
in this lesson.
The established match uses conntrack. The return packet of a NAT’d flow is matched against the conntrack entry; the entry shows the flow is ESTABLISHED; the firewall admits the packet.
This is the canonical pattern for outbound NAT:
- LAN host sends a packet to the Internet.
- The router SNATs the source.
- The firewall (FORWARD chain) sees the post-NAT packet.
- The firewall rule accepts the outbound traffic (typically a permissive rule for the LAN zone).
- The Internet server replies.
- The reply arrives at the router.
- The router uses conntrack to reverse the SNAT (destination = private IP).
- The firewall sees the post-NAT packet on the LAN interface (destination = private IP).
- The firewall rule matches the established/related state; the packet is admitted.
- The reply is forwarded to the LAN host.
The established match is the production default for the LAN-side return traffic. Without it, the operator must write explicit rules for every protocol the LAN hosts may use (HTTP, HTTPS, DNS, SSH, etc.).
Rule placement discipline
The production rule placement discipline:
| Direction | NAT | Firewall |
|---|---|---|
| Inbound (WAN to LAN) | DNAT in PREROUTING | forward chain matches the post-DNAT destination — the private IP |
| Outbound (LAN to WAN) | SNAT in POSTROUTING | forward chain matches the pre-SNAT source — the private IP |
| Return (WAN to LAN) | Reverse DNAT in PREROUTING | forward chain matches established/related |
| Return (LAN to WAN) | Reverse SNAT in POSTROUTING | forward chain matches established/related |
Both traffic-initiating rows are written against the private address, for the two different reasons set out above. The return rows are written against connection state and need no address at all, which is the whole reason the established match is worth having.
# WAN-IN rule set — traffic arriving from the WAN
set firewall ipv4 name WAN-IN default-action 'drop'
set firewall ipv4 name WAN-IN enable-default-log
set firewall ipv4 name WAN-IN rule 10 action 'accept'
set firewall ipv4 name WAN-IN rule 10 state 'established'
set firewall ipv4 name WAN-IN rule 10 state 'related'
set firewall ipv4 name WAN-IN rule 20 action 'drop'
set firewall ipv4 name WAN-IN rule 20 state 'invalid'
set firewall ipv4 name WAN-IN rule 20 log
# ... explicit permits for legitimate inbound services, such as
# the DNAT'd HTTPS service written against 192.168.1.100 above
# LAN-FW rule set — traffic arriving from the LAN
set firewall ipv4 name LAN-FW default-action 'drop'
set firewall ipv4 name LAN-FW enable-default-log
set firewall ipv4 name LAN-FW rule 10 action 'accept'
set firewall ipv4 name LAN-FW rule 10 state 'established'
set firewall ipv4 name LAN-FW rule 10 state 'related'
set firewall ipv4 name LAN-FW rule 20 action 'accept'
set firewall ipv4 name LAN-FW rule 20 source address '192.168.1.0/24'
set firewall ipv4 name LAN-FW rule 20 state 'new'
# Reach them from the forward hook
set firewall ipv4 forward filter default-action 'drop'
set firewall ipv4 forward filter rule 10 inbound-interface name 'eth0'
set firewall ipv4 forward filter rule 10 action 'jump'
set firewall ipv4 forward filter rule 10 jump-target 'WAN-IN'
set firewall ipv4 forward filter rule 20 inbound-interface name 'eth1'
set firewall ipv4 forward filter rule 20 action 'jump'
set firewall ipv4 forward filter rule 20 jump-target 'LAN-FW'
A named rule set on 1.4 and 1.5 is inert on its own. It is a
chain nothing enters until a rule in one of the three base
hooks — forward filter for transit traffic, input filter
for traffic addressed to the router, output filter for traffic
it originates — jumps into it. That is the replacement for the
1.3 per-interface firewall in name ... binding, and it is not
a rename: the interface is now a match inside an ordered rule,
so the ordering of your jumps relative to each other, and
relative to the base chain’s own default-action, is something
you now own.
The two chains together permit:
- Inbound traffic that is established/related (return of outbound NAT’d traffic).
- Inbound traffic that is explicitly permitted (DNAT’d services).
- Outbound traffic from the LAN.
- Return traffic from outbound flows (established/related on the LAN side).
Production failure modes
The five production failure modes the operator must recognise:
- Firewall rule against pre-NAT address — the
operator writes
destination address 203.0.113.10(the public IP) but the firewall sees192.168.1.100(the private IP). The rule never matches; the traffic is dropped. - Missing established match — the LAN-side firewall does not have an established/related accept rule. The return of NAT’d outbound traffic is dropped.
- The jump never happens. The named rule set is
perfect and nothing reaches it, because no rule in
forward filterjumps to it — or one does, but a broader rule earlier in that chain accepted or dropped the packet first. The evidence is a rule set whose counters stay at zero while traffic is plainly moving. - Stale conntrack entry — the NAT rule is changed but the conntrack entries are not flushed. Existing flows use the old translation.
- A source rule that translates nothing. A
nat sourcerule with matchers but notranslationclause commits happily and forwards the packet untranslated. For masquerade the clause is explicit:set nat source rule 100 translation address 'masquerade'. A rule that only says which traffic to match has not said what to do with it.
Operational commands
The NAT-with-firewall state is exercised through the standard VyOS operational commands:
# Show the NAT rules and their translation counters
show nat source rules
show nat destination rules
# Show the firewall rule sets and the hook that reaches them
show firewall ipv4 name WAN-IN
show firewall ipv4 name LAN-FW
show firewall ipv4 forward filter
# The rendered ruleset — the authority on hook order
sudo nft list ruleset
# Conntrack: the table, and a live event stream
sudo conntrack -L
sudo conntrack -E
# Capture on both sides of the translation
sudo tcpdump -ni eth0 'port 443'
sudo tcpdump -ni eth1 'port 443'
The rule counters are the fastest diagnosis available: a rule set whose counters are all zero was never reached, which is a different problem from a rule that was reached and did not match.
Rollback
The NAT-with-firewall 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 rules and re-commit
delete nat destination rule 10
delete firewall ipv4 name WAN-IN rule 30
commit
save
Production discipline
Cross-course references
- Part XXXVII-05 (
XXXVII-VyOS-Firewall/ default deny) covers the firewall chains. - Part XXXVIII-01 (
XXXVIII-VyOS-NAT/ SNAT vs DNAT concept) covers the NAT hooks. - 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
Q1. An operator configures DNAT for HTTPS (public IP 203.0.113.10 -> private IP 192.168.1.100). The operator writes a firewall rule with `destination address 203.0.113.10`. Why does the rule never match?
Q2. The established/related match on the LAN-side firewall is required to admit the return of NAT'd outbound traffic.
Q3. An operator configures DNAT for HTTPS and writes a firewall rule against the public IP. The port forwarding does not work. The operator's first action is to verify whether the rule is correct. What does the operator check?
DNAT rule: destination address 203.0.113.10 -> translation address 192.168.1.100. Firewall rule: destination address 203.0.113.10, destination port 443, protocol tcp, action accept. The port forwarding is broken.
Q4. An operator deploys NAT and firewall for outbound traffic. The outbound traffic works (LAN hosts can reach the Internet). The return traffic is silently dropped. The operator observes that `sudo conntrack -L` shows the ESTABLISHED entries but the firewall log shows the return packets are dropped at the catch-all deny. What is the cause, and what is the fix?
Outbound traffic: LAN hosts send packets to the Internet. SNAT rule rewrites the source. Firewall rule on WAN accepts the post-NAT traffic. Internet server replies. Reply arrives at router. Router uses conntrack to reverse SNAT. Firewall rule on LAN checks the reply. The firewall log shows the reply is dropped at the catch-all deny of the LAN-FW chain.
Passing score: 75%. Answers are checked in this browser.