Skip to content

fix: honor TCPRoute/UDPRoute sectionName and listener port for StreamRoute matching - #445

Open
AlinsRan wants to merge 5 commits into
masterfrom
ee-2802-l4-server-port
Open

fix: honor TCPRoute/UDPRoute sectionName and listener port for StreamRoute matching#445
AlinsRan wants to merge 5 commits into
masterfrom
ee-2802-l4-server-port

Conversation

@AlinsRan

@AlinsRan AlinsRan commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Type of change:

  • Bugfix

What this PR does / why we need it:

Sync from apache/apisix-ingress-controller#2818 (fixes apache/apisix-ingress-controller#2802).

TranslateTCPRoute / TranslateUDPRoute emitted a single StreamRoute with no match criteria (the old // TODO: ... server_port). In APISIX stream mode a StreamRoute without server_port matches every connection on any stream listener, so all L4 routes across all Gateways collided onto one backend — parentRefs.sectionName / per-listener port routing were ignored.

This makes TCPRoute/UDPRoute participate in the existing listener_port_match_mode mechanism (added for HTTPRoute/GRPCRoute in #419):

  • Controllers (tcproute_controller.go, udproute_controller.go): populate tctx.Listeners from the listeners ParseRouteParentRefs matched for each parentRef (honoring sectionName, port, protocol, allowedRoutes), mirroring the HTTPRoute/GRPCRoute path.
  • Translator (tcproute.go, udproute.go): buildL4StreamRoutes sets StreamRoute.server_port from the matched listener port(s), one StreamRoute per port with a port-qualified name/ID, gated by the same shouldInjectServerPortVars.

Rebased on latest master

An earlier revision of this PR carried a self-contained gating helper because EE master did not yet have listener_port_match_mode. Master has since merged port-based routing (#419), so this PR is rebuilt on master and now reuses listener_port_match_mode directly — no duplicated gating, no new config. Diff is line-for-line equivalent to the upstream OSS PR #2818.

Backward compatibility

listener_port_match_mode defaults to off, so L4 behavior is unchanged by default (single portless StreamRoute) — no upgrade risk. Isolation is opt-in: set listener_port_match_mode: auto/explicit and make the Gateway listener port equal APISIX's physical stream_proxy port (9100/9200), same contract as HTTPRoute.

Limitation: server_port isolates across distinct listener ports only; two L4 routes on the same physical port can't be disambiguated (no SNI/host at bare TCP/UDP).

Pre-submission checklist:

  • Test cases added (l4route_serverport_test.go, exercised in auto mode)
  • Backward compatible (default off → unchanged L4 behavior)

Summary by CodeRabbit

  • New Features
    • L4 TCP and UDP routes can now set server_port based on the matched Gateway listener (including auto port matching and explicit listener targeting).
    • When multiple listener ports match, the system generates one stream route per distinct port, deduplicating duplicate ports.
    • If no matched listener port can be determined, it preserves the existing portless fallback behavior.
  • Bug Fixes
    • Server-port variable injection decisions for HTTP, gRPC, and listener-port matching are now based on the actual matched listeners.
  • Tests
    • Added unit tests for TCP/UDP server_port injection, fan-out, deduplication, and fallback.
    • Updated TCP/UDP Gateway e2e YAML to use the APISIX stream proxy ports for correct port matching.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

TCP and UDP reconciliation now passes matched Gateway listeners into translation. Shared L4 route construction injects selected listener ports into StreamRoutes, supports fan-out and deduplication, preserves portless fallback, and adds listener-aware injection tests and E2E fixture updates.

Changes

L4 listener port isolation

Layer / File(s) Summary
Propagate matched listeners
internal/controller/tcproute_controller.go, internal/controller/udproute_controller.go
TCP and UDP reconcilers append matched single or multiple Gateway listeners to the translation context.
Resolve listener targets
internal/adc/translator/translator.go, internal/adc/translator/http*.go, internal/adc/translator/annotations_test.go
Parent references are considered explicit only when their section name or port resolves against matched listeners; HTTP and gRPC callers and tests pass the listener set through.
Construct port-aware StreamRoutes
internal/adc/translator/tcproute.go, internal/adc/translator/udproute.go
Shared construction selects, deduplicates, sorts, and injects listener ports into TCP and UDP StreamRoutes, while retaining portless fallback behavior.
Validate listener-specific routing
internal/adc/translator/l4route_serverport_test.go, test/e2e/gatewayapi/tcproute.go, test/e2e/gatewayapi/udproute.go
Tests cover explicit targeting, multi-listener fan-out, duplicate-port removal, fallback behavior, route identity uniqueness, and physical stream-proxy port alignment.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TCPRouteReconciler
  participant UDPRouteReconciler
  participant Translator
  participant buildL4StreamRoutes
  participant StreamRoutes
  TCPRouteReconciler->>Translator: pass matched Gateway listeners
  UDPRouteReconciler->>Translator: pass matched Gateway listeners
  Translator->>buildL4StreamRoutes: resolve parent references and listener ports
  buildL4StreamRoutes->>StreamRoutes: create port-specific or portless routes
Loading

Possibly related PRs

Suggested reviewers: nic-6443, shreemaan-abhishek

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning The new E2E cases only prove a single TCP/UDP listener works; they never create a competing listener or enable auto/explicit mode, so the collision regression remains untested. Add an E2E regression with two listeners/gateways, run in auto/explicit mode, and assert the selected port routes while the non-target port is refused.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: restricting StreamRoute matching by sectionName and listener port.
Linked Issues check ✅ Passed The changes address #2802 by honoring matched listener sectionName/port and generating port-qualified StreamRoutes as needed.
Out of Scope Changes check ✅ Passed No obvious unrelated changes are present; the extra HTTP/GRPCRoute and UDPRoute updates support the shared listener-port matching fix.
Security Check ✅ Passed No security issues found; changes are limited to listener-port routing logic and tests, with no secret logging, auth, DB, TLS, or permission changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ee-2802-l4-server-port

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

conformance test report - apisix-standalone mode

apiVersion: gateway.networking.k8s.io/v1
date: "2026-07-30T07:54:57Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.6.0
implementation:
  contact:
  - https://github.com/apache/apisix-ingress-controller/issues
  organization: APISIX
  project: apisix-ingress-controller
  url: https://github.com/apache/apisix-ingress-controller.git
  version: v2.0.0
kind: ConformanceReport
mode: default
profiles:
- core:
    result: partial
    skippedTests:
    - HTTPRouteHTTPSListener
    - HTTPRouteInvalidBackendRefUnknownKind
    - HTTPRouteInvalidCrossNamespaceBackendRef
    - HTTPRouteInvalidNonExistentBackendRef
    - HTTPRouteListenerHostnameMatching
    - HTTPRouteMultipleGateways
    - HTTPRouteNoBackendRefs
    statistics:
      Failed: 0
      Passed: 30
      Skipped: 7
  extended:
    result: partial
    skippedTests:
    - HTTPRouteRedirectPortAndScheme
    statistics:
      Failed: 0
      Passed: 12
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - HTTPRouteBackendProtocolWebSocket
    - HTTPRouteDestinationPortMatching
    - HTTPRouteHostRewrite
    - HTTPRouteMethodMatching
    - HTTPRoutePathRewrite
    - HTTPRoutePortRedirect
    - HTTPRouteQueryParamMatching
    - HTTPRouteRequestMirror
    - HTTPRouteResponseHeaderModification
    - HTTPRouteSchemeRedirect
    unsupportedFeatures:
    - BackendTLSPolicy
    - BackendTLSPolicySANValidation
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - HTTPRoute303RedirectStatusCode
    - HTTPRoute307RedirectStatusCode
    - HTTPRoute308RedirectStatusCode
    - HTTPRouteBackendProtocolH2C
    - HTTPRouteBackendRequestHeaderModification
    - HTTPRouteBackendTimeout
    - HTTPRouteCORS
    - HTTPRouteNamedRouteRule
    - HTTPRouteParentRefPort
    - HTTPRoutePathRedirect
    - HTTPRouteRequestMultipleMirrors
    - HTTPRouteRequestPercentageMirror
    - HTTPRouteRequestTimeout
    - HTTPRouteRetry
    - HTTPRouteRetryBackendTimeout
    - HTTPRouteRetryConnectionError
    - ListenerSet
  name: GATEWAY-HTTP
  summary: Core tests partially succeeded with 7 test skips. Extended tests partially
    succeeded with 1 test skips.
- core:
    result: partial
    skippedTests:
    - GRPCRouteListenerHostnameMatching
    statistics:
      Failed: 0
      Passed: 14
      Skipped: 1
  extended:
    result: success
    statistics:
      Failed: 0
      Passed: 1
      Skipped: 0
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
  name: GATEWAY-GRPC
  summary: Core tests partially succeeded with 1 test skips. Extended tests succeeded.
- core:
    result: partial
    skippedTests:
    - TLSRouteHostnameIntersection
    - TLSRouteInvalidBackendRefNonexistent
    - TLSRouteInvalidBackendRefUnknownKind
    - TLSRouteSimpleSameNamespace
    statistics:
      Failed: 0
      Passed: 16
      Skipped: 4
  extended:
    result: partial
    skippedTests:
    - TLSRouteTerminateSimpleSameNamespace
    statistics:
      Failed: 0
      Passed: 3
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - TLSRouteModeTerminate
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
    - TLSRouteModeMixed
  name: GATEWAY-TLS
  summary: Core tests partially succeeded with 4 test skips. Extended tests partially
    succeeded with 1 test skips.
succeededProvisionalTests:
- GatewayOptionalAddressValue

@AlinsRan AlinsRan self-assigned this Jul 22, 2026
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

conformance test report - apisix mode

apiVersion: gateway.networking.k8s.io/v1
date: "2026-07-30T07:55:11Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.6.0
implementation:
  contact:
  - https://github.com/apache/apisix-ingress-controller/issues
  organization: APISIX
  project: apisix-ingress-controller
  url: https://github.com/apache/apisix-ingress-controller.git
  version: v2.0.0
kind: ConformanceReport
mode: default
profiles:
- core:
    result: partial
    skippedTests:
    - HTTPRouteHTTPSListener
    - HTTPRouteInvalidBackendRefUnknownKind
    - HTTPRouteInvalidCrossNamespaceBackendRef
    - HTTPRouteInvalidNonExistentBackendRef
    - HTTPRouteListenerHostnameMatching
    - HTTPRouteMultipleGateways
    - HTTPRouteNoBackendRefs
    statistics:
      Failed: 0
      Passed: 30
      Skipped: 7
  extended:
    result: partial
    skippedTests:
    - HTTPRouteRedirectPortAndScheme
    statistics:
      Failed: 0
      Passed: 12
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - HTTPRouteBackendProtocolWebSocket
    - HTTPRouteDestinationPortMatching
    - HTTPRouteHostRewrite
    - HTTPRouteMethodMatching
    - HTTPRoutePathRewrite
    - HTTPRoutePortRedirect
    - HTTPRouteQueryParamMatching
    - HTTPRouteRequestMirror
    - HTTPRouteResponseHeaderModification
    - HTTPRouteSchemeRedirect
    unsupportedFeatures:
    - BackendTLSPolicy
    - BackendTLSPolicySANValidation
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - HTTPRoute303RedirectStatusCode
    - HTTPRoute307RedirectStatusCode
    - HTTPRoute308RedirectStatusCode
    - HTTPRouteBackendProtocolH2C
    - HTTPRouteBackendRequestHeaderModification
    - HTTPRouteBackendTimeout
    - HTTPRouteCORS
    - HTTPRouteNamedRouteRule
    - HTTPRouteParentRefPort
    - HTTPRoutePathRedirect
    - HTTPRouteRequestMultipleMirrors
    - HTTPRouteRequestPercentageMirror
    - HTTPRouteRequestTimeout
    - HTTPRouteRetry
    - HTTPRouteRetryBackendTimeout
    - HTTPRouteRetryConnectionError
    - ListenerSet
  name: GATEWAY-HTTP
  summary: Core tests partially succeeded with 7 test skips. Extended tests partially
    succeeded with 1 test skips.
- core:
    result: partial
    skippedTests:
    - GRPCRouteListenerHostnameMatching
    statistics:
      Failed: 0
      Passed: 14
      Skipped: 1
  extended:
    result: success
    statistics:
      Failed: 0
      Passed: 1
      Skipped: 0
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
  name: GATEWAY-GRPC
  summary: Core tests partially succeeded with 1 test skips. Extended tests succeeded.
- core:
    result: partial
    skippedTests:
    - TLSRouteHostnameIntersection
    - TLSRouteInvalidBackendRefNonexistent
    - TLSRouteInvalidBackendRefUnknownKind
    - TLSRouteSimpleSameNamespace
    statistics:
      Failed: 0
      Passed: 16
      Skipped: 4
  extended:
    result: partial
    skippedTests:
    - TLSRouteTerminateSimpleSameNamespace
    statistics:
      Failed: 0
      Passed: 3
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - TLSRouteModeTerminate
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
    - TLSRouteModeMixed
  name: GATEWAY-TLS
  summary: Core tests partially succeeded with 4 test skips. Extended tests partially
    succeeded with 1 test skips.
succeededProvisionalTests:
- GatewayOptionalAddressValue

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

conformance test report

apiVersion: gateway.networking.k8s.io/v1
date: "2026-07-30T08:13:06Z"
gatewayAPIChannel: experimental
gatewayAPIVersion: v1.6.0
implementation:
  contact:
  - https://github.com/apache/apisix-ingress-controller/issues
  organization: APISIX
  project: apisix-ingress-controller
  url: https://github.com/apache/apisix-ingress-controller.git
  version: v2.0.0
kind: ConformanceReport
mode: default
profiles:
- core:
    failedTests:
    - GatewayModifyListeners
    - HTTPRouteMultipleGateways
    - HTTPRouteNoBackendRefs
    result: failure
    skippedTests:
    - HTTPRouteHTTPSListener
    statistics:
      Failed: 3
      Passed: 33
      Skipped: 1
  extended:
    result: partial
    skippedTests:
    - HTTPRouteRedirectPortAndScheme
    statistics:
      Failed: 0
      Passed: 12
      Skipped: 1
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - HTTPRouteBackendProtocolWebSocket
    - HTTPRouteDestinationPortMatching
    - HTTPRouteHostRewrite
    - HTTPRouteMethodMatching
    - HTTPRoutePathRewrite
    - HTTPRoutePortRedirect
    - HTTPRouteQueryParamMatching
    - HTTPRouteRequestMirror
    - HTTPRouteResponseHeaderModification
    - HTTPRouteSchemeRedirect
    unsupportedFeatures:
    - BackendTLSPolicy
    - BackendTLSPolicySANValidation
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - HTTPRoute303RedirectStatusCode
    - HTTPRoute307RedirectStatusCode
    - HTTPRoute308RedirectStatusCode
    - HTTPRouteBackendProtocolH2C
    - HTTPRouteBackendRequestHeaderModification
    - HTTPRouteBackendTimeout
    - HTTPRouteCORS
    - HTTPRouteNamedRouteRule
    - HTTPRouteParentRefPort
    - HTTPRoutePathRedirect
    - HTTPRouteRequestMultipleMirrors
    - HTTPRouteRequestPercentageMirror
    - HTTPRouteRequestTimeout
    - HTTPRouteRetry
    - HTTPRouteRetryBackendTimeout
    - HTTPRouteRetryConnectionError
    - ListenerSet
  name: GATEWAY-HTTP
  summary: Core tests failed with 3 test failures. Extended tests partially succeeded
    with 1 test skips.
- core:
    failedTests:
    - GatewayModifyListeners
    result: failure
    statistics:
      Failed: 1
      Passed: 14
      Skipped: 0
  extended:
    result: success
    statistics:
      Failed: 0
      Passed: 1
      Skipped: 0
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
  name: GATEWAY-GRPC
  summary: Core tests failed with 1 test failures. Extended tests succeeded.
- core:
    failedTests:
    - GatewayModifyListeners
    - TLSRouteHostnameIntersection
    - TLSRouteInvalidBackendRefNonexistent
    - TLSRouteInvalidBackendRefUnknownKind
    - TLSRouteSimpleSameNamespace
    result: failure
    statistics:
      Failed: 5
      Passed: 15
      Skipped: 0
  extended:
    failedTests:
    - TLSRouteTerminateSimpleSameNamespace
    result: failure
    statistics:
      Failed: 1
      Passed: 3
      Skipped: 0
    supportedFeatures:
    - GatewayAddressEmpty
    - GatewayPort8080
    - TLSRouteModeTerminate
    unsupportedFeatures:
    - GatewayBackendClientCertificate
    - GatewayFrontendClientCertificateValidation
    - GatewayFrontendClientCertificateValidationInsecureFallback
    - GatewayHTTPListenerIsolation
    - GatewayHTTPSListenerDetectMisdirectedRequests
    - GatewayInfrastructurePropagation
    - GatewayStaticAddresses
    - ListenerSet
    - TLSRouteModeMixed
  name: GATEWAY-TLS
  summary: Core tests failed with 5 test failures. Extended tests failed with 1 test
    failures.
succeededProvisionalTests:
- GatewayOptionalAddressValue

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/controller/tcproute_controller.go (1)

291-303: 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Zero-value listener leak into tctx.Listeners for rejected parentRefs, in both TCP and UDP reconcilers. Root cause: RouteParentRefContext.Listener (set in internal/controller/utils.go's ParseRouteParentRefs) is never nil — it points to a zero-value gatewayv1.Listener{} even when the parentRef didn't match any listener — so the new else if gateway.Listener != nil fallback added in both reconcilers appends a bogus Listener{Port: 0} whenever a route has a mix of accepted and rejected parentRefs, poisoning the port set the translator uses for server_port isolation.

  • internal/controller/tcproute_controller.go#L291-L303: guard the fallback, e.g. else if gateway.Listener != nil && gateway.Listener.Port != 0.
  • internal/controller/udproute_controller.go#L291-L303: apply the same guard.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/tcproute_controller.go` around lines 291 - 303, Prevent
zero-value listeners from being appended in the gateway listener population
logic: in internal/controller/tcproute_controller.go lines 291-303 and
internal/controller/udproute_controller.go lines 291-303, update the fallback
within the ProcessGatewayProxy loop to require gateway.Listener.Port != 0 in
addition to gateway.Listener != nil. Keep the existing gateway.Listeners
handling unchanged.
internal/controller/utils.go (1)

373-442: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

internal/controller/utils.go: keep collecting all matching listeners

ParseRouteParentRefs is meant to fill RouteParentRefContext.Listeners with every compatible listener for a parentRef, but the unconditional break stops at the first match. For TCP/UDP routes that leaves tctx.Listeners with one port, so multi-port fan-out never kicks in; keep Listener/ListenerName on the first hit, but continue scanning and appending to Listeners.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/utils.go` around lines 373 - 442, Remove the
unconditional break after a successful match in ParseRouteParentRefs so the loop
continues scanning all listeners compatible with the parentRef. Preserve
ListenerName and Listener from the first match, while appending every subsequent
compatible listener to matchedListeners for RouteParentRefContext.Listeners and
multi-port fan-out.

Source: Coding guidelines

🧹 Nitpick comments (2)
internal/adc/translator/tcproute.go (1)

46-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded "Gateway" kind literal.

Duplicates the Gateway kind check already expressed via KindGateway in internal/controller/utils.go. Cross-package, so may not be trivially shareable, but worth a local constant to avoid silent divergence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/adc/translator/tcproute.go` around lines 46 - 52, Replace the
hardcoded "Gateway" kind comparison in hasExplicitListenerTarget with a local
named constant representing the Gateway kind, keeping the existing non-Gateway
filtering behavior unchanged and aligned with the KindGateway value used
elsewhere.
internal/adc/translator/l4route_serverport_test.go (1)

62-144: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Good translator-level coverage, but doesn't exercise the real ParseRouteParentRefstctx.Listeners wiring.

These tests inject tctx.Listeners directly, so the "multiple listener ports fan out even without explicit targeting" case never runs through ParseRouteParentRefs, where the loop's break (flagged separately in internal/controller/utils.go) actually prevents that scenario from ever populating more than one listener in production. Consider adding a ParseRouteParentRefs-level test for an untargeted parentRef matching multiple compatible listeners on one Gateway to catch this class of regression.

Also applies to: 146-216

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/adc/translator/l4route_serverport_test.go` around lines 62 - 144,
Add coverage for the ParseRouteParentRefs path rather than only assigning
tctx.Listeners directly in TestTranslateTCPRouteServerPort. Create an untargeted
parentRef matching multiple compatible listeners on the same Gateway, invoke
ParseRouteParentRefs, and assert that all matching listeners populate the
translation context so TCP route translation fans out across their ports.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/controller/tcproute_controller.go`:
- Around line 291-303: Prevent zero-value listeners from being appended in the
gateway listener population logic: in internal/controller/tcproute_controller.go
lines 291-303 and internal/controller/udproute_controller.go lines 291-303,
update the fallback within the ProcessGatewayProxy loop to require
gateway.Listener.Port != 0 in addition to gateway.Listener != nil. Keep the
existing gateway.Listeners handling unchanged.

In `@internal/controller/utils.go`:
- Around line 373-442: Remove the unconditional break after a successful match
in ParseRouteParentRefs so the loop continues scanning all listeners compatible
with the parentRef. Preserve ListenerName and Listener from the first match,
while appending every subsequent compatible listener to matchedListeners for
RouteParentRefContext.Listeners and multi-port fan-out.

---

Nitpick comments:
In `@internal/adc/translator/l4route_serverport_test.go`:
- Around line 62-144: Add coverage for the ParseRouteParentRefs path rather than
only assigning tctx.Listeners directly in TestTranslateTCPRouteServerPort.
Create an untargeted parentRef matching multiple compatible listeners on the
same Gateway, invoke ParseRouteParentRefs, and assert that all matching
listeners populate the translation context so TCP route translation fans out
across their ports.

In `@internal/adc/translator/tcproute.go`:
- Around line 46-52: Replace the hardcoded "Gateway" kind comparison in
hasExplicitListenerTarget with a local named constant representing the Gateway
kind, keeping the existing non-Gateway filtering behavior unchanged and aligned
with the KindGateway value used elsewhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 81ff22b5-f4d5-4964-b6ac-317ae84db223

📥 Commits

Reviewing files that changed from the base of the PR and between e2a2ff4 and e356616.

📒 Files selected for processing (9)
  • internal/adc/translator/l4route_serverport_test.go
  • internal/adc/translator/tcproute.go
  • internal/adc/translator/udproute.go
  • internal/controller/context.go
  • internal/controller/tcproute_controller.go
  • internal/controller/udproute_controller.go
  • internal/controller/utils.go
  • test/e2e/gatewayapi/tcproute.go
  • test/e2e/gatewayapi/udproute.go

…Route matching

TranslateTCPRoute/TranslateUDPRoute emitted a single StreamRoute with no
match criteria, so every TCP/UDP connection on any stream listener matched
it and all L4 routes collided onto one backend, ignoring parentRefs.sectionName
and per-listener port.

Make TCPRoute/UDPRoute participate in the existing listener_port_match_mode
(added for HTTPRoute/GRPCRoute in #419): the controllers populate tctx.Listeners
from the listeners ParseRouteParentRefs matched for each parentRef, and the
translator sets StreamRoute.server_port from the matched listener port(s), one
StreamRoute per port, gated by shouldInjectServerPortVars. Default mode is off,
so L4 behavior is unchanged unless the user opts into auto/explicit.

Sync from apache/apisix-ingress-controller#2818 (fixes #2802).
@AlinsRan
AlinsRan force-pushed the ee-2802-l4-server-port branch from 666ee3c to 3f520d1 Compare July 29, 2026 07:12
AlinsRan added 2 commits July 29, 2026 16:07
gateway-api v1.6.0 defines PortNumber as an int32 alias, so the explicit
int32()/PortNumber() conversions are no-ops that fail the unconvert linter.
Keep the L4 e2e Gateway listeners at APISIX's physical stream_proxy ports
(TCP 9100, UDP 9200), mirroring apache/apisix-ingress-controller#2818, so the
tests are correct if listener_port_match_mode is ever set to auto. Under EE's
current default (off) the StreamRoute is portless and the port is irrelevant, so
behavior is unchanged.
Comment thread internal/adc/translator/tcproute.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/adc/translator/l4route_serverport_test.go (1)

149-221: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mirror duplicate-port coverage for UDP.

The TCP cases cover duplicate listener ports and uniqueness of generated IDs and names, but the UDP cases do not. Add an equivalent UDP duplicate-port scenario so the UDP translation path cannot regress independently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/adc/translator/l4route_serverport_test.go` around lines 149 - 221,
Add a UDP test case to TestTranslateUDPRouteServerPort with multiple listeners
sharing the same port, mirroring the TCP duplicate-port coverage. Assert that
translation produces the expected StreamRoutes and that both generated route IDs
and names remain unique.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/adc/translator/l4route_serverport_test.go`:
- Around line 149-221: Add a UDP test case to TestTranslateUDPRouteServerPort
with multiple listeners sharing the same port, mirroring the TCP duplicate-port
coverage. Assert that translation produces the expected StreamRoutes and that
both generated route IDs and names remain unique.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88a23e41-05ee-45f6-9bf9-1e514911eaa2

📥 Commits

Reviewing files that changed from the base of the PR and between 3f520d1 and cca5d77.

📒 Files selected for processing (4)
  • internal/adc/translator/l4route_serverport_test.go
  • internal/adc/translator/tcproute.go
  • test/e2e/gatewayapi/tcproute.go
  • test/e2e/gatewayapi/udproute.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/adc/translator/tcproute.go

hasExplicitListenerTarget previously inspected the raw route parentRefs, so an
explicit parentRef that did not resolve to any matched listener (e.g. a
sectionName pointing at a non-existent listener) still forced server_port
injection. Combined with a valid implicit parentRef on a single listener, that
pinned the route to a logical port that need not equal APISIX's physical listen
port, making it unreachable in auto mode.

Derive the explicit-target signal from the listeners the route actually matched
(tctx.Listeners), passed through shouldInjectServerPortVars. Shared by
HTTPRoute/GRPCRoute/TCPRoute/UDPRoute.

Sync from apache/apisix-ingress-controller#2818.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Relevant declarations and call sites =='
ast-grep outline internal/controller/utils.go --items all --match 'ParseRouteParentRefs' --view expanded
ast-grep outline internal/controller/tcproute_controller.go --items all --match 'Reconcile' --view expanded
ast-grep outline internal/controller/udproute_controller.go --items all --match 'Reconcile' --view expanded
ast-grep outline internal/adc/translator/tcproute.go --items all --match 'buildL4StreamRoutes|shouldInject|explicit|parentRef' --view expanded || true

echo '== Parser and reconciler context =='
rg -n -C 12 'func ParseRouteParentRefs|ParseRouteParentRefs\(|gateway\.Listeners|gateway\.Listener' \
  internal/controller/utils.go internal/controller/tcproute_controller.go internal/controller/udproute_controller.go

echo '== L4 explicit-target / port-injection context =='
rg -n -i -C 10 'explicit|sectionname|routeparentrefs|serverport|server_port|matched.*listener' \
  internal/adc/translator/tcproute.go internal/adc/translator/udproute.go

echo '== Referenced commit =='
git show --stat --oneline 512f826e
git show --format=fuller --find-renames --find-copies 512f826e -- \
  internal/adc/translator/tcproute.go \
  internal/adc/translator/udproute.go \
  internal/adc/translator/l4route_serverport_test.go

Length of output: 24164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== ParseRouteParentRefs implementation =='
sed -n '329,510p' internal/controller/utils.go

echo '== Explicit listener-target predicate =='
ast-grep outline internal/adc/translator/translator.go --items all --match 'shouldInjectServerPortVars|hasExplicitListenerTarget' --view expanded
rg -n -C 14 'func \(t \*Translator\) shouldInjectServerPortVars|func hasExplicitListenerTarget|shouldInjectServerPortVars\(' \
  internal/adc/translator/translator.go

echo '== Tests covering parser selection / rejection and port injection =='
rg -n -i -C 5 'ParseRouteParentRefs|sectionname|sectionName|Listener: nil|listener.*fan|serverport|server_port|explicit.*target' \
  internal/controller --glob '*_test.go' \
  internal/adc/translator --glob '*_test.go'

Length of output: 60159


@AlinsRan Confirmed. I verified the current implementations:

  • ParseRouteParentRefs sets Listener: nil when no listener matches, so the TCP/UDP reconciler fallback cannot add a bogus port-0 listener.
  • The parser appends every compatible listener for an implicit parentRef and only breaks for an explicitly non-empty sectionName, preserving multi-port fan-out.
  • 512f826e correctly derives explicit targeting from tctx.Listeners, not raw parentRefs. The focused test covers an unresolved explicit sectionName alongside a valid implicit parentRef and confirms it does not force injection.

These findings are no longer applicable.


✏️ Learnings added
Learnt from: AlinsRan
URL: https://github.com/api7/api7-ingress-controller/pull/445

Timestamp: 2026-07-30T05:32:26.089Z
Learning: In `internal/controller/utils.go`, `ParseRouteParentRefs` returns `Listener: nil` for rejected parentRefs and aggregates every compatible listener in `Listeners` for implicit parentRefs; it only stops after an explicitly non-empty `ParentReference.SectionName`. L4 translators derive explicit listener targeting by matching parentRef section names or ports against the matched listener list, preventing unresolved explicit refs from forcing server-port injection.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/adc/translator/translator.go`:
- Around line 67-80: The explicit-target matching in the translator must
preserve parent Gateway provenance instead of matching sectionName and port
across the union of listeners. Update the parsing and resolution flow around the
parentRef/listeners logic to retain each parentRef’s namespace, name, group, and
associated Gateway/listener, then require all supplied target fields to match
the same Gateway and listener; add coverage for name/port collisions across
Gateways.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 784f5c99-7567-4970-bf89-06499f390cb2

📥 Commits

Reviewing files that changed from the base of the PR and between cca5d77 and 512f826.

📒 Files selected for processing (5)
  • internal/adc/translator/annotations_test.go
  • internal/adc/translator/grpcroute.go
  • internal/adc/translator/httproute.go
  • internal/adc/translator/tcproute.go
  • internal/adc/translator/translator.go

Comment thread internal/adc/translator/translator.go Outdated
hasExplicitListenerTarget matched a parentRef's sectionName/port against the
flattened union of matched listeners, losing Gateway provenance: an invalid
explicit ref on one Gateway could be satisfied by an identically named/ported
listener matched through a different parentRef's Gateway, wrongly forcing
server_port injection in auto mode.

Compute the explicit signal during ParseRouteParentRefs, where each parentRef's
Gateway and matched listeners are known (RouteParentRefContext.ExplicitListenerMatch),
aggregate it onto TranslateContext.HasExplicitListenerMatch, and have
shouldInjectServerPortVars consume the boolean. This removes the fragile
translator-side re-matching entirely.

Add cross-Gateway name/port-collision coverage in ParseRouteParentRefs tests.
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.

bug: Gateway API TCPRoute ignores parentRefs.sectionName / listener port, causing all TCP listeners to match the same backend

3 participants