@@ -14,6 +14,7 @@ package controller
1414
1515import (
1616 "context"
17+ "errors"
1718 "fmt"
1819 "path"
1920 "reflect"
@@ -48,6 +49,10 @@ const (
4849
4950const defaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class"
5051
52+ var (
53+ ErrNoMatchingListenerHostname = errors .New ("no matching hostnames in listener" )
54+ )
55+
5156// IsDefaultIngressClass returns whether an IngressClass is the default IngressClass.
5257func IsDefaultIngressClass (obj client.Object ) bool {
5358 if ingressClass , ok := obj .(* networkingv1.IngressClass ); ok {
@@ -229,10 +234,15 @@ func SetRouteConditionAccepted(routeParentStatus *gatewayv1.RouteParentStatus, g
229234 conditionStatus = metav1 .ConditionFalse
230235 }
231236
237+ reason := gatewayv1 .RouteReasonAccepted
238+ if message == ErrNoMatchingListenerHostname .Error () {
239+ reason = gatewayv1 .RouteReasonNoMatchingListenerHostname
240+ }
241+
232242 condition := metav1.Condition {
233243 Type : string (gatewayv1 .RouteConditionAccepted ),
234244 Status : conditionStatus ,
235- Reason : string (gatewayv1 . RouteReasonAccepted ),
245+ Reason : string (reason ),
236246 ObservedGeneration : generation ,
237247 Message : message ,
238248 LastTransitionTime : metav1 .Now (),
@@ -458,39 +468,51 @@ func HostnamesIntersect(a, b string) bool {
458468 return HostnamesMatch (a , b ) || HostnamesMatch (b , a )
459469}
460470
471+ // HostnamesMatch checks that the hostnameB matches the hostnameA. HostnameA is treated as mask
472+ // to be checked against the hostnameB.
461473func HostnamesMatch (hostnameA , hostnameB string ) bool {
462- labelsA := strings .Split (hostnameA , "." )
463- labelsB := strings .Split (hostnameB , "." )
474+ // the hostnames are in the form of "foo.bar.com"; split them
475+ // in a slice of substrings
476+ hostnameALabels := strings .Split (hostnameA , "." )
477+ hostnameBLabels := strings .Split (hostnameB , "." )
464478
465- var i , j int
479+ var a , b int
466480 var wildcard bool
467481
468- for i , j = 0 , 0 ; i < len (labelsA ) && j < len (labelsB ); i , j = i + 1 , j + 1 {
482+ // iterate over the parts of both the hostnames
483+ for a , b = 0 , 0 ; a < len (hostnameALabels ) && b < len (hostnameBLabels ); a , b = a + 1 , b + 1 {
484+ var matchFound bool
485+
486+ // if the current part of B is a wildcard, we need to find the first
487+ // A part that matches with the following B part
469488 if wildcard {
470- for ; j < len (labelsB ); j ++ {
471- if labelsA [i ] == labelsB [j ] {
489+ for ; b < len (hostnameBLabels ); b ++ {
490+ if hostnameALabels [a ] == hostnameBLabels [b ] {
491+ matchFound = true
472492 break
473493 }
474494 }
475- if j == len (labelsB ) {
476- return false
477- }
478495 }
479496
480- if labelsA [i ] == "*" {
497+ // if no match was found, the hostnames don't match
498+ if wildcard && ! matchFound {
499+ return false
500+ }
501+
502+ // check if at least on of the current parts are a wildcard; if so, continue
503+ if hostnameALabels [a ] == "*" {
481504 wildcard = true
482- j --
483505 continue
484506 }
485-
507+ // reset the wildcard variables
486508 wildcard = false
487509
488- if labelsA [i ] != labelsB [j ] {
510+ // if the current a part is different from the b part, the hostnames are incompatible
511+ if hostnameALabels [a ] != hostnameBLabels [b ] {
489512 return false
490513 }
491514 }
492-
493- return len (labelsA )- i == len (labelsB )- j
515+ return len (hostnameBLabels )- b == len (hostnameALabels )- a
494516}
495517
496518func routeMatchesListenerAllowedRoutes (
@@ -892,3 +914,115 @@ func IsInvalidKindError(err error) bool {
892914 _ , ok := err .(* InvalidKindError )
893915 return ok
894916}
917+
918+ // 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.
919+ // If the HTTPRoute hostnames do not intersect with the listener hostnames of the gateways, it returns an ErrNoMatchingListenerHostname error.
920+ func filterHostnames (gateways []RouteParentRefContext , httpRoute * gatewayv1.HTTPRoute ) (* gatewayv1.HTTPRoute , error ) {
921+ filteredHostnames := make ([]gatewayv1.Hostname , 0 )
922+
923+ // If the HTTPRoute does not specify hostnames, we use the union of the listener hostnames of all supported gateways
924+ // If any supported listener does not specify a hostname, the HTTPRoute hostnames remain empty to match any hostname
925+ if len (httpRoute .Spec .Hostnames ) == 0 {
926+ hostnames , matchAnyHost := getUnionOfGatewayHostnames (gateways )
927+ if matchAnyHost {
928+ return httpRoute , nil
929+ }
930+ filteredHostnames = hostnames
931+ } else {
932+ // If the HTTPRoute specifies hostnames, we need to find the intersection with the gateway listener hostnames
933+ for _ , hostname := range httpRoute .Spec .Hostnames {
934+ if hostnameMatching := getMinimumHostnameIntersection (gateways , hostname ); hostnameMatching != "" {
935+ filteredHostnames = append (filteredHostnames , hostnameMatching )
936+ }
937+ }
938+ if len (filteredHostnames ) == 0 {
939+ return httpRoute , ErrNoMatchingListenerHostname
940+ }
941+ }
942+
943+ log .Debugw ("filtered hostnames" , zap .Any ("httpRouteHostnames" , httpRoute .Spec .Hostnames ), zap .Any ("hostnames" , filteredHostnames ))
944+ httpRoute .Spec .Hostnames = filteredHostnames
945+ return httpRoute , nil
946+ }
947+
948+ // getUnionOfGatewayHostnames returns the union of the hostnames specified in all supported gateways
949+ // The second return value indicates whether any listener can match any hostname
950+ func getUnionOfGatewayHostnames (gateways []RouteParentRefContext ) ([]gatewayv1.Hostname , bool ) {
951+ hostnames := make ([]gatewayv1.Hostname , 0 )
952+
953+ for _ , gateway := range gateways {
954+ if gateway .ListenerName != "" {
955+ // If a listener name is specified, only check that listener
956+ for _ , listener := range gateway .Gateway .Spec .Listeners {
957+ if string (listener .Name ) == gateway .ListenerName {
958+ // If a listener does not specify a hostname, it can match any hostname
959+ if listener .Hostname == nil {
960+ return nil , true
961+ }
962+ hostnames = append (hostnames , * listener .Hostname )
963+ break
964+ }
965+ }
966+ } else {
967+ // Otherwise, check all listeners
968+ for _ , listener := range gateway .Gateway .Spec .Listeners {
969+ // Only consider listeners that can effectively configure hostnames (HTTP, HTTPS, or TLS)
970+ if isListenerHostnameEffective (listener ) {
971+ if listener .Hostname == nil {
972+ return nil , true
973+ }
974+ hostnames = append (hostnames , * listener .Hostname )
975+ }
976+ }
977+ }
978+ }
979+
980+ return hostnames , false
981+ }
982+
983+ // getMinimumHostnameIntersection returns the smallest intersection hostname
984+ // - If the listener hostname is empty, return the HTTPRoute hostname
985+ // - If the listener hostname is a wildcard of the HTTPRoute hostname, return the HTTPRoute hostname
986+ // - If the HTTPRoute hostname is a wildcard of the listener hostname, return the listener hostname
987+ // - If the HTTPRoute hostname and listener hostname are the same, return it
988+ // - If none of the above, return an empty string
989+ func getMinimumHostnameIntersection (gateways []RouteParentRefContext , hostname gatewayv1.Hostname ) gatewayv1.Hostname {
990+ for _ , gateway := range gateways {
991+ for _ , listener := range gateway .Gateway .Spec .Listeners {
992+ // If a listener name is specified, only check that listener
993+ // If the listener name is not specified, check all listeners
994+ if gateway .ListenerName == "" || gateway .ListenerName == string (listener .Name ) {
995+ if listener .Hostname == nil || * listener .Hostname == "" {
996+ return hostname
997+ }
998+ if HostnamesMatch (string (* listener .Hostname ), string (hostname )) {
999+ return hostname
1000+ }
1001+ if HostnamesMatch (string (hostname ), string (* listener .Hostname )) {
1002+ return * listener .Hostname
1003+ }
1004+ }
1005+ }
1006+ }
1007+
1008+ return ""
1009+ }
1010+
1011+ // isListenerHostnameEffective checks if a listener can specify a hostname to match the hostname in the request
1012+ // Basically, check if the listener uses HTTP, HTTPS, or TLS protocol
1013+ func isListenerHostnameEffective (listener gatewayv1.Listener ) bool {
1014+ return listener .Protocol == gatewayv1 .HTTPProtocolType ||
1015+ listener .Protocol == gatewayv1 .HTTPSProtocolType ||
1016+ listener .Protocol == gatewayv1 .TLSProtocolType
1017+ }
1018+
1019+ func isRouteAccepted (gateways []RouteParentRefContext ) bool {
1020+ for _ , gateway := range gateways {
1021+ for _ , condition := range gateway .Conditions {
1022+ if condition .Type == string (gatewayv1 .RouteConditionAccepted ) && condition .Status == metav1 .ConditionTrue {
1023+ return true
1024+ }
1025+ }
1026+ }
1027+ return false
1028+ }
0 commit comments