istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/gateway.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 model
    16  
    17  import (
    18  	"fmt"
    19  	"strconv"
    20  	"strings"
    21  
    22  	networking "istio.io/api/networking/v1alpha3"
    23  	"istio.io/istio/pilot/pkg/features"
    24  	"istio.io/istio/pilot/pkg/model/credentials"
    25  	"istio.io/istio/pkg/config"
    26  	"istio.io/istio/pkg/config/gateway"
    27  	"istio.io/istio/pkg/config/protocol"
    28  	"istio.io/istio/pkg/config/schema/gvk"
    29  	"istio.io/istio/pkg/monitoring"
    30  	"istio.io/istio/pkg/util/sets"
    31  )
    32  
    33  // ServerPort defines port for the gateway server.
    34  type ServerPort struct {
    35  	// A valid non-negative integer port number.
    36  	Number uint32
    37  	// The protocol exposed on the port.
    38  	Protocol string
    39  	// The bind server specified on this port.
    40  	Bind string
    41  }
    42  
    43  // MergedServers describes set of servers defined in all gateways per port.
    44  type MergedServers struct {
    45  	Servers   []*networking.Server
    46  	RouteName string // RouteName for http servers. For HTTPS, TLSServerInfo will hold the route name.
    47  }
    48  
    49  // TLSServerInfo contains additional information for TLS Servers.
    50  type TLSServerInfo struct {
    51  	RouteName string
    52  	SNIHosts  []string
    53  }
    54  
    55  // MergedGateway describes a set of gateways for a workload merged into a single logical gateway.
    56  type MergedGateway struct {
    57  	// ServerPorts maintains a list of unique server ports, used for stable ordering.
    58  	ServerPorts []ServerPort
    59  
    60  	// MergedServers map from physical port to virtual servers
    61  	// using TCP protocols (like HTTP1.1, H2, mysql, redis etc)
    62  	MergedServers map[ServerPort]*MergedServers
    63  
    64  	// MergedQUICTransportServers map from physical port to servers listening
    65  	// on QUIC (like HTTP3). Currently the support is experimental and
    66  	// is limited to HTTP3 only
    67  	MergedQUICTransportServers map[ServerPort]*MergedServers
    68  
    69  	// HTTP3AdvertisingRoutes represents the set of HTTP routes which advertise HTTP/3.
    70  	// This mapping is used to generate alt-svc header that is needed for HTTP/3 server discovery.
    71  	HTTP3AdvertisingRoutes sets.String
    72  
    73  	// GatewayNameForServer maps from server to the owning gateway name.
    74  	// Used for select the set of virtual services that apply to a port.
    75  	GatewayNameForServer map[*networking.Server]string
    76  
    77  	// ServersByRouteName maps from port names to virtual hosts
    78  	// Used for RDS. No two port names share same port except for HTTPS
    79  	// The typical length of the value is always 1, except for HTTP (not HTTPS),
    80  	ServersByRouteName map[string][]*networking.Server
    81  
    82  	// TLSServerInfo maps from server to a corresponding TLS information like TLS Routename and SNIHosts.
    83  	TLSServerInfo map[*networking.Server]*TLSServerInfo
    84  
    85  	// ContainsAutoPassthroughGateways determines if there are any type AUTO_PASSTHROUGH Gateways, requiring additional
    86  	// clusters to be sent to the workload
    87  	ContainsAutoPassthroughGateways bool
    88  
    89  	// PortMap defines a mapping of targetPorts to the set of Service ports that reference them
    90  	PortMap GatewayPortMap
    91  
    92  	// VerifiedCertificateReferences contains a set of all credentialNames referenced by gateways *in the same namespace as the proxy*.
    93  	// These are considered "verified", since there is mutually agreement from the pod, Secret, and Gateway, as all
    94  	// reside in the same namespace and trust boundary.
    95  	// Note: Secrets that are not referenced by any Gateway, but are in the same namespace as the pod, are explicitly *not*
    96  	// included. This ensures we don't give permission to unexpected secrets, such as the citadel root key/cert.
    97  	VerifiedCertificateReferences sets.String
    98  }
    99  
   100  func (g *MergedGateway) HasAutoPassthroughGateways() bool {
   101  	if g != nil {
   102  		return g.ContainsAutoPassthroughGateways
   103  	}
   104  	return false
   105  }
   106  
   107  // PrevMergedGateway describes previous state of the gateway.
   108  // Currently, it only contains information relevant for CDS.
   109  type PrevMergedGateway struct {
   110  	ContainsAutoPassthroughGateways bool
   111  	AutoPassthroughSNIHosts         sets.Set[string]
   112  }
   113  
   114  func (g *PrevMergedGateway) HasAutoPassthroughGateway() bool {
   115  	if g != nil {
   116  		return g.ContainsAutoPassthroughGateways
   117  	}
   118  	return false
   119  }
   120  
   121  func (g *PrevMergedGateway) GetAutoPassthroughSNIHosts() sets.Set[string] {
   122  	if g != nil {
   123  		return g.AutoPassthroughSNIHosts
   124  	}
   125  	return sets.Set[string]{}
   126  }
   127  
   128  var (
   129  	typeTag = monitoring.CreateLabel("type")
   130  	nameTag = monitoring.CreateLabel("name")
   131  
   132  	totalRejectedConfigs = monitoring.NewSum(
   133  		"pilot_total_rejected_configs",
   134  		"Total number of configs that Pilot had to reject or ignore.",
   135  	)
   136  )
   137  
   138  func RecordRejectedConfig(gatewayName string) {
   139  	totalRejectedConfigs.With(typeTag.Value("gateway"), nameTag.Value(gatewayName)).Increment()
   140  }
   141  
   142  // DisableGatewayPortTranslationLabel is a label on Service that declares that, for that particular
   143  // service, we should not translate Gateway ports to target ports. For example, if I have a Service
   144  // on port 80 with target port 8080, with the label. Gateways on port 80 would *not* match. Instead,
   145  // only Gateways on port 8080 would be used. This prevents ambiguities when there are multiple
   146  // Services on port 80 referring to different target ports. Long term, this will be replaced by
   147  // Gateways directly referencing a Service, rather than label selectors. Warning: this label is
   148  // intended solely for as a workaround for Knative's Istio integration, and not intended for any
   149  // other usage. It can, and will, be removed immediately after the new direct reference is ready for
   150  // use.
   151  const DisableGatewayPortTranslationLabel = "experimental.istio.io/disable-gateway-port-translation"
   152  
   153  // MergeGateways combines multiple gateways targeting the same workload into a single logical Gateway.
   154  // Note that today any Servers in the combined gateways listening on the same port must have the same protocol.
   155  // If servers with different protocols attempt to listen on the same port, one of the protocols will be chosen at random.
   156  func MergeGateways(gateways []gatewayWithInstances, proxy *Proxy, ps *PushContext) *MergedGateway {
   157  	gatewayPorts := sets.New[uint32]()
   158  	nonPlainTextGatewayPortsBindMap := map[uint32]sets.String{}
   159  	mergedServers := make(map[ServerPort]*MergedServers)
   160  	mergedQUICServers := make(map[ServerPort]*MergedServers)
   161  	serverPorts := make([]ServerPort, 0)
   162  	plainTextServers := make(map[uint32]ServerPort)
   163  	serversByRouteName := make(map[string][]*networking.Server)
   164  	tlsServerInfo := make(map[*networking.Server]*TLSServerInfo)
   165  	gatewayNameForServer := make(map[*networking.Server]string)
   166  	verifiedCertificateReferences := sets.New[string]()
   167  	http3AdvertisingRoutes := sets.New[string]()
   168  	tlsHostsByPort := map[uint32]map[string]string{} // port -> host/bind map
   169  	autoPassthrough := false
   170  
   171  	log.Debugf("MergeGateways: merging %d gateways", len(gateways))
   172  	for _, gwAndInstance := range gateways {
   173  		gatewayConfig := gwAndInstance.gateway
   174  		gatewayName := gatewayConfig.Namespace + "/" + gatewayConfig.Name // Format: %s/%s
   175  		gatewayCfg := gatewayConfig.Spec.(*networking.Gateway)
   176  		log.Debugf("MergeGateways: merging gateway %q :\n%v", gatewayName, gatewayCfg)
   177  		snames := sets.String{}
   178  		for _, s := range gatewayCfg.Servers {
   179  			if len(s.Name) > 0 {
   180  				if snames.InsertContains(s.Name) {
   181  					log.Warnf("Server name %s is not unique in gateway %s and may create possible issues like stat prefix collision ",
   182  						s.Name, gatewayName)
   183  				}
   184  			}
   185  			if s.Port == nil {
   186  				// Should be rejected in validation, this is an extra check
   187  				log.Debugf("invalid server without port: %q", gatewayName)
   188  				RecordRejectedConfig(gatewayName)
   189  				continue
   190  			}
   191  			sanitizeServerHostNamespace(s, gatewayConfig.Namespace)
   192  			gatewayNameForServer[s] = gatewayName
   193  			log.Debugf("MergeGateways: gateway %q processing server %s :%v", gatewayName, s.Name, s.Hosts)
   194  
   195  			cn := s.GetTls().GetCredentialName()
   196  			if cn != "" && proxy.VerifiedIdentity != nil {
   197  				rn := credentials.ToResourceName(cn)
   198  				parse, _ := credentials.ParseResourceName(rn, proxy.VerifiedIdentity.Namespace, "", "")
   199  				if gatewayConfig.Namespace == proxy.VerifiedIdentity.Namespace && parse.Namespace == proxy.VerifiedIdentity.Namespace {
   200  					// Same namespace is always allowed
   201  					verifiedCertificateReferences.Insert(rn)
   202  					if s.GetTls().GetMode() == networking.ServerTLSSettings_MUTUAL {
   203  						verifiedCertificateReferences.Insert(rn + credentials.SdsCaSuffix)
   204  					}
   205  				} else if ps.ReferenceAllowed(gvk.Secret, rn, proxy.VerifiedIdentity.Namespace) {
   206  					// Explicitly allowed by some policy
   207  					verifiedCertificateReferences.Insert(rn)
   208  				}
   209  			}
   210  			for _, resolvedPort := range resolvePorts(s.Port.Number, gwAndInstance.instances, gwAndInstance.legacyGatewaySelector) {
   211  				routeName := gatewayRDSRouteName(s, resolvedPort, gatewayConfig)
   212  				if s.Tls != nil {
   213  					// Envoy will reject config that has multiple filter chain matches with the same matching rules.
   214  					// To avoid this, we need to make sure we don't have duplicated hosts, which will become
   215  					// SNI filter chain matches.
   216  
   217  					// When there is Bind specified in the Gateway, the listener is built per IP instead of
   218  					// sharing one wildcard listener. So different Gateways can
   219  					// have same host as long as they have different Bind.
   220  					if tlsHostsByPort[resolvedPort] == nil {
   221  						tlsHostsByPort[resolvedPort] = map[string]string{}
   222  					}
   223  					if duplicateHosts := CheckDuplicates(s.Hosts, s.Bind, tlsHostsByPort[resolvedPort]); len(duplicateHosts) != 0 {
   224  						log.Warnf("skipping server on gateway %s, duplicate host names: %v", gatewayName, duplicateHosts)
   225  						RecordRejectedConfig(gatewayName)
   226  						continue
   227  					}
   228  					tlsServerInfo[s] = &TLSServerInfo{SNIHosts: GetSNIHostsForServer(s), RouteName: routeName}
   229  					if s.Tls.Mode == networking.ServerTLSSettings_AUTO_PASSTHROUGH {
   230  						autoPassthrough = true
   231  					}
   232  				}
   233  				serverPort := ServerPort{resolvedPort, s.Port.Protocol, s.Bind}
   234  				serverProtocol := protocol.Parse(serverPort.Protocol)
   235  				if gatewayPorts.Contains(resolvedPort) {
   236  					// We have two servers on the same port. Should we merge?
   237  					// 1. Yes if both servers are plain text and HTTP
   238  					// 2. Yes if both servers are using TLS
   239  					//    if using HTTPS ensure that port name is distinct so that we can setup separate RDS
   240  					//    for each server (as each server ends up as a separate http connection manager due to filter chain match)
   241  					// 3. No for everything else.
   242  					if current, exists := plainTextServers[resolvedPort]; exists {
   243  						if !canMergeProtocols(serverProtocol, protocol.Parse(current.Protocol)) && current.Bind == serverPort.Bind {
   244  							log.Infof("skipping server on gateway %s port %s.%d.%s: conflict with existing server %d.%s",
   245  								gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol, serverPort.Number, serverPort.Protocol)
   246  							RecordRejectedConfig(gatewayName)
   247  							continue
   248  						}
   249  						// For TCP gateway/route the route name is empty but if they are different binds, should continue to generate the listener
   250  						// i.e gateway 10.0.0.1:8000:TCP should not conflict with 10.0.0.2:8000:TCP
   251  						if routeName == "" && current.Bind == serverPort.Bind {
   252  							log.Debugf("skipping server on gateway %s port %s.%d.%s: could not build RDS name from server",
   253  								gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol)
   254  							RecordRejectedConfig(gatewayName)
   255  							continue
   256  						}
   257  						if current.Bind != serverPort.Bind {
   258  							// Merge it to servers with the same port and bind.
   259  							if mergedServers[serverPort] == nil {
   260  								mergedServers[serverPort] = &MergedServers{Servers: []*networking.Server{}}
   261  								serverPorts = append(serverPorts, serverPort)
   262  							}
   263  							ms := mergedServers[serverPort]
   264  							ms.RouteName = routeName
   265  							ms.Servers = append(ms.Servers, s)
   266  						} else {
   267  							// Merge this to current known port with same bind.
   268  							ms := mergedServers[current]
   269  							ms.Servers = append(ms.Servers, s)
   270  						}
   271  						serversByRouteName[routeName] = append(serversByRouteName[routeName], s)
   272  					} else {
   273  						// We have duplicate port. Its not in plaintext servers. So, this has to be a TLS server.
   274  						// Check if this is also a HTTP server and if so, ensure uniqueness of port name.
   275  						if gateway.IsHTTPServer(s) {
   276  							if routeName == "" {
   277  								log.Debugf("skipping server on gateway %s port %s.%d.%s: could not build RDS name from server",
   278  									gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol)
   279  								RecordRejectedConfig(gatewayName)
   280  								continue
   281  							}
   282  
   283  							// Both servers are HTTPS servers. Make sure the port names are different so that RDS can pick out individual servers.
   284  							// We cannot have two servers with same port name because we need the port name to distinguish one HTTPS server from another.
   285  							// We cannot merge two HTTPS servers even if their TLS settings have same path to the keys, because we don't know if the contents
   286  							// of the keys are same. So we treat them as effectively different TLS settings.
   287  							// This check is largely redundant now since we create rds names for https using gateway name, namespace
   288  							// and validation ensures that all port names within a single gateway config are unique.
   289  							if _, exists := serversByRouteName[routeName]; exists {
   290  								log.Infof("skipping server on gateway %s port %s.%d.%s: non unique port name for HTTPS port",
   291  									gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol)
   292  								RecordRejectedConfig(gatewayName)
   293  								continue
   294  							}
   295  							serversByRouteName[routeName] = []*networking.Server{s}
   296  						}
   297  						// build the port bind map for none plain text protocol, thus can avoid protocol conflict if it's different bind
   298  						var newBind bool
   299  						if bindsPortMap, ok := nonPlainTextGatewayPortsBindMap[resolvedPort]; ok {
   300  							newBind = !bindsPortMap.InsertContains(serverPort.Bind)
   301  						} else {
   302  							nonPlainTextGatewayPortsBindMap[resolvedPort] = sets.New(serverPort.Bind)
   303  							newBind = true
   304  						}
   305  						// If the bind/port combination is not being used as non-plaintext, they are different
   306  						// listeners and won't get conflicted even with same port different protocol
   307  						// i.e 0.0.0.0:443:GRPC/1.0.0.1:443:GRPC/1.0.0.2:443:HTTPS they are not conflicted, otherwise
   308  						// We have another TLS server on the same port. Can differentiate servers using SNI
   309  						if s.Tls == nil && !newBind {
   310  							log.Warnf("TLS server without TLS options %s %s", gatewayName, s.String())
   311  							RecordRejectedConfig(gatewayName)
   312  							continue
   313  						}
   314  						if mergedServers[serverPort] == nil {
   315  							mergedServers[serverPort] = &MergedServers{Servers: []*networking.Server{s}}
   316  							serverPorts = append(serverPorts, serverPort)
   317  						} else {
   318  							mergedServers[serverPort].Servers = append(mergedServers[serverPort].Servers, s)
   319  						}
   320  
   321  						// We have TLS settings defined and we have already taken care of unique route names
   322  						// if it is HTTPS. So we can construct a QUIC server on the same port. It is okay as
   323  						// QUIC listens on UDP port, not TCP
   324  						if features.EnableQUICListeners && gateway.IsEligibleForHTTP3Upgrade(s) &&
   325  							udpSupportedPort(s.GetPort().GetNumber(), gwAndInstance.instances) {
   326  							log.Debugf("Server at port %d eligible for HTTP3 upgrade. Add UDP listener for QUIC", serverPort.Number)
   327  							if mergedQUICServers[serverPort] == nil {
   328  								mergedQUICServers[serverPort] = &MergedServers{Servers: []*networking.Server{}}
   329  							}
   330  							mergedQUICServers[serverPort].Servers = append(mergedQUICServers[serverPort].Servers, s)
   331  							http3AdvertisingRoutes.Insert(routeName)
   332  						}
   333  					}
   334  				} else {
   335  					// This is a new gateway on this port. Create MergedServers for it.
   336  					gatewayPorts.Insert(resolvedPort)
   337  					if !gateway.IsTLSServer(s) {
   338  						plainTextServers[serverPort.Number] = serverPort
   339  					}
   340  					if gateway.IsHTTPServer(s) {
   341  						serversByRouteName[routeName] = []*networking.Server{s}
   342  
   343  						if features.EnableQUICListeners && gateway.IsEligibleForHTTP3Upgrade(s) &&
   344  							udpSupportedPort(s.GetPort().GetNumber(), gwAndInstance.instances) {
   345  							log.Debugf("Server at port %d eligible for HTTP3 upgrade. So QUIC listener will be added", serverPort.Number)
   346  							http3AdvertisingRoutes.Insert(routeName)
   347  
   348  							if mergedQUICServers[serverPort] == nil {
   349  								// This should be treated like non-passthrough HTTPS case. There will be multiple filter
   350  								// chains, multiple routes per server port. So just like in TLS server case we do not
   351  								// track route name here. Instead, TLS server info is used (it is fine for now because
   352  								// this would be a mirror of an existing non-passthrough HTTPS server)
   353  								mergedQUICServers[serverPort] = &MergedServers{Servers: []*networking.Server{s}}
   354  							}
   355  						}
   356  					}
   357  					mergedServers[serverPort] = &MergedServers{Servers: []*networking.Server{s}, RouteName: routeName}
   358  					serverPorts = append(serverPorts, serverPort)
   359  				}
   360  				log.Debugf("MergeGateways: gateway %q merged server %v", gatewayName, s.Hosts)
   361  			}
   362  		}
   363  	}
   364  
   365  	return &MergedGateway{
   366  		MergedServers:                   mergedServers,
   367  		MergedQUICTransportServers:      mergedQUICServers,
   368  		ServerPorts:                     serverPorts,
   369  		GatewayNameForServer:            gatewayNameForServer,
   370  		TLSServerInfo:                   tlsServerInfo,
   371  		ServersByRouteName:              serversByRouteName,
   372  		HTTP3AdvertisingRoutes:          http3AdvertisingRoutes,
   373  		ContainsAutoPassthroughGateways: autoPassthrough,
   374  		PortMap:                         getTargetPortMap(serversByRouteName),
   375  		VerifiedCertificateReferences:   verifiedCertificateReferences,
   376  	}
   377  }
   378  
   379  func (g *MergedGateway) GetAutoPassthrughGatewaySNIHosts() sets.Set[string] {
   380  	hosts := sets.Set[string]{}
   381  	if g == nil {
   382  		return hosts
   383  	}
   384  	if g.ContainsAutoPassthroughGateways {
   385  		for _, tls := range g.MergedServers {
   386  			for _, s := range tls.Servers {
   387  				if s.GetTls().GetMode() == networking.ServerTLSSettings_AUTO_PASSTHROUGH {
   388  					hosts.InsertAll(s.Hosts...)
   389  				}
   390  			}
   391  		}
   392  	}
   393  	return hosts
   394  }
   395  
   396  func udpSupportedPort(number uint32, instances []ServiceTarget) bool {
   397  	for _, w := range instances {
   398  		if int(number) == w.Port.Port && w.Port.Protocol == protocol.UDP {
   399  			return true
   400  		}
   401  	}
   402  	return false
   403  }
   404  
   405  // resolvePorts takes a Gateway port, and resolves it to the port that will actually be listened on.
   406  // When legacyGatewaySelector=false, then the gateway is directly referencing a Service. In this
   407  // case, the translation is un-ambiguous - we just find the matching port and return the targetPort
   408  // When legacyGatewaySelector=true things are a bit more complex, as we support referencing a Service
   409  // port and translating to the targetPort in addition to just directly referencing a port. In this
   410  // case, we just make a best effort guess by picking the first match.
   411  func resolvePorts(number uint32, instances []ServiceTarget, legacyGatewaySelector bool) []uint32 {
   412  	ports := sets.New[uint32]()
   413  	for _, w := range instances {
   414  		if _, disablePortTranslation := w.Service.Attributes.Labels[DisableGatewayPortTranslationLabel]; disablePortTranslation && legacyGatewaySelector {
   415  			// Skip this Service, they opted out of port translation
   416  			// This is only done for legacyGatewaySelector, as the new gateway selection mechanism *only* allows
   417  			// referencing the Service port, and references are un-ambiguous.
   418  			continue
   419  		}
   420  		if w.Port.Port == int(number) {
   421  			if legacyGatewaySelector {
   422  				// When we are using legacy gateway label selection, we only resolve to a single port
   423  				// This has pros and cons; we don't allow merging of routes when it would be desirable, but
   424  				// we also avoid accidentally merging routes when we didn't intend to. While neither option is great,
   425  				// picking the first one here preserves backwards compatibility.
   426  				return []uint32{w.Port.TargetPort}
   427  			}
   428  			ports.Insert(w.Port.TargetPort)
   429  		}
   430  	}
   431  	ret := ports.UnsortedList()
   432  	if len(ret) == 0 && legacyGatewaySelector {
   433  		// When we are using legacy gateway label selection, we should bind to the port as-is if there is
   434  		// no matching ServiceInstance.
   435  		return []uint32{number}
   436  	}
   437  	// For cases where we are directly referencing a Service, we know that they port *must* be in the Service,
   438  	// so we have no fallback. If there was no match, the Gateway is a no-op.
   439  	return ret
   440  }
   441  
   442  func canMergeProtocols(current protocol.Instance, p protocol.Instance) bool {
   443  	return (current.IsHTTP() || current == p) && p.IsHTTP()
   444  }
   445  
   446  func GetSNIHostsForServer(server *networking.Server) []string {
   447  	if server.Tls == nil {
   448  		return nil
   449  	}
   450  	// sanitize the server hosts as it could contain hosts of form ns/host
   451  	sniHosts := sets.String{}
   452  	for _, h := range server.Hosts {
   453  		if strings.Contains(h, "/") {
   454  			parts := strings.Split(h, "/")
   455  			h = parts[1]
   456  		}
   457  		// do not add hosts, that have already been added
   458  		sniHosts.Insert(h)
   459  	}
   460  	return sets.SortedList(sniHosts)
   461  }
   462  
   463  // CheckDuplicates returns all of the hosts provided that are already known
   464  // If there were no duplicates, all hosts are added to the known hosts.
   465  func CheckDuplicates(hosts []string, bind string, knownHosts map[string]string) []string {
   466  	var duplicates []string
   467  	for _, h := range hosts {
   468  		if existingBind, ok := knownHosts[h]; ok && bind == existingBind {
   469  			duplicates = append(duplicates, h)
   470  		}
   471  	}
   472  	// No duplicates found, so we can mark all of these hosts as known
   473  	if len(duplicates) == 0 {
   474  		for _, h := range hosts {
   475  			knownHosts[h] = bind
   476  		}
   477  	}
   478  	return duplicates
   479  }
   480  
   481  // gatewayRDSRouteName generates the RDS route config name for gateway's servers.
   482  // Unlike sidecars where the RDS route name is the listener port number, gateways have a different
   483  // structure for RDS.
   484  // HTTP servers have route name set to http.<portNumber>.
   485  //
   486  //	Multiple HTTP servers can exist on the same port and the code will combine all of them into
   487  //	one single RDS payload for http.<portNumber>
   488  //
   489  // HTTPS servers with TLS termination (i.e. envoy decoding the content, and making outbound http calls to backends)
   490  // will use route name https.<portNumber>.<portName>.<gatewayName>.<namespace>. HTTPS servers using SNI passthrough or
   491  // non-HTTPS servers (e.g., TCP+TLS) with SNI passthrough will be setup as opaque TCP proxies without terminating
   492  // the SSL connection. They would inspect the SNI header and forward to the appropriate upstream as opaque TCP.
   493  //
   494  // Within HTTPS servers terminating TLS, user could setup multiple servers in the gateway. each server could have
   495  // one or more hosts but have different TLS certificates. In this case, we end up having separate filter chain
   496  // for each server, with the filter chain match matching on the server specific TLS certs and SNI headers.
   497  // We have two options here: either have all filter chains use the same RDS route name (e.g. "443") and expose
   498  // all virtual hosts on that port to every filter chain uniformly or expose only the set of virtual hosts
   499  // configured under the server for those certificates. We adopt the latter approach. In other words, each
   500  // filter chain in the multi-filter-chain listener will have a distinct RDS route name
   501  // (https.<portNumber>.<portName>.<gatewayName>.<namespace>) so that when a RDS request comes in, we serve the virtual
   502  // hosts and associated routes for that server.
   503  //
   504  // Note that the common case is one where multiple servers are exposed under a single multi-SAN cert on a single port.
   505  // In this case, we have a single https.<portNumber>.<portName>.<gatewayName>.<namespace> RDS for the HTTPS server.
   506  // While we can use the same RDS route name for two servers (say HTTP and HTTPS) exposing the same set of hosts on
   507  // different ports, the optimization (one RDS instead of two) could quickly become useless the moment the set of
   508  // hosts on the two servers start differing -- necessitating the need for two different RDS routes.
   509  func gatewayRDSRouteName(server *networking.Server, portNumber uint32, cfg config.Config) string {
   510  	p := protocol.Parse(server.Port.Protocol)
   511  	bind := ""
   512  	if server.Bind != "" {
   513  		bind = "." + server.Bind
   514  	}
   515  	if p.IsHTTP() {
   516  		return "http" + "." + strconv.Itoa(int(portNumber)) + bind // Format: http.%d.%s
   517  	}
   518  
   519  	if p == protocol.HTTPS && !gateway.IsPassThroughServer(server) {
   520  		return "https" + "." + strconv.Itoa(int(server.Port.Number)) + "." +
   521  			server.Port.Name + "." + cfg.Name + "." + cfg.Namespace + bind // Format: https.%d.%s.%s.%s.%s
   522  	}
   523  
   524  	return ""
   525  }
   526  
   527  // ParseGatewayRDSRouteName is used by the EnvoyFilter patching logic to match
   528  // a specific route configuration to patch.
   529  func ParseGatewayRDSRouteName(name string) (portNumber int, portName, gatewayName string) {
   530  	parts := strings.Split(name, ".")
   531  	if strings.HasPrefix(name, "http.") {
   532  		// this is a http gateway. Parse port number and return empty string for rest
   533  		if len(parts) >= 2 {
   534  			portNumber, _ = strconv.Atoi(parts[1])
   535  		}
   536  	} else if strings.HasPrefix(name, "https.") {
   537  		if len(parts) >= 5 {
   538  			portNumber, _ = strconv.Atoi(parts[1])
   539  			portName = parts[2]
   540  			// gateway name should be ns/name
   541  			gatewayName = parts[4] + "/" + parts[3]
   542  		}
   543  	}
   544  	return
   545  }
   546  
   547  // convert ./host to currentNamespace/Host
   548  // */host to just host
   549  // */* to just *
   550  func sanitizeServerHostNamespace(server *networking.Server, namespace string) {
   551  	for i, h := range server.Hosts {
   552  		if strings.Contains(h, "/") {
   553  			parts := strings.Split(h, "/")
   554  			if parts[0] == "." {
   555  				server.Hosts[i] = fmt.Sprintf("%s/%s", namespace, parts[1])
   556  			} else if parts[0] == "*" {
   557  				if parts[1] == "*" {
   558  					server.Hosts = []string{"*"}
   559  					return
   560  				}
   561  				server.Hosts[i] = parts[1]
   562  			}
   563  		}
   564  	}
   565  }
   566  
   567  type GatewayPortMap map[int]sets.Set[int]
   568  
   569  func getTargetPortMap(serversByRouteName map[string][]*networking.Server) GatewayPortMap {
   570  	pm := GatewayPortMap{}
   571  	for r, s := range serversByRouteName {
   572  		portNumber, _, _ := ParseGatewayRDSRouteName(r)
   573  		if _, f := pm[portNumber]; !f {
   574  			pm[portNumber] = sets.New[int]()
   575  		}
   576  		for _, se := range s {
   577  			if se.Port == nil {
   578  				continue
   579  			}
   580  			pm[portNumber].Insert(int(se.Port.Number))
   581  		}
   582  	}
   583  	return pm
   584  }