diff --git a/internal/gatewayapi/overlap_test.go b/internal/gatewayapi/overlap_test.go new file mode 100644 index 0000000000..ae19e2b001 --- /dev/null +++ b/internal/gatewayapi/overlap_test.go @@ -0,0 +1,286 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package gatewayapi + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/utils/ptr" + + "github.com/envoyproxy/gateway/internal/ir" +) + +func TestRouteMatchesOverlap(t *testing.T) { + tests := []struct { + name string + a *ir.HTTPRoute + b *ir.HTTPRoute + overlap bool + }{ + { + name: "identical exact path and hostname", + a: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + }, + b: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + }, + overlap: true, + }, + { + name: "different exact paths", + a: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + }, + b: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/bar")}, + }, + overlap: false, + }, + { + name: "different hostnames", + a: &ir.HTTPRoute{ + Hostname: "a.example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + }, + b: &ir.HTTPRoute{ + Hostname: "b.example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + }, + overlap: false, + }, + { + name: "prefix paths are not detected as overlap", + a: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Prefix: ptr.To("/api")}, + }, + b: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Prefix: ptr.To("/api")}, + }, + overlap: false, + }, + { + name: "nil path matches are not detected as overlap", + a: &ir.HTTPRoute{ + Hostname: "example.com", + }, + b: &ir.HTTPRoute{ + Hostname: "example.com", + }, + overlap: false, + }, + { + name: "exact vs prefix same value not overlap", + a: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + }, + b: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Prefix: ptr.To("/foo")}, + }, + overlap: false, + }, + { + name: "identical exact path with identical header matches", + a: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + HeaderMatches: []*ir.StringMatch{ + {Name: "X-Custom", Exact: ptr.To("val1")}, + }, + }, + b: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + HeaderMatches: []*ir.StringMatch{ + {Name: "X-Custom", Exact: ptr.To("val1")}, + }, + }, + overlap: true, + }, + { + name: "identical exact path with different header matches", + a: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + HeaderMatches: []*ir.StringMatch{ + {Name: "X-Custom", Exact: ptr.To("val1")}, + }, + }, + b: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + HeaderMatches: []*ir.StringMatch{ + {Name: "X-Custom", Exact: ptr.To("val2")}, + }, + }, + overlap: false, + }, + { + name: "identical exact path one has headers other does not", + a: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + HeaderMatches: []*ir.StringMatch{ + {Name: "X-Custom", Exact: ptr.To("val1")}, + }, + }, + b: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + }, + overlap: false, + }, + { + name: "identical exact path with identical query param matches", + a: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + QueryParamMatches: []*ir.StringMatch{ + {Name: "key", Exact: ptr.To("value")}, + }, + }, + b: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{Exact: ptr.To("/foo")}, + QueryParamMatches: []*ir.StringMatch{ + {Name: "key", Exact: ptr.To("value")}, + }, + }, + overlap: true, + }, + { + name: "regex path matches are not detected as overlap", + a: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{SafeRegex: ptr.To("/foo.*")}, + }, + b: &ir.HTTPRoute{ + Hostname: "example.com", + PathMatch: &ir.StringMatch{SafeRegex: ptr.To("/foo.*")}, + }, + overlap: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.overlap, routeMatchesOverlap(tt.a, tt.b)) + }) + } +} + +func TestStringMatchEqual(t *testing.T) { + tests := []struct { + name string + a *ir.StringMatch + b *ir.StringMatch + equal bool + }{ + { + name: "both nil", + a: nil, + b: nil, + equal: true, + }, + { + name: "one nil", + a: &ir.StringMatch{Exact: ptr.To("foo")}, + b: nil, + equal: false, + }, + { + name: "identical exact", + a: &ir.StringMatch{Name: "h", Exact: ptr.To("v")}, + b: &ir.StringMatch{Name: "h", Exact: ptr.To("v")}, + equal: true, + }, + { + name: "different name", + a: &ir.StringMatch{Name: "a", Exact: ptr.To("v")}, + b: &ir.StringMatch{Name: "b", Exact: ptr.To("v")}, + equal: false, + }, + { + name: "different value", + a: &ir.StringMatch{Name: "h", Exact: ptr.To("v1")}, + b: &ir.StringMatch{Name: "h", Exact: ptr.To("v2")}, + equal: false, + }, + { + name: "different distinct", + a: &ir.StringMatch{Name: "h", Distinct: true}, + b: &ir.StringMatch{Name: "h", Distinct: false}, + equal: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.equal, stringMatchEqual(tt.a, tt.b)) + }) + } +} + +func TestStringMatchSliceEqual(t *testing.T) { + tests := []struct { + name string + a []*ir.StringMatch + b []*ir.StringMatch + equal bool + }{ + { + name: "both empty", + a: nil, + b: nil, + equal: true, + }, + { + name: "different lengths", + a: []*ir.StringMatch{{Name: "a", Exact: ptr.To("1")}}, + b: nil, + equal: false, + }, + { + name: "identical", + a: []*ir.StringMatch{ + {Name: "a", Exact: ptr.To("1")}, + {Name: "b", Exact: ptr.To("2")}, + }, + b: []*ir.StringMatch{ + {Name: "a", Exact: ptr.To("1")}, + {Name: "b", Exact: ptr.To("2")}, + }, + equal: true, + }, + { + name: "same elements different order", + a: []*ir.StringMatch{ + {Name: "a", Exact: ptr.To("1")}, + {Name: "b", Exact: ptr.To("2")}, + }, + b: []*ir.StringMatch{ + {Name: "b", Exact: ptr.To("2")}, + {Name: "a", Exact: ptr.To("1")}, + }, + equal: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.equal, stringMatchSliceEqual(tt.a, tt.b)) + }) + } +} diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index aa3e205054..e4776eb4e6 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net" + "sort" "strconv" "strings" "time" @@ -1248,6 +1249,7 @@ func (t *Translator) processGRPCRouteMethodRegularExpression(method *gwapiv1.GRP func (t *Translator) processHTTPRouteParentRefListener(route RouteContext, routeRoutes []*ir.HTTPRoute, parentRef *RouteParentContext, xdsIR resource.XdsIRMap) bool { // need to check hostname intersection if there are listeners hasHostnameIntersection := len(parentRef.listeners) == 0 + for _, listener := range parentRef.listeners { hosts := computeHosts(GetHostnames(route), listener) if len(hosts) == 0 { @@ -1301,6 +1303,7 @@ func (t *Translator) processHTTPRouteParentRefListener(route RouteContext, route irListener.GRPC.EnableGRPCWeb = ptr.To(true) irListener.GRPC.EnableGRPCStats = ptr.To(true) } + irListener.Routes = append(irListener.Routes, perHostRoutes...) } } @@ -1308,6 +1311,203 @@ func (t *Translator) processHTTPRouteParentRefListener(route RouteContext, route return hasHostnameIntersection } +// checkRouteOverlaps detects overlapping route matches across all IR listeners +// and sets a warning Overlap condition on the affected HTTPRoutes. +// routeKey returns a "namespace/name" key for a route resource metadata. +func routeKey(namespace, name string) string { + return namespace + "/" + name +} + +// checkRouteOverlaps detects overlapping route matches across all IR listeners +// and sets a warning Overlap condition on the affected HTTPRoutes and GRPCRoutes. +func (t *Translator) checkRouteOverlaps(httpRoutes []*HTTPRouteContext, grpcRoutes []*GRPCRouteContext, xdsIR resource.XdsIRMap) { + // Build a combined lookup from "namespace/name" to RouteContext and its ParentRefs. + type routeInfo struct { + route RouteContext + parentRefs map[gwapiv1.ParentReference]*RouteParentContext + } + routeByKey := make(map[string]*routeInfo, len(httpRoutes)+len(grpcRoutes)) + for _, hr := range httpRoutes { + routeByKey[routeKey(hr.GetNamespace(), hr.GetName())] = &routeInfo{route: hr, parentRefs: hr.ParentRefs} + } + for _, gr := range grpcRoutes { + routeByKey[routeKey(gr.GetNamespace(), gr.GetName())] = &routeInfo{route: gr, parentRefs: gr.ParentRefs} + } + + // overlaps tracks per IR listener which routes overlap with which others. + // Key: IR listener name -> route key -> set of conflicting route keys. + type listenerOverlaps map[string]map[string]struct{} + overlaps := make(map[string]listenerOverlaps) + + for _, xds := range xdsIR { + for _, httpListener := range xds.HTTP { + routes := httpListener.Routes + for i := 0; i < len(routes); i++ { + if routes[i].Metadata == nil { + continue + } + aKey := routeKey(routes[i].Metadata.Namespace, routes[i].Metadata.Name) + for j := i + 1; j < len(routes); j++ { + if routes[j].Metadata == nil { + continue + } + bKey := routeKey(routes[j].Metadata.Namespace, routes[j].Metadata.Name) + if aKey == bKey { + continue + } + if routeMatchesOverlap(routes[i], routes[j]) { + if overlaps[httpListener.Name] == nil { + overlaps[httpListener.Name] = make(listenerOverlaps) + } + lo := overlaps[httpListener.Name] + if lo[aKey] == nil { + lo[aKey] = make(map[string]struct{}) + } + if lo[bKey] == nil { + lo[bKey] = make(map[string]struct{}) + } + lo[aKey][bKey] = struct{}{} + lo[bKey][aKey] = struct{}{} + } + } + } + } + } + + // Set the Overlap warning condition only on parentRefs whose listeners + // match an IR listener where the overlap was detected. + for rKey, info := range routeByKey { + // Collect all conflicts for this route across the parentRefs that have overlaps. + for _, parentRef := range info.parentRefs { + var conflicts map[string]struct{} + for _, listener := range parentRef.listeners { + irKey := t.getIRKey(listener.gateway.Gateway) + irListener := xdsIR[irKey].GetHTTPListener(irListenerName(listener)) + if irListener == nil { + continue + } + lo, ok := overlaps[irListener.Name] + if !ok { + continue + } + routeConflicts, ok := lo[rKey] + if !ok { + continue + } + if conflicts == nil { + conflicts = make(map[string]struct{}) + } + for c := range routeConflicts { + conflicts[c] = struct{}{} + } + } + if len(conflicts) == 0 { + continue + } + + conflictNames := make([]string, 0, len(conflicts)) + for name := range conflicts { + conflictNames = append(conflictNames, name) + } + sort.Strings(conflictNames) + + msg := fmt.Sprintf("Overlapping match conditions with route(s): %s", strings.Join(conflictNames, ", ")) + + routeStatus := GetRouteStatus(info.route) + status.SetRouteStatusCondition(routeStatus, + parentRef.routeParentStatusIdx, + info.route.GetGeneration(), + status.RouteConditionOverlap, + metav1.ConditionTrue, + status.RouteReasonOverlap, + msg, + ) + } + } +} + +// routeMatchesOverlap checks if two IR HTTP routes have identical match conditions, +// indicating that they match the same set of requests. +// This detects routes with the same hostname and identical exact path, header, query param, +// and cookie match conditions. Only routes with explicit exact path matches are checked; +// prefix path matches can intentionally overlap across HTTPRoute resources +// and are resolved through precedence ordering. +func routeMatchesOverlap(a, b *ir.HTTPRoute) bool { + if a.Hostname != b.Hostname { + return false + } + // Only detect overlap for routes with explicit exact path matches. + if a.PathMatch == nil || b.PathMatch == nil { + return false + } + if a.PathMatch.Exact == nil || b.PathMatch.Exact == nil { + return false + } + if !stringMatchEqual(a.PathMatch, b.PathMatch) { + return false + } + if !stringMatchSliceEqual(a.HeaderMatches, b.HeaderMatches) { + return false + } + if !stringMatchSliceEqual(a.QueryParamMatches, b.QueryParamMatches) { + return false + } + if !stringMatchSliceEqual(a.CookieMatches, b.CookieMatches) { + return false + } + return true +} + +func stringMatchEqual(a, b *ir.StringMatch) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + if !ptrStringEqual(a.Exact, b.Exact) { + return false + } + if !ptrStringEqual(a.Prefix, b.Prefix) { + return false + } + if !ptrStringEqual(a.Suffix, b.Suffix) { + return false + } + if !ptrStringEqual(a.SafeRegex, b.SafeRegex) { + return false + } + if a.Name != b.Name { + return false + } + if a.Distinct != b.Distinct { + return false + } + return true +} + +func stringMatchSliceEqual(a, b []*ir.StringMatch) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if !stringMatchEqual(a[i], b[i]) { + return false + } + } + return true +} + +func ptrStringEqual(a, b *string) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + return *a == *b +} + func buildResourceMetadata(resource client.Object, sectionName *gwapiv1.SectionName) *ir.ResourceMetadata { metadata := &ir.ResourceMetadata{ Kind: resource.GetObjectKind().GroupVersionKind().Kind, diff --git a/internal/gatewayapi/status/error.go b/internal/gatewayapi/status/error.go index 7e310da62b..8f7274a7c0 100644 --- a/internal/gatewayapi/status/error.go +++ b/internal/gatewayapi/status/error.go @@ -31,6 +31,10 @@ const ( RouteReasonInvalidAddress gwapiv1.RouteConditionReason = "InvalidAddress" RouteReasonEndpointsNotFound gwapiv1.RouteConditionReason = "EndpointsNotFound" + // Route overlap reason and condition type + RouteReasonOverlap gwapiv1.RouteConditionReason = "Overlap" + RouteConditionOverlap gwapiv1.RouteConditionType = "Overlap" + // Network configuration related condition types RouteConditionBackendsAvailable gwapiv1.RouteConditionType = "BackendsAvailable" ) diff --git a/internal/gatewayapi/testdata/httproute-overlapping-matches.in.yaml b/internal/gatewayapi/testdata/httproute-overlapping-matches.in.yaml new file mode 100644 index 0000000000..9510e62777 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-overlapping-matches.in.yaml @@ -0,0 +1,50 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + type: Exact + value: "/testPath" + backendRefs: + - name: service-1 + port: 8080 + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + type: Exact + value: "/testPath" + backendRefs: + - name: service-2 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-overlapping-matches.out.yaml b/internal/gatewayapi/testdata/httproute-overlapping-matches.out.yaml new file mode 100644 index 0000000000..63e9ea18a0 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-overlapping-matches.out.yaml @@ -0,0 +1,245 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-1 + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: Exact + value: /testPath + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + - lastTransitionTime: null + message: 'Overlapping match conditions with route(s): default/httproute-2' + reason: Overlap + status: "True" + type: Overlap + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-2 + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + rules: + - backendRefs: + - name: service-2 + port: 8080 + matches: + - path: + type: Exact + value: /testPath + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + - lastTransitionTime: null + message: 'Overlapping match conditions with route(s): default/httproute-1' + reason: Overlap + status: "True" + type: Overlap + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/* + pathMatch: + distinct: false + exact: /testPath + name: "" + - destination: + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-2 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/* + pathMatch: + distinct: false + exact: /testPath + name: "" + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 19dc472683..ef4c58b976 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -286,6 +286,10 @@ func (t *Translator) Translate(resources *resource.Resources) (*TranslateResult, // Process all relevant GRPCRoutes. grpcRoutes := t.ProcessGRPCRoutes(resources.GRPCRoutes, acceptedGateways, resources, xdsIR) + // Check for overlapping route matches across all listeners after all HTTP and + // GRPC routes have been processed. + t.checkRouteOverlaps(httpRoutes, grpcRoutes, xdsIR) + // Process all relevant TLSRoutes. tlsRoutes := t.ProcessTLSRoutes(resources.TLSRoutes, acceptedGateways, resources, xdsIR)