Match any listed port or protocol in a rule #15

Merged
benvin merged 10 commits from benvin/multiport-multiproto into main 2026-10-03 21:13:10 +10:00
Member

Rules with several ports or a comma protocol list (80,443, tcp,udp) never match: each port becomes an AND-ed compare in one rule, and tcp,udp parses to l4proto 0. Shorewall low:high ranges also fail to parse.

  • expand proto/dport/sport lists into one rule per combination in a shared l4Matches, used by filter, DNAT, SNAT and conntrack rules
  • accept comma-joined port strings and : range separators
  • resolve common IANA protocol names (ospf, gre, vrrp, ...) or numbers 0-255
  • reject empty list elements, unknown protocols and ICMP types, ports on portless protocols, dports on mixed ICMP proto lists, and ratelimit/connlimit on multi-rule expansions
  • add compile tests for lists, protocol names/numbers, ICMP lists, : ranges, per-proto reject, expansion counts and errors
Rules with several ports or a comma protocol list (`80,443`, `tcp,udp`) never match: each port becomes an AND-ed compare in one rule, and `tcp,udp` parses to l4proto 0. Shorewall `low:high` ranges also fail to parse. - expand proto/dport/sport lists into one rule per combination in a shared `l4Matches`, used by filter, DNAT, SNAT and conntrack rules - accept comma-joined port strings and `:` range separators - resolve common IANA protocol names (ospf, gre, vrrp, ...) or numbers 0-255 - reject empty list elements, unknown protocols and ICMP types, ports on portless protocols, dports on mixed ICMP proto lists, and ratelimit/connlimit on multi-rule expansions - add compile tests for lists, protocol names/numbers, ICMP lists, `:` ranges, per-proto reject, expansion counts and errors
unkin-agent added 1 commit 2026-10-03 20:41:43 +10:00
Match any listed port or protocol instead of AND-ing them
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
9976bc9190
Author
Member
  • internal/nftables/compiler.go:956 — empty element after split (tcp, or ,udp) yields a rule with no proto match but with port compares, i.e. matches any protocol (accept fails open) → skip empty elements or return an error
  • internal/nftables/compiler.go:308 — rate limit and connlimit are applied per expanded rule via applyRuleExtras, so ratelimit: 10/s on ports 80,443 or tcp,udp becomes 10/s per expansion (N independent limiters) → share one limiter across the expansion (e.g. a named limit/meter) or reject RateLimit/ConnLimit combined with a list
  • internal/nftables/compiler_test.go:1539 — no test for reject per proto (tcp,udp reject must give TCP RST for tcp and icmpx unreach for udp) → add one asserting the Reject type per expansion
  • internal/nftables/compiler_test.go:1539 — no coverage for SNAT, conntrack, sport lists, proto x multi-port cross-product count (tcp,udp x 80,443 = 4), or an invalid port in a list erroring → add cases
  • internal/nftables/compiler_test.go:1539 — nothing asserts tag stability: expanded rules share one tag (rule:0 x N) and rely on computeDiff grouping by tag with positional compare → add a test that computeDiff(state, state) is empty for a multi-expansion rule
  • nit: internal/nftables/compiler.go:965 — matchICMPType returns nil for an unknown type, so a typo in an ICMP list now silently becomes an unconditioned all-ICMP rule → return an error for unresolved types
- internal/nftables/compiler.go:956 — empty element after split (`tcp,` or `,udp`) yields a rule with no proto match but with port compares, i.e. matches any protocol (accept fails open) → skip empty elements or return an error - internal/nftables/compiler.go:308 — rate limit and connlimit are applied per expanded rule via applyRuleExtras, so `ratelimit: 10/s` on ports 80,443 or tcp,udp becomes 10/s per expansion (N independent limiters) → share one limiter across the expansion (e.g. a named limit/meter) or reject RateLimit/ConnLimit combined with a list - internal/nftables/compiler_test.go:1539 — no test for reject per proto (`tcp,udp` reject must give TCP RST for tcp and icmpx unreach for udp) → add one asserting the Reject type per expansion - internal/nftables/compiler_test.go:1539 — no coverage for SNAT, conntrack, sport lists, proto x multi-port cross-product count (tcp,udp x 80,443 = 4), or an invalid port in a list erroring → add cases - internal/nftables/compiler_test.go:1539 — nothing asserts tag stability: expanded rules share one tag (`rule:0` x N) and rely on computeDiff grouping by tag with positional compare → add a test that computeDiff(state, state) is empty for a multi-expansion rule - nit: internal/nftables/compiler.go:965 — matchICMPType returns nil for an unknown type, so a typo in an ICMP list now silently becomes an unconditioned all-ICMP rule → return an error for unresolved types
unkin-agent added 1 commit 2026-10-03 20:45:19 +10:00
Reject empty list elements, unknown ICMP types and limits on list rules
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
30758855ff
Author
Member
  • internal/nftables/compiler.go:1067 — matchProto still discards the Atoi error, so an unknown or typo proto element (tcp,udpp, tpc) compiles to l4proto 0 silently, the same failure this PR fixes for tcp,udp → make l4Matches validate each element (known name or 0-255 number) and error otherwise.
  • internal/nftables/compiler.go:975 — in a mixed list such as icmp,tcp with dport 80, the icmp branch treats 80 as an ICMP type and tcp as a port, so one list silently yields two unrelated matches → reject port lists that combine ICMP and non-ICMP protos.
  • internal/nftables/compiler_test.go:1748 — computeDiff(state, state) compares a state with itself and is always empty, so it does not test diff stability of duplicate-tag expanded rules → compile twice and diff the results, and diff against a state with one expanded rule removed to assert a non-empty change set.
  • internal/nftables/compiler_test.go:1712 — ICMP coverage is only bogus; the type/code error path (echo-request/abc) and ICMP list expansion (echo-request,echo-reply, a path this PR changed) are untested → add one case each.
  • internal/nftables/compiler_test.go:1543 — : range is tested only for dport on filter rules; sport range, SNAT and conntrack : ranges are untested → add a sport 1024:2048 case and one SNAT or conntrack range case.
  • nit: internal/nftables/compiler.go:270 — the ratelimit/connlimit guard runs only in compileRules; a zone with several interfaces still gives each expanded rule its own limiter (pre-existing), so the error text "proto or port lists" is incomplete → leave, or note the limitation in the error.
  • nit: internal/nftables/compiler.go:439 — net.ParseIP(dnatAddr) is now re-parsed inside the per-match loop → hoist it above the loops and return the error before expansion.
