mirror of
				https://github.com/kubernetes-sigs/external-dns.git
				synced 2025-11-04 12:41:00 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			628 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			628 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2021 The Kubernetes Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package source
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"net/netip"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"text/template"
 | 
						|
 | 
						|
	log "github.com/sirupsen/logrus"
 | 
						|
	corev1 "k8s.io/api/core/v1"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/labels"
 | 
						|
	"k8s.io/apimachinery/pkg/types"
 | 
						|
	"k8s.io/apimachinery/pkg/util/wait"
 | 
						|
	kubeinformers "k8s.io/client-go/informers"
 | 
						|
	coreinformers "k8s.io/client-go/informers/core/v1"
 | 
						|
	cache "k8s.io/client-go/tools/cache"
 | 
						|
	v1 "sigs.k8s.io/gateway-api/apis/v1"
 | 
						|
	v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
 | 
						|
	gateway "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
 | 
						|
	informers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions"
 | 
						|
	informers_v1beta1 "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions/apis/v1beta1"
 | 
						|
 | 
						|
	"sigs.k8s.io/external-dns/endpoint"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	gatewayGroup = "gateway.networking.k8s.io"
 | 
						|
	gatewayKind  = "Gateway"
 | 
						|
	// gatewayAPIDualstackAnnotationKey is the annotation used for determining if a Gateway Route is dualstack
 | 
						|
	gatewayAPIDualstackAnnotationKey = "external-dns.alpha.kubernetes.io/dualstack"
 | 
						|
	// gatewayAPIDualstackAnnotationValue is the value of the Gateway Route dualstack annotation that indicates it is dualstack
 | 
						|
	gatewayAPIDualstackAnnotationValue = "true"
 | 
						|
)
 | 
						|
 | 
						|
type gatewayRoute interface {
 | 
						|
	// Object returns the underlying route object to be used by templates.
 | 
						|
	Object() kubeObject
 | 
						|
	// Metadata returns the route's metadata.
 | 
						|
	Metadata() *metav1.ObjectMeta
 | 
						|
	// Hostnames returns the route's specified hostnames.
 | 
						|
	Hostnames() []v1.Hostname
 | 
						|
	// Protocol returns the route's protocol type.
 | 
						|
	Protocol() v1.ProtocolType
 | 
						|
	// RouteStatus returns the route's common status.
 | 
						|
	RouteStatus() v1.RouteStatus
 | 
						|
}
 | 
						|
 | 
						|
type newGatewayRouteInformerFunc func(informers.SharedInformerFactory) gatewayRouteInformer
 | 
						|
 | 
						|
type gatewayRouteInformer interface {
 | 
						|
	List(namespace string, selector labels.Selector) ([]gatewayRoute, error)
 | 
						|
	Informer() cache.SharedIndexInformer
 | 
						|
}
 | 
						|
 | 
						|
func newGatewayInformerFactory(client gateway.Interface, namespace string, labelSelector labels.Selector) informers.SharedInformerFactory {
 | 
						|
	var opts []informers.SharedInformerOption
 | 
						|
	if namespace != "" {
 | 
						|
		opts = append(opts, informers.WithNamespace(namespace))
 | 
						|
	}
 | 
						|
	if labelSelector != nil && !labelSelector.Empty() {
 | 
						|
		lbls := labelSelector.String()
 | 
						|
		opts = append(opts, informers.WithTweakListOptions(func(o *metav1.ListOptions) {
 | 
						|
			o.LabelSelector = lbls
 | 
						|
		}))
 | 
						|
	}
 | 
						|
	return informers.NewSharedInformerFactoryWithOptions(client, 0, opts...)
 | 
						|
}
 | 
						|
 | 
						|
type gatewayRouteSource struct {
 | 
						|
	gwNamespace string
 | 
						|
	gwLabels    labels.Selector
 | 
						|
	gwInformer  informers_v1beta1.GatewayInformer
 | 
						|
 | 
						|
	rtKind        string
 | 
						|
	rtNamespace   string
 | 
						|
	rtLabels      labels.Selector
 | 
						|
	rtAnnotations labels.Selector
 | 
						|
	rtInformer    gatewayRouteInformer
 | 
						|
 | 
						|
	nsInformer coreinformers.NamespaceInformer
 | 
						|
 | 
						|
	fqdnTemplate             *template.Template
 | 
						|
	combineFQDNAnnotation    bool
 | 
						|
	ignoreHostnameAnnotation bool
 | 
						|
}
 | 
						|
 | 
						|
func newGatewayRouteSource(clients ClientGenerator, config *Config, kind string, newInformerFn newGatewayRouteInformerFunc) (Source, error) {
 | 
						|
	ctx := context.TODO()
 | 
						|
 | 
						|
	gwLabels, err := getLabelSelector(config.GatewayLabelFilter)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	rtLabels := config.LabelFilter
 | 
						|
	if rtLabels == nil {
 | 
						|
		rtLabels = labels.Everything()
 | 
						|
	}
 | 
						|
	rtAnnotations, err := getLabelSelector(config.AnnotationFilter)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	tmpl, err := parseTemplate(config.FQDNTemplate)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	client, err := clients.GatewayClient()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	informerFactory := newGatewayInformerFactory(client, config.GatewayNamespace, gwLabels)
 | 
						|
	gwInformer := informerFactory.Gateway().V1beta1().Gateways() // TODO: Gateway informer should be shared across gateway sources.
 | 
						|
	gwInformer.Informer()                                        // Register with factory before starting.
 | 
						|
 | 
						|
	rtInformerFactory := informerFactory
 | 
						|
	if config.Namespace != config.GatewayNamespace || !selectorsEqual(rtLabels, gwLabels) {
 | 
						|
		rtInformerFactory = newGatewayInformerFactory(client, config.Namespace, rtLabels)
 | 
						|
	}
 | 
						|
	rtInformer := newInformerFn(rtInformerFactory)
 | 
						|
	rtInformer.Informer() // Register with factory before starting.
 | 
						|
 | 
						|
	kubeClient, err := clients.KubeClient()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, 0)
 | 
						|
	nsInformer := kubeInformerFactory.Core().V1().Namespaces() // TODO: Namespace informer should be shared across gateway sources.
 | 
						|
	nsInformer.Informer()                                      // Register with factory before starting.
 | 
						|
 | 
						|
	informerFactory.Start(wait.NeverStop)
 | 
						|
	kubeInformerFactory.Start(wait.NeverStop)
 | 
						|
	if rtInformerFactory != informerFactory {
 | 
						|
		rtInformerFactory.Start(wait.NeverStop)
 | 
						|
 | 
						|
		if err := waitForCacheSync(ctx, rtInformerFactory); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if err := waitForCacheSync(ctx, informerFactory); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if err := waitForCacheSync(ctx, kubeInformerFactory); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	src := &gatewayRouteSource{
 | 
						|
		gwNamespace: config.GatewayNamespace,
 | 
						|
		gwLabels:    gwLabels,
 | 
						|
		gwInformer:  gwInformer,
 | 
						|
 | 
						|
		rtKind:        kind,
 | 
						|
		rtNamespace:   config.Namespace,
 | 
						|
		rtLabels:      rtLabels,
 | 
						|
		rtAnnotations: rtAnnotations,
 | 
						|
		rtInformer:    rtInformer,
 | 
						|
 | 
						|
		nsInformer: nsInformer,
 | 
						|
 | 
						|
		fqdnTemplate:             tmpl,
 | 
						|
		combineFQDNAnnotation:    config.CombineFQDNAndAnnotation,
 | 
						|
		ignoreHostnameAnnotation: config.IgnoreHostnameAnnotation,
 | 
						|
	}
 | 
						|
	return src, nil
 | 
						|
}
 | 
						|
 | 
						|
func (src *gatewayRouteSource) AddEventHandler(ctx context.Context, handler func()) {
 | 
						|
	log.Debugf("Adding event handlers for %s", src.rtKind)
 | 
						|
	eventHandler := eventHandlerFunc(handler)
 | 
						|
	src.gwInformer.Informer().AddEventHandler(eventHandler)
 | 
						|
	src.rtInformer.Informer().AddEventHandler(eventHandler)
 | 
						|
	src.nsInformer.Informer().AddEventHandler(eventHandler)
 | 
						|
}
 | 
						|
 | 
						|
