Page MenuHomeVyOS Platform

show nat source rules displays protocol/port match criteria that do not exist in the generated nftables ruleset
Open, NormalPublicBUG

Description

Summary

The operational command show nat source rules renders Proto and dport columns for source-NAT rules that have no protocol / destination port configured. For such rules the op-mode table shows the layer-4 match criteria of a preceding rule, while the actual nftables ruleset for those rules contains only ip saddr / ip daddr matches (no protocol, no port) and therefore applies to all traffic between the two subnets.

In other words, the op-mode output disagrees with the dataplane: it displays match criteria that are narrower (and simply different) from what is actually installed in nftables. Operators reading show nat source rules are misled about the real scope of exclude (and translation) rules.

This is particularly dangerous for exclude rules used to keep policy-based IPsec traffic out of source NAT: an operator may believe an exclude is scoped to tcp dport 80,443, when in fact it excludes every protocol/port between the subnets.

Environment
Version: VyOS 2026.05.26-0047-rolling
Release train: current
Release flavor: generic
Build commit ID: 4c0a57920573ff
Architecture: x86_64
Reproducible on a clean config: yes (see minimal reproducer below)
Current behaviour
show nat source rules (op-mode)
Rule Source Destination Proto Out-Int Translation


20 10.0.0.0/24 192.0.2.100 TCP any 10.0.0.254

sport any    dport 80,443

90 10.0.0.0/24 172.16.0.0/25 TCP any exclude

sport any    dport 80,443

100 10.0.0.0/24 10.0.1.0/24 TCP any exclude

sport any    dport 80,443

900 10.0.0.0/24 0.0.0.0/0 any eth0 203.0.113.1

sport any    dport any

Rules 90 and 100 are shown with Proto TCP and dport 80,443.

Actual nftables ruleset
ip saddr 10.0.0.0/24 ip daddr 172.16.0.0/25 counter packets 5 bytes 300 return comment "SRC-NAT-90"
ip saddr 10.0.0.0/24 ip daddr 10.0.1.0/24 counter packets 0 bytes 0 return comment "SRC-NAT-100"

The installed rules for SRC-NAT-90 and SRC-NAT-100 contain no protocol match and no port match — only source and destination address. They match all traffic between the respective subnets and return (exclude from SNAT).

Note that rule 20 legitimately has tcp dport 80,443, and rule 900 is correctly rendered as any / dport any. Only the rules that sit after an L4-qualified rule and themselves carry no L4 criteria are rendered with the preceding rule's Proto / dport values — which strongly suggests the L4 fields are being carried over from the previous rendered row instead of being read per-rule.

Expected behaviour

The Proto and dport columns for each rule must reflect that rule's own configuration and the corresponding nftables match. A rule with no protocol and no destination port configured (and whose nftables rule has no L4 match) must be displayed as:

90 10.0.0.0/24 172.16.0.0/25 all any exclude

sport any    dport any

The op-mode table must never show match criteria that are not present in the generated ruleset.

Steps to reproduce

Minimal configuration (anonymised):

set nat source rule 20 source address '10.0.0.0/24'
set nat source rule 20 destination address '192.0.2.100'
set nat source rule 20 protocol 'tcp'
set nat source rule 20 destination port '80,443'
set nat source rule 20 translation address '10.0.0.254'

set nat source rule 90 source address '10.0.0.0/24'
set nat source rule 90 destination address '172.16.0.0/25'
set nat source rule 90 exclude

Then:

commit
run show nat source rules
sudo nft list chain ip vyos_nat POSTROUTING

Observed: rule 90 (which has no protocol and no destination port) is rendered by show nat source rules as TCP / dport 80,443, while the nftables rule for SRC-NAT-90 contains only ip saddr / ip daddr.

The affected rule's configuration confirms that no protocol and no destination port are set — yet the op-mode table renders TCP / dport 80,443 for it:

set nat source rule 90 description 'no-nat tunnel remote'
set nat source rule 90 destination address '172.16.0.0/25'
set nat source rule 90 exclude
set nat source rule 90 source address '10.0.0.0/24'

The generated nftables rule (SRC-NAT-90, shown above) matches the config exactly — only saddr/daddr, no L4 match. The op-mode table is the sole component that shows the phantom TCP / dport 80,443.

Impact
The op-mode output does not match the dataplane, undermining trust in show nat source rules.
Operators misjudge the scope of exclude / translation rules. In policy-based IPsec deployments, exclude source-NAT rules are used to keep tunnel-bound traffic out of masquerade; a phantom tcp dport 80,443 in the display leads operators to believe only web traffic is excluded when in fact all traffic between the subnets is. This produces hard-to-diagnose behaviour.
No dataplane impact by itself (nftables is correct); the defect is in the op-mode rendering. However, because the display is authoritative for most operators, the practical risk is real.
Suspected area / notes
The defect is confined to the op-mode formatter for show nat source rules (NAT op-mode module). Both the configuration and the generated nftables ruleset for the affected rule are correct and consistent (no protocol, no destination port); only the op-mode table shows fabricated TCP / dport 80,443 values. This is a rendering bug, not a ruleset-generation bug — there is no dataplane impact.
The pattern points to carried-over state in the formatting loop: Proto / dport appear only on rules that follow an L4-qualified rule (rule 20) and that carry no L4 criteria of their own (rules 90, 100), whereas rule 900 — which resets these fields — is rendered correctly as all / dport any. This suggests the protocol / destination port values from the previous rendered row are being retained instead of being (re)initialised and read per rule.

Details

Version
VyOS 2026.05.26-0047-rolling
Is it a breaking change?
Unspecified (possibly destroys the router)
Issue type
Bug (incorrect behavior)

Event Timeline

Adding the raw command outputs as a comment — the code blocks in the description lost their monospace formatting and I don't have edit rights to fix them inline. All addresses are anonymised (RFC 1918 / RFC 5737 documentation ranges).

show nat source rules (op-mode) — observed:

Rule    Source       Destination     Proto    Out-Int    Translation
------  -----------  --------------  -------  ---------  --------------
20      10.0.0.0/24  192.0.2.100     TCP      any        10.0.0.254
        sport any    dport 80,443
90      10.0.0.0/24  172.16.0.0/25   TCP      any        exclude
        sport any    dport 80,443
100     10.0.0.0/24  10.0.1.0/24     TCP      any        exclude
        sport any    dport 80,443
900     10.0.0.0/24  0.0.0.0/0       any      eth0       203.0.113.1
        sport any    dport any

Actual nftables ruleset (nft list chain ip vyos_nat POSTROUTING):

ip saddr 10.0.0.0/24 ip daddr 172.16.0.0/25 counter packets 5 bytes 300 return comment "SRC-NAT-90"
ip saddr 10.0.0.0/24 ip daddr 10.0.1.0/24  counter packets 0 bytes 0   return comment "SRC-NAT-100"

Configuration of the affected rule (no protocol, no destination port set):

set nat source rule 90 description 'no-nat tunnel remote'
set nat source rule 90 destination address '172.16.0.0/25'
set nat source rule 90 exclude
set nat source rule 90 source address '10.0.0.0/24'

Rules 90 and 100 have no protocol and no destination port, and the generated nftables rules match only saddr/daddr — yet the op-mode table shows TCP / dport 80,443, carried over from rule 20. Rule 900, which resets these fields, renders correctly as any / dport any. So the config and the dataplane are consistent and correct; only the op-mode formatter is wrong.

Minimal reproducer (clean config):

set nat source rule 20 source address '10.0.0.0/24'
set nat source rule 20 destination address '192.0.2.100'
set nat source rule 20 protocol 'tcp'
set nat source rule 20 destination port '80,443'
set nat source rule 20 translation address '10.0.0.254'

set nat source rule 90 source address '10.0.0.0/24'
set nat source rule 90 destination address '172.16.0.0/25'
set nat source rule 90 exclude

After commit, show nat source rules renders rule 90 as TCP / dport 80,443, while nft list chain ip vyos_nat POSTROUTING shows only saddr/daddr for SRC-NAT-90.

Version: VyOS 2026.05.26-0047-rolling (release train current, flavor generic), build commit 4c0a57920573ff, x86_64.