diff --git a/command/troubleshoot/upstreams/troubleshoot_upstreams.go b/command/troubleshoot/upstreams/troubleshoot_upstreams.go index 65571b1fb..1bf34d0e7 100644 --- a/command/troubleshoot/upstreams/troubleshoot_upstreams.go +++ b/command/troubleshoot/upstreams/troubleshoot_upstreams.go @@ -68,15 +68,20 @@ func (c *cmd) Run(args []string) int { c.UI.Error("error generating troubleshoot client: " + err.Error()) return 1 } - upstreams, err := t.GetUpstreams() + envoyIDs, upstreamIPs, err := t.GetUpstreams() if err != nil { c.UI.Error("error calling GetUpstreams: " + err.Error()) return 1 } - for _, u := range upstreams { + for _, u := range envoyIDs { c.UI.Output(u) } + + for _, u := range upstreamIPs { + c.UI.Output(fmt.Sprintf("%+v %v %+v", u.IPs, u.IsVirtual, u.ClusterNames)) + } + return 0 } diff --git a/troubleshoot/proxy/testdata/listeners.json b/troubleshoot/proxy/testdata/listeners.json new file mode 100644 index 000000000..3cbcdebc4 --- /dev/null +++ b/troubleshoot/proxy/testdata/listeners.json @@ -0,0 +1,90 @@ +{ + "name": "outbound_listener:127.0.0.1:15001", + "address": { + "socket_address": { + "address": "127.0.0.1", + "port_value": 15001 + } + }, + "filter_chains": [ + { + "filter_chain_match": { + "prefix_ranges": [ + { + "address_prefix": "10.244.0.63", + "prefix_len": 32 + }, + { + "address_prefix": "10.244.0.64", + "prefix_len": 32 + } + ] + }, + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "stat_prefix": "upstream.foo.default.default.dc1", + "cluster": "passthrough~foo.default.dc1.internal.dc1.consul" + } + } + ] + }, + { + "filter_chain_match": { + "prefix_ranges": [ + { + "address_prefix": "10.96.5.96", + "prefix_len": 32 + }, + { + "address_prefix": "240.0.0.1", + "prefix_len": 32 + } + ] + }, + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "stat_prefix": "upstream.foo.default.default.dc1", + "route_config": { + "name": "foo", + "virtual_hosts": [ + { + "name": "foo.default.default.dc1", + "domains": [ + "*" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "foo.default.dc1.internal.dc1.consul" + } + } + ] + } + ] + }, + "http_filters": [ + { + "name": "envoy.filters.http.router", + "typed_config": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "random_sampling": {} + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/troubleshoot/proxy/troubleshoot_proxy.go b/troubleshoot/proxy/troubleshoot_proxy.go index cbb83deb0..7adcced33 100644 --- a/troubleshoot/proxy/troubleshoot_proxy.go +++ b/troubleshoot/proxy/troubleshoot_proxy.go @@ -5,17 +5,18 @@ import ( "net" envoy_admin_v3 "github.com/envoyproxy/go-control-plane/envoy/admin/v3" + "github.com/hashicorp/consul/api" "github.com/hashicorp/go-multierror" - "google.golang.org/protobuf/proto" ) const ( - listeners string = "type.googleapis.com/envoy.admin.v3.ListenersConfigDump" - clusters string = "type.googleapis.com/envoy.admin.v3.ClustersConfigDump" - routes string = "type.googleapis.com/envoy.admin.v3.RoutesConfigDump" - endpoints string = "type.googleapis.com/envoy.admin.v3.EndpointsConfigDump" - bootstrap string = "type.googleapis.com/envoy.admin.v3.BootstrapConfigDump" + listeners string = "type.googleapis.com/envoy.admin.v3.ListenersConfigDump" + clusters string = "type.googleapis.com/envoy.admin.v3.ClustersConfigDump" + routes string = "type.googleapis.com/envoy.admin.v3.RoutesConfigDump" + endpoints string = "type.googleapis.com/envoy.admin.v3.EndpointsConfigDump" + bootstrap string = "type.googleapis.com/envoy.admin.v3.BootstrapConfigDump" + httpConnManager string = "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" ) type Troubleshoot struct { @@ -95,35 +96,3 @@ func (t *Troubleshoot) RunAllTests(envoyID string) ([]string, error) { } return output, resultErr } - -func (t *Troubleshoot) GetUpstreams() ([]string, error) { - - upstreams := []string{} - - err := t.GetEnvoyConfigDump() - if err != nil { - return nil, err - } - - for _, cfg := range t.envoyConfigDump.Configs { - switch cfg.TypeUrl { - case listeners: - lcd := &envoy_admin_v3.ListenersConfigDump{} - - err := proto.Unmarshal(cfg.GetValue(), lcd) - if err != nil { - return nil, err - } - - for _, listener := range lcd.GetDynamicListeners() { - upstream := envoyID(listener.Name) - if upstream != "" && upstream != "public_listener" && - upstream != "outbound_listener" && - upstream != "inbound_listener" { - upstreams = append(upstreams, upstream) - } - } - } - } - return upstreams, nil -} diff --git a/troubleshoot/proxy/upstreams.go b/troubleshoot/proxy/upstreams.go new file mode 100644 index 000000000..3c22daa60 --- /dev/null +++ b/troubleshoot/proxy/upstreams.go @@ -0,0 +1,120 @@ +package troubleshoot + +import ( + 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_resource_v3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3" + + "github.com/hashicorp/consul/envoyextensions/extensioncommon" + "google.golang.org/protobuf/proto" +) + +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 listeners: + 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()) + if err != nil { + return nil, nil, err + } + } + } + } + } + return upstream_envoy_ids, upstream_ips, nil +} + +func getUpstreamIPsFromFilterChain(filterChains []*envoy_listener_v3.FilterChain) ([]UpstreamIP, 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() + + clusterNames = extensioncommon.RouteClusterNames(cfg) + } + 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 +} diff --git a/troubleshoot/proxy/upstreams_test.go b/troubleshoot/proxy/upstreams_test.go new file mode 100644 index 000000000..2ae5d9fe2 --- /dev/null +++ b/troubleshoot/proxy/upstreams_test.go @@ -0,0 +1,53 @@ +package troubleshoot + +import ( + "io" + "os" + "testing" + + envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/encoding/protojson" +) + +func TestGetUpstreamIPsFromFilterChain(t *testing.T) { + file, err := os.Open("testdata/listeners.json") + require.NoError(t, err) + jsonBytes, err := io.ReadAll(file) + require.NoError(t, err) + + expected := []UpstreamIP{ + { + IPs: []string{ + "10.244.0.63", + "10.244.0.64", + }, + IsVirtual: false, + ClusterNames: map[string]struct{}{ + "passthrough~foo.default.dc1.internal.dc1.consul": {}, + }, + }, + { + IPs: []string{ + "10.96.5.96", + "240.0.0.1", + }, + IsVirtual: true, + ClusterNames: map[string]struct{}{ + "foo.default.dc1.internal.dc1.consul": {}, + }, + }, + } + + var listener envoy_listener_v3.Listener + unmarshal := &protojson.UnmarshalOptions{ + DiscardUnknown: true, + } + err = unmarshal.Unmarshal(jsonBytes, &listener) + require.NoError(t, err) + + upstream_ips, err := getUpstreamIPsFromFilterChain(listener.GetFilterChains()) + require.NoError(t, err) + + require.Equal(t, expected, upstream_ips) +}