func (src *gatewayRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
 | 
						|
	var endpoints []*endpoint.Endpoint
 | 
						|
	routes, err := src.rtInformer.List(src.rtNamespace, src.rtLabels)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	gateways, err := src.gwInformer.Lister().Gateways(src.gwNamespace).List(src.gwLabels)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	namespaces, err := src.nsInformer.Lister().List(labels.Everything())
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	kind := strings.ToLower(src.rtKind)
 | 
						|
	resolver := newGatewayRouteResolver(src, gateways, namespaces)
 | 
						|
	for _, rt := range routes {
 | 
						|
		// Filter by annotations.
 | 
						|
		meta := rt.Metadata()
 | 
						|
		annots := meta.Annotations
 | 
						|
		if !src.rtAnnotations.Matches(labels.Set(annots)) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Check controller annotation to see if we are responsible.
 | 
						|
		if v, ok := annots[controllerAnnotationKey]; ok && v != controllerAnnotationValue {
 | 
						|
			log.Debugf("Skipping %s %s/%s because controller value does not match, found: %s, required: %s",
 | 
						|
				src.rtKind, meta.Namespace, meta.Name, v, controllerAnnotationValue)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Get Route hostnames and their targets.
 | 
						|
		hostTargets, err := resolver.resolve(rt)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		if len(hostTargets) == 0 {
 | 
						|
			log.Debugf("No endpoints could be generated from %s %s/%s", src.rtKind, meta.Namespace, meta.Name)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Create endpoints from hostnames and targets.
 | 
						|
		resource := fmt.Sprintf("%s/%s/%s", kind, meta.Namespace, meta.Name)
 | 
						|
		providerSpecific, setIdentifier := getProviderSpecificAnnotations(annots)
 | 
						|
		ttl := getTTLFromAnnotations(annots, resource)
 | 
						|
		for host, targets := range hostTargets {
 | 
						|
			endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific, setIdentifier, resource)...)
 | 
						|
		}
 | 
						|
		setDualstackLabel(rt, endpoints)
 | 
						|
		log.Debugf("Endpoints generated from %s %s/%s: %v", src.rtKind, meta.Namespace, meta.Name, endpoints)
 | 
						|
	}
 | 
						|
	return endpoints, nil
 | 
						|
}
 | 
						|
 | 
						|
func namespacedName(namespace, name string) types.NamespacedName {
 | 
						|
	return types.NamespacedName{Namespace: namespace, Name: name}
 | 
						|
}
 | 
						|
 | 
						|
type gatewayRouteResolver struct {
 | 
						|
	src *gatewayRouteSource
 | 
						|
	gws map[types.NamespacedName]gatewayListeners
 | 
						|
	nss map[string]*corev1.Namespace
 | 
						|
}
 | 
						|
 | 
						|
type gatewayListeners struct {
 | 
						|
	gateway   *v1beta1.Gateway
 | 
						|
	listeners map[v1.SectionName][]v1.Listener
 | 
						|
}
 | 
						|
 | 
						|
