istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/writer/envoy/configdump/listener.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package configdump
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"reflect"
    21  	"sort"
    22  	"strings"
    23  	"text/tabwriter"
    24  
    25  	matcher "github.com/cncf/xds/go/xds/type/matcher/v3"
    26  	listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    27  	route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    28  	hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
    29  	tcp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
    30  	"sigs.k8s.io/yaml"
    31  
    32  	"istio.io/istio/istioctl/pkg/util/proto"
    33  	"istio.io/istio/pilot/pkg/networking/util"
    34  	"istio.io/istio/pilot/pkg/util/protoconv"
    35  	v3 "istio.io/istio/pilot/pkg/xds/v3"
    36  	"istio.io/istio/pkg/wellknown"
    37  )
    38  
    39  const (
    40  	// HTTPListener identifies a listener as being of HTTP type by the presence of an HTTP connection manager filter
    41  	HTTPListener = wellknown.HTTPConnectionManager
    42  
    43  	// TCPListener identifies a listener as being of TCP type by the presence of TCP proxy filter
    44  	TCPListener = wellknown.TCPProxy
    45  
    46  	IPMatcher = "type.googleapis.com/xds.type.matcher.v3.IPMatcher"
    47  )
    48  
    49  // ListenerFilter is used to pass filter information into listener based config writer print functions
    50  type ListenerFilter struct {
    51  	Address string
    52  	Port    uint32
    53  	Type    string
    54  	Verbose bool
    55  }
    56  
    57  // Verify returns true if the passed listener matches the filter fields
    58  func (l *ListenerFilter) Verify(listener *listener.Listener) bool {
    59  	if l.Address == "" && l.Port == 0 && l.Type == "" {
    60  		return true
    61  	}
    62  	if l.Address != "" {
    63  		addresses := retrieveListenerAdditionalAddresses(listener)
    64  		addresses = append(addresses, retrieveListenerAddress(listener))
    65  		found := false
    66  		for _, address := range addresses {
    67  			if strings.EqualFold(address, l.Address) {
    68  				found = true
    69  			}
    70  		}
    71  		if !found {
    72  			return false
    73  		}
    74  	}
    75  	if l.Port != 0 && retrieveListenerPort(listener) != l.Port {
    76  		return false
    77  	}
    78  	if l.Type != "" && !strings.EqualFold(retrieveListenerType(listener), l.Type) {
    79  		return false
    80  	}
    81  	return true
    82  }
    83  
    84  func getFilterChains(l *listener.Listener) []*listener.FilterChain {
    85  	res := l.FilterChains
    86  	if l.DefaultFilterChain != nil {
    87  		res = append(res, l.DefaultFilterChain)
    88  	}
    89  	return res
    90  }
    91  
    92  // retrieveListenerType classifies a Listener as HTTP|TCP|HTTP+TCP|UNKNOWN
    93  func retrieveListenerType(l *listener.Listener) string {
    94  	nHTTP := 0
    95  	nTCP := 0
    96  	for _, filterChain := range getFilterChains(l) {
    97  		for _, filter := range filterChain.GetFilters() {
    98  			if filter.Name == HTTPListener {
    99  				nHTTP++
   100  			} else if filter.Name == TCPListener {
   101  				if !strings.Contains(string(filter.GetTypedConfig().GetValue()), util.BlackHoleCluster) {
   102  					nTCP++
   103  				}
   104  			}
   105  		}
   106  	}
   107  
   108  	if nHTTP > 0 {
   109  		if nTCP == 0 {
   110  			return "HTTP"
   111  		}
   112  		return "HTTP+TCP"
   113  	} else if nTCP > 0 {
   114  		return "TCP"
   115  	}
   116  
   117  	return "UNKNOWN"
   118  }
   119  
   120  func retrieveListenerAddress(l *listener.Listener) string {
   121  	sockAddr := l.Address.GetSocketAddress()
   122  	if sockAddr != nil {
   123  		return sockAddr.Address
   124  	}
   125  
   126  	pipe := l.Address.GetPipe()
   127  	if pipe != nil {
   128  		return pipe.Path
   129  	}
   130  
   131  	return ""
   132  }
   133  
   134  func retrieveListenerAdditionalAddresses(l *listener.Listener) []string {
   135  	var addrs []string
   136  	socketAddresses := l.GetAdditionalAddresses()
   137  	for _, socketAddr := range socketAddresses {
   138  		addr := socketAddr.Address
   139  		addrs = append(addrs, addr.GetSocketAddress().Address)
   140  	}
   141  
   142  	return addrs
   143  }
   144  
   145  func retrieveListenerPort(l *listener.Listener) uint32 {
   146  	return l.Address.GetSocketAddress().GetPortValue()
   147  }
   148  
   149  func (c *ConfigWriter) PrintRemoteListenerSummary() error {
   150  	w, listeners, err := c.setupListenerConfigWriter()
   151  	if err != nil {
   152  		return err
   153  	}
   154  	// Sort by port, addr, type
   155  	sort.Slice(listeners, func(i, j int) bool {
   156  		if listeners[i].GetInternalListener() != nil && listeners[j].GetInternalListener() != nil {
   157  			return listeners[i].GetName() < listeners[j].GetName()
   158  		}
   159  		iPort := retrieveListenerPort(listeners[i])
   160  		jPort := retrieveListenerPort(listeners[j])
   161  		if iPort != jPort {
   162  			return iPort < jPort
   163  		}
   164  		iAddr := retrieveListenerAddress(listeners[i])
   165  		jAddr := retrieveListenerAddress(listeners[j])
   166  		if iAddr != jAddr {
   167  			return iAddr < jAddr
   168  		}
   169  		iType := retrieveListenerType(listeners[i])
   170  		jType := retrieveListenerType(listeners[j])
   171  		return iType < jType
   172  	})
   173  
   174  	fmt.Fprintln(w, "LISTENER\tCHAIN\tMATCH\tDESTINATION")
   175  	for _, l := range listeners {
   176  		chains := getFilterChains(l)
   177  		lname := "envoy://" + l.GetName()
   178  		// Avoid duplicating the listener and filter name
   179  		if l.GetInternalListener() != nil && len(chains) == 1 && chains[0].GetName() == lname {
   180  			lname = "internal"
   181  		}
   182  		for _, fc := range chains {
   183  
   184  			name := fc.GetName()
   185  			matches := newMatcher(fc, l)
   186  			destination := getFilterType(fc.GetFilters())
   187  			for _, match := range matches {
   188  				fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", lname, name, match, destination)
   189  			}
   190  		}
   191  	}
   192  	return w.Flush()
   193  }
   194  
   195  func newMatcher(fc *listener.FilterChain, l *listener.Listener) []string {
   196  	if l.FilterChainMatcher == nil {
   197  		return []string{getMatches(fc.GetFilterChainMatch())}
   198  	}
   199  	switch v := l.GetFilterChainMatcher().GetOnNoMatch().GetOnMatch().(type) {
   200  	case *matcher.Matcher_OnMatch_Action:
   201  		if v.Action.GetName() == fc.GetName() {
   202  			return []string{"UNMATCHED"}
   203  		}
   204  	case *matcher.Matcher_OnMatch_Matcher:
   205  		ms, f := recurse(fc.GetName(), v.Matcher)
   206  		if !f {
   207  			return []string{"NONE"}
   208  		}
   209  		return ms
   210  	}
   211  	ms, f := recurse(fc.GetName(), l.GetFilterChainMatcher())
   212  	if !f {
   213  		return []string{"NONE"}
   214  	}
   215  	return ms
   216  }
   217  
   218  func recurse(name string, match *matcher.Matcher) ([]string, bool) {
   219  	switch v := match.GetOnNoMatch().GetOnMatch().(type) {
   220  	case *matcher.Matcher_OnMatch_Action:
   221  		if v.Action.GetName() == name {
   222  			// TODO this only makes sense in context of a chain... do we need a way to give it context
   223  			return []string{"ANY"}, true
   224  		}
   225  	case *matcher.Matcher_OnMatch_Matcher:
   226  		ms, f := recurse(name, v.Matcher)
   227  		if !f {
   228  			return []string{"NONE"}, true
   229  		}
   230  		return ms, true
   231  	}
   232  	// TODO support list
   233  	n := match.GetMatcherTree().GetInput().GetName()
   234  
   235  	var m map[string]*matcher.Matcher_OnMatch
   236  	equality := "="
   237  	switch v := match.GetMatcherTree().GetTreeType().(type) {
   238  	case *matcher.Matcher_MatcherTree_ExactMatchMap:
   239  		m = v.ExactMatchMap.Map
   240  	case *matcher.Matcher_MatcherTree_PrefixMatchMap:
   241  		m = v.PrefixMatchMap.Map
   242  		equality = "^"
   243  	case *matcher.Matcher_MatcherTree_CustomMatch:
   244  		tc := v.CustomMatch.GetTypedConfig()
   245  		switch tc.TypeUrl {
   246  		case IPMatcher:
   247  			ip := protoconv.SilentlyUnmarshalAny[matcher.IPMatcher](tc)
   248  			m = map[string]*matcher.Matcher_OnMatch{}
   249  			for _, rm := range ip.GetRangeMatchers() {
   250  				for _, r := range rm.Ranges {
   251  					cidr := r.AddressPrefix
   252  					pl := r.PrefixLen.GetValue()
   253  					if pl != 32 && pl != 128 {
   254  						cidr += fmt.Sprintf("/%d", pl)
   255  					}
   256  					m[cidr] = rm.OnMatch
   257  				}
   258  			}
   259  		default:
   260  			panic("unhandled")
   261  		}
   262  	}
   263  	outputs := []string{}
   264  	for k, v := range m {
   265  		switch v := v.GetOnMatch().(type) {
   266  		case *matcher.Matcher_OnMatch_Action:
   267  			if v.Action.GetName() == name {
   268  				outputs = append(outputs, fmt.Sprintf("%v%v%v", n, equality, k))
   269  			}
   270  			continue
   271  		case *matcher.Matcher_OnMatch_Matcher:
   272  			children, match := recurse(name, v.Matcher)
   273  			if !match {
   274  				continue
   275  			}
   276  			for _, child := range children {
   277  				outputs = append(outputs, fmt.Sprintf("%v%v%v -> %v", n, equality, k, child))
   278  			}
   279  		}
   280  	}
   281  	return outputs, len(outputs) > 0
   282  }
   283  
   284  // PrintListenerSummary prints a summary of the relevant listeners in the config dump to the ConfigWriter stdout
   285  func (c *ConfigWriter) PrintListenerSummary(filter ListenerFilter) error {
   286  	w, listeners, err := c.setupListenerConfigWriter()
   287  	if err != nil {
   288  		return err
   289  	}
   290  
   291  	verifiedListeners := make([]*listener.Listener, 0, len(listeners))
   292  	for _, l := range listeners {
   293  		if filter.Verify(l) {
   294  			verifiedListeners = append(verifiedListeners, l)
   295  		}
   296  	}
   297  
   298  	// Sort by port, addr, type
   299  	sort.Slice(verifiedListeners, func(i, j int) bool {
   300  		iPort := retrieveListenerPort(verifiedListeners[i])
   301  		jPort := retrieveListenerPort(verifiedListeners[j])
   302  		if iPort != jPort {
   303  			return iPort < jPort
   304  		}
   305  		iAddr := retrieveListenerAddress(verifiedListeners[i])
   306  		jAddr := retrieveListenerAddress(verifiedListeners[j])
   307  		if iAddr != jAddr {
   308  			return iAddr < jAddr
   309  		}
   310  		iType := retrieveListenerType(verifiedListeners[i])
   311  		jType := retrieveListenerType(verifiedListeners[j])
   312  		return iType < jType
   313  	})
   314  
   315  	printStr := "ADDRESSES\tPORT"
   316  	if includeConfigType {
   317  		printStr = "NAME\t" + printStr
   318  	}
   319  	if filter.Verbose {
   320  		printStr += "\tMATCH\tDESTINATION"
   321  	} else {
   322  		printStr += "\tTYPE"
   323  	}
   324  	fmt.Fprintln(w, printStr)
   325  	for _, l := range verifiedListeners {
   326  		addresses := []string{retrieveListenerAddress(l)}
   327  		addresses = append(addresses, retrieveListenerAdditionalAddresses(l)...)
   328  		port := retrieveListenerPort(l)
   329  		if filter.Verbose {
   330  
   331  			matches := retrieveListenerMatches(l)
   332  			sort.Slice(matches, func(i, j int) bool {
   333  				return matches[i].destination > matches[j].destination
   334  			})
   335  			for _, match := range matches {
   336  				if includeConfigType {
   337  					name := fmt.Sprintf("listener/%s", l.Name)
   338  					fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n", name, strings.Join(addresses, ","), port, match.match, match.destination)
   339  				} else {
   340  					fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", strings.Join(addresses, ","), port, match.match, match.destination)
   341  				}
   342  			}
   343  		} else {
   344  			listenerType := retrieveListenerType(l)
   345  			if includeConfigType {
   346  				name := fmt.Sprintf("listener/%s", l.Name)
   347  				fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", name, strings.Join(addresses, ","), port, listenerType)
   348  			} else {
   349  				fmt.Fprintf(w, "%v\t%v\t%v\n", strings.Join(addresses, ","), port, listenerType)
   350  			}
   351  		}
   352  	}
   353  	return w.Flush()
   354  }
   355  
   356  type filterchain struct {
   357  	match       string
   358  	destination string
   359  }
   360  
   361  var (
   362  	plaintextHTTPALPNs = []string{"http/1.0", "http/1.1", "h2c"}
   363  	istioHTTPPlaintext = []string{"istio", "istio-http/1.0", "istio-http/1.1", "istio-h2"}
   364  	httpTLS            = []string{"http/1.0", "http/1.1", "h2c", "istio-http/1.0", "istio-http/1.1", "istio-h2"}
   365  	tcpTLS             = []string{"istio-peer-exchange", "istio"}
   366  
   367  	protDescrs = map[string][]string{
   368  		"App: HTTP TLS":         httpTLS,
   369  		"App: Istio HTTP Plain": istioHTTPPlaintext,
   370  		"App: TCP TLS":          tcpTLS,
   371  		"App: HTTP":             plaintextHTTPALPNs,
   372  	}
   373  )
   374  
   375  func retrieveListenerMatches(l *listener.Listener) []filterchain {
   376  	fChains := getFilterChains(l)
   377  	resp := make([]filterchain, 0, len(fChains))
   378  	for _, filterChain := range fChains {
   379  		fc := filterchain{
   380  			destination: getFilterType(filterChain.GetFilters()),
   381  			match:       getMatches(filterChain.FilterChainMatch),
   382  		}
   383  		resp = append(resp, fc)
   384  	}
   385  	return resp
   386  }
   387  
   388  func getMatches(f *listener.FilterChainMatch) string {
   389  	match := f
   390  	if match == nil {
   391  		match = &listener.FilterChainMatch{}
   392  	}
   393  	// filterChaince also has SuffixLen, SourceType, SourcePrefixRanges which are not rendered.
   394  
   395  	descrs := []string{}
   396  	if len(match.ServerNames) > 0 {
   397  		descrs = append(descrs, fmt.Sprintf("SNI: %s", strings.Join(match.ServerNames, ",")))
   398  	}
   399  	if len(match.TransportProtocol) > 0 {
   400  		descrs = append(descrs, fmt.Sprintf("Trans: %s", match.TransportProtocol))
   401  	}
   402  
   403  	if len(match.ApplicationProtocols) > 0 {
   404  		found := false
   405  		for protDescr, protocols := range protDescrs {
   406  			if reflect.DeepEqual(match.ApplicationProtocols, protocols) {
   407  				found = true
   408  				descrs = append(descrs, protDescr)
   409  				break
   410  			}
   411  		}
   412  		if !found {
   413  			descrs = append(descrs, fmt.Sprintf("App: %s", strings.Join(match.ApplicationProtocols, ",")))
   414  		}
   415  	}
   416  
   417  	port := ""
   418  	if match.DestinationPort != nil {
   419  		port = fmt.Sprintf(":%d", match.DestinationPort.GetValue())
   420  	}
   421  	if len(match.PrefixRanges) > 0 {
   422  		pf := []string{}
   423  		for _, p := range match.PrefixRanges {
   424  			pf = append(pf, fmt.Sprintf("%s/%d", p.AddressPrefix, p.GetPrefixLen().GetValue()))
   425  		}
   426  		descrs = append(descrs, fmt.Sprintf("Addr: %s%s", strings.Join(pf, ","), port))
   427  	} else if port != "" {
   428  		descrs = append(descrs, fmt.Sprintf("Addr: *%s", port))
   429  	}
   430  	if len(descrs) == 0 {
   431  		descrs = []string{"ALL"}
   432  	}
   433  	return strings.Join(descrs, "; ")
   434  }
   435  
   436  func getFilterType(filters []*listener.Filter) string {
   437  	for _, filter := range filters {
   438  		if filter.Name == HTTPListener {
   439  			httpProxy := &hcm.HttpConnectionManager{}
   440  			// Allow Unmarshal to work even if Envoy and istioctl are different
   441  			filter.GetTypedConfig().TypeUrl = "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
   442  			err := filter.GetTypedConfig().UnmarshalTo(httpProxy)
   443  			if err != nil {
   444  				return err.Error()
   445  			}
   446  			if httpProxy.GetRouteConfig() != nil {
   447  				return describeRouteConfig(httpProxy.GetRouteConfig())
   448  			}
   449  			if httpProxy.GetRds().GetRouteConfigName() != "" {
   450  				return fmt.Sprintf("Route: %s", httpProxy.GetRds().GetRouteConfigName())
   451  			}
   452  			return "HTTP"
   453  		} else if filter.Name == TCPListener {
   454  			if !strings.Contains(string(filter.GetTypedConfig().GetValue()), util.BlackHoleCluster) {
   455  				tcpProxy := &tcp.TcpProxy{}
   456  				// Allow Unmarshal to work even if Envoy and istioctl are different
   457  				filter.GetTypedConfig().TypeUrl = "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy"
   458  				err := filter.GetTypedConfig().UnmarshalTo(tcpProxy)
   459  				if err != nil {
   460  					return err.Error()
   461  				}
   462  				if strings.Contains(tcpProxy.GetCluster(), "Cluster") {
   463  					return tcpProxy.GetCluster()
   464  				}
   465  				return fmt.Sprintf("Cluster: %s", tcpProxy.GetCluster())
   466  			}
   467  		}
   468  	}
   469  	return "Non-HTTP/Non-TCP"
   470  }
   471  
   472  func describeRouteConfig(route *route.RouteConfiguration) string {
   473  	if cluster := getMatchAllCluster(route); cluster != "" {
   474  		return cluster
   475  	}
   476  	vhosts := []string{}
   477  	for _, vh := range route.GetVirtualHosts() {
   478  		if describeDomains(vh) == "" {
   479  			vhosts = append(vhosts, describeRoutes(vh))
   480  		} else {
   481  			vhosts = append(vhosts, fmt.Sprintf("%s %s", describeDomains(vh), describeRoutes(vh)))
   482  		}
   483  	}
   484  	return fmt.Sprintf("Inline Route: %s", strings.Join(vhosts, "; "))
   485  }
   486  
   487  // If this is a route that matches everything and forwards to a cluster, just report the cluster.
   488  func getMatchAllCluster(er *route.RouteConfiguration) string {
   489  	if len(er.GetVirtualHosts()) != 1 {
   490  		return ""
   491  	}
   492  	vh := er.GetVirtualHosts()[0]
   493  	if !reflect.DeepEqual(vh.Domains, []string{"*"}) {
   494  		return ""
   495  	}
   496  	if len(vh.GetRoutes()) != 1 {
   497  		return ""
   498  	}
   499  	r := vh.GetRoutes()[0]
   500  	if r.GetMatch().GetPrefix() != "/" {
   501  		return ""
   502  	}
   503  	a, ok := r.GetAction().(*route.Route_Route)
   504  	if !ok {
   505  		return ""
   506  	}
   507  	cl, ok := a.Route.ClusterSpecifier.(*route.RouteAction_Cluster)
   508  	if !ok {
   509  		return ""
   510  	}
   511  	if strings.Contains(cl.Cluster, "Cluster") {
   512  		return cl.Cluster
   513  	}
   514  	return fmt.Sprintf("Cluster: %s", cl.Cluster)
   515  }
   516  
   517  func describeDomains(vh *route.VirtualHost) string {
   518  	if len(vh.GetDomains()) == 1 && vh.GetDomains()[0] == "*" {
   519  		return ""
   520  	}
   521  	return strings.Join(vh.GetDomains(), "/")
   522  }
   523  
   524  func describeRoutes(vh *route.VirtualHost) string {
   525  	routes := make([]string, 0, len(vh.GetRoutes()))
   526  	for _, route := range vh.GetRoutes() {
   527  		routes = append(routes, describeMatch(route.GetMatch()))
   528  	}
   529  	return strings.Join(routes, ", ")
   530  }
   531  
   532  func describeMatch(match *route.RouteMatch) string {
   533  	conds := []string{}
   534  	if match.GetPrefix() != "" {
   535  		conds = append(conds, fmt.Sprintf("%s*", match.GetPrefix()))
   536  	}
   537  	if match.GetPathSeparatedPrefix() != "" {
   538  		conds = append(conds, fmt.Sprintf("PathPrefix:%s", match.GetPathSeparatedPrefix()))
   539  	}
   540  	if match.GetPath() != "" {
   541  		conds = append(conds, match.GetPath())
   542  	}
   543  	if match.GetSafeRegex() != nil {
   544  		conds = append(conds, fmt.Sprintf("regex %s", match.GetSafeRegex().Regex))
   545  	}
   546  	// Ignore headers
   547  	return strings.Join(conds, " ")
   548  }
   549  
   550  // PrintListenerDump prints the relevant listeners in the config dump to the ConfigWriter stdout
   551  func (c *ConfigWriter) PrintListenerDump(filter ListenerFilter, outputFormat string) error {
   552  	_, listeners, err := c.setupListenerConfigWriter()
   553  	if err != nil {
   554  		return err
   555  	}
   556  	filteredListeners := proto.MessageSlice{}
   557  	for _, listener := range listeners {
   558  		if filter.Verify(listener) {
   559  			filteredListeners = append(filteredListeners, listener)
   560  		}
   561  	}
   562  	out, err := json.MarshalIndent(filteredListeners, "", "    ")
   563  	if err != nil {
   564  		return fmt.Errorf("failed to marshal listeners: %v", err)
   565  	}
   566  	if outputFormat == "yaml" {
   567  		if out, err = yaml.JSONToYAML(out); err != nil {
   568  			return err
   569  		}
   570  	}
   571  	fmt.Fprintln(c.Stdout, string(out))
   572  	return nil
   573  }
   574  
   575  func (c *ConfigWriter) setupListenerConfigWriter() (*tabwriter.Writer, []*listener.Listener, error) {
   576  	listeners, err := c.retrieveSortedListenerSlice()
   577  	if err != nil {
   578  		return nil, nil, err
   579  	}
   580  	w := new(tabwriter.Writer).Init(c.Stdout, 0, 8, 1, ' ', 0)
   581  	return w, listeners, nil
   582  }
   583  
   584  func (c *ConfigWriter) retrieveSortedListenerSlice() ([]*listener.Listener, error) {
   585  	if c.configDump == nil {
   586  		return nil, fmt.Errorf("config writer has not been primed")
   587  	}
   588  	listenerDump, err := c.configDump.GetListenerConfigDump()
   589  	if err != nil {
   590  		return nil, fmt.Errorf("listener dump: %v", err)
   591  	}
   592  	listeners := make([]*listener.Listener, 0)
   593  	for _, l := range listenerDump.DynamicListeners {
   594  		if l.ActiveState != nil && l.ActiveState.Listener != nil {
   595  			listenerTyped := &listener.Listener{}
   596  			// Support v2 or v3 in config dump. See ads.go:RequestedTypes for more info.
   597  			l.ActiveState.Listener.TypeUrl = v3.ListenerType
   598  			err = l.ActiveState.Listener.UnmarshalTo(listenerTyped)
   599  			if err != nil {
   600  				return nil, fmt.Errorf("unmarshal listener: %v", err)
   601  			}
   602  			listeners = append(listeners, listenerTyped)
   603  		}
   604  	}
   605  
   606  	for _, l := range listenerDump.StaticListeners {
   607  		if l.Listener != nil {
   608  			listenerTyped := &listener.Listener{}
   609  			// Support v2 or v3 in config dump. See ads.go:RequestedTypes for more info.
   610  			l.Listener.TypeUrl = v3.ListenerType
   611  			err = l.Listener.UnmarshalTo(listenerTyped)
   612  			if err != nil {
   613  				return nil, fmt.Errorf("unmarshal listener: %v", err)
   614  			}
   615  			listeners = append(listeners, listenerTyped)
   616  		}
   617  	}
   618  	if len(listeners) == 0 {
   619  		return nil, fmt.Errorf("no listeners found")
   620  	}
   621  	return listeners, nil
   622  }