// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package troubleshoot
import (
"fmt"
envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3"
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
"google.golang.org/protobuf/proto"
"github.com/hashicorp/consul/envoyextensions/extensioncommon"
)
type UpstreamIP struct {
IPs [ ] string
IsVirtual bool
ClusterNames map [ string ] struct { }
}
func ( t * Troubleshoot ) GetUpstreams ( ) ( [ ] string , [ ] UpstreamIP , error ) {
upstream_envoy_ids := [ ] string { }
upstream_ips := [ ] UpstreamIP { }
err := t . GetEnvoyConfigDump ( )
if err != nil {
return nil , nil , err
}
for _ , cfg := range t . envoyConfigDump . Configs {
switch cfg . TypeUrl {
case listenersType :
lcd := & envoy_admin_v3 . ListenersConfigDump { }
err := proto . Unmarshal ( cfg . GetValue ( ) , lcd )
if err != nil {
return nil , nil , err
}
for _ , listener := range lcd . GetDynamicListeners ( ) {
eid := envoyID ( listener . Name )
if eid != "" && eid != "public_listener" &&
eid != "outbound_listener" && eid != "inbound_listener" {
upstream_envoy_ids = append ( upstream_envoy_ids , eid )
} else if eid == "outbound_listener" {
l := & envoy_listener_v3 . Listener { }
err = proto . Unmarshal ( listener . GetActiveState ( ) . GetListener ( ) . GetValue ( ) , l )
if err != nil {
return nil , nil , err
}
upstream_ips , err = getUpstreamIPsFromFilterChain ( l . GetFilterChains ( ) , t . envoyConfigDump )
if err != nil {
return nil , nil , err
}
}
}
}
}
return upstream_envoy_ids , upstream_ips , nil
}
func getUpstreamIPsFromFilterChain ( filterChains [ ] * envoy_listener_v3 . FilterChain ,
cfgDump * envoy_admin_v3 . ConfigDump ) ( [ ] UpstreamIP , error ) {
var err error
if filterChains == nil {
return [ ] UpstreamIP { } , nil
}
upstreamIPs := [ ] UpstreamIP { }
for _ , fc := range filterChains {
if fc . GetFilters ( ) == nil {
continue
}
if fc . GetFilterChainMatch ( ) == nil {
continue
}
if fc . GetFilterChainMatch ( ) . GetPrefixRanges ( ) == nil {
continue
}
cidrs := fc . GetFilterChainMatch ( ) . GetPrefixRanges ( )
ips := [ ] string { }
for _ , cidr := range cidrs {
ips = append ( ips , cidr . AddressPrefix )
}
for _ , filter := range fc . GetFilters ( ) {
isVirtual := false
if filter . GetTypedConfig ( ) == nil {
continue
}
clusterNames := map [ string ] struct { } { }
if config := envoy_resource_v3 . GetHTTPConnectionManager ( filter ) ; config != nil {
isVirtual = true
cfg := config . GetRouteConfig ( )
if cfg != nil {
clusterNames = extensioncommon . RouteClusterNames ( cfg )
} else {
// If there are no route configs, look for RDS.
routeName := config . GetRds ( ) . GetRouteConfigName ( )
if routeName != "" {
clusterNames , err = getClustersFromRoutes ( routeName , cfgDump )
if err != nil {
return nil , fmt . Errorf ( "error in getting clusters for route %q: %w" , routeName , err )
}
}
}
}
if config := extensioncommon . GetTCPProxy ( filter ) ; config != nil {
if config . GetCluster ( ) != "" {
clusterNames [ config . GetCluster ( ) ] = struct { } { }
}
}
upstreamIPs = append ( upstreamIPs , UpstreamIP {
IPs : ips ,
IsVirtual : isVirtual ,
ClusterNames : clusterNames ,
} )
}
}
return upstreamIPs , nil
}
func getClustersFromRoutes ( routeName string , cfgDump * envoy_admin_v3 . ConfigDump ) ( map [ string ] struct { } , error ) {
for _ , cfg := range cfgDump . Configs {
switch cfg . TypeUrl {
case routesType :
rcd := & envoy_admin_v3 . RoutesConfigDump { }
err := proto . Unmarshal ( cfg . GetValue ( ) , rcd )
if err != nil {
return nil , err
}
for _ , route := range rcd . GetDynamicRouteConfigs ( ) {
routeConfig := & envoy_route_v3 . RouteConfiguration { }
err = proto . Unmarshal ( route . GetRouteConfig ( ) . GetValue ( ) , routeConfig )
if err != nil {
return nil , err
}
if routeConfig . GetName ( ) == routeName {
return extensioncommon . RouteClusterNames ( routeConfig ) , nil
}
}
}
}
return nil , nil
}