Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/gatewayapi/contexts.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ type ListenerContext struct {

namespaceSelector labels.Selector

// specValid indicates whether per-listener spec validation succeeded.
// Conflict detection should only consider listeners with specValid=true.
specValid bool

tls ListenerTLSConfig

httpIR *ir.HTTPListener
Expand Down
142 changes: 97 additions & 45 deletions internal/gatewayapi/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,19 +219,92 @@ func validClientCertificateRef(ref *gwapiv1.SecretObjectReference) error {
return nil
}

// allowedRouteKindsForProtocol returns the route kinds supported by the given listener protocol.
func allowedRouteKindsForProtocol(protocol gwapiv1.ProtocolType, tlsMode *gwapiv1.TLSModeType) []gwapiv1.Kind {
switch protocol {
case gwapiv1.HTTPProtocolType, gwapiv1.HTTPSProtocolType:
return []gwapiv1.Kind{resource.KindHTTPRoute, resource.KindGRPCRoute}
case gwapiv1.TLSProtocolType:
if tlsMode != nil && *tlsMode == gwapiv1.TLSModePassthrough {
return []gwapiv1.Kind{resource.KindTLSRoute}
}
// Terminate mode or unspecified defaults to accept both TCP and TLS routes
return []gwapiv1.Kind{resource.KindTCPRoute, resource.KindTLSRoute}
case gwapiv1.TCPProtocolType:
return []gwapiv1.Kind{resource.KindTCPRoute}
case gwapiv1.UDPProtocolType:
return []gwapiv1.Kind{resource.KindUDPRoute}
default:
return nil
}
}

func (t *Translator) validateListenerSpec(listener *ListenerContext, resources *resource.Resources) bool {
// Validate listener spec directly without relying on conditions.
// Start with valid assumption and invalidate on failures.
// Phase 1: Validate fundamental rules
specValid := t.validateAllowedNamespaces(listener)

// Phase 2: Validate allowed routes based on protocol
if isSupportedListenerProtocol(listener.Protocol) {
var tlsMode *gwapiv1.TLSModeType
if listener.TLS != nil {
tlsMode = listener.TLS.Mode
}
allowedKinds := allowedRouteKindsForProtocol(listener.Protocol, tlsMode)
if !t.validateAllowedRoutes(listener, allowedKinds...) {
specValid = false
}
} else {
// Unsupported protocol
specValid = false
listener.SetSupportedKinds()
listener.SetCondition(
gwapiv1.ListenerConditionAccepted,
metav1.ConditionFalse,
gwapiv1.ListenerReasonUnsupportedProtocol,
fmt.Sprintf("Protocol %s is unsupported, must be %s, %s, %s or %s.", listener.Protocol,
gwapiv1.HTTPProtocolType, gwapiv1.HTTPSProtocolType, gwapiv1.TCPProtocolType, gwapiv1.UDPProtocolType),
)
}

// Phase 3: Validate TLS configuration details
if !t.validateTLSConfiguration(listener, resources) {
specValid = false
}

// Phase 4: Validate Hostname configuration
if !t.validateHostName(listener) {
specValid = false
}

return specValid
}

func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR resource.XdsIRMap, infraIR resource.InfraIRMap, resources *resource.Resources) {
// Infra IR proxy ports must be unique.
Comment thread
zirain marked this conversation as resolved.
foundPorts := make(map[string][]*protocolPort)

// Phase 1: Validate each listener's spec independently.
// This must happen before conflict resolution so that invalid listeners
// don't block valid ones during conflict detection.
for _, gateway := range gateways {
for _, listener := range gateway.listeners {
listener.specValid = t.validateListenerSpec(listener, resources)
}
}

// Phase 2: Run conflict detection.
// Only listeners that haven't been marked as invalid will participate in conflict resolution.
t.validateConflictedProtocolsListeners(gateways)
t.validateConflictedLayer7Listeners(gateways)
t.validateConflictedLayer4Listeners(gateways, gwapiv1.TCPProtocolType)
t.validateConflictedLayer4Listeners(gateways, gwapiv1.UDPProtocolType)
if t.MergeGateways {
t.validateConflictedMergedListeners(gateways)
}

// Iterate through all listeners to validate spec
// and compute status for each, and add valid ones
// to the Xds IR.
// Phase 3: Build IR for valid listeners.
for _, gateway := range gateways {
irKey := t.getIRKey(gateway.Gateway)

Expand All @@ -242,49 +315,12 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR resource
t.processProxyObservability(gateway, xdsIR[irKey], infraIR[irKey].Proxy, resources)

for _, listener := range gateway.listeners {
// Process protocol & supported kinds
switch listener.Protocol {
case gwapiv1.TLSProtocolType:
if listener.TLS != nil {
switch *listener.TLS.Mode {
case gwapiv1.TLSModePassthrough:
t.validateAllowedRoutes(listener, resource.KindTLSRoute)
case gwapiv1.TLSModeTerminate:
t.validateAllowedRoutes(listener, resource.KindTCPRoute, resource.KindTLSRoute)
default:
t.validateAllowedRoutes(listener, resource.KindTCPRoute, resource.KindTLSRoute)
}
} else {
t.validateAllowedRoutes(listener, resource.KindTCPRoute, resource.KindTLSRoute)
}
case gwapiv1.HTTPProtocolType, gwapiv1.HTTPSProtocolType:
t.validateAllowedRoutes(listener, resource.KindHTTPRoute, resource.KindGRPCRoute)
case gwapiv1.TCPProtocolType:
t.validateAllowedRoutes(listener, resource.KindTCPRoute)
case gwapiv1.UDPProtocolType:
t.validateAllowedRoutes(listener, resource.KindUDPRoute)
default:
listener.SetSupportedKinds()
listener.SetCondition(
gwapiv1.ListenerConditionAccepted,
metav1.ConditionFalse,
gwapiv1.ListenerReasonUnsupportedProtocol,
fmt.Sprintf("Protocol %s is unsupported, must be %s, %s, %s or %s.", listener.Protocol,
gwapiv1.HTTPProtocolType, gwapiv1.HTTPSProtocolType, gwapiv1.TCPProtocolType, gwapiv1.UDPProtocolType),
)
}

// Validate allowed namespaces
t.validateAllowedNamespaces(listener)

// Process TLS configuration
t.validateTLSConfiguration(listener, resources)

// Process Hostname configuration
t.validateHostName(listener)

// Process conditions and check if the listener is ready
// Finalize listener conditions and check readiness.
t.validateListenerConditions(listener)
if !listener.IsReady() {
// Skip invalid listeners from IR building
continue
}

// Skip listeners with invalid frontend TLS validation as they are not functional.
if listener.frontendTLSValidationInvalid() {
Expand Down Expand Up @@ -427,10 +463,18 @@ func checkOverlappingHostnames(httpsListeners []*ListenerContext) {
if overlappingListeners[i] != nil {
continue
}
// Skip listeners that are already marked as invalid from per-listener validation
if hasInvalidCondition(httpsListeners[i]) {
continue
}
for j := i + 1; j < len(httpsListeners); j++ {
if overlappingListeners[j] != nil {
continue
}
// Skip listeners that are already marked as invalid from per-listener validation
if hasInvalidCondition(httpsListeners[j]) {
continue
}
if httpsListeners[i].Port != httpsListeners[j].Port {
continue
}
Expand Down Expand Up @@ -509,10 +553,18 @@ func checkOverlappingCertificates(httpsListeners []*ListenerContext) {
if overlappingListeners[i] != nil {
continue
}
// Skip listeners that are already marked as invalid from per-listener validation
if hasInvalidCondition(httpsListeners[i]) {
continue
}
for j := i + 1; j < len(httpsListeners); j++ {
if overlappingListeners[j] != nil {
continue
}
// Skip listeners that are already marked as invalid from per-listener validation
if hasInvalidCondition(httpsListeners[j]) {
continue
}
if httpsListeners[i].Port != httpsListeners[j].Port {
continue
}
Expand Down
2 changes: 1 addition & 1 deletion internal/gatewayapi/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -2287,7 +2287,7 @@ func (t *Translator) processAllowedListenersForParentRefs(
}
parentRefCtx.SetListeners(allowedListeners...)

if !HasReadyListener(selectedListeners) {
if !HasReadyListener(allowedListeners) {
routeStatus := GetRouteStatus(routeContext)
status.SetRouteStatusCondition(routeStatus,
parentRefCtx.routeParentStatusIdx,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,12 +508,6 @@ infraIR:
name: http-80
protocol: HTTP
servicePort: 80
- name: envoy-gateway/gateway-2/https
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.

why is existing logic for Gateway listeners changing ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

- attachedRoutes: 1
      conditions:
      - lastTransitionTime: null
        message: Listener must have TLS set when protocol is HTTPS.
        reason: Invalid
        status: "False"
        type: Programmed
      - lastTransitionTime: null
        message: Listener references have been resolved
        reason: ResolvedRefs
        status: "True"
        type: ResolvedRefs
      name: https
      supportedKinds:
      - group: gateway.networking.k8s.io
        kind: HTTPRoute
      - group: gateway.networking.k8s.io
        kind: GRPCRoute

ports:
- containerPort: 10443
name: https-443
protocol: HTTPS
servicePort: 443
- name: envoy-gateway/gateway-2/tcp
ports:
- containerPort: 10053
Expand Down Expand Up @@ -735,20 +729,6 @@ xdsIR:
namespace: envoy-gateway
name: grpcroute/envoy-gateway/grpcroute-1/rule/0/match/0/*
traffic: {}
- address: 0.0.0.0
externalPort: 443
hostnames:
- '*'
metadata:
kind: Gateway
name: gateway-2
namespace: envoy-gateway
sectionName: https
name: envoy-gateway/gateway-2/https
path:
escapedSlashesAction: UnescapeAndRedirect
mergeSlashes: true
port: 10443
readyListener:
address: 0.0.0.0
ipFamily: IPv4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,6 @@ infraIR:
name: http-80
protocol: HTTP
servicePort: 80
- name: envoy-gateway/gateway-1/https
ports:
- containerPort: 10443
name: https-443
protocol: HTTPS
servicePort: 443
metadata:
labels:
gateway.envoyproxy.io/owning-gateway-name: gateway-1
Expand Down Expand Up @@ -309,20 +303,6 @@ xdsIR:
name: ""
prefix: /foo
traffic: {}
- address: 0.0.0.0
externalPort: 443
hostnames:
- '*'
metadata:
kind: Gateway
name: gateway-1
namespace: envoy-gateway
sectionName: https
name: envoy-gateway/gateway-1/https
path:
escapedSlashesAction: UnescapeAndRedirect
mergeSlashes: true
port: 10443
readyListener:
address: 0.0.0.0
ipFamily: IPv4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,12 +453,6 @@ infraIR:
name: http-80
protocol: HTTP
servicePort: 80
- name: envoy-gateway/gateway-2/https
ports:
- containerPort: 10443
name: https-443
protocol: HTTPS
servicePort: 443
- name: envoy-gateway/gateway-2/tcp
ports:
- containerPort: 10053
Expand Down Expand Up @@ -596,20 +590,6 @@ xdsIR:
escapedSlashesAction: UnescapeAndRedirect
mergeSlashes: true
port: 10080
- address: 0.0.0.0
externalPort: 443
hostnames:
- '*'
metadata:
kind: Gateway
name: gateway-2
namespace: envoy-gateway
sectionName: https
name: envoy-gateway/gateway-2/https
path:
escapedSlashesAction: UnescapeAndRedirect
mergeSlashes: true
port: 10443
readyListener:
address: 0.0.0.0
ipFamily: IPv4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,12 +508,6 @@ infraIR:
name: http-80
protocol: HTTP
servicePort: 80
- name: envoy-gateway/gateway-2/https
ports:
- containerPort: 10443
name: https-443
protocol: HTTPS
servicePort: 443
- name: envoy-gateway/gateway-2/tcp
ports:
- containerPort: 10053
Expand Down Expand Up @@ -727,20 +721,6 @@ xdsIR:
name: grpcroute-1
namespace: envoy-gateway
name: grpcroute/envoy-gateway/grpcroute-1/rule/0/match/0/*
- address: 0.0.0.0
externalPort: 443
hostnames:
- '*'
metadata:
kind: Gateway
name: gateway-2
namespace: envoy-gateway
sectionName: https
name: envoy-gateway/gateway-2/https
path:
escapedSlashesAction: UnescapeAndRedirect
mergeSlashes: true
port: 10443
readyListener:
address: 0.0.0.0
ipFamily: IPv4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,13 +326,6 @@ infraIR:
namespace: envoy-gateway-system
envoy-gateway/unresolved-gateway-with-one-attached-unresolved-route:
proxy:
listeners:
- name: envoy-gateway/unresolved-gateway-with-one-attached-unresolved-route/tls
ports:
- containerPort: 10443
name: https-443
protocol: HTTPS
servicePort: 443
metadata:
labels:
gateway.envoyproxy.io/owning-gateway-name: unresolved-gateway-with-one-attached-unresolved-route
Expand Down Expand Up @@ -487,21 +480,6 @@ xdsIR:
sectionName: "8080"
name: envoy-gateway/unresolved-gateway-with-one-attached-unresolved-route
protocol: TCP
http:
- address: 0.0.0.0
externalPort: 443
hostnames:
- '*'
metadata:
kind: Gateway
name: unresolved-gateway-with-one-attached-unresolved-route
namespace: envoy-gateway
sectionName: tls
name: envoy-gateway/unresolved-gateway-with-one-attached-unresolved-route/tls
path:
escapedSlashesAction: UnescapeAndRedirect
mergeSlashes: true
port: 10443
readyListener:
address: 0.0.0.0
ipFamily: IPv4
Expand Down
Loading
Loading