Skip to content

[v3.31] app-policy: match named ports against the IP+port set key - #13892

Open
dimitri-nicolo wants to merge 1 commit into
projectcalico:release-v3.31from
dimitri-nicolo:auto-pick-of-#13668-upstream-release-v3.31
Open

[v3.31] app-policy: match named ports against the IP+port set key#13892
dimitri-nicolo wants to merge 1 commit into
projectcalico:release-v3.31from
dimitri-nicolo:auto-pick-of-#13668-upstream-release-v3.31

Conversation

@dimitri-nicolo

Copy link
Copy Markdown
Contributor

Cherry-pick history

Backport note (release-v3.31): this branch predates the memoized request cache, so the change is adapted rather than a clean pick. The <IP>,<protocol>:<port> key is built per call (as matchDstIPPortSetIds already did here) through a shared ipProtoPortKey; matchPort/matchNotPort take the leg's key accessor exactly as on master. Tests are the same as master minus TestMatchPort, which does not exist on this branch.

Fixes #13174.

The problem

A policy rule that references a named port never matches in the app-policy checker (Dikastes). An Allow rule therefore falls through to the default deny, and a Deny rule silently does nothing. The main dataplanes allow the same flow, so ALP and non-ALP disagree.

Why

Felix does not hand out a set of port numbers for a named port. It resolves the name against every endpoint that declares it and emits an IP+port set (IPSetUpdate_IP_AND_PORT) whose members look like 10.0.0.1,tcp:8080 — the set means "this port, on these endpoints" (felix/calc/rule_scanner.go, felix/labelindex/ipsetmember/ip_set_member.go).

Every other dataplane matches it that way:

  • iptables/nftables render --match-set <set> src,src / dst,dst (felix/rules/policy.go)
  • BPF builds the same (IP, protocol, port) key from the leg the rule names (felix/bpf/polprog/pol_prog_builder.go)

matchPort/matchNotPort in app-policy/checker/match.go instead looked up the bare port number, e.g. "8080", which no member of such a set can equal. The lookup always missed.

The change

  • matchPort/matchNotPort take the leg's <IP>,<protocol>:<port> accessor and look that key up, matching what the other dataplanes do.
  • requestCache memoizes the source-side key alongside the destination one it already had; both now share an ipProtoPortKey helper.
  • protocolMapL4 gains 132: "sctp". A named port can be declared on sctp, and the key would otherwise carry an empty protocol field. This closes the same gap for matchDstIPPortSetIds, which uses the same key.

Testing

TestMatchNamedPorts is the reported case plus the wrong-endpoint, wrong-port, wrong-protocol, negated and source-leg variants, and the numeric-OR-named case. I confirmed it reproduces the bug: with the old bare-port lookup restored, three of its cases fail (dst named port matches…, negated dst named port excludes…, src named port matches on the source leg).

TestMatchPort (added by #13408 while this PR was open) keyed its named port set on the bare port number; it now uses an IP+port member and gives the flow an IP and protocol.

The sctp entry in protocolMapL4 is covered by an sctp named port in TestMatchNamedPorts, an sctp set in TestMatchDstIPPortSetIds, and TestIPProtoPortKey.

go test ./app-policy/... passes.

Release note:

Fixed application layer policy (Dikastes) never matching rules that reference a named port, which caused Allow rules to deny and Deny rules to have no effect.

A policy rule that referenced a named port never matched in the app-policy
checker (Dikastes), so an Allow rule fell through to the default deny and a
Deny rule did nothing. The main dataplanes allowed the same flow.

Felix does not hand out a set of port numbers for a named port. It resolves
the name against every endpoint that declares it and emits an IP+port set
(IPSetUpdate_IP_AND_PORT) whose members are "<IP>,<protocol>:<port>" - the
set means "this port, on these endpoints". iptables/nftables match it with
--match-set <set> src,src / dst,dst and the BPF dataplane builds the same
(IP, protocol, port) key, both taken from the leg the rule names.

The checker instead looked up the bare port number, e.g. "8080", which no
member of such a set can equal, so the lookup always missed. Build the same
key the other dataplanes use and look that up instead: matchPort and
matchNotPort now take the leg's "<IP>,<protocol>:<port>" accessor, and
requestCache memoizes the source-side key alongside the destination one it
already had. sctp joins tcp and udp in protocolMapL4, since a named port can
be declared on any of the three.

TestMatchNamedPorts covers the reported case plus the wrong-endpoint,
wrong-port, wrong-protocol, negated and source-leg variants.

TestMatchPort, added by projectcalico#13408 while this change was open, keyed its named
port set on the bare port number; it now uses an IP+port member and gives
the flow an IP and protocol, as the checker looks the key up.

Envoy's ext_authz adapter can only report tcp or udp, so protocolMap in the
adapter stays as it is; protocolMapL4 is different, since the checker builds
the key from whatever the Flow reports and Felix emits sctp members. The sctp
entry is covered by TestMatchNamedPorts, TestMatchDstIPPortSetIds and
ipProtoPortKey's own test.

Backport to release-v3.31: this branch has no memoized request cache, so the
"<IP>,<protocol>:<port>" key is built per call, as matchDstIPPortSetIds
already did here. That function now shares the key builder.

(cherry picked from commit 7753bba)
Fixes projectcalico#13174
Copilot AI lite review requested due to automatic review settings September 11, 2026 18:35
@dimitri-nicolo
dimitri-nicolo requested a review from a team as a code owner September 11, 2026 18:35
@dimitri-nicolo dimitri-nicolo added release-note-required Change has user-facing impact (no matter how small) docs-not-required Docs not required for this change labels Sep 11, 2026
@marvin-tigera marvin-tigera added this to the Calico v3.31.8 milestone Sep 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No unresolved review comments remain, and the supplied assessments find the changes ready for approval.

Pull request overview

Fixes Dikastes named-port matching by using endpoint-specific IP/protocol/port keys, including SCTP support.

Changes:

  • Adds shared source and destination key generation.
  • Updates positive and negated named-port matching.
  • Adds regression tests for named ports, protocols, and key formatting.
File summaries
File Description
app-policy/checker/requestcache.go Builds source and destination IP-port keys.
app-policy/checker/requestcache_test.go Tests key formatting.
app-policy/checker/match.go Uses endpoint-specific keys for named-port matching.
app-policy/checker/match_test.go Tests named-port, negation, source-leg, and SCTP behavior.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

Copy link
Copy Markdown
Contributor

CI triage — Calico

Recommendation: Re-run CI before merging — PR probably not at fault, but tests did not execute

Unrelated failures

Type Test Note
infra e2e tests / Conformance e2e tests Job was stopped before it started due to pipeline timeout
infra Node: kind-cluster tests Pipeline timed out while waiting for background VM test runners

workflow_id: 86f8cf40-faf9-4589-8bf2-515d4f576b93

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-not-required Docs not required for this change release-note-required Change has user-facing impact (no matter how small)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants