blob: 142fb31e07ce3e381aab358ddd14a21f40c66276 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v1alpha3
import (
"fmt"
"sort"
"strconv"
"strings"
)
import (
route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
"google.golang.org/protobuf/types/known/durationpb"
networking "istio.io/api/networking/v1alpha3"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/features"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/core/v1alpha3/envoyfilter"
istio_route "github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/core/v1alpha3/route"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/telemetry"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/util"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/serviceregistry/provider"
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/constants"
"github.com/apache/dubbo-go-pixiu/pkg/config/host"
"github.com/apache/dubbo-go-pixiu/pkg/config/protocol"
"github.com/apache/dubbo-go-pixiu/pkg/proto"
"github.com/apache/dubbo-go-pixiu/pkg/util/sets"
)
const (
wildcardDomainPrefix = "*."
inboundVirtualHostPrefix = string(model.TrafficDirectionInbound) + "|http|"
)
// BuildHTTPRoutes produces a list of routes for the proxy
func (configgen *ConfigGeneratorImpl) BuildHTTPRoutes(
node *model.Proxy,
req *model.PushRequest,
routeNames []string,
) ([]*discovery.Resource, model.XdsLogDetails) {
var routeConfigurations model.Resources
efw := req.Push.EnvoyFilters(node)
hit, miss := 0, 0
switch node.Type {
case model.SidecarProxy:
vHostCache := make(map[int][]*route.VirtualHost)
// dependent envoyfilters' key, calculate in front once to prevent calc for each route.
envoyfilterKeys := efw.Keys()
for _, routeName := range routeNames {
rc, cached := configgen.buildSidecarOutboundHTTPRouteConfig(node, req, routeName, vHostCache, efw, envoyfilterKeys)
if cached && !features.EnableUnsafeAssertions {
hit++
} else {
miss++
}
if rc == nil {
emptyRoute := &route.RouteConfiguration{
Name: routeName,
VirtualHosts: []*route.VirtualHost{},
ValidateClusters: proto.BoolFalse,
}
rc = &discovery.Resource{
Name: routeName,
Resource: util.MessageToAny(emptyRoute),
}
}
routeConfigurations = append(routeConfigurations, rc)
}
case model.Router:
for _, routeName := range routeNames {
rc := configgen.buildGatewayHTTPRouteConfig(node, req.Push, routeName)
if rc != nil {
rc = envoyfilter.ApplyRouteConfigurationPatches(networking.EnvoyFilter_GATEWAY, node, efw, rc)
resource := &discovery.Resource{
Name: routeName,
Resource: util.MessageToAny(rc),
}
routeConfigurations = append(routeConfigurations, resource)
}
}
}
if !features.EnableRDSCaching {
return routeConfigurations, model.DefaultXdsLogDetails
}
return routeConfigurations, model.XdsLogDetails{AdditionalInfo: fmt.Sprintf("cached:%v/%v", hit, hit+miss)}
}
// buildSidecarInboundHTTPRouteConfig builds the route config with a single wildcard virtual host on the inbound path
// TODO: trace decorators, inbound timeouts
func buildSidecarInboundHTTPRouteConfig(lb *ListenerBuilder, cc inboundChainConfig) *route.RouteConfiguration {
traceOperation := telemetry.TraceOperation(string(cc.telemetryMetadata.InstanceHostname), int(cc.port.Port))
defaultRoute := istio_route.BuildDefaultHTTPInboundRoute(cc.clusterName, traceOperation)
inboundVHost := &route.VirtualHost{
Name: inboundVirtualHostPrefix + strconv.Itoa(int(cc.port.Port)), // Format: "inbound|http|%d"
Domains: []string{"*"},
Routes: []*route.Route{defaultRoute},
}
r := &route.RouteConfiguration{
Name: cc.clusterName,
VirtualHosts: []*route.VirtualHost{inboundVHost},
ValidateClusters: proto.BoolFalse,
}
efw := lb.push.EnvoyFilters(lb.node)
r = envoyfilter.ApplyRouteConfigurationPatches(networking.EnvoyFilter_SIDECAR_INBOUND, lb.node, efw, r)
return r
}
// buildSidecarOutboundHTTPRouteConfig builds an outbound HTTP Route for sidecar.
// Based on port, will determine all virtual hosts that listen on the port.
func (configgen *ConfigGeneratorImpl) buildSidecarOutboundHTTPRouteConfig(
node *model.Proxy,
req *model.PushRequest,
routeName string,
vHostCache map[int][]*route.VirtualHost,
efw *model.EnvoyFilterWrapper,
efKeys []string,
) (*discovery.Resource, bool) {
var virtualHosts []*route.VirtualHost
listenerPort := 0
useSniffing := false
var err error
if features.EnableProtocolSniffingForOutbound &&
!strings.HasPrefix(routeName, model.UnixAddressPrefix) {
index := strings.IndexRune(routeName, ':')
if index != -1 {
useSniffing = true
}
listenerPort, err = strconv.Atoi(routeName[index+1:])
} else {
listenerPort, err = strconv.Atoi(routeName)
}
if err != nil {
// we have a port whose name is http_proxy or unix:///foo/bar
// check for both.
if routeName != model.RDSHttpProxy && !strings.HasPrefix(routeName, model.UnixAddressPrefix) {
// TODO: This is potentially one place where envoyFilter ADD operation can be helpful if the
// user wants to ship a custom RDS. But at this point, the match semantics are murky. We have no
// object to match upon. This needs more thought. For now, we will continue to return nil for
// unknown routes
return nil, false
}
}
var routeCache *istio_route.Cache
var resource *discovery.Resource
cacheHit := false
if useSniffing && listenerPort != 0 {
// Check if we have already computed the list of all virtual hosts for this port
// If so, then we simply have to return only the relevant virtual hosts for
// this listener's host:port
if vhosts, exists := vHostCache[listenerPort]; exists {
virtualHosts = getVirtualHostsForSniffedServicePort(vhosts, routeName)
cacheHit = true
}
}
if !cacheHit {
virtualHosts, resource, routeCache = BuildSidecarOutboundVirtualHosts(node, req.Push, routeName, listenerPort, efKeys, configgen.Cache)
if resource != nil {
return resource, true
}
if listenerPort > 0 {
// only cache for tcp ports and not for uds
vHostCache[listenerPort] = virtualHosts
}
// FIXME: This will ignore virtual services with hostnames that do not match any service in the registry
// per api spec, these hostnames + routes should appear in the virtual hosts (think bookinfo.com and
// productpage.ns1.svc.cluster.local). See the TODO in BuildSidecarOutboundVirtualHosts for the right solution
if useSniffing {
virtualHosts = getVirtualHostsForSniffedServicePort(virtualHosts, routeName)
}
}
util.SortVirtualHosts(virtualHosts)
if !useSniffing {
virtualHosts = append(virtualHosts, buildCatchAllVirtualHost(node))
}
out := &route.RouteConfiguration{
Name: routeName,
VirtualHosts: virtualHosts,
ValidateClusters: proto.BoolFalse,
}
// apply envoy filter patches
out = envoyfilter.ApplyRouteConfigurationPatches(networking.EnvoyFilter_SIDECAR_OUTBOUND, node, efw, out)
resource = &discovery.Resource{
Name: out.Name,
Resource: util.MessageToAny(out),
}
if features.EnableRDSCaching && routeCache != nil {
configgen.Cache.Add(routeCache, req, resource)
}
return resource, false
}
// TODO: merge with IstioEgressListenerWrapper.selectVirtualServices
// selectVirtualServices selects the virtual services by matching given services' host names.
func selectVirtualServices(virtualServices []config.Config, servicesByName map[host.Name]*model.Service) []config.Config {
out := make([]config.Config, 0)
for _, c := range virtualServices {
rule := c.Spec.(*networking.VirtualService)
var match bool
// Selection algorithm:
// virtualservices have a list of hosts in the API spec
// if any host in the list matches one service hostname, select the virtual service
// and break out of the loop.
for _, h := range rule.Hosts {
// TODO: This is a bug. VirtualServices can have many hosts
// while the user might be importing only a single host
// We need to generate a new VirtualService with just the matched host
if servicesByName[host.Name(h)] != nil {
match = true
break
}
for svcHost := range servicesByName {
if host.Name(h).Matches(svcHost) {
match = true
break
}
}
if match {
break
}
}
if match {
out = append(out, c)
}
}
return out
}
func BuildSidecarOutboundVirtualHosts(node *model.Proxy, push *model.PushContext,
routeName string,
listenerPort int,
efKeys []string,
xdsCache model.XdsCache,
) ([]*route.VirtualHost, *discovery.Resource, *istio_route.Cache) {
var virtualServices []config.Config
var services []*model.Service
// Get the services from the egress listener. When sniffing is enabled, we send
// route name as foo.bar.com:8080 which is going to match against the wildcard
// egress listener only. A route with sniffing would not have been generated if there
// was a sidecar with explicit port (and hence protocol declaration). A route with
// sniffing is generated only in the case of the catch all egress listener.
egressListener := node.SidecarScope.GetEgressListenerForRDS(listenerPort, routeName)
// We should never be getting a nil egress listener because the code that setup this RDS
// call obviously saw an egress listener
if egressListener == nil {
return nil, nil, nil
}
services = egressListener.Services()
// To maintain correctness, we should only use the virtualservices for
// this listener and not all virtual services accessible to this proxy.
virtualServices = egressListener.VirtualServices()
// When generating RDS for ports created via the SidecarScope, we treat ports as HTTP proxy style ports
// if ports protocol is HTTP_PROXY.
if egressListener.IstioListener != nil && egressListener.IstioListener.Port != nil &&
protocol.Parse(egressListener.IstioListener.Port.Protocol) == protocol.HTTP_PROXY {
listenerPort = 0
}
servicesByName := make(map[host.Name]*model.Service)
for _, svc := range services {
if listenerPort == 0 {
// Take all ports when listen port is 0 (http_proxy or uds)
// Expect virtualServices to resolve to right port
servicesByName[svc.Hostname] = svc
} else if svcPort, exists := svc.Ports.GetByPort(listenerPort); exists {
servicesByName[svc.Hostname] = &model.Service{
Hostname: svc.Hostname,
DefaultAddress: svc.GetAddressForProxy(node),
MeshExternal: svc.MeshExternal,
Resolution: svc.Resolution,
Ports: []*model.Port{svcPort},
Attributes: model.ServiceAttributes{
Namespace: svc.Attributes.Namespace,
ServiceRegistry: svc.Attributes.ServiceRegistry,
},
}
}
}
var routeCache *istio_route.Cache
if listenerPort > 0 {
services = make([]*model.Service, 0, len(servicesByName))
// sort services
for _, svc := range servicesByName {
services = append(services, svc)
}
sort.SliceStable(services, func(i, j int) bool {
return services[i].Hostname <= services[j].Hostname
})
routeCache = &istio_route.Cache{
RouteName: routeName,
ProxyVersion: node.Metadata.IstioVersion,
ClusterID: string(node.Metadata.ClusterID),
DNSDomain: node.DNSDomain,
DNSCapture: bool(node.Metadata.DNSCapture),
DNSAutoAllocate: bool(node.Metadata.DNSAutoAllocate),
AllowAny: util.IsAllowAnyOutbound(node),
ListenerPort: listenerPort,
Services: services,
VirtualServices: virtualServices,
DelegateVirtualServices: push.DelegateVirtualServicesConfigKey(virtualServices),
EnvoyFilterKeys: efKeys,
}
}
// This is hack to keep consistent with previous behavior.
if listenerPort != 80 {
// only select virtualServices that matches a service
virtualServices = selectVirtualServices(virtualServices, servicesByName)
}
// Get list of virtual services bound to the mesh gateway
virtualHostWrappers := istio_route.BuildSidecarVirtualHostWrapper(routeCache, node, push, servicesByName, virtualServices, listenerPort)
resource, exist := xdsCache.Get(routeCache)
if exist && !features.EnableUnsafeAssertions {
return nil, resource, routeCache
}
vHostPortMap := make(map[int][]*route.VirtualHost)
vhosts := sets.Set{}
vhdomains := sets.Set{}
knownFQDN := sets.Set{}
buildVirtualHost := func(hostname string, vhwrapper istio_route.VirtualHostWrapper, svc *model.Service) *route.VirtualHost {
name := util.DomainName(hostname, vhwrapper.Port)
if duplicateVirtualHost(name, vhosts) {
// This means this virtual host has caused duplicate virtual host name.
var msg string
if svc == nil {
msg = fmt.Sprintf("duplicate domain from virtual service: %s", name)
} else {
msg = fmt.Sprintf("duplicate domain from service: %s", name)
}
push.AddMetric(model.DuplicatedDomains, name, node.ID, msg)
return nil
}
var domains []string
var altHosts []string
if svc == nil {
domains = []string{util.IPv6Compliant(hostname), name}
} else {
domains, altHosts = generateVirtualHostDomains(svc, vhwrapper.Port, node)
}
dl := len(domains)
domains = dedupeDomains(domains, vhdomains, altHosts, knownFQDN)
if dl != len(domains) {
var msg string
if svc == nil {
msg = fmt.Sprintf("duplicate domain from virtual service: %s", name)
} else {
msg = fmt.Sprintf("duplicate domain from service: %s", name)
}
// This means this virtual host has caused duplicate virtual host domain.
push.AddMetric(model.DuplicatedDomains, name, node.ID, msg)
}
if len(domains) > 0 {
return &route.VirtualHost{
Name: name,
Domains: domains,
Routes: vhwrapper.Routes,
IncludeRequestAttemptCount: true,
}
}
return nil
}
for _, virtualHostWrapper := range virtualHostWrappers {
for _, svc := range virtualHostWrapper.Services {
name := util.DomainName(string(svc.Hostname), virtualHostWrapper.Port)
knownFQDN.InsertAll(name, string(svc.Hostname))
}
}
for _, virtualHostWrapper := range virtualHostWrappers {
// If none of the routes matched by source, skip this virtual host
if len(virtualHostWrapper.Routes) == 0 {
continue
}
virtualHosts := make([]*route.VirtualHost, 0, len(virtualHostWrapper.VirtualServiceHosts)+len(virtualHostWrapper.Services))
for _, hostname := range virtualHostWrapper.VirtualServiceHosts {
if vhost := buildVirtualHost(hostname, virtualHostWrapper, nil); vhost != nil {
virtualHosts = append(virtualHosts, vhost)
}
}
for _, svc := range virtualHostWrapper.Services {
if vhost := buildVirtualHost(string(svc.Hostname), virtualHostWrapper, svc); vhost != nil {
virtualHosts = append(virtualHosts, vhost)
}
}
vHostPortMap[virtualHostWrapper.Port] = append(vHostPortMap[virtualHostWrapper.Port], virtualHosts...)
}
var out []*route.VirtualHost
if listenerPort == 0 {
out = mergeAllVirtualHosts(vHostPortMap)
} else {
out = vHostPortMap[listenerPort]
}
return out, nil, routeCache
}
// duplicateVirtualHost checks whether the virtual host with the same name exists in the route.
func duplicateVirtualHost(vhost string, vhosts sets.Set) bool {
if vhosts.Contains(vhost) {
return true
}
vhosts.Insert(vhost)
return false
}
// dedupeDomains removes the duplicate domains from the passed in domains.
func dedupeDomains(domains []string, vhdomains sets.Set, expandedHosts []string, knownFQDNs sets.Set) []string {
temp := domains[:0]
for _, d := range domains {
if vhdomains.Contains(d) {
continue
}
// Check if the domain is an "expanded" host, and its also a known FQDN
// This prevents a case where a domain like "foo.com.cluster.local" gets expanded to "foo.com", overwriting
// the real "foo.com"
// This works by providing a list of domains that were added as expanding the DNS domain as part of expandedHosts,
// and a list of known unexpanded FQDNs to compare against
if util.ListContains(expandedHosts, d) && knownFQDNs.Contains(d) { // O(n) search, but n is at most 10
continue
}
temp = append(temp, d)
vhdomains.Insert(d)
}
return temp
}
// Returns the set of virtual hosts that correspond to the listener that has HTTP protocol detection
// setup. This listener should only get the virtual hosts that correspond to this service+port and not
// all virtual hosts that are usually supplied for 0.0.0.0:PORT.
func getVirtualHostsForSniffedServicePort(vhosts []*route.VirtualHost, routeName string) []*route.VirtualHost {
var virtualHosts []*route.VirtualHost
for _, vh := range vhosts {
for _, domain := range vh.Domains {
if domain == routeName {
virtualHosts = append(virtualHosts, vh)
break
}
}
}
if len(virtualHosts) == 0 {
return virtualHosts
}
if len(virtualHosts) == 1 {
virtualHosts[0].Domains = []string{"*"}
return virtualHosts
}
if features.EnableUnsafeAssertions {
panic(fmt.Sprintf("unexpectedly matched multiple virtual hosts for %v: %v", routeName, virtualHosts))
}
return virtualHosts
}
// generateVirtualHostDomains generates the set of domain matches for a service being accessed from
// a proxy node
func generateVirtualHostDomains(service *model.Service, port int, node *model.Proxy) ([]string, []string) {
altHosts := GenerateAltVirtualHosts(string(service.Hostname), port, node.DNSDomain)
domains := []string{util.IPv6Compliant(string(service.Hostname)), util.DomainName(string(service.Hostname), port)}
domains = append(domains, altHosts...)
if service.Resolution == model.Passthrough &&
service.Attributes.ServiceRegistry == provider.Kubernetes {
for _, domain := range domains {
domains = append(domains, wildcardDomainPrefix+domain)
}
}
svcAddr := service.GetAddressForProxy(node)
if len(svcAddr) > 0 && svcAddr != constants.UnspecifiedIP {
domains = append(domains, util.IPv6Compliant(svcAddr), util.DomainName(svcAddr, port))
}
return domains, altHosts
}
// GenerateAltVirtualHosts given a service and a port, generates all possible HTTP Host headers.
// For example, a service of the form foo.local.campus.net on port 80, with local domain "local.campus.net"
// could be accessed as http://foo:80 within the .local network, as http://foo.local:80 (by other clients
// in the campus.net domain), as http://foo.local.campus:80, etc.
// NOTE: When a sidecar in remote.campus.net domain is talking to foo.local.campus.net,
// we should only generate foo.local, foo.local.campus, etc (and never just "foo").
//
// - Given foo.local.campus.net on proxy domain local.campus.net, this function generates
// foo:80, foo.local:80, foo.local.campus:80, with and without ports. It will not generate
// foo.local.campus.net (full hostname) since its already added elsewhere.
//
// - Given foo.local.campus.net on proxy domain remote.campus.net, this function generates
// foo.local:80, foo.local.campus:80
//
// - Given foo.local.campus.net on proxy domain "" or proxy domain example.com, this
// function returns nil
func GenerateAltVirtualHosts(hostname string, port int, proxyDomain string) []string {
if strings.Contains(proxyDomain, ".svc.") {
return generateAltVirtualHostsForKubernetesService(hostname, port, proxyDomain)
}
var vhosts []string
uniqueHostnameParts, sharedDNSDomainParts := getUniqueAndSharedDNSDomain(hostname, proxyDomain)
// If there is no shared DNS name (e.g., foobar.com service on local.net proxy domain)
// do not generate any alternate virtual host representations
if len(sharedDNSDomainParts) == 0 {
return nil
}
uniqueHostname := strings.Join(uniqueHostnameParts, ".")
// Add the uniqueHost.
vhosts = append(vhosts, uniqueHostname, util.DomainName(uniqueHostname, port))
if len(uniqueHostnameParts) == 2 {
// This is the case of uniqHostname having namespace already.
dnsHostName := uniqueHostname + "." + sharedDNSDomainParts[0]
vhosts = append(vhosts, dnsHostName, util.DomainName(dnsHostName, port))
}
return vhosts
}
func generateAltVirtualHostsForKubernetesService(hostname string, port int, proxyDomain string) []string {
id := strings.Index(proxyDomain, ".svc.")
ih := strings.Index(hostname, ".svc.")
if ih > 0 { // Proxy and service hostname are in kube
ns := strings.Index(hostname, ".")
if ns+1 >= len(hostname) || ns+1 > ih {
// Invalid domain
return nil
}
if hostname[ns+1:ih] == proxyDomain[:id] {
// Same namespace
return []string{
hostname[:ns],
util.DomainName(hostname[:ns], port),
hostname[:ih] + ".svc",
util.DomainName(hostname[:ih]+".svc", port),
hostname[:ih],
util.DomainName(hostname[:ih], port),
}
}
// Different namespace
return []string{
hostname[:ih],
util.DomainName(hostname[:ih], port),
hostname[:ih] + ".svc",
util.DomainName(hostname[:ih]+".svc", port),
}
}
// Proxy is in k8s, but service isn't. No alt hosts
return nil
}
// mergeAllVirtualHosts across all ports. On routes for ports other than port 80,
// virtual hosts without an explicit port suffix (IP:PORT) should not be added
func mergeAllVirtualHosts(vHostPortMap map[int][]*route.VirtualHost) []*route.VirtualHost {
var virtualHosts []*route.VirtualHost
for p, vhosts := range vHostPortMap {
if p == 80 {
virtualHosts = append(virtualHosts, vhosts...)
} else {
for _, vhost := range vhosts {
var newDomains []string
for _, domain := range vhost.Domains {
if strings.Contains(domain, ":") {
newDomains = append(newDomains, domain)
}
}
if len(newDomains) > 0 {
vhost.Domains = newDomains
virtualHosts = append(virtualHosts, vhost)
}
}
}
}
return virtualHosts
}
// reverseArray returns its argument string array reversed
func reverseArray(r []string) []string {
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return r
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// getUniqueAndSharedDNSDomain computes the unique and shared DNS suffix from a FQDN service name and
// the proxy's local domain with namespace. This is especially useful in Kubernetes environments, where
// a two services can have same name in different namespaces (e.g., foo.ns1.svc.cluster.local,
// foo.ns2.svc.cluster.local). In this case, if the proxy is in ns2.svc.cluster.local, then while
// generating alt virtual hosts for service foo.ns1 for the sidecars in ns2 namespace, we should generate
// foo.ns1, foo.ns1.svc, foo.ns1.svc.cluster.local and should not generate a virtual host called "foo" for
// foo.ns1 service.
// So given foo.ns1.svc.cluster.local and ns2.svc.cluster.local, this function will return
// foo.ns1, and svc.cluster.local.
// When given foo.ns2.svc.cluster.local and ns2.svc.cluster.local, this function will return
// foo, ns2.svc.cluster.local.
func getUniqueAndSharedDNSDomain(fqdnHostname, proxyDomain string) (partsUnique []string, partsShared []string) {
// split them by the dot and reverse the arrays, so that we can
// start collecting the shared bits of DNS suffix.
// E.g., foo.ns1.svc.cluster.local -> local,cluster,svc,ns1,foo
// ns2.svc.cluster.local -> local,cluster,svc,ns2
partsFQDN := strings.Split(fqdnHostname, ".")
partsProxyDomain := strings.Split(proxyDomain, ".")
partsFQDNInReverse := reverseArray(partsFQDN)
partsProxyDomainInReverse := reverseArray(partsProxyDomain)
var sharedSuffixesInReverse []string // pieces shared between proxy and svc. e.g., local,cluster,svc
for i := 0; i < min(len(partsFQDNInReverse), len(partsProxyDomainInReverse)); i++ {
if partsFQDNInReverse[i] == partsProxyDomainInReverse[i] {
sharedSuffixesInReverse = append(sharedSuffixesInReverse, partsFQDNInReverse[i])
} else {
break
}
}
if len(sharedSuffixesInReverse) == 0 {
partsUnique = partsFQDN
} else {
// get the non shared pieces (ns1, foo) and reverse Array
partsUnique = reverseArray(partsFQDNInReverse[len(sharedSuffixesInReverse):])
partsShared = reverseArray(sharedSuffixesInReverse)
}
return
}
func buildCatchAllVirtualHost(node *model.Proxy) *route.VirtualHost {
if util.IsAllowAnyOutbound(node) {
egressCluster := util.PassthroughCluster
notimeout := durationpb.New(0)
// no need to check for nil value as the previous if check has checked
if node.SidecarScope.OutboundTrafficPolicy.EgressProxy != nil {
// user has provided an explicit destination for all the unknown traffic.
// build a cluster out of this destination
egressCluster = istio_route.GetDestinationCluster(node.SidecarScope.OutboundTrafficPolicy.EgressProxy,
nil, 0)
}
routeAction := &route.RouteAction{
ClusterSpecifier: &route.RouteAction_Cluster{Cluster: egressCluster},
// Disable timeout instead of assuming some defaults.
Timeout: notimeout,
// Use deprecated value for now as the replacement MaxStreamDuration has some regressions.
// nolint: staticcheck
MaxGrpcTimeout: notimeout,
}
return &route.VirtualHost{
Name: util.Passthrough,
Domains: []string{"*"},
Routes: []*route.Route{
{
Name: util.Passthrough,
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{Prefix: "/"},
},
Action: &route.Route_Route{
Route: routeAction,
},
},
},
IncludeRequestAttemptCount: true,
}
}
return &route.VirtualHost{
Name: util.BlackHole,
Domains: []string{"*"},
Routes: []*route.Route{
{
Name: util.BlackHole,
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{Prefix: "/"},
},
Action: &route.Route_DirectResponse{
DirectResponse: &route.DirectResponseAction{
Status: 502,
},
},
},
},
IncludeRequestAttemptCount: true,
}
}