istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/route/route.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package route
    16  
    17  import (
    18  	"fmt"
    19  	"regexp"
    20  	"sort"
    21  	"strconv"
    22  	"strings"
    23  
    24  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    25  	route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    26  	xdsfault "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/common/fault/v3"
    27  	cors "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/cors/v3"
    28  	xdshttpfault "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3"
    29  	statefulsession "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/stateful_session/v3"
    30  	matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
    31  	xdstype "github.com/envoyproxy/go-control-plane/envoy/type/v3"
    32  	"google.golang.org/protobuf/types/known/anypb"
    33  	"google.golang.org/protobuf/types/known/durationpb"
    34  	"google.golang.org/protobuf/types/known/wrapperspb"
    35  	"k8s.io/apimachinery/pkg/types"
    36  
    37  	meshconfig "istio.io/api/mesh/v1alpha1"
    38  	networking "istio.io/api/networking/v1alpha3"
    39  	"istio.io/istio/pilot/pkg/features"
    40  	"istio.io/istio/pilot/pkg/model"
    41  	"istio.io/istio/pilot/pkg/networking/core/route/retry"
    42  	"istio.io/istio/pilot/pkg/networking/telemetry"
    43  	"istio.io/istio/pilot/pkg/networking/util"
    44  	authz "istio.io/istio/pilot/pkg/security/authz/model"
    45  	"istio.io/istio/pilot/pkg/util/protoconv"
    46  	"istio.io/istio/pkg/config"
    47  	"istio.io/istio/pkg/config/constants"
    48  	"istio.io/istio/pkg/config/host"
    49  	"istio.io/istio/pkg/config/labels"
    50  	"istio.io/istio/pkg/jwt"
    51  	"istio.io/istio/pkg/log"
    52  	"istio.io/istio/pkg/util/grpc"
    53  	"istio.io/istio/pkg/util/sets"
    54  	"istio.io/istio/pkg/wellknown"
    55  )
    56  
    57  // Headers with special meaning in Envoy
    58  const (
    59  	HeaderMethod    = ":method"
    60  	HeaderAuthority = ":authority"
    61  	HeaderScheme    = ":scheme"
    62  )
    63  
    64  // DefaultRouteName is the name assigned to a route generated by default in absence of a virtual service.
    65  const DefaultRouteName = "default"
    66  
    67  var Notimeout = durationpb.New(0)
    68  
    69  // DefaultMaxDirectResponseBodySizeBytes is 1mb, the same limit the control plane validates via webhook. Set this to increase from envoy default of 4k
    70  var DefaultMaxDirectResponseBodySizeBytes = wrapperspb.UInt32(1024 * 1024)
    71  
    72  type DestinationHashMap map[*networking.HTTPRouteDestination]*networking.LoadBalancerSettings_ConsistentHashLB
    73  
    74  // VirtualHostWrapper is a context-dependent virtual host entry with guarded routes.
    75  // Note: Currently we are not fully utilizing this structure. We could invoke this logic
    76  // once for all sidecars in the cluster to compute all RDS for inside the mesh and arrange
    77  // it by listener port. However to properly use such an optimization, we need to have an
    78  // eventing subsystem to invalidate the computed routes if any service changes/virtual Services change.
    79  type VirtualHostWrapper struct {
    80  	// Port is the listener port for outbound sidecar (e.g. service port)
    81  	Port int
    82  
    83  	// Services are the Services from the registry. Each service
    84  	// in this list should have a virtual host entry
    85  	Services []*model.Service
    86  
    87  	// VirtualServiceHosts is a list of hosts defined in the virtual service
    88  	// if virtual service hostname is same as a the service registry host, then
    89  	// the host would appear in Services as we need to generate all variants of the
    90  	// service's hostname within a platform (e.g., foo, foo.default, foo.default.svc, etc.)
    91  	VirtualServiceHosts []string
    92  
    93  	// Routes in the virtual host
    94  	Routes []*route.Route
    95  }
    96  
    97  // BuildSidecarVirtualHostWrapper creates virtual hosts from the given set of virtual Services
    98  // and a list of Services from the service registry. Services are indexed by FQDN hostnames.
    99  // The list of Services is also passed to allow maintaining consistent ordering.
   100  func BuildSidecarVirtualHostWrapper(routeCache *Cache, node *model.Proxy, push *model.PushContext, serviceRegistry map[host.Name]*model.Service,
   101  	virtualServices []config.Config, listenPort int, mostSpecificWildcardVsIndex map[host.Name]types.NamespacedName,
   102  ) []VirtualHostWrapper {
   103  	out := make([]VirtualHostWrapper, 0)
   104  
   105  	// dependentDestinationRules includes all the destinationrules referenced by
   106  	// the virtualservices, which have consistent hash policy.
   107  	dependentDestinationRules := []*model.ConsolidatedDestRule{}
   108  
   109  	// First build virtual host wrappers for services that have virtual services.
   110  	for _, virtualService := range virtualServices {
   111  		hashByDestination, destinationRules := hashForVirtualService(push, node, virtualService)
   112  		dependentDestinationRules = append(dependentDestinationRules, destinationRules...)
   113  		wrappers := buildSidecarVirtualHostsForVirtualService(
   114  			node, virtualService, serviceRegistry, hashByDestination, listenPort, push.Mesh, mostSpecificWildcardVsIndex,
   115  		)
   116  		out = append(out, wrappers...)
   117  	}
   118  
   119  	// Now exclude the services that have virtual services.
   120  	for _, wrapper := range out {
   121  		for _, service := range wrapper.Services {
   122  			delete(serviceRegistry, service.Hostname)
   123  		}
   124  	}
   125  
   126  	for _, svc := range serviceRegistry {
   127  		for _, port := range svc.Ports {
   128  			if port.Protocol.IsHTTPOrSniffed() {
   129  				hash, destinationRule := hashForService(push, node, svc, port)
   130  				if hash != nil {
   131  					dependentDestinationRules = append(dependentDestinationRules, destinationRule)
   132  				}
   133  				// append default hosts for the service missing virtual Services.
   134  				out = append(out, buildSidecarVirtualHostForService(svc, port, hash, push.Mesh))
   135  			}
   136  		}
   137  	}
   138  
   139  	if routeCache != nil {
   140  		routeCache.DestinationRules = dependentDestinationRules
   141  	}
   142  
   143  	return out
   144  }
   145  
   146  // separateVSHostsAndServices splits the virtual service hosts into Services (if they are found in the registry) and
   147  // plain non-registry hostnames
   148  func separateVSHostsAndServices(virtualService config.Config,
   149  	serviceRegistry map[host.Name]*model.Service,
   150  	mostSpecificWildcardVsIndex map[host.Name]types.NamespacedName,
   151  ) ([]string, []*model.Service) {
   152  	// TODO: A further optimization would be to completely rely on the index and not do the loop below
   153  	// However, that requires assuming that serviceRegistry never got filtered after the
   154  	// egressListener was created.
   155  	rule := virtualService.Spec.(*networking.VirtualService)
   156  	// Stores VS hosts that don't correspond to services in the registry
   157  	// Currently, the only use for this list is to enable VirtualService configuration to affect
   158  	// traffic to hosts outside of the service registry (e.g. google.com) on port 80
   159  	nonServiceRegistryHosts := make([]string, 0)
   160  	// Stores services for this VirtualService that are in the registry (based on hostname)
   161  	matchingRegistryServices := make([]*model.Service, 0)
   162  	wchosts := make([]host.Name, 0)
   163  
   164  	// As a performance optimization, process non wildcard hosts first, so that they can be
   165  	// looked up directly in the service registry map.
   166  	for _, hostname := range rule.Hosts {
   167  		vshost := host.Name(hostname)
   168  		if vshost.IsWildCarded() {
   169  			// We'll process wild card hosts later
   170  			wchosts = append(wchosts, vshost)
   171  			continue
   172  		}
   173  		if svc, exists := serviceRegistry[vshost]; exists {
   174  			matchingRegistryServices = append(matchingRegistryServices, svc)
   175  		} else {
   176  			nonServiceRegistryHosts = append(nonServiceRegistryHosts, hostname)
   177  		}
   178  	}
   179  
   180  	// Now process wild card hosts as they need to follow the slow path of looping through all Services in the registry.
   181  	for _, hostname := range wchosts {
   182  		if model.UseGatewaySemantics(virtualService) {
   183  			nonServiceRegistryHosts = append(nonServiceRegistryHosts, string(hostname))
   184  			continue
   185  		}
   186  		// foundSvcMatch's only purpose is to make sure we don't add hosts that correspond to services
   187  		// to the list of non-serviceregistry hosts
   188  		foundSvcMatch := false
   189  		for svcHost, svc := range serviceRegistry {
   190  			// First, check if this service matches the VS host.
   191  			// If it does, then we never want to add it to the nonServiceRegistryHosts list.
   192  			// The result is OR'd so we don't overwrite a previously true value
   193  			// localMatch is tracking the match found within this iteration of the loop
   194  			localMatch := svcHost.Matches(hostname)
   195  			// foundSvcMatch is tracking in the wider context whether or not ANY match was found during an iteration
   196  			foundSvcMatch = foundSvcMatch || localMatch
   197  			if !localMatch {
   198  				// If the wildcard doesn't even match this service, it won't be in the index
   199  				continue
   200  			}
   201  			// The mostSpecificWildcardVsIndex ensures that each VirtualService host is only associated with
   202  			// a single service in the registry. This is generally results in the most specific wildcard match for
   203  			// a given wildcard host (unless PERSIST_OLDEST_FIRST_HEURISTIC_FOR_VIRTUAL_SERVICE_HOST_MATCHING is true).
   204  			vs, ok := mostSpecificWildcardVsIndex[svcHost]
   205  			if !ok {
   206  				// This service doesn't have a virtualService that matches it.
   207  				continue
   208  			}
   209  			if vs != virtualService.NamespacedName() {
   210  				// This virtual service is not the most specific wildcard match for this service.
   211  				// So we don't add it to the list of services in this virtual service so as
   212  				// to avoid duplicates
   213  				continue
   214  			}
   215  			matchingRegistryServices = append(matchingRegistryServices, svc)
   216  		}
   217  
   218  		// If we never found a match for this hostname in the service registry, add it to the list of non-service hosts
   219  		if !foundSvcMatch {
   220  			nonServiceRegistryHosts = append(nonServiceRegistryHosts, string(hostname))
   221  		}
   222  	}
   223  
   224  	return nonServiceRegistryHosts, matchingRegistryServices
   225  }
   226  
   227  // buildSidecarVirtualHostsForVirtualService creates virtual hosts corresponding to a virtual service.
   228  // Called for each port to determine the list of vhosts on the given port.
   229  // It may return an empty list if no VirtualService rule has a matching service.
   230  func buildSidecarVirtualHostsForVirtualService(
   231  	node *model.Proxy,
   232  	virtualService config.Config,
   233  	serviceRegistry map[host.Name]*model.Service,
   234  	hashByDestination DestinationHashMap,
   235  	listenPort int,
   236  	mesh *meshconfig.MeshConfig,
   237  	mostSpecificWildcardVsIndex map[host.Name]types.NamespacedName,
   238  ) []VirtualHostWrapper {
   239  	meshGateway := sets.New(constants.IstioMeshGateway)
   240  	opts := RouteOptions{
   241  		// Sidecar is never terminating TLS
   242  		IsTLS: false,
   243  		// Sidecar is never doing H3 (yet)
   244  		IsHTTP3AltSvcHeaderNeeded: false,
   245  		Mesh:                      mesh,
   246  	}
   247  	routes, err := BuildHTTPRoutesForVirtualService(node, virtualService, serviceRegistry, hashByDestination,
   248  		listenPort, meshGateway, opts)
   249  	if err != nil || len(routes) == 0 {
   250  		return nil
   251  	}
   252  
   253  	hosts, matchingRegistryServices := separateVSHostsAndServices(virtualService, serviceRegistry, mostSpecificWildcardVsIndex)
   254  
   255  	// Gateway allows only routes from the namespace of the proxy, or namespace of the destination.
   256  	if model.UseGatewaySemantics(virtualService) {
   257  		res := make([]*model.Service, 0, len(matchingRegistryServices))
   258  		for _, s := range matchingRegistryServices {
   259  			if s.Attributes.Namespace != virtualService.Namespace && node.ConfigNamespace != virtualService.Namespace {
   260  				continue
   261  			}
   262  			res = append(res, s)
   263  		}
   264  		if len(res) == 0 {
   265  			return nil
   266  		}
   267  	}
   268  
   269  	// Now group these Services by port so that we can infer the destination.port if the user
   270  	// doesn't specify any port for a multiport service. We need to know the destination port in
   271  	// order to build the cluster name (outbound|<port>|<subset>|<serviceFQDN>)
   272  	// If the destination service is being accessed on port X, we set that as the default
   273  	// destination port
   274  	serviceByPort := make(map[int][]*model.Service)
   275  	for _, svc := range matchingRegistryServices {
   276  		for _, port := range svc.Ports {
   277  			if port.Protocol.IsHTTPOrSniffed() {
   278  				serviceByPort[port.Port] = append(serviceByPort[port.Port], svc)
   279  			}
   280  		}
   281  	}
   282  
   283  	if len(serviceByPort) == 0 {
   284  		if listenPort == 80 {
   285  			// TODO: This is a gross HACK. Fix me. Its a much bigger surgery though, due to the way
   286  			// the current code is written.
   287  			serviceByPort[80] = nil
   288  		}
   289  	}
   290  
   291  	out := make([]VirtualHostWrapper, 0, len(serviceByPort))
   292  	for port, services := range serviceByPort {
   293  		out = append(out, VirtualHostWrapper{
   294  			Port:                port,
   295  			Services:            services,
   296  			VirtualServiceHosts: hosts,
   297  			Routes:              routes,
   298  		})
   299  	}
   300  
   301  	return out
   302  }
   303  
   304  func buildSidecarVirtualHostForService(svc *model.Service,
   305  	port *model.Port,
   306  	hash *networking.LoadBalancerSettings_ConsistentHashLB,
   307  	mesh *meshconfig.MeshConfig,
   308  ) VirtualHostWrapper {
   309  	cluster := model.BuildSubsetKey(model.TrafficDirectionOutbound, "", svc.Hostname, port.Port)
   310  	traceOperation := telemetry.TraceOperation(string(svc.Hostname), port.Port)
   311  	httpRoute := BuildDefaultHTTPOutboundRoute(cluster, traceOperation, mesh)
   312  
   313  	// if this host has no virtualservice, the consistentHash on its destinationRule will be useless
   314  	hashPolicy := consistentHashToHashPolicy(hash)
   315  	if hashPolicy != nil {
   316  		httpRoute.GetRoute().HashPolicy = []*route.RouteAction_HashPolicy{hashPolicy}
   317  	}
   318  	return VirtualHostWrapper{
   319  		Port:     port.Port,
   320  		Services: []*model.Service{svc},
   321  		Routes:   []*route.Route{httpRoute},
   322  	}
   323  }
   324  
   325  // GetDestinationCluster generates a cluster name for the route, or error if no cluster
   326  // can be found. Called by translateRule to determine if
   327  func GetDestinationCluster(destination *networking.Destination, service *model.Service, listenerPort int) string {
   328  	if len(destination.GetHost()) == 0 {
   329  		// only happens when the gateway-api BackendRef is invalid
   330  		return "UnknownService"
   331  	}
   332  	h := host.Name(destination.Host)
   333  	// If this is an Alias, point to the concrete service
   334  	// TODO: this will not work if we have Alias -> Alias -> Concrete service.
   335  	if features.EnableExternalNameAlias && service != nil && service.Attributes.K8sAttributes.ExternalName != "" {
   336  		h = host.Name(service.Attributes.K8sAttributes.ExternalName)
   337  	}
   338  	port := listenerPort
   339  	if destination.GetPort() != nil {
   340  		port = int(destination.GetPort().GetNumber())
   341  	} else if service != nil && len(service.Ports) == 1 {
   342  		// if service only has one port defined, use that as the port, otherwise use default listenerPort
   343  		port = service.Ports[0].Port
   344  
   345  		// Do not return blackhole cluster for service==nil case as there is a legitimate use case for
   346  		// calling this function with nil service: to route to a pre-defined statically configured cluster
   347  		// declared as part of the bootstrap.
   348  		// If blackhole cluster is needed, do the check on the caller side. See gateway and tls.go for examples.
   349  	}
   350  
   351  	return model.BuildSubsetKey(model.TrafficDirectionOutbound, destination.Subset, h, port)
   352  }
   353  
   354  type RouteOptions struct {
   355  	// IsTLS indicates if the route is intended for a TLS listener
   356  	IsTLS bool
   357  	// IsHTTP3AltSvcHeaderNeeded indicates if HTTP3 alt-svc header needs to be inserted
   358  	IsHTTP3AltSvcHeaderNeeded bool
   359  	Mesh                      *meshconfig.MeshConfig
   360  }
   361  
   362  // BuildHTTPRoutesForVirtualService creates data plane HTTP routes from the virtual service spec.
   363  // The rule should be adapted to destination names (outbound clusters).
   364  // Each rule is guarded by source labels.
   365  //
   366  // This is called for each port to compute virtual hosts.
   367  // Each VirtualService is tried, with a list of Services that listen on the port.
   368  // Error indicates the given virtualService can't be used on the port.
   369  // This function is used by both the gateway and the sidecar
   370  func BuildHTTPRoutesForVirtualService(
   371  	node *model.Proxy,
   372  	virtualService config.Config,
   373  	serviceRegistry map[host.Name]*model.Service,
   374  	hashByDestination DestinationHashMap,
   375  	listenPort int,
   376  	gatewayNames sets.String,
   377  	opts RouteOptions,
   378  ) ([]*route.Route, error) {
   379  	vs, ok := virtualService.Spec.(*networking.VirtualService)
   380  	if !ok { // should never happen
   381  		return nil, fmt.Errorf("in not a virtual service: %#v", virtualService)
   382  	}
   383  
   384  	out := make([]*route.Route, 0, len(vs.Http))
   385  
   386  	catchall := false
   387  	for _, http := range vs.Http {
   388  		if len(http.Match) == 0 {
   389  			if r := translateRoute(node, http, nil, listenPort, virtualService, serviceRegistry,
   390  				hashByDestination, gatewayNames, opts); r != nil {
   391  				out = append(out, r)
   392  			}
   393  			catchall = true
   394  		} else {
   395  			for _, match := range http.Match {
   396  				if r := translateRoute(node, http, match, listenPort, virtualService, serviceRegistry,
   397  					hashByDestination, gatewayNames, opts); r != nil {
   398  					out = append(out, r)
   399  					// This is a catch all path. Routes are matched in order, so we will never go beyond this match
   400  					// As an optimization, we can just stop sending any more routes here.
   401  					if IsCatchAllRoute(r) {
   402  						catchall = true
   403  						break
   404  					}
   405  				}
   406  			}
   407  		}
   408  		if catchall {
   409  			break
   410  		}
   411  	}
   412  
   413  	if len(out) == 0 {
   414  		return nil, fmt.Errorf("no routes matched")
   415  	}
   416  	return out, nil
   417  }
   418  
   419  // sourceMatchHttp checks if the sourceLabels or the gateways in a match condition match with the
   420  // labels for the proxy or the gateway name for which we are generating a route
   421  func sourceMatchHTTP(match *networking.HTTPMatchRequest, proxyLabels labels.Instance, gatewayNames sets.String, proxyNamespace string) bool {
   422  	if match == nil {
   423  		return true
   424  	}
   425  
   426  	// Trim by source labels or mesh gateway
   427  	if len(match.Gateways) > 0 {
   428  		for _, g := range match.Gateways {
   429  			if gatewayNames.Contains(g) {
   430  				return true
   431  			}
   432  		}
   433  	} else if labels.Instance(match.GetSourceLabels()).SubsetOf(proxyLabels) {
   434  		return match.SourceNamespace == "" || match.SourceNamespace == proxyNamespace
   435  	}
   436  
   437  	return false
   438  }
   439  
   440  // translateRoute translates HTTP routes
   441  func translateRoute(
   442  	node *model.Proxy,
   443  	in *networking.HTTPRoute,
   444  	match *networking.HTTPMatchRequest,
   445  	listenPort int,
   446  	virtualService config.Config,
   447  	serviceRegistry map[host.Name]*model.Service,
   448  	hashByDestination DestinationHashMap,
   449  	gatewayNames sets.String,
   450  	opts RouteOptions,
   451  ) *route.Route {
   452  	// When building routes, it's okay if the target cluster cannot be
   453  	// resolved. Traffic to such clusters will blackhole.
   454  
   455  	// Match by the destination port specified in the match condition
   456  	if match != nil && match.Port != 0 && match.Port != uint32(listenPort) {
   457  		return nil
   458  	}
   459  	// Match by source labels/gateway names inside the match condition
   460  	if !sourceMatchHTTP(match, node.Labels, gatewayNames, node.Metadata.Namespace) {
   461  		return nil
   462  	}
   463  
   464  	routeName := in.Name
   465  	if match != nil && match.Name != "" {
   466  		routeName = routeName + "." + match.Name
   467  	}
   468  
   469  	out := &route.Route{
   470  		Name:     routeName,
   471  		Match:    TranslateRouteMatch(virtualService, match, node.SupportsEnvoyExtendedJwt()),
   472  		Metadata: util.BuildConfigInfoMetadata(virtualService.Meta),
   473  	}
   474  
   475  	if match != nil && match.StatPrefix != "" {
   476  		out.StatPrefix = match.StatPrefix
   477  	}
   478  
   479  	authority := ""
   480  	if in.Headers != nil {
   481  		operations := TranslateHeadersOperations(in.Headers)
   482  		out.RequestHeadersToAdd = operations.RequestHeadersToAdd
   483  		out.ResponseHeadersToAdd = operations.ResponseHeadersToAdd
   484  		out.RequestHeadersToRemove = operations.RequestHeadersToRemove
   485  		out.ResponseHeadersToRemove = operations.ResponseHeadersToRemove
   486  		authority = operations.Authority
   487  	}
   488  
   489  	var hostnames []host.Name
   490  	if in.Redirect != nil {
   491  		ApplyRedirect(out, in.Redirect, listenPort, opts.IsTLS, model.UseGatewaySemantics(virtualService))
   492  	} else if in.DirectResponse != nil {
   493  		ApplyDirectResponse(out, in.DirectResponse)
   494  	} else {
   495  		hostnames = applyHTTPRouteDestination(out, node, virtualService, in, opts.Mesh, authority, serviceRegistry, listenPort, hashByDestination)
   496  	}
   497  
   498  	out.Decorator = &route.Decorator{
   499  		Operation: GetRouteOperation(out, virtualService.Name, listenPort),
   500  	}
   501  	if in.Fault != nil || in.CorsPolicy != nil {
   502  		out.TypedPerFilterConfig = make(map[string]*anypb.Any)
   503  	}
   504  	if in.Fault != nil {
   505  		out.TypedPerFilterConfig[wellknown.Fault] = protoconv.MessageToAny(TranslateFault(in.Fault))
   506  	}
   507  	if in.CorsPolicy != nil {
   508  		out.TypedPerFilterConfig[wellknown.CORS] = protoconv.MessageToAny(TranslateCORSPolicy(node, in.CorsPolicy))
   509  	}
   510  	var statefulConfig *statefulsession.StatefulSession
   511  	for _, hostname := range hostnames {
   512  		perSvcStatefulConfig := util.MaybeBuildStatefulSessionFilterConfig(serviceRegistry[hostname])
   513  		// This means we have more than one stateful config for the same route because of weighed destinations.
   514  		// We should just pick the first and give a warning.
   515  		if perSvcStatefulConfig != nil && statefulConfig != nil {
   516  			log.Warnf("More than one stateful config for the same route %s. Picking the first one.", routeName)
   517  			break
   518  		}
   519  		statefulConfig = perSvcStatefulConfig
   520  	}
   521  	// Build stateful set config if the svc has appropriate labels attached.
   522  	if statefulConfig != nil {
   523  		if out.TypedPerFilterConfig == nil {
   524  			out.TypedPerFilterConfig = make(map[string]*anypb.Any)
   525  		}
   526  		perRouteStatefulSession := &statefulsession.StatefulSessionPerRoute{
   527  			Override: &statefulsession.StatefulSessionPerRoute_StatefulSession{
   528  				StatefulSession: statefulConfig,
   529  			},
   530  		}
   531  		out.TypedPerFilterConfig[util.StatefulSessionFilter] = protoconv.MessageToAny(perRouteStatefulSession)
   532  	}
   533  
   534  	if opts.IsHTTP3AltSvcHeaderNeeded {
   535  		http3AltSvcHeader := buildHTTP3AltSvcHeader(listenPort, util.ALPNHttp3OverQUIC)
   536  		if out.ResponseHeadersToAdd == nil {
   537  			out.ResponseHeadersToAdd = make([]*core.HeaderValueOption, 0)
   538  		}
   539  		out.ResponseHeadersToAdd = append(out.ResponseHeadersToAdd, http3AltSvcHeader)
   540  	}
   541  
   542  	return out
   543  }
   544  
   545  func applyHTTPRouteDestination(
   546  	out *route.Route,
   547  	node *model.Proxy,
   548  	vs config.Config,
   549  	in *networking.HTTPRoute,
   550  	mesh *meshconfig.MeshConfig,
   551  	authority string,
   552  	serviceRegistry map[host.Name]*model.Service,
   553  	listenerPort int,
   554  	hashByDestination DestinationHashMap,
   555  ) []host.Name {
   556  	action := &route.RouteAction{}
   557  
   558  	setTimeout(action, in.Timeout, node)
   559  
   560  	if model.UseGatewaySemantics(vs) {
   561  		// return 500 for invalid backends
   562  		// https://github.com/kubernetes-sigs/gateway-api/blob/cea484e38e078a2c1997d8c7a62f410a1540f519/apis/v1beta1/httproute_types.go#L204
   563  		action.ClusterNotFoundResponseCode = route.RouteAction_INTERNAL_SERVER_ERROR
   564  	}
   565  
   566  	out.Action = &route.Route_Route{Route: action}
   567  
   568  	if in.Rewrite != nil {
   569  		action.ClusterSpecifier = &route.RouteAction_Cluster{
   570  			Cluster: in.Name,
   571  		}
   572  
   573  		if regexRewrite := in.Rewrite.GetUriRegexRewrite(); regexRewrite != nil {
   574  			action.RegexRewrite = &matcher.RegexMatchAndSubstitute{
   575  				Pattern: &matcher.RegexMatcher{
   576  					Regex: regexRewrite.Match,
   577  				},
   578  				Substitution: regexRewrite.Rewrite,
   579  			}
   580  		} else if uri := in.Rewrite.GetUri(); uri != "" {
   581  			if model.UseGatewaySemantics(vs) && uri == "/" {
   582  				// remove the prefix
   583  				action.RegexRewrite = &matcher.RegexMatchAndSubstitute{
   584  					Pattern: &matcher.RegexMatcher{
   585  						Regex: fmt.Sprintf(`^%s(/?)(.*)`, regexp.QuoteMeta(out.Match.GetPathSeparatedPrefix())),
   586  					},
   587  					// hold `/` in case the entire path is removed
   588  					Substitution: `/\2`,
   589  				}
   590  			} else {
   591  				action.PrefixRewrite = uri
   592  			}
   593  		}
   594  		if in.Rewrite.GetAuthority() != "" {
   595  			authority = in.Rewrite.GetAuthority()
   596  		}
   597  	}
   598  	if authority != "" {
   599  		action.HostRewriteSpecifier = &route.RouteAction_HostRewriteLiteral{
   600  			HostRewriteLiteral: authority,
   601  		}
   602  	}
   603  
   604  	if in.Mirror != nil {
   605  		if mp := MirrorPercent(in); mp != nil {
   606  			action.RequestMirrorPolicies = append(action.RequestMirrorPolicies,
   607  				TranslateRequestMirrorPolicy(in.Mirror, serviceRegistry[host.Name(in.Mirror.Host)], listenerPort, mp))
   608  		}
   609  	}
   610  	for _, mirror := range in.Mirrors {
   611  		if mp := MirrorPercentByPolicy(mirror); mp != nil && mirror.Destination != nil {
   612  			action.RequestMirrorPolicies = append(action.RequestMirrorPolicies,
   613  				TranslateRequestMirrorPolicy(mirror.Destination, serviceRegistry[host.Name(mirror.Destination.Host)], listenerPort, mp))
   614  		}
   615  	}
   616  
   617  	var hostnames []host.Name
   618  	policy := in.Retries
   619  	if policy == nil {
   620  		// No VS policy set, use mesh defaults
   621  		policy = mesh.GetDefaultHttpRetryPolicy()
   622  	}
   623  	consistentHash := false
   624  	if len(in.Route) == 1 {
   625  		hostnames = append(hostnames, processDestination(in.Route[0], serviceRegistry, listenerPort, hashByDestination, out, action))
   626  		hash := hashByDestination[in.Route[0]]
   627  		consistentHash = hash != nil
   628  	} else {
   629  		weighted := make([]*route.WeightedCluster_ClusterWeight, 0)
   630  		for _, dst := range in.Route {
   631  			if dst.Weight == 0 {
   632  				// Ignore 0 weighted clusters if there are other clusters in the route.
   633  				continue
   634  			}
   635  			destinationweight, hostname := processWeightedDestination(dst, serviceRegistry, listenerPort, hashByDestination, action)
   636  			weighted = append(weighted, destinationweight)
   637  			hostnames = append(hostnames, hostname)
   638  		}
   639  		action.ClusterSpecifier = &route.RouteAction_WeightedClusters{
   640  			WeightedClusters: &route.WeightedCluster{
   641  				Clusters: weighted,
   642  			},
   643  		}
   644  	}
   645  	action.RetryPolicy = retry.ConvertPolicy(policy, consistentHash)
   646  	return hostnames
   647  }
   648  
   649  // processDestination processes a single destination in a route. It specifies to which cluster the route should
   650  // be routed to. It also sets the headers and hash policy if specified.
   651  // Returns the hostname of the destination.
   652  func processDestination(dst *networking.HTTPRouteDestination, serviceRegistry map[host.Name]*model.Service,
   653  	listenerPort int,
   654  	hashByDestination DestinationHashMap,
   655  	out *route.Route,
   656  	action *route.RouteAction,
   657  ) host.Name {
   658  	hostname := host.Name(dst.GetDestination().GetHost())
   659  	action.ClusterSpecifier = &route.RouteAction_Cluster{
   660  		Cluster: GetDestinationCluster(dst.Destination, serviceRegistry[hostname], listenerPort),
   661  	}
   662  	if dst.Headers != nil {
   663  		operations := TranslateHeadersOperations(dst.Headers)
   664  		out.RequestHeadersToAdd = append(out.RequestHeadersToAdd, operations.RequestHeadersToAdd...)
   665  		out.RequestHeadersToRemove = append(out.RequestHeadersToRemove, operations.RequestHeadersToRemove...)
   666  		out.ResponseHeadersToAdd = append(out.ResponseHeadersToAdd, operations.ResponseHeadersToAdd...)
   667  		out.ResponseHeadersToRemove = append(out.ResponseHeadersToRemove, operations.ResponseHeadersToRemove...)
   668  		if operations.Authority != "" && action.HostRewriteSpecifier == nil {
   669  			// Ideally, if the weighted cluster overwrites authority, it has precedence. This mirrors behavior of headers,
   670  			// because for headers we append the weighted last which allows it to Set and wipe out previous Adds.
   671  			// However, Envoy behavior is different when we set at both cluster level and route level, and we want
   672  			// behavior to be consistent with a single cluster and multiple clusters.
   673  			// As a result, we only override if the top level rewrite is not set
   674  			action.HostRewriteSpecifier = &route.RouteAction_HostRewriteLiteral{
   675  				HostRewriteLiteral: operations.Authority,
   676  			}
   677  		}
   678  	}
   679  	hash := hashByDestination[dst]
   680  	hashPolicy := consistentHashToHashPolicy(hash)
   681  	if hashPolicy != nil {
   682  		action.HashPolicy = append(action.HashPolicy, hashPolicy)
   683  	}
   684  	return hostname
   685  }
   686  
   687  // processWeightedDestination processes a weighted destination in a route. It specifies to which cluster the route should
   688  // be routed to. It also sets the headers and hash policy if specified.
   689  // Returns the hostname of the destination along with its weight.
   690  func processWeightedDestination(dst *networking.HTTPRouteDestination, serviceRegistry map[host.Name]*model.Service,
   691  	listenerPort int,
   692  	hashByDestination DestinationHashMap,
   693  	action *route.RouteAction,
   694  ) (*route.WeightedCluster_ClusterWeight, host.Name) {
   695  	hostname := host.Name(dst.GetDestination().GetHost())
   696  	clusterWeight := &route.WeightedCluster_ClusterWeight{
   697  		Name:   GetDestinationCluster(dst.Destination, serviceRegistry[hostname], listenerPort),
   698  		Weight: &wrapperspb.UInt32Value{Value: uint32(dst.Weight)},
   699  	}
   700  	if dst.Headers != nil {
   701  		operations := TranslateHeadersOperations(dst.Headers)
   702  		// If weighted destination has headers, we need to set them on the cluster weight.
   703  		clusterWeight.RequestHeadersToAdd = operations.RequestHeadersToAdd
   704  		clusterWeight.RequestHeadersToRemove = operations.RequestHeadersToRemove
   705  		clusterWeight.ResponseHeadersToAdd = operations.ResponseHeadersToAdd
   706  		clusterWeight.ResponseHeadersToRemove = operations.ResponseHeadersToRemove
   707  		if operations.Authority != "" {
   708  			clusterWeight.HostRewriteSpecifier = &route.WeightedCluster_ClusterWeight_HostRewriteLiteral{
   709  				HostRewriteLiteral: operations.Authority,
   710  			}
   711  		}
   712  	}
   713  	hash := hashByDestination[dst]
   714  	hashPolicy := consistentHashToHashPolicy(hash)
   715  	if hashPolicy != nil {
   716  		action.HashPolicy = append(action.HashPolicy, hashPolicy)
   717  	}
   718  	return clusterWeight, hostname
   719  }
   720  
   721  func ApplyRedirect(out *route.Route, redirect *networking.HTTPRedirect, port int, isTLS bool, useGatewaySemantics bool) {
   722  	action := &route.Route_Redirect{
   723  		Redirect: &route.RedirectAction{
   724  			HostRedirect: redirect.Authority,
   725  			PathRewriteSpecifier: &route.RedirectAction_PathRedirect{
   726  				PathRedirect: redirect.Uri,
   727  			},
   728  		},
   729  	}
   730  
   731  	if useGatewaySemantics {
   732  		if uri, isPrefixReplace := cutPrefix(redirect.Uri, "%PREFIX()%"); isPrefixReplace {
   733  			action.Redirect.PathRewriteSpecifier = &route.RedirectAction_PrefixRewrite{
   734  				PrefixRewrite: uri,
   735  			}
   736  		}
   737  	}
   738  
   739  	if redirect.Scheme != "" {
   740  		action.Redirect.SchemeRewriteSpecifier = &route.RedirectAction_SchemeRedirect{SchemeRedirect: redirect.Scheme}
   741  	}
   742  
   743  	if redirect.RedirectPort != nil {
   744  		switch rp := redirect.RedirectPort.(type) {
   745  		case *networking.HTTPRedirect_DerivePort:
   746  			if rp.DerivePort == networking.HTTPRedirect_FROM_REQUEST_PORT {
   747  				// Envoy doesn't actually support deriving the port from the request dynamically. However,
   748  				// we always generate routes in the context of a specific request port. As a result, we can just
   749  				// use that port
   750  				action.Redirect.PortRedirect = uint32(port)
   751  			}
   752  			// Otherwise, no port needed; HTTPRedirect_FROM_PROTOCOL_DEFAULT is Envoy's default behavior
   753  		case *networking.HTTPRedirect_Port:
   754  			action.Redirect.PortRedirect = rp.Port
   755  		}
   756  		scheme := redirect.Scheme
   757  		if scheme == "" {
   758  			if isTLS {
   759  				scheme = "https"
   760  			} else {
   761  				scheme = "http"
   762  			}
   763  		}
   764  		// Do not put explicit :80 or :443 when its http/https
   765  		if action.Redirect.PortRedirect == 80 && scheme == "http" {
   766  			action.Redirect.PortRedirect = 0
   767  		}
   768  		if action.Redirect.PortRedirect == 443 && scheme == "https" {
   769  			action.Redirect.PortRedirect = 0
   770  		}
   771  	}
   772  
   773  	switch redirect.RedirectCode {
   774  	case 0, 301:
   775  		action.Redirect.ResponseCode = route.RedirectAction_MOVED_PERMANENTLY
   776  	case 302:
   777  		action.Redirect.ResponseCode = route.RedirectAction_FOUND
   778  	case 303:
   779  		action.Redirect.ResponseCode = route.RedirectAction_SEE_OTHER
   780  	case 307:
   781  		action.Redirect.ResponseCode = route.RedirectAction_TEMPORARY_REDIRECT
   782  	case 308:
   783  		action.Redirect.ResponseCode = route.RedirectAction_PERMANENT_REDIRECT
   784  	default:
   785  		log.Warnf("Redirect Code %d is not yet supported", redirect.RedirectCode)
   786  		action = nil
   787  	}
   788  
   789  	out.Action = action
   790  }
   791  
   792  func ApplyDirectResponse(out *route.Route, directResponse *networking.HTTPDirectResponse) {
   793  	action := &route.Route_DirectResponse{
   794  		DirectResponse: &route.DirectResponseAction{
   795  			Status: directResponse.Status,
   796  		},
   797  	}
   798  
   799  	if directResponse.Body != nil {
   800  		switch op := directResponse.Body.Specifier.(type) {
   801  		case *networking.HTTPBody_String_:
   802  			action.DirectResponse.Body = &core.DataSource{
   803  				Specifier: &core.DataSource_InlineString{
   804  					InlineString: op.String_,
   805  				},
   806  			}
   807  		case *networking.HTTPBody_Bytes:
   808  			action.DirectResponse.Body = &core.DataSource{
   809  				Specifier: &core.DataSource_InlineBytes{
   810  					InlineBytes: op.Bytes,
   811  				},
   812  			}
   813  		}
   814  	}
   815  
   816  	out.Action = action
   817  }
   818  
   819  func buildHTTP3AltSvcHeader(port int, h3Alpns []string) *core.HeaderValueOption {
   820  	// For example, www.cloudflare.com returns the following
   821  	// alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400, h3=":443"; ma=86400
   822  	valParts := make([]string, 0, len(h3Alpns))
   823  	for _, alpn := range h3Alpns {
   824  		// Max-age is hardcoded to 1 day for now.
   825  		valParts = append(valParts, fmt.Sprintf(`%s=":%d"; ma=86400`, alpn, port))
   826  	}
   827  	headerVal := strings.Join(valParts, ", ")
   828  	return &core.HeaderValueOption{
   829  		AppendAction: core.HeaderValueOption_APPEND_IF_EXISTS_OR_ADD,
   830  		Header: &core.HeaderValue{
   831  			Key:   util.AltSvcHeader,
   832  			Value: headerVal,
   833  		},
   834  	}
   835  }
   836  
   837  // SortHeaderValueOption type and the functions below (Len, Less and Swap) are for sort.Stable for type HeaderValueOption
   838  type SortHeaderValueOption []*core.HeaderValueOption
   839  
   840  // MirrorPercent computes the mirror percent to be used based on "Mirror" data in route.
   841  func MirrorPercent(in *networking.HTTPRoute) *core.RuntimeFractionalPercent {
   842  	switch {
   843  	case in.MirrorPercentage != nil:
   844  		if in.MirrorPercentage.GetValue() > 0 {
   845  			return &core.RuntimeFractionalPercent{
   846  				DefaultValue: translatePercentToFractionalPercent(in.MirrorPercentage),
   847  			}
   848  		}
   849  		// If zero percent is provided explicitly, we should not mirror.
   850  		return nil
   851  	// nolint: staticcheck
   852  	case in.MirrorPercent != nil:
   853  		if in.MirrorPercent.GetValue() > 0 {
   854  			return &core.RuntimeFractionalPercent{
   855  				DefaultValue: translateIntegerToFractionalPercent((int32(in.MirrorPercent.GetValue()))),
   856  			}
   857  		}
   858  		// If zero percent is provided explicitly, we should not mirror.
   859  		return nil
   860  	default:
   861  		// Default to 100 percent if percent is not given.
   862  		return &core.RuntimeFractionalPercent{
   863  			DefaultValue: translateIntegerToFractionalPercent(100),
   864  		}
   865  	}
   866  }
   867  
   868  // MirrorPercentByPolicy computes the mirror percent to be used based on HTTPMirrorPolicy.
   869  func MirrorPercentByPolicy(mirror *networking.HTTPMirrorPolicy) *core.RuntimeFractionalPercent {
   870  	switch {
   871  	case mirror.Percentage != nil:
   872  		if mirror.Percentage.GetValue() > 0 {
   873  			return &core.RuntimeFractionalPercent{
   874  				DefaultValue: translatePercentToFractionalPercent(mirror.Percentage),
   875  			}
   876  		}
   877  		// If zero percent is provided explicitly, we should not mirror.
   878  		return nil
   879  	default:
   880  		// Default to 100 percent if percent is not given.
   881  		return &core.RuntimeFractionalPercent{
   882  			DefaultValue: translateIntegerToFractionalPercent(100),
   883  		}
   884  	}
   885  }
   886  
   887  // Len is i the sort.Interface for SortHeaderValueOption
   888  func (b SortHeaderValueOption) Len() int {
   889  	return len(b)
   890  }
   891  
   892  // Less is in the sort.Interface for SortHeaderValueOption
   893  func (b SortHeaderValueOption) Less(i, j int) bool {
   894  	if b[i] == nil || b[i].Header == nil {
   895  		return false
   896  	} else if b[j] == nil || b[j].Header == nil {
   897  		return true
   898  	}
   899  	return strings.Compare(b[i].Header.Key, b[j].Header.Key) < 0
   900  }
   901  
   902  // Swap is in the sort.Interface for SortHeaderValueOption
   903  func (b SortHeaderValueOption) Swap(i, j int) {
   904  	b[i], b[j] = b[j], b[i]
   905  }
   906  
   907  // translateAppendHeaders translates headers
   908  func translateAppendHeaders(headers map[string]string, appendFlag bool) ([]*core.HeaderValueOption, string) {
   909  	if len(headers) == 0 {
   910  		return nil, ""
   911  	}
   912  	authority := ""
   913  	headerValueOptionList := make([]*core.HeaderValueOption, 0, len(headers))
   914  	for key, value := range headers {
   915  		if isAuthorityHeader(key) {
   916  			// If there are multiple, last one wins; validation will reject
   917  			authority = value
   918  		}
   919  		if isInternalHeader(key) {
   920  			continue
   921  		}
   922  		headerValueOption := &core.HeaderValueOption{
   923  			Header: &core.HeaderValue{
   924  				Key:   key,
   925  				Value: value,
   926  			},
   927  		}
   928  		if appendFlag {
   929  			headerValueOption.AppendAction = core.HeaderValueOption_APPEND_IF_EXISTS_OR_ADD
   930  		} else {
   931  			headerValueOption.AppendAction = core.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD
   932  		}
   933  		headerValueOptionList = append(headerValueOptionList, headerValueOption)
   934  	}
   935  	sort.Stable(SortHeaderValueOption(headerValueOptionList))
   936  	return headerValueOptionList, authority
   937  }
   938  
   939  type HeadersOperations struct {
   940  	RequestHeadersToAdd     []*core.HeaderValueOption
   941  	ResponseHeadersToAdd    []*core.HeaderValueOption
   942  	RequestHeadersToRemove  []string
   943  	ResponseHeadersToRemove []string
   944  	Authority               string
   945  }
   946  
   947  // isInternalHeader returns true if a header refers to an internal value that cannot be modified by Envoy
   948  func isInternalHeader(headerKey string) bool {
   949  	return strings.HasPrefix(headerKey, ":") || strings.EqualFold(headerKey, "host")
   950  }
   951  
   952  // isAuthorityHeader returns true if a header refers to the authority header
   953  func isAuthorityHeader(headerKey string) bool {
   954  	return strings.EqualFold(headerKey, ":authority") || strings.EqualFold(headerKey, "host")
   955  }
   956  
   957  func dropInternal(keys []string) []string {
   958  	result := make([]string, 0, len(keys))
   959  	for _, k := range keys {
   960  		if isInternalHeader(k) {
   961  			continue
   962  		}
   963  		result = append(result, k)
   964  	}
   965  	return result
   966  }
   967  
   968  // TranslateHeadersOperations translates headers operations
   969  func TranslateHeadersOperations(headers *networking.Headers) HeadersOperations {
   970  	req := headers.GetRequest()
   971  	resp := headers.GetResponse()
   972  
   973  	requestHeadersToAdd, setAuthority := translateAppendHeaders(req.GetSet(), false)
   974  	reqAdd, addAuthority := translateAppendHeaders(req.GetAdd(), true)
   975  	requestHeadersToAdd = append(requestHeadersToAdd, reqAdd...)
   976  
   977  	responseHeadersToAdd, _ := translateAppendHeaders(resp.GetSet(), false)
   978  	respAdd, _ := translateAppendHeaders(resp.GetAdd(), true)
   979  	responseHeadersToAdd = append(responseHeadersToAdd, respAdd...)
   980  
   981  	auth := addAuthority
   982  	if setAuthority != "" {
   983  		// If authority is set in 'add' and 'set', pick the one from 'set'
   984  		auth = setAuthority
   985  	}
   986  	return HeadersOperations{
   987  		RequestHeadersToAdd:     requestHeadersToAdd,
   988  		ResponseHeadersToAdd:    responseHeadersToAdd,
   989  		RequestHeadersToRemove:  dropInternal(req.GetRemove()),
   990  		ResponseHeadersToRemove: dropInternal(resp.GetRemove()),
   991  		Authority:               auth,
   992  	}
   993  }
   994  
   995  // TranslateRouteMatch translates match condition
   996  func TranslateRouteMatch(vs config.Config, in *networking.HTTPMatchRequest, useExtendedJwt bool) *route.RouteMatch {
   997  	out := &route.RouteMatch{PathSpecifier: &route.RouteMatch_Prefix{Prefix: "/"}}
   998  	if in == nil {
   999  		return out
  1000  	}
  1001  
  1002  	for name, stringMatch := range in.Headers {
  1003  		// The metadata matcher takes precedence over the header matcher.
  1004  		if metadataMatcher := translateMetadataMatch(name, stringMatch, useExtendedJwt); metadataMatcher != nil {
  1005  			out.DynamicMetadata = append(out.DynamicMetadata, metadataMatcher)
  1006  		} else {
  1007  			matcher := translateHeaderMatch(name, stringMatch)
  1008  			out.Headers = append(out.Headers, matcher)
  1009  		}
  1010  	}
  1011  
  1012  	for name, stringMatch := range in.WithoutHeaders {
  1013  		if metadataMatcher := translateMetadataMatch(name, stringMatch, useExtendedJwt); metadataMatcher != nil {
  1014  			metadataMatcher.Invert = true
  1015  			out.DynamicMetadata = append(out.DynamicMetadata, metadataMatcher)
  1016  		} else {
  1017  			matcher := translateHeaderMatch(name, stringMatch)
  1018  			matcher.InvertMatch = true
  1019  			// treat_missing_header_as_empty conflict with present_match
  1020  			if !canBeConvertedToPresentMatch(stringMatch) {
  1021  				matcher.TreatMissingHeaderAsEmpty = true
  1022  			}
  1023  			out.Headers = append(out.Headers, matcher)
  1024  		}
  1025  	}
  1026  
  1027  	// guarantee ordering of headers
  1028  	sort.Slice(out.Headers, func(i, j int) bool {
  1029  		return out.Headers[i].Name < out.Headers[j].Name
  1030  	})
  1031  
  1032  	if in.Uri != nil {
  1033  		switch m := in.Uri.MatchType.(type) {
  1034  		case *networking.StringMatch_Exact:
  1035  			out.PathSpecifier = &route.RouteMatch_Path{Path: m.Exact}
  1036  		case *networking.StringMatch_Prefix:
  1037  			if (model.UseIngressSemantics(vs) || model.UseGatewaySemantics(vs)) && m.Prefix != "/" {
  1038  				path := strings.TrimSuffix(m.Prefix, "/")
  1039  				out.PathSpecifier = &route.RouteMatch_PathSeparatedPrefix{PathSeparatedPrefix: path}
  1040  			} else {
  1041  				out.PathSpecifier = &route.RouteMatch_Prefix{Prefix: m.Prefix}
  1042  			}
  1043  		case *networking.StringMatch_Regex:
  1044  			out.PathSpecifier = &route.RouteMatch_SafeRegex{
  1045  				SafeRegex: &matcher.RegexMatcher{
  1046  					Regex: m.Regex,
  1047  				},
  1048  			}
  1049  		}
  1050  	}
  1051  
  1052  	out.CaseSensitive = &wrapperspb.BoolValue{Value: !in.IgnoreUriCase}
  1053  
  1054  	if in.Method != nil {
  1055  		matcher := translateHeaderMatch(HeaderMethod, in.Method)
  1056  		out.Headers = append(out.Headers, matcher)
  1057  	}
  1058  
  1059  	if in.Authority != nil {
  1060  		matcher := translateHeaderMatch(HeaderAuthority, in.Authority)
  1061  		out.Headers = append(out.Headers, matcher)
  1062  	}
  1063  
  1064  	if in.Scheme != nil {
  1065  		matcher := translateHeaderMatch(HeaderScheme, in.Scheme)
  1066  		out.Headers = append(out.Headers, matcher)
  1067  	}
  1068  
  1069  	for name, stringMatch := range in.QueryParams {
  1070  		matcher := translateQueryParamMatch(name, stringMatch)
  1071  		out.QueryParameters = append(out.QueryParameters, matcher)
  1072  	}
  1073  
  1074  	return out
  1075  }
  1076  
  1077  // translateQueryParamMatch translates a StringMatch to a QueryParameterMatcher.
  1078  func translateQueryParamMatch(name string, in *networking.StringMatch) *route.QueryParameterMatcher {
  1079  	out := &route.QueryParameterMatcher{
  1080  		Name: name,
  1081  	}
  1082  
  1083  	if canBeConvertedToPresentMatch(in) {
  1084  		out.QueryParameterMatchSpecifier = &route.QueryParameterMatcher_PresentMatch{
  1085  			PresentMatch: true,
  1086  		}
  1087  		return out
  1088  	}
  1089  
  1090  	if em := util.ConvertToEnvoyMatch(in); em != nil {
  1091  		out.QueryParameterMatchSpecifier = &route.QueryParameterMatcher_StringMatch{
  1092  			StringMatch: em,
  1093  		}
  1094  	}
  1095  
  1096  	return out
  1097  }
  1098  
  1099  // canBeConvertedToPresentMatch determines if the given matcher can be converted to present_match or not.
  1100  // Currently, if the regex is "*" value, it returns true
  1101  func canBeConvertedToPresentMatch(in *networking.StringMatch) bool {
  1102  	if in == nil || in.MatchType == nil {
  1103  		return true
  1104  	}
  1105  
  1106  	catchall := false
  1107  
  1108  	switch m := in.MatchType.(type) {
  1109  	case *networking.StringMatch_Regex:
  1110  		// `*` is NOT a RE2 style regex, it's a metacharacter.
  1111  		// It will be translated as present_match, rather than matching "any string".
  1112  		// see https://github.com/istio/istio/pull/20629
  1113  		catchall = m.Regex == "*"
  1114  	}
  1115  
  1116  	return catchall
  1117  }
  1118  
  1119  // translateMetadataMatch translates a header match to dynamic metadata matcher. Returns nil if the header is not supported
  1120  // or the header format is invalid for generating metadata matcher.
  1121  //
  1122  // The currently only supported header is @request.auth.claims for JWT claims matching. Claims of type string or list of string
  1123  // are supported and nested claims are also supported using `.` or `[]` as a separator for claim names, `[]` is recommended.
  1124  //
  1125  // Examples using `.` as a separator:
  1126  // - `@request.auth.claims.admin` matches the claim "admin".
  1127  // - `@request.auth.claims.group.id` matches the nested claims "group" and "id".
  1128  //
  1129  // Examples using `[]` as a separator:
  1130  // - `@request.auth.claims[admin]` matches the claim "admin".
  1131  // - `@request.auth.claims[group][id]` matches the nested claims "group" and "id".
  1132  func translateMetadataMatch(name string, in *networking.StringMatch, useExtendedJwt bool) *matcher.MetadataMatcher {
  1133  	rc := jwt.ToRoutingClaim(name)
  1134  	if !rc.Match {
  1135  		return nil
  1136  	}
  1137  	return authz.MetadataMatcherForJWTClaims(rc.Claims, util.ConvertToEnvoyMatch(in), useExtendedJwt)
  1138  }
  1139  
  1140  // translateHeaderMatch translates to HeaderMatcher
  1141  func translateHeaderMatch(name string, in *networking.StringMatch) *route.HeaderMatcher {
  1142  	out := &route.HeaderMatcher{
  1143  		Name: name,
  1144  	}
  1145  
  1146  	if canBeConvertedToPresentMatch(in) {
  1147  		out.HeaderMatchSpecifier = &route.HeaderMatcher_PresentMatch{PresentMatch: true}
  1148  		return out
  1149  	}
  1150  
  1151  	if em := util.ConvertToEnvoyMatch(in); em != nil {
  1152  		out.HeaderMatchSpecifier = &route.HeaderMatcher_StringMatch{
  1153  			StringMatch: em,
  1154  		}
  1155  	}
  1156  
  1157  	return out
  1158  }
  1159  
  1160  func forwardNotMatchingPreflights(cors *networking.CorsPolicy) *wrapperspb.BoolValue {
  1161  	if cors.GetUnmatchedPreflights() == networking.CorsPolicy_IGNORE {
  1162  		return wrapperspb.Bool(false)
  1163  	}
  1164  
  1165  	// This is the default behavior before envoy 1.30.
  1166  	return wrapperspb.Bool(true)
  1167  }
  1168  
  1169  // TranslateCORSPolicy translates CORS policy
  1170  func TranslateCORSPolicy(proxy *model.Proxy, in *networking.CorsPolicy) *cors.CorsPolicy {
  1171  	if in == nil {
  1172  		return nil
  1173  	}
  1174  
  1175  	// CORS filter is enabled by default
  1176  	out := cors.CorsPolicy{}
  1177  	// Start from Envoy 1.30(istio 1.22), cors filter will not forward preflight requests to upstream by default.
  1178  	// Istio start support this feature from 1.23.
  1179  	if proxy.VersionGreaterAndEqual(&model.IstioVersion{Major: 1, Minor: 23, Patch: -1}) {
  1180  		out.ForwardNotMatchingPreflights = forwardNotMatchingPreflights(in)
  1181  	}
  1182  
  1183  	// nolint: staticcheck
  1184  	if in.AllowOrigins != nil {
  1185  		out.AllowOriginStringMatch = util.ConvertToEnvoyMatches(in.AllowOrigins)
  1186  	} else if in.AllowOrigin != nil {
  1187  		out.AllowOriginStringMatch = util.StringToExactMatch(in.AllowOrigin)
  1188  	}
  1189  
  1190  	out.FilterEnabled = &core.RuntimeFractionalPercent{
  1191  		DefaultValue: &xdstype.FractionalPercent{
  1192  			Numerator:   100,
  1193  			Denominator: xdstype.FractionalPercent_HUNDRED,
  1194  		},
  1195  	}
  1196  
  1197  	out.AllowCredentials = in.AllowCredentials
  1198  	out.AllowHeaders = strings.Join(in.AllowHeaders, ",")
  1199  	out.AllowMethods = strings.Join(in.AllowMethods, ",")
  1200  	out.ExposeHeaders = strings.Join(in.ExposeHeaders, ",")
  1201  	if in.MaxAge != nil {
  1202  		out.MaxAge = strconv.FormatInt(in.MaxAge.GetSeconds(), 10)
  1203  	}
  1204  	return &out
  1205  }
  1206  
  1207  // GetRouteOperation returns readable route description for trace.
  1208  func GetRouteOperation(in *route.Route, vsName string, port int) string {
  1209  	path := "/*"
  1210  	m := in.GetMatch()
  1211  	ps := m.GetPathSpecifier()
  1212  	if ps != nil {
  1213  		switch ps.(type) {
  1214  		case *route.RouteMatch_Prefix:
  1215  			path = m.GetPrefix() + "*"
  1216  		case *route.RouteMatch_Path:
  1217  			path = m.GetPath()
  1218  		case *route.RouteMatch_SafeRegex:
  1219  			path = m.GetSafeRegex().GetRegex()
  1220  		}
  1221  	}
  1222  
  1223  	// If there is only one destination cluster in route, return host:port/uri as description of route.
  1224  	// Otherwise there are multiple destination clusters and destination host is not clear. For that case
  1225  	// return virtual service name:port/uri as substitute.
  1226  	if c := in.GetRoute().GetCluster(); model.IsValidSubsetKey(c) {
  1227  		// Parse host and port from cluster name.
  1228  		_, _, h, p := model.ParseSubsetKey(c)
  1229  		return string(h) + ":" + strconv.Itoa(p) + path
  1230  	}
  1231  	return vsName + ":" + strconv.Itoa(port) + path
  1232  }
  1233  
  1234  // BuildDefaultHTTPInboundRoute builds a default inbound route.
  1235  func BuildDefaultHTTPInboundRoute(clusterName string, operation string) *route.Route {
  1236  	out := buildDefaultHTTPRoute(clusterName, operation)
  1237  	// For inbound, configure with notimeout.
  1238  	out.GetRoute().Timeout = Notimeout
  1239  	out.GetRoute().MaxStreamDuration = &route.RouteAction_MaxStreamDuration{
  1240  		MaxStreamDuration: Notimeout,
  1241  		// If not configured at all, the grpc-timeout header is not used and
  1242  		// gRPC requests time out like any other requests using timeout or its default.
  1243  		GrpcTimeoutHeaderMax: Notimeout,
  1244  	}
  1245  	return out
  1246  }
  1247  
  1248  func buildDefaultHTTPRoute(clusterName string, operation string) *route.Route {
  1249  	routeAction := &route.RouteAction{
  1250  		ClusterSpecifier: &route.RouteAction_Cluster{Cluster: clusterName},
  1251  	}
  1252  	val := &route.Route{
  1253  		Match: TranslateRouteMatch(config.Config{}, nil, true),
  1254  		Decorator: &route.Decorator{
  1255  			Operation: operation,
  1256  		},
  1257  		Action: &route.Route_Route{
  1258  			Route: routeAction,
  1259  		},
  1260  	}
  1261  
  1262  	val.Name = DefaultRouteName
  1263  	return val
  1264  }
  1265  
  1266  // setTimeout sets timeout for a route.
  1267  func setTimeout(action *route.RouteAction, vsTimeout *durationpb.Duration, node *model.Proxy) {
  1268  	// Configure timeouts specified by Virtual Service if they are provided, otherwise set it to defaults.
  1269  	action.Timeout = Notimeout
  1270  	if vsTimeout != nil {
  1271  		action.Timeout = vsTimeout
  1272  	}
  1273  	if node != nil && node.IsProxylessGrpc() {
  1274  		// TODO(stevenctl) merge these paths; grpc's xDS impl will not read the deprecated value
  1275  		action.MaxStreamDuration = &route.RouteAction_MaxStreamDuration{
  1276  			MaxStreamDuration: action.Timeout,
  1277  		}
  1278  	} else {
  1279  		// If not configured at all, the grpc-timeout header is not used and
  1280  		// gRPC requests time out like any other requests using timeout or its default.
  1281  		// Use deprecated value for now as the replacement MaxStreamDuration has some regressions.
  1282  		// nolint: staticcheck
  1283  		if action.Timeout.AsDuration().Nanoseconds() == 0 {
  1284  			action.MaxGrpcTimeout = Notimeout
  1285  		} else {
  1286  			action.MaxGrpcTimeout = action.Timeout
  1287  		}
  1288  	}
  1289  }
  1290  
  1291  // BuildDefaultHTTPOutboundRoute builds a default outbound route, including a retry policy.
  1292  func BuildDefaultHTTPOutboundRoute(clusterName string, operation string, mesh *meshconfig.MeshConfig) *route.Route {
  1293  	out := buildDefaultHTTPRoute(clusterName, operation)
  1294  	// Add a default retry policy for outbound routes.
  1295  	out.GetRoute().RetryPolicy = retry.ConvertPolicy(mesh.GetDefaultHttpRetryPolicy(), false)
  1296  	setTimeout(out.GetRoute(), nil, nil)
  1297  	return out
  1298  }
  1299  
  1300  // translatePercentToFractionalPercent translates an v1alpha3 Percent instance
  1301  // to an envoy.type.FractionalPercent instance.
  1302  func translatePercentToFractionalPercent(p *networking.Percent) *xdstype.FractionalPercent {
  1303  	return &xdstype.FractionalPercent{
  1304  		Numerator:   uint32(p.Value * 10000),
  1305  		Denominator: xdstype.FractionalPercent_MILLION,
  1306  	}
  1307  }
  1308  
  1309  // translateIntegerToFractionalPercent translates an int32 instance to an
  1310  // envoy.type.FractionalPercent instance.
  1311  func translateIntegerToFractionalPercent(p int32) *xdstype.FractionalPercent {
  1312  	return &xdstype.FractionalPercent{
  1313  		Numerator:   uint32(p),
  1314  		Denominator: xdstype.FractionalPercent_HUNDRED,
  1315  	}
  1316  }
  1317  
  1318  // TranslateFault translates networking.HTTPFaultInjection into Envoy's HTTPFault
  1319  func TranslateFault(in *networking.HTTPFaultInjection) *xdshttpfault.HTTPFault {
  1320  	if in == nil {
  1321  		return nil
  1322  	}
  1323  
  1324  	out := xdshttpfault.HTTPFault{}
  1325  	if in.Delay != nil {
  1326  		out.Delay = &xdsfault.FaultDelay{}
  1327  		if in.Delay.Percentage != nil {
  1328  			out.Delay.Percentage = translatePercentToFractionalPercent(in.Delay.Percentage)
  1329  		} else {
  1330  			out.Delay.Percentage = translateIntegerToFractionalPercent(in.Delay.Percent) // nolint: staticcheck
  1331  		}
  1332  		switch d := in.Delay.HttpDelayType.(type) {
  1333  		case *networking.HTTPFaultInjection_Delay_FixedDelay:
  1334  			out.Delay.FaultDelaySecifier = &xdsfault.FaultDelay_FixedDelay{
  1335  				FixedDelay: d.FixedDelay,
  1336  			}
  1337  		default:
  1338  			log.Warnf("Exponential faults are not yet supported")
  1339  			out.Delay = nil
  1340  		}
  1341  	}
  1342  
  1343  	if in.Abort != nil {
  1344  		out.Abort = &xdshttpfault.FaultAbort{}
  1345  		if in.Abort.Percentage != nil {
  1346  			out.Abort.Percentage = translatePercentToFractionalPercent(in.Abort.Percentage)
  1347  		}
  1348  		switch a := in.Abort.ErrorType.(type) {
  1349  		case *networking.HTTPFaultInjection_Abort_HttpStatus:
  1350  			out.Abort.ErrorType = &xdshttpfault.FaultAbort_HttpStatus{
  1351  				HttpStatus: uint32(a.HttpStatus),
  1352  			}
  1353  		case *networking.HTTPFaultInjection_Abort_GrpcStatus:
  1354  			// We wouldn't have an unknown gRPC code here. This is because
  1355  			// the validation webhook would have already caught the invalid
  1356  			// code and we wouldn't reach here.
  1357  			out.Abort.ErrorType = &xdshttpfault.FaultAbort_GrpcStatus{
  1358  				GrpcStatus: uint32(grpc.SupportedGRPCStatus[a.GrpcStatus]),
  1359  			}
  1360  		default:
  1361  			log.Warnf("Only HTTP and gRPC type abort faults are supported")
  1362  			out.Abort = nil
  1363  		}
  1364  	}
  1365  
  1366  	if out.Delay == nil && out.Abort == nil {
  1367  		return nil
  1368  	}
  1369  
  1370  	return &out
  1371  }
  1372  
  1373  func TranslateRequestMirrorPolicy(dst *networking.Destination, service *model.Service,
  1374  	listenerPort int, mp *core.RuntimeFractionalPercent,
  1375  ) *route.RouteAction_RequestMirrorPolicy {
  1376  	return &route.RouteAction_RequestMirrorPolicy{
  1377  		Cluster:         GetDestinationCluster(dst, service, listenerPort),
  1378  		RuntimeFraction: mp,
  1379  		TraceSampled:    &wrapperspb.BoolValue{Value: false},
  1380  	}
  1381  }
  1382  
  1383  func portLevelSettingsConsistentHash(dst *networking.Destination,
  1384  	pls []*networking.TrafficPolicy_PortTrafficPolicy,
  1385  ) *networking.LoadBalancerSettings_ConsistentHashLB {
  1386  	if dst.Port != nil {
  1387  		portNumber := dst.GetPort().GetNumber()
  1388  		for _, setting := range pls {
  1389  			number := setting.GetPort().GetNumber()
  1390  			if number == portNumber {
  1391  				return setting.GetLoadBalancer().GetConsistentHash()
  1392  			}
  1393  		}
  1394  	}
  1395  
  1396  	return nil
  1397  }
  1398  
  1399  func consistentHashToHashPolicy(consistentHash *networking.LoadBalancerSettings_ConsistentHashLB) *route.RouteAction_HashPolicy {
  1400  	switch consistentHash.GetHashKey().(type) {
  1401  	case *networking.LoadBalancerSettings_ConsistentHashLB_HttpHeaderName:
  1402  		return &route.RouteAction_HashPolicy{
  1403  			PolicySpecifier: &route.RouteAction_HashPolicy_Header_{
  1404  				Header: &route.RouteAction_HashPolicy_Header{
  1405  					HeaderName: consistentHash.GetHttpHeaderName(),
  1406  				},
  1407  			},
  1408  		}
  1409  	case *networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie:
  1410  		cookie := consistentHash.GetHttpCookie()
  1411  		var ttl *durationpb.Duration
  1412  		if cookie.GetTtl() != nil {
  1413  			ttl = cookie.GetTtl()
  1414  		}
  1415  		return &route.RouteAction_HashPolicy{
  1416  			PolicySpecifier: &route.RouteAction_HashPolicy_Cookie_{
  1417  				Cookie: &route.RouteAction_HashPolicy_Cookie{
  1418  					Name: cookie.GetName(),
  1419  					Ttl:  ttl,
  1420  					Path: cookie.GetPath(),
  1421  				},
  1422  			},
  1423  		}
  1424  	case *networking.LoadBalancerSettings_ConsistentHashLB_UseSourceIp:
  1425  		return &route.RouteAction_HashPolicy{
  1426  			PolicySpecifier: &route.RouteAction_HashPolicy_ConnectionProperties_{
  1427  				ConnectionProperties: &route.RouteAction_HashPolicy_ConnectionProperties{
  1428  					SourceIp: consistentHash.GetUseSourceIp(),
  1429  				},
  1430  			},
  1431  		}
  1432  	case *networking.LoadBalancerSettings_ConsistentHashLB_HttpQueryParameterName:
  1433  		return &route.RouteAction_HashPolicy{
  1434  			PolicySpecifier: &route.RouteAction_HashPolicy_QueryParameter_{
  1435  				QueryParameter: &route.RouteAction_HashPolicy_QueryParameter{
  1436  					Name: consistentHash.GetHttpQueryParameterName(),
  1437  				},
  1438  			},
  1439  		}
  1440  	}
  1441  	return nil
  1442  }
  1443  
  1444  func hashForService(push *model.PushContext,
  1445  	node *model.Proxy,
  1446  	svc *model.Service,
  1447  	port *model.Port,
  1448  ) (*networking.LoadBalancerSettings_ConsistentHashLB, *model.ConsolidatedDestRule) {
  1449  	if push == nil {
  1450  		return nil, nil
  1451  	}
  1452  	mergedDR := node.SidecarScope.DestinationRule(model.TrafficDirectionOutbound, node, svc.Hostname)
  1453  	destinationRule := mergedDR.GetRule()
  1454  	if destinationRule == nil {
  1455  		return nil, nil
  1456  	}
  1457  	rule := destinationRule.Spec.(*networking.DestinationRule)
  1458  	consistentHash := rule.GetTrafficPolicy().GetLoadBalancer().GetConsistentHash()
  1459  	portLevelSettings := rule.GetTrafficPolicy().GetPortLevelSettings()
  1460  	for _, setting := range portLevelSettings {
  1461  		number := setting.GetPort().GetNumber()
  1462  		if int(number) == port.Port {
  1463  			if setting.GetLoadBalancer().GetConsistentHash() != nil {
  1464  				consistentHash = setting.GetLoadBalancer().GetConsistentHash()
  1465  			}
  1466  			break
  1467  		}
  1468  	}
  1469  
  1470  	return consistentHash, mergedDR
  1471  }
  1472  
  1473  func hashForVirtualService(push *model.PushContext,
  1474  	node *model.Proxy,
  1475  	virtualService config.Config,
  1476  ) (DestinationHashMap, []*model.ConsolidatedDestRule) {
  1477  	hashByDestination := DestinationHashMap{}
  1478  	destinationRules := make([]*model.ConsolidatedDestRule, 0)
  1479  	for _, httpRoute := range virtualService.Spec.(*networking.VirtualService).Http {
  1480  		for _, destination := range httpRoute.Route {
  1481  			hash, dr := hashForHTTPDestination(push, node, destination)
  1482  			if hash != nil {
  1483  				hashByDestination[destination] = hash
  1484  				destinationRules = append(destinationRules, dr)
  1485  			}
  1486  		}
  1487  	}
  1488  	return hashByDestination, destinationRules
  1489  }
  1490  
  1491  func GetConsistentHashForVirtualService(push *model.PushContext, node *model.Proxy, virtualService config.Config) DestinationHashMap {
  1492  	hashByDestination, _ := hashForVirtualService(push, node, virtualService)
  1493  	return hashByDestination
  1494  }
  1495  
  1496  // hashForHTTPDestination return the ConsistentHashLB and the DestinationRule associated with HTTP route destination.
  1497  func hashForHTTPDestination(push *model.PushContext, node *model.Proxy,
  1498  	dst *networking.HTTPRouteDestination,
  1499  ) (*networking.LoadBalancerSettings_ConsistentHashLB, *model.ConsolidatedDestRule) {
  1500  	if push == nil {
  1501  		return nil, nil
  1502  	}
  1503  
  1504  	destination := dst.GetDestination()
  1505  	mergedDR := node.SidecarScope.DestinationRule(model.TrafficDirectionOutbound, node, host.Name(destination.Host))
  1506  	destinationRule := mergedDR.GetRule()
  1507  	if destinationRule == nil {
  1508  		return nil, nil
  1509  	}
  1510  
  1511  	rule := destinationRule.Spec.(*networking.DestinationRule)
  1512  
  1513  	consistentHash := rule.GetTrafficPolicy().GetLoadBalancer().GetConsistentHash()
  1514  	portLevelSettings := rule.GetTrafficPolicy().GetPortLevelSettings()
  1515  	plsHash := portLevelSettingsConsistentHash(destination, portLevelSettings)
  1516  
  1517  	var subsetHash, subsetPLSHash *networking.LoadBalancerSettings_ConsistentHashLB
  1518  	for _, subset := range rule.GetSubsets() {
  1519  		if subset.GetName() == destination.GetSubset() {
  1520  			subsetPortLevelSettings := subset.GetTrafficPolicy().GetPortLevelSettings()
  1521  			subsetHash = subset.GetTrafficPolicy().GetLoadBalancer().GetConsistentHash()
  1522  			subsetPLSHash = portLevelSettingsConsistentHash(destination, subsetPortLevelSettings)
  1523  			break
  1524  		}
  1525  	}
  1526  
  1527  	switch {
  1528  	case subsetPLSHash != nil:
  1529  		consistentHash = subsetPLSHash
  1530  	case subsetHash != nil:
  1531  		consistentHash = subsetHash
  1532  	case plsHash != nil:
  1533  		consistentHash = plsHash
  1534  	}
  1535  	return consistentHash, mergedDR
  1536  }
  1537  
  1538  // SortVHostRoutes moves the catch all routes alone to the end, while retaining
  1539  // the relative order of other routes in the slice.
  1540  func SortVHostRoutes(routes []*route.Route) []*route.Route {
  1541  	allroutes := make([]*route.Route, 0, len(routes))
  1542  	catchAllRoutes := make([]*route.Route, 0)
  1543  	for _, r := range routes {
  1544  		if IsCatchAllRoute(r) {
  1545  			catchAllRoutes = append(catchAllRoutes, r)
  1546  		} else {
  1547  			allroutes = append(allroutes, r)
  1548  		}
  1549  	}
  1550  	return append(allroutes, catchAllRoutes...)
  1551  }
  1552  
  1553  // IsCatchAllRoute returns true if an Envoy route is a catchall route otherwise false.
  1554  func IsCatchAllRoute(r *route.Route) bool {
  1555  	catchall := false
  1556  	// A Match is catch all if and only if it has no header/query param match
  1557  	// and URI has a prefix `/` or regex `.*`.
  1558  	switch ir := r.Match.PathSpecifier.(type) {
  1559  	case *route.RouteMatch_Prefix:
  1560  		catchall = ir.Prefix == "/"
  1561  	case *route.RouteMatch_PathSeparatedPrefix:
  1562  		catchall = ir.PathSeparatedPrefix == "/"
  1563  	case *route.RouteMatch_SafeRegex:
  1564  		catchall = ir.SafeRegex.GetRegex() == ".*"
  1565  	}
  1566  
  1567  	return catchall && len(r.Match.Headers) == 0 && len(r.Match.QueryParameters) == 0 && len(r.Match.DynamicMetadata) == 0
  1568  }
  1569  
  1570  func cutPrefix(s, prefix string) (after string, found bool) {
  1571  	if !strings.HasPrefix(s, prefix) {
  1572  		return s, false
  1573  	}
  1574  	return s[len(prefix):], true
  1575  }