envoyproxy / envoyproxy/gateway
Merged gateways: conflicted listeners still reserve a hostname, blocking a valid listener on another Gateway
- Dominant language
- Go
- Stars
- 3k
- Forks
- 864
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 140
Description
*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.
```yaml
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.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start at validateConflictedMergedListeners, then trace validateConflictedHostnameListeners, the protocol-conflict pass, and readers of protocolConflicted and hostnameConflictLoser. Reproduce the issue with the Gateway A and Gateway B manifests in the report. Done means listeners excluded by earlier conflict passes do not reserve merged keys, Gateway B is Accepted, and its listener appears in the IR.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kubernetes
- Domain
- backend-api-design, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100