@@ -14,6 +14,7 @@ package controller
1414
1515import (
1616 "context"
17+ "errors"
1718 "fmt"
1819 "path"
1920 "reflect"
@@ -49,6 +50,10 @@ const (
4950
5051const defaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class"
5152
53+ var (
54+ ErrNoMatchingListenerHostname = errors .New ("no matching hostnames in listener" )
55+ )
56+
5257// IsDefaultIngressClass returns whether an IngressClass is the default IngressClass.
5358func IsDefaultIngressClass (obj client.Object ) bool {
5459 if ingressClass , ok := obj .(* networkingv1.IngressClass ); ok {
@@ -240,6 +245,9 @@ func SetRouteConditionAccepted(routeParentStatus *gatewayv1.RouteParentStatus, g
240245 Message : message ,
241246 LastTransitionTime : metav1 .Now (),
242247 }
248+ if message == ErrNoMatchingListenerHostname .Error () {
249+ condition .Reason = string (gatewayv1 .RouteReasonNoMatchingListenerHostname )
250+ }
243251
244252 if ! IsConditionPresentAndEqual (routeParentStatus .Conditions , condition ) && ! slices .ContainsFunc (routeParentStatus .Conditions , func (item metav1.Condition ) bool {
245253 return item .Type == condition .Type && item .Status == metav1 .ConditionFalse && condition .Status == metav1 .ConditionTrue
@@ -461,39 +469,51 @@ func HostnamesIntersect(a, b string) bool {
461469 return HostnamesMatch (a , b ) || HostnamesMatch (b , a )
462470}
463471
472+ // HostnamesMatch checks that the hostnameB matches the hostnameA. HostnameA is treated as mask
473+ // to be checked against the hostnameB.
464474func HostnamesMatch (hostnameA , hostnameB string ) bool {
465- labelsA := strings .Split (hostnameA , "." )
466- labelsB := strings .Split (hostnameB , "." )
475+ // the hostnames are in the form of "foo.bar.com"; split them
476+ // in a slice of substrings
477+ hostnameALabels := strings .Split (hostnameA , "." )
478+ hostnameBLabels := strings .Split (hostnameB , "." )
467479
468- var i , j int
480+ var a , b int
469481 var wildcard bool
470482
471- for i , j = 0 , 0 ; i < len (labelsA ) && j < len (labelsB ); i , j = i + 1 , j + 1 {
483+ // iterate over the parts of both the hostnames
484+ for a , b = 0 , 0 ; a < len (hostnameALabels ) && b < len (hostnameBLabels ); a , b = a + 1 , b + 1 {
485+ var matchFound bool
486+
487+ // if the current part of B is a wildcard, we need to find the first
488+ // A part that matches with the following B part
472489 if wildcard {
473- for ; j < len (labelsB ); j ++ {
474- if labelsA [i ] == labelsB [j ] {
490+ for ; b < len (hostnameBLabels ); b ++ {
491+ if hostnameALabels [a ] == hostnameBLabels [b ] {
492+ matchFound = true
475493 break
476494 }
477495 }
478- if j == len (labelsB ) {
479- return false
480- }
481496 }
482497
483- if labelsA [i ] == "*" {
498+ // if no match was found, the hostnames don't match
499+ if wildcard && ! matchFound {
500+ return false
501+ }
502+
503+ // check if at least on of the current parts are a wildcard; if so, continue
504+ if hostnameALabels [a ] == "*" {
484505 wildcard = true
485- j --
486506 continue
487507 }
488-
508+ // reset the wildcard variables
489509 wildcard = false
490510
491- if labelsA [i ] != labelsB [j ] {
511+ // if the current a part is different from the b part, the hostnames are incompatible
512+ if hostnameALabels [a ] != hostnameBLabels [b ] {
492513 return false
493514 }
494515 }
495-
496- return len (labelsA )- i == len (labelsB )- j
516+ return len (hostnameBLabels )- b == len (hostnameALabels )- a
497517}
498518
499519func routeMatchesListenerAllowedRoutes (
@@ -895,3 +915,115 @@ func IsInvalidKindError(err error) bool {
895915 _ , ok := err .(* InvalidKindError )
896916 return ok
897917}
918+
919+ // 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.
920+ // If the HTTPRoute hostnames do not intersect with the listener hostnames of the gateways, it returns an ErrNoMatchingListenerHostname error.
921+ func filterHostnames (gateways []RouteParentRefContext , httpRoute * gatewayv1.HTTPRoute ) (* gatewayv1.HTTPRoute , error ) {
922+ filteredHostnames := make ([]gatewayv1.Hostname , 0 )
923+
924+ // If the HTTPRoute does not specify hostnames, we use the union of the listener hostnames of all supported gateways
925+ // If any supported listener does not specify a hostname, the HTTPRoute hostnames remain empty to match any hostname
926+ if len (httpRoute .Spec .Hostnames ) == 0 {
927+ hostnames , matchAnyHost := getUnionOfGatewayHostnames (gateways )
928+ if matchAnyHost {
929+ return httpRoute , nil
930+ }
931+ filteredHostnames = hostnames
932+ } else {
933+ // If the HTTPRoute specifies hostnames, we need to find the intersection with the gateway listener hostnames
934+ for _ , hostname := range httpRoute .Spec .Hostnames {
935+ if hostnameMatching := getMinimumHostnameIntersection (gateways , hostname ); hostnameMatching != "" {
936+ filteredHostnames = append (filteredHostnames , hostnameMatching )
937+ }
938+ }
939+ if len (filteredHostnames ) == 0 {
940+ return httpRoute , ErrNoMatchingListenerHostname
941+ }
942+ }
943+
944+ log .Debugw ("filtered hostnames" , zap .Any ("httpRouteHostnames" , httpRoute .Spec .Hostnames ), zap .Any ("hostnames" , filteredHostnames ))
945+ httpRoute .Spec .Hostnames = filteredHostnames
946+ return httpRoute , nil
947+ }
948+
949+ // getUnionOfGatewayHostnames returns the union of the hostnames specified in all supported gateways
950+ // The second return value indicates whether any listener can match any hostname
951+ func getUnionOfGatewayHostnames (gateways []RouteParentRefContext ) ([]gatewayv1.Hostname , bool ) {
952+ hostnames := make ([]gatewayv1.Hostname , 0 )
953+
954+ for _ , gateway := range gateways {
955+ if gateway .ListenerName != "" {
956+ // If a listener name is specified, only check that listener
957+ for _ , listener := range gateway .Gateway .Spec .Listeners {
958+ if string (listener .Name ) == gateway .ListenerName {
959+ // If a listener does not specify a hostname, it can match any hostname
960+ if listener .Hostname == nil {
961+ return nil , true
962+ }
963+ hostnames = append (hostnames , * listener .Hostname )
964+ break
965+ }
966+ }
967+ } else {
968+ // Otherwise, check all listeners
969+ for _ , listener := range gateway .Gateway .Spec .Listeners {
970+ // Only consider listeners that can effectively configure hostnames (HTTP, HTTPS, or TLS)
971+ if isListenerHostnameEffective (listener ) {
972+ if listener .Hostname == nil {
973+ return nil , true
974+ }
975+ hostnames = append (hostnames , * listener .Hostname )
976+ }
977+ }
978+ }
979+ }
980+
981+ return hostnames , false
982+ }
983+
984+ // getMinimumHostnameIntersection returns the smallest intersection hostname
985+ // - If the listener hostname is empty, return the HTTPRoute hostname
986+ // - If the listener hostname is a wildcard of the HTTPRoute hostname, return the HTTPRoute hostname
987+ // - If the HTTPRoute hostname is a wildcard of the listener hostname, return the listener hostname
988+ // - If the HTTPRoute hostname and listener hostname are the same, return it
989+ // - If none of the above, return an empty string
990+ func getMinimumHostnameIntersection (gateways []RouteParentRefContext , hostname gatewayv1.Hostname ) gatewayv1.Hostname {
991+ for _ , gateway := range gateways {
992+ for _ , listener := range gateway .Gateway .Spec .Listeners {
993+ // If a listener name is specified, only check that listener
994+ // If the listener name is not specified, check all listeners
995+ if gateway .ListenerName == "" || gateway .ListenerName == string (listener .Name ) {
996+ if listener .Hostname == nil || * listener .Hostname == "" {
997+ return hostname
998+ }
999+ if HostnamesMatch (string (* listener .Hostname ), string (hostname )) {
1000+ return hostname
1001+ }
1002+ if HostnamesMatch (string (hostname ), string (* listener .Hostname )) {
1003+ return * listener .Hostname
1004+ }
1005+ }
1006+ }
1007+ }
1008+
1009+ return ""
1010+ }
1011+
1012+ // isListenerHostnameEffective checks if a listener can specify a hostname to match the hostname in the request
1013+ // Basically, check if the listener uses HTTP, HTTPS, or TLS protocol
1014+ func isListenerHostnameEffective (listener gatewayv1.Listener ) bool {
1015+ return listener .Protocol == gatewayv1 .HTTPProtocolType ||
1016+ listener .Protocol == gatewayv1 .HTTPSProtocolType ||
1017+ listener .Protocol == gatewayv1 .TLSProtocolType
1018+ }
1019+
1020+ func isRouteAccepted (gateways []RouteParentRefContext ) bool {
1021+ for _ , gateway := range gateways {
1022+ for _ , condition := range gateway .Conditions {
1023+ if condition .Type == string (gatewayv1 .RouteConditionAccepted ) && condition .Status == metav1 .ConditionTrue {
1024+ return true
1025+ }
1026+ }
1027+ }
1028+ return false
1029+ }
0 commit comments