Skip to content

v1.61.0: port-match-never-matches (warning) and port-option-repeated (error) - #89

Merged
DannyRuizB merged 1 commit into
mainfrom
smell-74-75-port-matches
Nov 6, 2026
Merged

DannyRuizB merged 1 commit into
mainfrom
smell-74-75-port-matches

Conversation

@DannyRuizB

Copy link
Copy Markdown
Owner

What

Two new linter smells for port matches that cannot hold together.

  • port-match-never-matches (warning). A packet has one destination port and one source port, and every match on a rule must hold at once. Two constraints on the same side with no port in common make a rule that loads (rc 0) and never matches. Covered: a stray --dport in front of a multiport list, two multiport lists, two -m tcp --dport matches, a range next to a disjoint list, the --sport/--sports pair, udp, ip6tables, and nft tcp dport 443 tcp dport { 22, 80 } (also th dport, ranges, sets).
  • port-option-repeated (error). The same side twice in one tcp/udp match (--dport 9100 --dport 9256) is refused with *Only one --destination-port' allowed* (or --source-port'`), rc 2, on both backends. The file loads nothing.

Measured first (debian:13, iptables 1.8.11 nf_tables and legacy, nftables 1.1.3, real loads, real SYNs)

  • 34 variants, linter and kernel agree on all 34. Every disjoint pair counted 0. Overlap counted (--dport 80 + --dports 22,80, 1000:2000 + 22,1500, --dport 22 + 20:30). So did what is deliberately not judged: multiport --ports (either side, --dport 443 --ports 22,80 matched sport 22 and 80), --dport vs --sport, negations and port names.
  • Sample, with traffic from another netns:
    • As written: rc 2 on both backends, INPUT left at ACCEPT.
    • Without the monitoring line: the web accept counted 0 of 3 and the DROP policy 3.
    • Fixed: 3 of 3.
  • nf_tables twist: -m tcp --dport 22 -m tcp --dport 80 (and implicit + explicit) stays in the kernel as tcp dport 22 tcp dport 80 (0 packets). iptables-nft-save prints only -m tcp --dport 80, and restoring that dump makes the rule live (1 packet after the round trip). Legacy keeps both in its dump. The finding says so when it applies.
  • :1023 is saved as 0:1023 and handled. ip6tables measured on nf_tables only: the legacy ip6 table is not available on this kernel, so it is not claimed.

How

iptPortConstraints reuses the option-binding model of multiport-one-option (#84) and keeps the values; portSpecIntervals + intersectIntervals sit on the existing portIntervals. A rule that does not load is never also called dead: a repeat inside a tcp/udp match goes to port-option-repeated, and a second list inside a multiport stays multiport-one-option. Sample iptables (--dport twice — won't load — + a port list that never matches) trips both.

Checks

  • npm test 382/382 (was 377), npm run lint clean.
  • Negative control: with main's linter.js the 4 new tests and 2 guardians fail (376/382).
  • The README guardian (count + list) passes: 75 smells.

🤖 Generated with Claude Code

…(error)

Two constraints on the same port side with nothing in common make a rule that
loads and never matches: `-p tcp --dport 443 -m multiport --dports 80,8080`,
two multiport lists, two `-m tcp --dport` matches, a range next to a disjoint
list, the sport pair, and nft `tcp dport 443 tcp dport { 22, 80 }`. Measured
with real SYNs on both iptables backends and nft: 34 variants, linter and
kernel agree on all 34. On the nf_tables backend two tcp matches stay dead in
the kernel while iptables-nft-save prints only the last port, and restoring
that dump makes the rule live.

The same side twice in one tcp/udp match does not load at all ("Only one
`--destination-port' allowed", rc 2): that is port-option-repeated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DannyRuizB
DannyRuizB merged commit edd5609 into main Nov 6, 2026
1 check passed
@DannyRuizB
DannyRuizB deleted the smell-74-75-port-matches branch November 6, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant