Skip to content

Merged gateways: conflicted listeners still reserve a hostname, blocking a valid listener on another Gateway #10014

Description

@zhaohuabing

Description:

With mergeGateways enabled, validateConflictedMergedListeners reserves a (protocol, hostname, port) key for every listener that passes isSpecValidForConflictChecks. That helper only reports per-listener spec validity, so a listener already rejected by an earlier conflict pass still reserves its key. A valid listener on another Gateway then collides with a reservation held by a listener that never reaches the IR, and the hostname ends up served by nobody.

Repro steps:

Enable mergeGateways. Gateway A puts HTTPS and TLS on the same port and hostname, which is admitted because the CRD's CEL rule keys on protocol as well as port and hostname. Gateway B has the only listener that can actually serve that hostname.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway-a
spec:
  gatewayClassName: envoy-gateway-class
  listeners:
    - name: https
      port: 443
      protocol: HTTPS
      hostname: foo.example.com
      tls:
        mode: Terminate
        certificateRefs: [{kind: Secret, name: tls-secret-1}]
    - name: tls
      port: 443
      protocol: TLS
      hostname: foo.example.com
      tls:
        mode: Passthrough
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway-b
spec:
  gatewayClassName: envoy-gateway-class
  listeners:
    - name: tls
      port: 443
      protocol: TLS
      hostname: foo.example.com
      tls:
        mode: Passthrough

Gateway A's two listeners are correctly marked conflicted with no winner, per validateConflictedHostnameListeners, so neither is emitted. But both have already reserved their merged keys, so Gateway B's listener is marked conflicted too:

gateway-a  https  Conflicted=True/HostnameConflict
gateway-a  tls    Conflicted=True/HostnameConflict
gateway-b  tls    Conflicted=True/HostnameConflict   <- should be Accepted

The resulting IR has no HTTP and no TCP listeners at all.

A second shape reaches the same place through the protocol pass. Gateway A has HTTP and HTTPS on port 443, so both are marked ProtocolConflict and excluded, yet the HTTPS listener still reserves foo.example.com:443, and Gateway B's TLS listener for that hostname is rejected.

Notes:

The no-winner branch in validateConflictedHostnameListeners calls setConflictedConditions but does not set hostnameConflictLoser, which is only set on the has-winner path. So there is currently no flag on ListenerContext that says "an earlier conflict pass excluded this listener" in the general case: protocolConflicted covers only protocol conflicts, and hostnameConflictLoser only hostname losers where a winner existed. Fixing this likely means adding a general conflicted flag, or checking the Conflicted condition, and auditing the existing readers of those two flags.

This predates #10013, which changes the merged key to use the protocol class. I checked both repro shapes with and without that change and the outcome is the same either way, since Gateway A's TLS listener reserves the TLS key regardless.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions