func MergeGateways()

in pilot/pkg/model/gateway.go [139:341]


func MergeGateways(gateways []gatewayWithInstances, proxy *Proxy, ps *PushContext) *MergedGateway {
	gatewayPorts := make(map[uint32]bool)
	mergedServers := make(map[ServerPort]*MergedServers)
	mergedQUICServers := make(map[ServerPort]*MergedServers)
	serverPorts := make([]ServerPort, 0)
	plainTextServers := make(map[uint32]ServerPort)
	serversByRouteName := make(map[string][]*networking.Server)
	tlsServerInfo := make(map[*networking.Server]*TLSServerInfo)
	gatewayNameForServer := make(map[*networking.Server]string)
	verifiedCertificateReferences := sets.New()
	http3AdvertisingRoutes := sets.New()
	tlsHostsByPort := map[uint32]sets.Set{} // port -> host set
	autoPassthrough := false

	log.Debugf("MergeGateways: merging %d gateways", len(gateways))
	for _, gwAndInstance := range gateways {
		gatewayConfig := gwAndInstance.gateway
		gatewayName := gatewayConfig.Namespace + "/" + gatewayConfig.Name // Format: %s/%s
		gatewayCfg := gatewayConfig.Spec.(*networking.Gateway)
		log.Debugf("MergeGateways: merging gateway %q :\n%v", gatewayName, gatewayCfg)
		snames := sets.Set{}
		for _, s := range gatewayCfg.Servers {
			if len(s.Name) > 0 {
				if snames.Contains(s.Name) {
					log.Warnf("Server name %s is not unique in gateway %s and may create possible issues like stat prefix collision ",
						s.Name, gatewayName)
				} else {
					snames.Insert(s.Name)
				}
			}
			if s.Port == nil {
				// Should be rejected in validation, this is an extra check
				log.Debugf("invalid server without port: %q", gatewayName)
				RecordRejectedConfig(gatewayName)
				continue
			}
			sanitizeServerHostNamespace(s, gatewayConfig.Namespace)
			gatewayNameForServer[s] = gatewayName
			log.Debugf("MergeGateways: gateway %q processing server %s :%v", gatewayName, s.Name, s.Hosts)

			cn := s.GetTls().GetCredentialName()
			if cn != "" && proxy.VerifiedIdentity != nil {
				rn := credentials.ToResourceName(cn)
				parse, _ := credentials.ParseResourceName(rn, proxy.VerifiedIdentity.Namespace, "", "")
				if gatewayConfig.Namespace == proxy.VerifiedIdentity.Namespace && parse.Namespace == proxy.VerifiedIdentity.Namespace {
					// Same namespace is always allowed
					verifiedCertificateReferences.Insert(rn)
				} else if ps.ReferenceAllowed(gvk.Secret, rn, proxy.VerifiedIdentity.Namespace) {
					// Explicitly allowed by some policy
					verifiedCertificateReferences.Insert(rn)
				}
			}
			for _, resolvedPort := range resolvePorts(s.Port.Number, gwAndInstance.instances, gwAndInstance.legacyGatewaySelector) {
				routeName := gatewayRDSRouteName(s, resolvedPort, gatewayConfig)
				if s.Tls != nil {
					// Envoy will reject config that has multiple filter chain matches with the same matching rules.
					// To avoid this, we need to make sure we don't have duplicated hosts, which will become
					// SNI filter chain matches.
					if tlsHostsByPort[resolvedPort] == nil {
						tlsHostsByPort[resolvedPort] = sets.New()
					}
					if duplicateHosts := CheckDuplicates(s.Hosts, tlsHostsByPort[resolvedPort]); len(duplicateHosts) != 0 {
						log.Debugf("skipping server on gateway %s, duplicate host names: %v", gatewayName, duplicateHosts)
						RecordRejectedConfig(gatewayName)
						continue
					}
					tlsServerInfo[s] = &TLSServerInfo{SNIHosts: GetSNIHostsForServer(s), RouteName: routeName}
					if s.Tls.Mode == networking.ServerTLSSettings_AUTO_PASSTHROUGH {
						autoPassthrough = true
					}
				}
				serverPort := ServerPort{resolvedPort, s.Port.Protocol, s.Bind}
				serverProtocol := protocol.Parse(serverPort.Protocol)
				if gatewayPorts[resolvedPort] {
					// We have two servers on the same port. Should we merge?
					// 1. Yes if both servers are plain text and HTTP
					// 2. Yes if both servers are using TLS
					//    if using HTTPS ensure that port name is distinct so that we can setup separate RDS
					//    for each server (as each server ends up as a separate http connection manager due to filter chain match)
					// 3. No for everything else.
					if current, exists := plainTextServers[resolvedPort]; exists {
						if !canMergeProtocols(serverProtocol, protocol.Parse(current.Protocol)) {
							log.Infof("skipping server on gateway %s port %s.%d.%s: conflict with existing server %d.%s",
								gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol, serverPort.Number, serverPort.Protocol)
							RecordRejectedConfig(gatewayName)
							continue
						}
						if routeName == "" {
							log.Debugf("skipping server on gateway %s port %s.%d.%s: could not build RDS name from server",
								gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol)
							RecordRejectedConfig(gatewayName)
							continue
						}
						if current.Bind != serverPort.Bind {
							// Merge it to servers with the same port and bind.
							if mergedServers[serverPort] == nil {
								mergedServers[serverPort] = &MergedServers{Servers: []*networking.Server{}}
								serverPorts = append(serverPorts, serverPort)
							}
							ms := mergedServers[serverPort]
							ms.RouteName = routeName
							ms.Servers = append(ms.Servers, s)
						} else {
							// Merge this to current known port with same bind.
							ms := mergedServers[current]
							ms.Servers = append(ms.Servers, s)
						}
						serversByRouteName[routeName] = append(serversByRouteName[routeName], s)
					} else {
						// We have duplicate port. Its not in plaintext servers. So, this has to be a TLS server.
						// Check if this is also a HTTP server and if so, ensure uniqueness of port name.
						if gateway.IsHTTPServer(s) {
							if routeName == "" {
								log.Debugf("skipping server on gateway %s port %s.%d.%s: could not build RDS name from server",
									gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol)
								RecordRejectedConfig(gatewayName)
								continue
							}

							// Both servers are HTTPS servers. Make sure the port names are different so that RDS can pick out individual servers.
							// We cannot have two servers with same port name because we need the port name to distinguish one HTTPS server from another.
							// 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
							// of the keys are same. So we treat them as effectively different TLS settings.
							// This check is largely redundant now since we create rds names for https using gateway name, namespace
							// and validation ensures that all port names within a single gateway config are unique.
							if _, exists := serversByRouteName[routeName]; exists {
								log.Infof("skipping server on gateway %s port %s.%d.%s: non unique port name for HTTPS port",
									gatewayConfig.Name, s.Port.Name, resolvedPort, s.Port.Protocol)
								RecordRejectedConfig(gatewayName)
								continue
							}
							serversByRouteName[routeName] = []*networking.Server{s}
						}

						// We have another TLS server on the same port. Can differentiate servers using SNI
						if s.Tls == nil {
							log.Warnf("TLS server without TLS options %s %s", gatewayName, s.String())
							continue
						}
						if mergedServers[serverPort] == nil {
							mergedServers[serverPort] = &MergedServers{Servers: []*networking.Server{s}}
							serverPorts = append(serverPorts, serverPort)
						} else {
							mergedServers[serverPort].Servers = append(mergedServers[serverPort].Servers, s)
						}

						// We have TLS settings defined and we have already taken care of unique route names
						// if it is HTTPS. So we can construct a QUIC server on the same port. It is okay as
						// QUIC listens on UDP port, not TCP
						if features.EnableQUICListeners && gateway.IsEligibleForHTTP3Upgrade(s) &&
							udpSupportedPort(s.GetPort().GetNumber(), gwAndInstance.instances) {
							log.Debugf("Server at port %d eligible for HTTP3 upgrade. Add UDP listener for QUIC", serverPort.Number)
							if mergedQUICServers[serverPort] == nil {
								mergedQUICServers[serverPort] = &MergedServers{Servers: []*networking.Server{}}
							}
							mergedQUICServers[serverPort].Servers = append(mergedQUICServers[serverPort].Servers, s)
							http3AdvertisingRoutes[routeName] = struct{}{}
						}
					}
				} else {
					// This is a new gateway on this port. Create MergedServers for it.
					gatewayPorts[resolvedPort] = true
					if !gateway.IsTLSServer(s) {
						plainTextServers[serverPort.Number] = serverPort
					}
					if gateway.IsHTTPServer(s) {
						serversByRouteName[routeName] = []*networking.Server{s}

						if features.EnableQUICListeners && gateway.IsEligibleForHTTP3Upgrade(s) &&
							udpSupportedPort(s.GetPort().GetNumber(), gwAndInstance.instances) {
							log.Debugf("Server at port %d eligible for HTTP3 upgrade. So QUIC listener will be added", serverPort.Number)
							http3AdvertisingRoutes[routeName] = struct{}{}

							if mergedQUICServers[serverPort] == nil {
								// This should be treated like non-passthrough HTTPS case. There will be multiple filter
								// chains, multiple routes per server port. So just like in TLS server case we do not
								// track route name here. Instead, TLS server info is used (it is fine for now because
								// this would be a mirror of an existing non-passthrough HTTPS server)
								mergedQUICServers[serverPort] = &MergedServers{Servers: []*networking.Server{s}}
							}
						}
					}
					mergedServers[serverPort] = &MergedServers{Servers: []*networking.Server{s}, RouteName: routeName}
					serverPorts = append(serverPorts, serverPort)
				}
				log.Debugf("MergeGateways: gateway %q merged server %v", gatewayName, s.Hosts)
			}
		}
	}

	return &MergedGateway{
		MergedServers:                   mergedServers,
		MergedQUICTransportServers:      mergedQUICServers,
		ServerPorts:                     serverPorts,
		GatewayNameForServer:            gatewayNameForServer,
		TLSServerInfo:                   tlsServerInfo,
		ServersByRouteName:              serversByRouteName,
		HTTP3AdvertisingRoutes:          http3AdvertisingRoutes,
		ContainsAutoPassthroughGateways: autoPassthrough,
		PortMap:                         getTargetPortMap(serversByRouteName),
		VerifiedCertificateReferences:   verifiedCertificateReferences,
	}
}