- internal/nftables/compiler.go:1067 — matchProto still discards the Atoi error, so an unknown or typo proto element (`tcp,udpp`, `tpc`) compiles to l4proto 0 silently, the same failure this PR fixes for `tcp,udp` → make l4Matches validate each element (known name or 0-255 number) and error otherwise. - internal/nftables/compiler.go:975 — in a mixed list such as `icmp,tcp` with dport `80`, the icmp branch treats 80 as an ICMP type and tcp as a port, so one list silently yields two unrelated matches → reject port lists that combine ICMP and non-ICMP protos. - internal/nftables/compiler_test.go:1748 — `computeDiff(state, state)` compares a state with itself and is always empty, so it does not test diff stability of duplicate-tag expanded rules → compile twice and diff the results, and diff against a state with one expanded rule removed to assert a non-empty change set. - internal/nftables/compiler_test.go:1712 — ICMP coverage is only `bogus`; the type/code error path (`echo-request/abc`) and ICMP list expansion (`echo-request,echo-reply`, a path this PR changed) are untested → add one case each. - internal/nftables/compiler_test.go:1543 — `:` range is tested only for dport on filter rules; sport range, SNAT and conntrack `:` ranges are untested → add a sport `1024:2048` case and one SNAT or conntrack range case. - nit: internal/nftables/compiler.go:270 — the ratelimit/connlimit guard runs only in compileRules; a zone with several interfaces still gives each expanded rule its own limiter (pre-existing), so the error text "proto or port lists" is incomplete → leave, or note the limitation in the error. - nit: internal/nftables/compiler.go:439 — `net.ParseIP(dnatAddr)` is now re-parsed inside the per-match loop → hoist it above the loops and return the error before expansion.
unkin-agent added 1 commit 2026-10-03 20:47:48 +10:00
Reject unknown protocols and dports on mixed ICMP proto lists
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
9c30f1fa54
Author
Member

No findings.

No findings.
unkin-agent added 1 commit 2026-10-03 20:49:55 +10:00
Resolve common IANA protocol names and reject ports on portless protocols
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
852d6bf2ca
Author
Member
  • internal/nftables/compiler.go:1694 — rejectExprs compares the proto string to "tcp", and compileOneRule now passes the raw list element (m.proto), so proto: 6 with REJECT gets icmp-port-unreach instead of tcp-reset, unlike tcp → compare the resolved number (protoNumber, == IPPROTO_TCP) or pass the byte via l4Match
  • internal/nftables/compiler_test.go:1637 — TestCompile_RejectPerProto only covers the names tcp,udp → add a case with 6 (and 6,17) asserting NFT_REJECT_TCP_RST
- internal/nftables/compiler.go:1694 — `rejectExprs` compares the proto string to "tcp", and `compileOneRule` now passes the raw list element (`m.proto`), so `proto: 6` with REJECT gets icmp-port-unreach instead of tcp-reset, unlike `tcp` → compare the resolved number (`protoNumber`, == IPPROTO_TCP) or pass the byte via `l4Match` - internal/nftables/compiler_test.go:1637 — `TestCompile_RejectPerProto` only covers the names `tcp,udp` → add a case with `6` (and `6,17`) asserting NFT_REJECT_TCP_RST
unkin-agent added 1 commit 2026-10-03 20:51:27 +10:00
Pick reject type by resolved protocol number
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
457056d58a
Author
Member

No findings.

No findings.
benvin added 5 commits 2026-10-03 21:12:53 +10:00
Expand comma zone lists in rule source and dest
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
211bacd507
Merge benvin/multiport-multiproto; reject limits on zone and address lists
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ad2dd9e3e7
Match exact all/any zone tokens, skip only interface-less ip zones, keep DNAT free of rule extras, reject '!' inside address lists
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
8efed72c96
Merge pull request 'Expand comma zone lists in rule source and dest' (#16) from benvin/comma-zones into benvin/multiport-multiproto
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
e6244d6bf0
Reviewed-on: #16
benvin merged commit a7be035456 into main 2026-10-03 21:13:10 +10:00
benvin deleted branch benvin/multiport-multiproto 2026-10-03 21:13:10 +10:00
Sign in to join this conversation.