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,
}
}