func newGatewayRouteResolver(src *gatewayRouteSource, gateways []*v1beta1.Gateway, namespaces []*corev1.Namespace) *gatewayRouteResolver {
 | 
						|
	// Create Gateway Listener lookup table.
 | 
						|
	gws := make(map[types.NamespacedName]gatewayListeners, len(gateways))
 | 
						|
	for _, gw := range gateways {
 | 
						|
		lss := make(map[v1.SectionName][]v1.Listener, len(gw.Spec.Listeners)+1)
 | 
						|
		for i, lis := range gw.Spec.Listeners {
 | 
						|
			lss[lis.Name] = gw.Spec.Listeners[i : i+1]
 | 
						|
		}
 | 
						|
		lss[""] = gw.Spec.Listeners
 | 
						|
		gws[namespacedName(gw.Namespace, gw.Name)] = gatewayListeners{
 | 
						|
			gateway:   gw,
 | 
						|
			listeners: lss,
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// Create Namespace lookup table.
 | 
						|
	nss := make(map[string]*corev1.Namespace, len(namespaces))
 | 
						|
	for _, ns := range namespaces {
 | 
						|
		nss[ns.Name] = ns
 | 
						|
	}
 | 
						|
	return &gatewayRouteResolver{
 | 
						|
		src: src,
 | 
						|
		gws: gws,
 | 
						|
		nss: nss,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Targets, error) {
 | 
						|
	rtHosts, err := c.hosts(rt)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	hostTargets := make(map[string]endpoint.Targets)
 | 
						|
 | 
						|
	meta := rt.Metadata()
 | 
						|
	for _, rps := range rt.RouteStatus().Parents {
 | 
						|
		// Confirm the Parent is the standard Gateway kind.
 | 
						|
		ref := rps.ParentRef
 | 
						|
		group := strVal((*string)(ref.Group), gatewayGroup)
 | 
						|
		kind := strVal((*string)(ref.Kind), gatewayKind)
 | 
						|
		if group != gatewayGroup || kind != gatewayKind {
 | 
						|
			log.Debugf("Unsupported parent %s/%s for %s %s/%s", group, kind, c.src.rtKind, meta.Namespace, meta.Name)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		// Lookup the Gateway and its Listeners.
 | 
						|
		namespace := strVal((*string)(ref.Namespace), meta.Namespace)
 | 
						|
		gw, ok := c.gws[namespacedName(namespace, string(ref.Name))]
 | 
						|
		if !ok {
 | 
						|
			log.Debugf("Gateway %s/%s not found for %s %s/%s", namespace, ref.Name, c.src.rtKind, meta.Namespace, meta.Name)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		// Confirm the Gateway has accepted the Route.
 | 
						|
		if !gwRouteIsAccepted(rps.Conditions) {
 | 
						|
			log.Debugf("Gateway %s/%s has not accepted %s %s/%s", namespace, ref.Name, c.src.rtKind, meta.Namespace, meta.Name)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		// Match the Route to all possible Listeners.
 | 
						|
		match := false
 | 
						|
		section := sectionVal(ref.SectionName, "")
 | 
						|
		listeners := gw.listeners[section]
 | 
						|
		for i := range listeners {
 | 
						|
			lis := &listeners[i]
 | 
						|
			// Confirm that the Listener and Route protocols match.
 | 
						|
			if !gwProtocolMatches(rt.Protocol(), lis.Protocol) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			// Confirm that the Listener and Route ports match, if specified.
 | 
						|
			// EXPERIMENTAL: https://gateway-api.sigs.k8s.io/geps/gep-957/
 | 
						|
			if ref.Port != nil && *ref.Port != lis.Port {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			// Confirm that the Listener allows the Route (based on namespace and kind).
 | 
						|
			if !c.routeIsAllowed(gw.gateway, lis, rt) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			// Find all overlapping hostnames between the Route and Listener.
 | 
						|
			// For {TCP,UDP}Routes, all annotation-generated hostnames should match since the Listener doesn't specify a hostname.
 | 
						|
			// For {HTTP,TLS}Routes, hostnames (including any annotation-generated) will be required to match any Listeners specified hostname.
 | 
						|
			gwHost := ""
 | 
						|
			if lis.Hostname != nil {
 | 
						|
				gwHost = string(*lis.Hostname)
 | 
						|
			}
 | 
						|
			for _, rtHost := range rtHosts {
 | 
						|
				if gwHost == "" && rtHost == "" {
 | 
						|
					// For {HTTP,TLS}Routes, this means the Route and the Listener both allow _any_ hostnames.
 | 
						|
					// For {TCP,UDP}Routes, this should always happen since neither specifies hostnames.
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				host, ok := gwMatchingHost(gwHost, rtHost)
 | 
						|
				if !ok {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				override := getTargetsFromTargetAnnotation(gw.gateway.Annotations)
 | 
						|
				hostTargets[host] = append(hostTargets[host], override...)
 | 
						|
				if len(override) == 0 {
 | 
						|
					for _, addr := range gw.gateway.Status.Addresses {
 | 
						|
						hostTargets[host] = append(hostTargets[host], addr.Value)
 | 
						|
					}
 | 
						|
				}
 | 
						|
				match = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !match {
 | 
						|
			log.Debugf("Gateway %s/%s section %q does not match %s %s/%s hostnames %q", namespace, ref.Name, section, c.src.rtKind, meta.Namespace, meta.Name, rtHosts)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// If a Gateway has multiple matching Listeners for the same host, then we'll
 | 
						|
	// add its IPs to the target list multiple times and should dedupe them.
 | 
						|
	for host, targets := range hostTargets {
 | 
						|
		hostTargets[host] = uniqueTargets(targets)
 | 
						|
	}
 | 
						|
	return hostTargets, nil
 | 
						|
}
 | 
						|
 | 
						|
func (c *gatewayRouteResolver) hosts(rt gatewayRoute) ([]string, error) {
 | 
						|
	var hostnames []string
 | 
						|
	for _, name := range rt.Hostnames() {
 | 
						|
		hostnames = append(hostnames, string(name))
 | 
						|
	}
 | 
						|
	// TODO: The ignore-hostname-annotation flag help says "valid only when using fqdn-template"
 | 
						|
	// but other sources don't check if fqdn-template is set. Which should it be?
 | 
						|
	if !c.src.ignoreHostnameAnnotation {
 | 
						|
		hostnames = append(hostnames, getHostnamesFromAnnotations(rt.Metadata().Annotations)...)
 | 
						|
	}
 | 
						|
	// TODO: The combine-fqdn-annotation flag is similarly vague.
 | 
						|
	if c.src.fqdnTemplate != nil && (len(hostnames) == 0 || c.src.combineFQDNAnnotation) {
 | 
						|
		hosts, err := execTemplate(c.src.fqdnTemplate, rt.Object())
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		hostnames = append(hostnames, hosts...)
 | 
						|
	}
 | 
						|
	// This means that the route doesn't specify a hostname and should use any provided by
 | 
						|
	// attached Gateway Listeners. This is only useful for {HTTP,TLS}Routes, but it doesn't
 | 
						|
	// break {TCP,UDP}Routes.
 | 
						|
	if len(rt.Hostnames()) == 0 {
 | 
						|
		hostnames = append(hostnames, "")
 | 
						|
	}
 | 
						|
	return hostnames, nil
 | 
						|
}
 | 
						|
 | 
						|
func (c *gatewayRouteResolver) routeIsAllowed(gw *v1beta1.Gateway, lis *v1.Listener, rt gatewayRoute) bool {
 | 
						|
	meta := rt.Metadata()
 | 
						|
	allow := lis.AllowedRoutes
 | 
						|
 | 
						|
	// Check the route's namespace.
 | 
						|
	from := v1.NamespacesFromSame
 | 
						|
	if allow != nil && allow.Namespaces != nil && allow.Namespaces.From != nil {
 | 
						|
		from = *allow.Namespaces.From
 | 
						|
	}
 | 
						|
	switch from {
 | 
						|
	case v1.NamespacesFromAll:
 | 
						|
		// OK
 | 
						|
	case v1.NamespacesFromSame:
 | 
						|
		if gw.Namespace != meta.Namespace {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	case v1.NamespacesFromSelector:
 | 
						|
		selector, err := metav1.LabelSelectorAsSelector(allow.Namespaces.Selector)
 | 
						|
		if err != nil {
 | 
						|
			log.Debugf("Gateway %s/%s section %q has invalid namespace selector: %v", gw.Namespace, gw.Name, lis.Name, err)
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		// Get namespace.
 | 
						|
		ns, ok := c.nss[meta.Namespace]
 | 
						|
		if !ok {
 | 
						|
			log.Errorf("Namespace not found for %s %s/%s", c.src.rtKind, meta.Namespace, meta.Name)
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if !selector.Matches(labels.Set(ns.Labels)) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		log.Debugf("Gateway %s/%s section %q has unknown namespace from %q", gw.Namespace, gw.Name, lis.Name, from)
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	// Check the route's kind, if any are specified by the listener.
 | 
						|
	// TODO: Do we need to consider SupportedKinds in the ListenerStatus instead of the Spec?
 | 
						|
	// We only support core kinds and already check the protocol... Does this matter at all?
 | 
						|
	if allow == nil || len(allow.Kinds) == 0 {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	gvk := rt.Object().GetObjectKind().GroupVersionKind()
 | 
						|
	for _, gk := range allow.Kinds {
 | 
						|
		group := strVal((*string)(gk.Group), gatewayGroup)
 | 
						|
		if gvk.Group == group && gvk.Kind == string(gk.Kind) {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func gwRouteIsAccepted(conds []metav1.Condition) bool {
 | 
						|
	for _, c := range conds {
 | 
						|
		if v1.RouteConditionType(c.Type) == v1.RouteConditionAccepted {
 | 
						|
			return c.Status == metav1.ConditionTrue
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func uniqueTargets(targets endpoint.Targets) endpoint.Targets {
 | 
						|
	if len(targets) < 2 {
 | 
						|
		return targets
 | 
						|
	}
 | 
						|
	sort.Strings([]string(targets))
 | 
						|
	prev := targets[0]
 | 
						|
	n := 1
 | 
						|
	for _, v := range targets[1:] {
 | 
						|
		if v == prev {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		prev = v
 | 
						|
		targets[n] = v
 | 
						|
		n++
 | 
						|
	}
 | 
						|
	return targets[:n]
 | 
						|
}
 | 
						|
 | 
						|
// gwProtocolMatches returns whether a and b are the same protocol,
 | 
						|
// where HTTP and HTTPS are considered the same.
 | 
						|
// and TLS and TCP are considered the same.
 | 
						|
func gwProtocolMatches(a, b v1.ProtocolType) bool {
 | 
						|
	if a == v1.HTTPSProtocolType {
 | 
						|
		a = v1.HTTPProtocolType
 | 
						|
	}
 | 
						|
	if b == v1.HTTPSProtocolType {
 | 
						|
		b = v1.HTTPProtocolType
 | 
						|
	}
 | 
						|
	// if Listener is TLS and Route is TCP set Listener type to TCP as to pass true and return valid match
 | 
						|
	if a == v1.TCPProtocolType && b == v1.TLSProtocolType {
 | 
						|
		b = v1.TCPProtocolType
 | 
						|
	}
 | 
						|
	return a == b
 | 
						|
}
 | 
						|
 | 
						|
// gwMatchingHost returns the most-specific overlapping host and a bool indicating if one was found.
 | 
						|
// 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 a == "" {
 | 
						|
		return b, 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
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
}
 | 
						|
 | 
						|
// 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 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 {
 | 
						|
	if ptr == nil || *ptr == "" {
 | 
						|
		return def
 | 
						|
	}
 | 
						|
	return *ptr
 | 
						|
}
 | 
						|
 | 
						|
func sectionVal(ptr *v1.SectionName, def v1.SectionName) v1.SectionName {
 | 
						|
	if ptr == nil || *ptr == "" {
 | 
						|
		return def
 | 
						|
	}
 | 
						|
	return *ptr
 | 
						|
}
 | 
						|
 | 
						|
func selectorsEqual(a, b labels.Selector) bool {
 | 
						|
	if a == nil || b == nil {
 | 
						|
		return a == b
 | 
						|
	}
 | 
						|
	aReq, aOK := a.DeepCopySelector().Requirements()
 | 
						|
	bReq, bOK := b.DeepCopySelector().Requirements()
 | 
						|
	if aOK != bOK || len(aReq) != len(bReq) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	sort.Stable(labels.ByKey(aReq))
 | 
						|
	sort.Stable(labels.ByKey(bReq))
 | 
						|
	for i, r := range aReq {
 | 
						|
		if !r.Equal(bReq[i]) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func setDualstackLabel(rt gatewayRoute, endpoints []*endpoint.Endpoint) {
 | 
						|
	val, ok := rt.Metadata().Annotations[gatewayAPIDualstackAnnotationKey]
 | 
						|
	if ok && val == gatewayAPIDualstackAnnotationValue {
 | 
						|
		log.Debugf("Adding dualstack label to GatewayRoute %s/%s.", rt.Metadata().Namespace, rt.Metadata().Name)
 | 
						|
		for _, ep := range endpoints {
 | 
						|
			ep.Labels[endpoint.DualstackLabelKey] = "true"
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |