Skip to content

Commit

Permalink
Merge pull request #4124 from abursavich/gateway-api-v1.0
Browse files Browse the repository at this point in the history
gateway-api: fix wildcard matching
  • Loading branch information
k8s-ci-robot committed Dec 23, 2023
2 parents ce323c0 + a50a4f9 commit 3794dfc
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 120 deletions.
99 changes: 75 additions & 24 deletions source/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package source
import (
"context"
"fmt"
"net/netip"
"sort"
"strings"
"text/template"
Expand Down Expand Up @@ -486,39 +487,89 @@ func gwProtocolMatches(a, b v1.ProtocolType) bool {
}

// gwMatchingHost returns the most-specific overlapping host and a bool indicating if one was found.
// For example, if one host is "*.foo.com" and the other is "bar.foo.com", "bar.foo.com" will be returned.
// An empty string matches anything.
func gwMatchingHost(gwHost, rtHost string) (string, bool) {
gwHost = toLowerCaseASCII(gwHost) // TODO: trim "." suffix?
rtHost = toLowerCaseASCII(rtHost) // TODO: trim "." suffix?
// Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match.
// That means that "*.example.com" would match both "test.example.com" and "foo.test.example.com",
// but not "example.com". An empty string matches anything.
func gwMatchingHost(a, b string) (string, bool) {
var ok bool
if a, ok = gwHost(a); !ok {
return "", false
}
if b, ok = gwHost(b); !ok {
return "", false
}

if gwHost == "" {
return rtHost, true
if a == "" {
return b, true
}
if rtHost == "" {
return gwHost, true
if b == "" || a == b {
return a, true
}
if na, nb := len(a), len(b); nb < na || (na == nb && strings.HasPrefix(b, "*.")) {
a, b = b, a
}
if strings.HasPrefix(a, "*.") && strings.HasSuffix(b, a[1:]) {
return b, true
}
return "", false
}

gwParts := strings.Split(gwHost, ".")
rtParts := strings.Split(rtHost, ".")
if len(gwParts) != len(rtParts) {
// gwHost returns the canonical host and a value indicating if it's valid.
func gwHost(host string) (string, bool) {
if host == "" {
return "", true
}
if isIPAddr(host) || !isDNS1123Domain(strings.TrimPrefix(host, "*.")) {
return "", false
}
return toLowerCaseASCII(host), true
}

// isIPAddr returns whether s in an IP address.
func isIPAddr(s string) bool {
_, err := netip.ParseAddr(s)
return err == nil
}

host := rtHost
for i, gwPart := range gwParts {
switch rtPart := rtParts[i]; {
case rtPart == gwPart:
// continue
case i == 0 && gwPart == "*":
// continue
case i == 0 && rtPart == "*":
host = gwHost // gwHost is more specific
default:
return "", false
// isDNS1123Domain returns whether s is a valid domain name according to RFC 1123.
func isDNS1123Domain(s string) bool {
if n := len(s); n == 0 || n > 255 {
return false
}
for lbl, rest := "", s; rest != ""; {
if lbl, rest, _ = strings.Cut(rest, "."); !isDNS1123Label(lbl) {
return false
}
}
return host, true
return true
}

// isDNS1123Label returns whether s is a valid domain label according to RFC 1123.
func isDNS1123Label(s string) bool {
n := len(s)
if n == 0 || n > 63 {
return false
}
if !isAlphaNum(s[0]) || !isAlphaNum(s[n-1]) {
return false
}
for i, k := 1, n-1; i < k; i++ {
if b := s[i]; b != '-' && !isAlphaNum(b) {
return false
}
}
return true
}

func isAlphaNum(b byte) bool {
switch {
case 'a' <= b && b <= 'z',
'A' <= b && b <= 'Z',
'0' <= b && b <= '9':
return true
default:
return false
}
}

func strVal(ptr *string, def string) string {
Expand Down
7 changes: 3 additions & 4 deletions source/gateway_grpcroute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (
"sigs.k8s.io/external-dns/endpoint"
v1 "sigs.k8s.io/gateway-api/apis/v1"
"sigs.k8s.io/gateway-api/apis/v1alpha2"
"sigs.k8s.io/gateway-api/apis/v1beta1"
gatewayfake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake"
)

Expand Down Expand Up @@ -55,7 +54,7 @@ func TestGatewayGRPCRouteSourceEndpoints(t *testing.T) {
Name: "internal",
Namespace: "default",
},
Spec: v1beta1.GatewaySpec{
Spec: v1.GatewaySpec{
Listeners: []v1.Listener{{
Protocol: v1.HTTPSProtocolType,
}},
Expand All @@ -74,10 +73,10 @@ func TestGatewayGRPCRouteSourceEndpoints(t *testing.T) {
},
},
Spec: v1alpha2.GRPCRouteSpec{
Hostnames: []v1alpha2.Hostname{"api-hostnames.foobar.internal"},
Hostnames: []v1.Hostname{"api-hostnames.foobar.internal"},
},
Status: v1alpha2.GRPCRouteStatus{
RouteStatus: v1a2RouteStatus(v1a2ParentRef("default", "internal")),
RouteStatus: gwRouteStatus(gwParentRef("default", "internal")),
},
}
_, err = gwClient.GatewayV1alpha2().GRPCRoutes(rt.Namespace).Create(ctx, rt, metav1.CreateOptions{})
Expand Down
Loading

0 comments on commit 3794dfc

Please sign in to comment.