@@ -14,6 +14,7 @@ package controller
14
14
15
15
import (
16
16
"context"
17
+ "errors"
17
18
"fmt"
18
19
"path"
19
20
"reflect"
@@ -48,6 +49,10 @@ const (
48
49
49
50
const defaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class"
50
51
52
+ var (
53
+ ErrNoMatchingListenerHostname = errors .New ("no matching hostnames in listener" )
54
+ )
55
+
51
56
// IsDefaultIngressClass returns whether an IngressClass is the default IngressClass.
52
57
func IsDefaultIngressClass (obj client.Object ) bool {
53
58
if ingressClass , ok := obj .(* networkingv1.IngressClass ); ok {
@@ -229,10 +234,15 @@ func SetRouteConditionAccepted(routeParentStatus *gatewayv1.RouteParentStatus, g
229
234
conditionStatus = metav1 .ConditionFalse
230
235
}
231
236
237
+ reason := gatewayv1 .RouteReasonAccepted
238
+ if message == ErrNoMatchingListenerHostname .Error () {
239
+ reason = gatewayv1 .RouteReasonNoMatchingListenerHostname
240
+ }
241
+
232
242
condition := metav1.Condition {
233
243
Type : string (gatewayv1 .RouteConditionAccepted ),
234
244
Status : conditionStatus ,
235
- Reason : string (gatewayv1 . RouteReasonAccepted ),
245
+ Reason : string (reason ),
236
246
ObservedGeneration : generation ,
237
247
Message : message ,
238
248
LastTransitionTime : metav1 .Now (),
@@ -458,39 +468,51 @@ func HostnamesIntersect(a, b string) bool {
458
468
return HostnamesMatch (a , b ) || HostnamesMatch (b , a )
459
469
}
460
470
471
+ // HostnamesMatch checks that the hostnameB matches the hostnameA. HostnameA is treated as mask
472
+ // to be checked against the hostnameB.
461
473
func 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 , "." )
464
478
465
- var i , j int
479
+ var a , b int
466
480
var wildcard bool
467
481
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
469
488
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
472
492
break
473
493
}
474
494
}
475
- if j == len (labelsB ) {
476
- return false
477
- }
478
495
}
479
496
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 ] == "*" {
481
504
wildcard = true
482
- j --
483
505
continue
484
506
}
485
-
507
+ // reset the wildcard variables
486
508
wildcard = false
487
509
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 ] {
489
512
return false
490
513
}
491
514
}
492
-
493
- return len (labelsA )- i == len (labelsB )- j
515
+ return len (hostnameBLabels )- b == len (hostnameALabels )- a
494
516
}
495
517
496
518
func routeMatchesListenerAllowedRoutes (
@@ -892,3 +914,115 @@ func IsInvalidKindError(err error) bool {
892
914
_ , ok := err .(* InvalidKindError )
893
915
return ok
894
916
}
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