Copilot commented on code in PR #2882:
URL: 
https://github.com/apache/apisix-ingress-controller/pull/2882#discussion_r4244240757


##########
internal/adc/translator/tlsroute.go:
##########
@@ -143,18 +141,90 @@ func (t *Translator) TranslateTLSRoute(tctx 
*provider.TranslateContext, tlsRoute
                        }
                }
 
-               for _, host := range hosts {
-                       streamRoutes, err := t.buildL4StreamRoutes(tctx, 
tlsRoute.Namespace, tlsRoute.Name, ruleIndex, "TLS", "TLSRoute", labels)
-                       if err != nil {
-                               return nil, err
+               // One set of stream routes per rule carrying every SNI, not 
one set per
+               // hostname: buildL4StreamRoutes names them by rule and port 
only, so a per
+               // hostname loop produced N sets sharing one ID and only the 
last survived.
+               streamRoutes, err := t.buildL4StreamRoutes(tctx, 
tlsRoute.Namespace, tlsRoute.Name, ruleIndex, "TLS", "TLSRoute", labels)
+               if err != nil {
+                       return nil, err
+               }
+               for _, streamRoute := range streamRoutes {
+                       // A single SNI keeps using the singular form: it is 
what every
+                       // APISIX version understands, and snis only earns its 
place once
+                       // there is more than one to match.
+                       if len(snis) == 1 {
+                               streamRoute.SNI = snis[0]
+                       } else {
+                               streamRoute.SNIs = snis
                        }

Review Comment:
   The same route-wide SNI list is copied to every per-port StreamRoute. If a 
hostname-less TLSRoute attaches to `a.example.com` on port 9110 and 
`b.example.com` on 9120, both generated routes accept both names, so each 
listener serves a hostname it does not accept. Derive the SNI set from the 
listeners matching each `ServerPort` (including a per-port hostname-less 
catch-all) instead of assigning the global union.
   
   This issue also appears on line 199 of the same file.



##########
internal/controller/utils.go:
##########
@@ -1392,30 +1392,56 @@ func FullTypeName(a any) string {
 // filterHostnames accepts a list of gateways and an HTTPRoute, and returns a 
copy of the HTTPRoute with only the hostnames that match the listener hostnames 
of the gateways.
 // If the HTTPRoute hostnames do not intersect with the listener hostnames of 
the gateways, it returns an ErrNoMatchingListenerHostname error.
 func filterHostnames(gateways []RouteParentRefContext, httpRoute 
*gatewayv1.HTTPRoute) (*gatewayv1.HTTPRoute, error) {
-       filteredHostnames := make([]gatewayv1.Hostname, 0)
+       hostnames, err := intersectRouteHostnames(gateways, 
httpRoute.Spec.Hostnames)
+       if err != nil {
+               return httpRoute, err
+       }
+       httpRoute.Spec.Hostnames = hostnames
+       return httpRoute, nil
+}
+
+// filterTLSRouteHostnames is filterHostnames for a TLSRoute. Its hostnames 
become
+// the SNIs the stream routes match on, so a route left carrying its own 
broader
+// hostname would serve names the listener it attached to never accepted - 
and, on
+// a single shared stream listen, would take them from the route that should 
have.
+func filterTLSRouteHostnames(gateways []RouteParentRefContext, tlsRoute 
*gatewayv1.TLSRoute) (*gatewayv1.TLSRoute, error) {
+       hostnames, err := intersectRouteHostnames(gateways, 
tlsRoute.Spec.Hostnames)

Review Comment:
   `intersectRouteHostnames` keeps only the first listener intersection for 
each route hostname because `getMinimumHostnameIntersection` returns on its 
first match. For example, a TLSRoute with `*.example.com` attached to same-port 
listeners for `a.example.com` and `b.example.com` is emitted only for the first 
name, so traffic accepted by the second listener is dropped. Collect and 
deduplicate every intersection across the accepted matched listeners for 
TLSRoute.



##########
test/conformance/conformance_test.go:
##########
@@ -62,6 +51,18 @@ var skippedTestsForKnownGaps = []string{
        // A single HTTPRoute attached to several Gateways is not served from 
each
        // parent independently.
        tests.HTTPRouteMultipleGateways.ShortName,
+
+       // The same limitation for TLSRoute, and not something the translator 
can
+       // fix. The test stands four Gateways up on port 443 with different 
listener
+       // hostnames; every Gateway resolves to the one data plane address and 
the one
+       // physical stream listen, so their SNI namespaces are shared. The 
Gateway
+       // whose listener carries no hostname keeps its route's "*.com" 
verbatim -
+       // correctly, and its own subtest depends on it - which then also 
answers
+       // "non.matching.com" on the address of the Gateway that should have 
rejected
+       // it. Which Gateway a connection was addressed to is not on the wire, 
so
+       // there is nothing left to discriminate on. Every other assertion in 
this
+       // test passes, including the hostname intersections themselves.
+       tests.TLSRouteHostnameIntersection.ShortName,

Review Comment:
   This remains in the skip list, contradicting the PR description's claim that 
all four Passthrough tests, including `TLSRouteHostnameIntersection`, are no 
longer skipped. Either make the test pass and remove this entry or update the 
stated conformance scope.



##########
docs/en/latest/concepts/gateway-api.md:
##########
@@ -87,7 +87,7 @@ The fields below are specified in the Gateway API 
specification but are either p
 | `spec.listeners[].port`               | Partially supported | Controls 
`server_port` route-var injection; off by default and enabled via 
[`listener_port_match_mode`](../reference/configuration-file.md) (`auto` / 
`explicit`). The controller cannot dynamically open data plane ports, so APISIX 
must already listen on the specified port. |
 | `spec.listeners[].tls.certificateRefs[].group` | Partially supported | Only 
`""` is supported; other group values cause validation failure. |
 | `spec.listeners[].tls.certificateRefs[].kind`        | Partially supported  
| Only `Secret` is supported.                                                   
                 |
-| `spec.listeners[].tls.mode`                          | Partially supported  
| `Terminate` is implemented; `Passthrough` is effectively unsupported for 
Gateway listeners.    |
+| `spec.listeners[].tls.mode`                          | Partially supported  
| `Terminate` and `Passthrough` are both implemented. A `Passthrough` listener 
needs APISIX to be listening on that port with 
[`tls_passthrough`](https://apisix.apache.org/docs/apisix/stream-proxy/); the 
controller cannot open data plane ports itself. A single port cannot mix the 
two modes: listeners that disagree on `tls.mode` for one port are reported 
`Accepted=False` with `ProtocolConflict`. |

Review Comment:
   This says all same-port disagreements are reported as `ProtocolConflict`, 
but that check is scoped to listeners within one Gateway. Across Gateways, the 
translator instead leaves `tls_passthrough` unset. Document that distinction so 
operators do not expect a rejection that never occurs.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to