1// Copyright 2017 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
15package util
16
17import (
18	"fmt"
19	"net"
20	"sort"
21	"strconv"
22	"strings"
23
24	xdsapi "github.com/envoyproxy/go-control-plane/envoy/api/v2"
25	core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
26	endpoint "github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
27	listener "github.com/envoyproxy/go-control-plane/envoy/api/v2/listener"
28	route "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
29	http_conn "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
30	matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher"
31	"github.com/envoyproxy/go-control-plane/pkg/conversion"
32	xdsutil "github.com/envoyproxy/go-control-plane/pkg/wellknown"
33	"github.com/gogo/protobuf/types"
34	"github.com/golang/protobuf/proto"
35	"github.com/golang/protobuf/ptypes"
36	"github.com/golang/protobuf/ptypes/any"
37	"github.com/golang/protobuf/ptypes/duration"
38	pstruct "github.com/golang/protobuf/ptypes/struct"
39	"github.com/golang/protobuf/ptypes/wrappers"
40
41	meshconfig "istio.io/api/mesh/v1alpha1"
42	networking "istio.io/api/networking/v1alpha3"
43	"istio.io/pkg/log"
44
45	"istio.io/istio/pilot/pkg/features"
46	"istio.io/istio/pilot/pkg/model"
47	"istio.io/istio/pilot/pkg/serviceregistry"
48	"istio.io/istio/pkg/config/host"
49	"istio.io/istio/pkg/util/strcase"
50)
51
52const (
53	// BlackHoleCluster to catch traffic from routes with unresolved clusters. Traffic arriving here goes nowhere.
54	BlackHoleCluster = "BlackHoleCluster"
55	// BlackHole is the name of the virtual host and route name used to block all traffic
56	BlackHole = "block_all"
57	// PassthroughCluster to forward traffic to the original destination requested. This cluster is used when
58	// traffic does not match any listener in envoy.
59	PassthroughCluster = "PassthroughCluster"
60	// Passthrough is the name of the virtual host used to forward traffic to the
61	// PassthroughCluster
62	Passthrough = "allow_any"
63	// PassthroughFilterChain to catch traffic that doesn't match other filter chains.
64	PassthroughFilterChain = "PassthroughFilterChain"
65
66	// Inbound pass through cluster need to the bind the loopback ip address for the security and loop avoidance.
67	InboundPassthroughClusterIpv4 = "InboundPassthroughClusterIpv4"
68	InboundPassthroughClusterIpv6 = "InboundPassthroughClusterIpv6"
69	// 6 is the magical number for inbound: 15006, 127.0.0.6, ::6
70	InboundPassthroughBindIpv4 = "127.0.0.6"
71	InboundPassthroughBindIpv6 = "::6"
72
73	// SniClusterFilter is the name of the sni_cluster envoy filter
74	SniClusterFilter = "envoy.filters.network.sni_cluster"
75	// ForwardDownstreamSniFilter forwards the sni from downstream connections to upstream
76	// Used only in the fallthrough filter stack for TLS connections
77	ForwardDownstreamSniFilter = "forward_downstream_sni"
78	// IstioMetadataKey is the key under which metadata is added to a route or cluster
79	// regarding the virtual service or destination rule used for each
80	IstioMetadataKey = "istio"
81
82	// EnvoyTransportSocketMetadataKey is the key under which metadata is added to an endpoint
83	// which determines the endpoint level transport socket configuration.
84	EnvoyTransportSocketMetadataKey = "envoy.transport_socket_match"
85
86	// EnvoyRawBufferSocketName matched with hardcoded built-in Envoy transport name which determines
87	// endpoint level plantext transport socket configuration
88	EnvoyRawBufferSocketName = "envoy.transport_sockets.raw_buffer"
89
90	// EnvoyTLSSocketName matched with hardcoded built-in Envoy transport name which determines endpoint
91	// level tls transport socket configuration
92	EnvoyTLSSocketName = "envoy.transport_sockets.tls"
93
94	// StatName patterns
95	serviceStatPattern         = "%SERVICE%"
96	serviceFQDNStatPattern     = "%SERVICE_FQDN%"
97	servicePortStatPattern     = "%SERVICE_PORT%"
98	servicePortNameStatPattern = "%SERVICE_PORT_NAME%"
99	subsetNameStatPattern      = "%SUBSET_NAME%"
100)
101
102// ALPNH2Only advertises that Proxy is going to use HTTP/2 when talking to the cluster.
103var ALPNH2Only = []string{"h2"}
104
105// ALPNInMeshH2 advertises that Proxy is going to use HTTP/2 when talking to the in-mesh cluster.
106// The custom "istio" value indicates in-mesh traffic and it's going to be used for routing decisions.
107// Once Envoy supports client-side ALPN negotiation, this should be {"istio", "h2", "http/1.1"}.
108var ALPNInMeshH2 = []string{"istio", "h2"}
109
110// ALPNInMeshH2WithMxc advertises that Proxy is going to use HTTP/2 when talking to the in-mesh cluster.
111// The custom "istio" value indicates in-mesh traffic and it's going to be used for routing decisions.
112// The custom "istio-peer-exchange" value indicates, metadata exchange is enabled for TCP.
113var ALPNInMeshH2WithMxc = []string{"istio-peer-exchange", "istio", "h2"}
114
115// ALPNInMesh advertises that Proxy is going to talk to the in-mesh cluster.
116// The custom "istio" value indicates in-mesh traffic and it's going to be used for routing decisions.
117var ALPNInMesh = []string{"istio"}
118
119// ALPNInMeshWithMxc advertises that Proxy is going to talk to the in-mesh cluster and has metadata exchange enabled for
120// TCP. The custom "istio-peer-exchange" value indicates, metadata exchange is enabled for TCP. The custom "istio" value
121// indicates in-mesh traffic and it's going to be used for routing decisions.
122var ALPNInMeshWithMxc = []string{"istio-peer-exchange", "istio"}
123
124// ALPNHttp advertises that Proxy is going to talking either http2 or http 1.1.
125var ALPNHttp = []string{"h2", "http/1.1"}
126
127// ALPNDownstream advertises that Proxy is going to talking either tcp(for metadata exchange), http2 or http 1.1.
128var ALPNDownstream = []string{"istio-peer-exchange", "h2", "http/1.1"}
129
130// FallThroughFilterChainBlackHoleService is the blackhole service used for fall though
131// filter chain
132var FallThroughFilterChainBlackHoleService = &model.Service{
133	Hostname: host.Name(BlackHoleCluster),
134	Attributes: model.ServiceAttributes{
135		Name: BlackHoleCluster,
136	},
137}
138
139// FallThroughFilterChainPassthroughService is the passthrough service used for fall though
140var FallThroughFilterChainPassthroughService = &model.Service{
141	Hostname: host.Name(PassthroughCluster),
142	Attributes: model.ServiceAttributes{
143		Name: PassthroughCluster,
144	},
145}
146
147func getMaxCidrPrefix(addr string) uint32 {
148	ip := net.ParseIP(addr)
149	if ip.To4() == nil {
150		// ipv6 address
151		return 128
152	}
153	// ipv4 address
154	return 32
155}
156
157// ConvertAddressToCidr converts from string to CIDR proto
158func ConvertAddressToCidr(addr string) *core.CidrRange {
159	if len(addr) == 0 {
160		return nil
161	}
162
163	cidr := &core.CidrRange{
164		AddressPrefix: addr,
165		PrefixLen: &wrappers.UInt32Value{
166			Value: getMaxCidrPrefix(addr),
167		},
168	}
169
170	if strings.Contains(addr, "/") {
171		parts := strings.Split(addr, "/")
172		cidr.AddressPrefix = parts[0]
173		prefix, _ := strconv.Atoi(parts[1])
174		cidr.PrefixLen.Value = uint32(prefix)
175	}
176	return cidr
177}
178
179// BuildAddress returns a SocketAddress with the given ip and port or uds.
180func BuildAddress(bind string, port uint32) *core.Address {
181	if port != 0 {
182		return &core.Address{
183			Address: &core.Address_SocketAddress{
184				SocketAddress: &core.SocketAddress{
185					Address: bind,
186					PortSpecifier: &core.SocketAddress_PortValue{
187						PortValue: port,
188					},
189				},
190			},
191		}
192	}
193
194	return &core.Address{
195		Address: &core.Address_Pipe{
196			Pipe: &core.Pipe{
197				Path: strings.TrimPrefix(bind, model.UnixAddressPrefix),
198			},
199		},
200	}
201}
202
203// MessageToAnyWithError converts from proto message to proto Any
204func MessageToAnyWithError(msg proto.Message) (*any.Any, error) {
205	b := proto.NewBuffer(nil)
206	b.SetDeterministic(true)
207	err := b.Marshal(msg)
208	if err != nil {
209		return nil, err
210	}
211	return &any.Any{
212		TypeUrl: "type.googleapis.com/" + proto.MessageName(msg),
213		Value:   b.Bytes(),
214	}, nil
215}
216
217// MessageToAny converts from proto message to proto Any
218func MessageToAny(msg proto.Message) *any.Any {
219	out, err := MessageToAnyWithError(msg)
220	if err != nil {
221		log.Error(fmt.Sprintf("error marshaling Any %s: %v", msg.String(), err))
222		return nil
223	}
224	return out
225}
226
227// MessageToStruct converts from proto message to proto Struct
228func MessageToStruct(msg proto.Message) *pstruct.Struct {
229	s, err := conversion.MessageToStruct(msg)
230	if err != nil {
231		log.Error(err.Error())
232		return &pstruct.Struct{}
233	}
234	return s
235}
236
237// GogoDurationToDuration converts from gogo proto duration to time.duration
238func GogoDurationToDuration(d *types.Duration) *duration.Duration {
239	if d == nil {
240		return nil
241	}
242	dur, err := types.DurationFromProto(d)
243	if err != nil {
244		// TODO(mostrowski): add error handling instead.
245		log.Warnf("error converting duration %#v, using 0: %v", d, err)
246		return nil
247	}
248	return ptypes.DurationProto(dur)
249}
250
251// SortVirtualHosts sorts a slice of virtual hosts by name.
252//
253// Envoy computes a hash of RDS to see if things have changed - hash is affected by order of elements in the filter. Therefore
254// we sort virtual hosts by name before handing them back so the ordering is stable across HTTP Route Configs.
255func SortVirtualHosts(hosts []*route.VirtualHost) {
256	sort.SliceStable(hosts, func(i, j int) bool {
257		return hosts[i].Name < hosts[j].Name
258	})
259}
260
261// IsIstioVersionGE15 checks whether the given Istio version is greater than or equals 1.5.
262func IsIstioVersionGE15(node *model.Proxy) bool {
263	return node.IstioVersion == nil ||
264		node.IstioVersion.Compare(&model.IstioVersion{Major: 1, Minor: 5, Patch: -1}) >= 0
265}
266
267func IsProtocolSniffingEnabledForPort(port *model.Port) bool {
268	return features.EnableProtocolSniffingForOutbound && port.Protocol.IsUnsupported()
269}
270
271func IsProtocolSniffingEnabledForInboundPort(port *model.Port) bool {
272	return features.EnableProtocolSniffingForInbound && port.Protocol.IsUnsupported()
273}
274
275func IsProtocolSniffingEnabledForOutboundPort(port *model.Port) bool {
276	return features.EnableProtocolSniffingForOutbound && port.Protocol.IsUnsupported()
277}
278
279// IsTCPMetadataExchangeEnabled checks whether Metadata Exchanged enabled for TCP using ALPN.
280func IsTCPMetadataExchangeEnabled(node *model.Proxy) bool {
281	return features.EnableTCPMetadataExchange && IsIstioVersionGE15(node)
282}
283
284// ConvertLocality converts '/' separated locality string to Locality struct.
285func ConvertLocality(locality string) *core.Locality {
286	if locality == "" {
287		return &core.Locality{}
288	}
289
290	region, zone, subzone := SplitLocality(locality)
291	return &core.Locality{
292		Region:  region,
293		Zone:    zone,
294		SubZone: subzone,
295	}
296}
297
298// ConvertLocality converts '/' separated locality string to Locality struct.
299func LocalityToString(l *core.Locality) string {
300	if l == nil {
301		return ""
302	}
303	resp := l.Region
304	if l.Zone == "" {
305		return resp
306	}
307	resp += "/" + l.Zone
308	if l.SubZone == "" {
309		return resp
310	}
311	resp += "/" + l.SubZone
312	return resp
313}
314
315// IsLocalityEmpty checks if a locality is empty (checking region is good enough, based on how its initialized)
316func IsLocalityEmpty(locality *core.Locality) bool {
317	if locality == nil || (len(locality.GetRegion()) == 0) {
318		return true
319	}
320	return false
321}
322
323func LocalityMatch(proxyLocality *core.Locality, ruleLocality string) bool {
324	ruleRegion, ruleZone, ruleSubzone := SplitLocality(ruleLocality)
325	regionMatch := ruleRegion == "*" || proxyLocality.GetRegion() == ruleRegion
326	zoneMatch := ruleZone == "*" || ruleZone == "" || proxyLocality.GetZone() == ruleZone
327	subzoneMatch := ruleSubzone == "*" || ruleSubzone == "" || proxyLocality.GetSubZone() == ruleSubzone
328
329	if regionMatch && zoneMatch && subzoneMatch {
330		return true
331	}
332	return false
333}
334
335func SplitLocality(locality string) (region, zone, subzone string) {
336	items := strings.Split(locality, "/")
337	switch len(items) {
338	case 1:
339		return items[0], "", ""
340	case 2:
341		return items[0], items[1], ""
342	default:
343		return items[0], items[1], items[2]
344	}
345}
346
347func LbPriority(proxyLocality, endpointsLocality *core.Locality) int {
348	if proxyLocality.GetRegion() == endpointsLocality.GetRegion() {
349		if proxyLocality.GetZone() == endpointsLocality.GetZone() {
350			if proxyLocality.GetSubZone() == endpointsLocality.GetSubZone() {
351				return 0
352			}
353			return 1
354		}
355		return 2
356	}
357	return 3
358}
359
360// return a shallow copy cluster
361func CloneCluster(cluster *xdsapi.Cluster) xdsapi.Cluster {
362	out := xdsapi.Cluster{}
363	if cluster == nil {
364		return out
365	}
366
367	out = *cluster
368	loadAssignment := CloneClusterLoadAssignment(cluster.LoadAssignment)
369	out.LoadAssignment = &loadAssignment
370
371	return out
372}
373
374// return a shallow copy ClusterLoadAssignment
375func CloneClusterLoadAssignment(original *xdsapi.ClusterLoadAssignment) xdsapi.ClusterLoadAssignment {
376	out := xdsapi.ClusterLoadAssignment{}
377	if original == nil {
378		return out
379	}
380
381	out = *original
382	out.Endpoints = cloneLocalityLbEndpoints(out.Endpoints)
383
384	return out
385}
386
387// return a shallow copy LocalityLbEndpoints
388func cloneLocalityLbEndpoints(endpoints []*endpoint.LocalityLbEndpoints) []*endpoint.LocalityLbEndpoints {
389	out := make([]*endpoint.LocalityLbEndpoints, 0, len(endpoints))
390	for _, ep := range endpoints {
391		clone := *ep
392		if ep.LoadBalancingWeight != nil {
393			clone.LoadBalancingWeight = &wrappers.UInt32Value{
394				Value: ep.GetLoadBalancingWeight().GetValue(),
395			}
396		}
397		out = append(out, &clone)
398	}
399	return out
400}
401
402// return a shallow copy LbEndpoint
403func CloneLbEndpoint(endpoint *endpoint.LbEndpoint) *endpoint.LbEndpoint {
404	if endpoint == nil {
405		return nil
406	}
407
408	clone := *endpoint
409	if endpoint.LoadBalancingWeight != nil {
410		clone.LoadBalancingWeight = &wrappers.UInt32Value{
411			Value: endpoint.GetLoadBalancingWeight().GetValue(),
412		}
413	}
414	return &clone
415}
416
417// BuildConfigInfoMetadata builds core.Metadata struct containing the
418// name.namespace of the config, the type, etc. Used by Mixer client
419// to generate attributes for policy and telemetry.
420func BuildConfigInfoMetadata(config model.ConfigMeta) *core.Metadata {
421	s := "/apis/" + config.Group + "/" + config.Version + "/namespaces/" + config.Namespace + "/" +
422		strcase.CamelCaseToKebabCase(config.Type) + "/" + config.Name
423	return &core.Metadata{
424		FilterMetadata: map[string]*pstruct.Struct{
425			IstioMetadataKey: {
426				Fields: map[string]*pstruct.Value{
427					"config": {
428						Kind: &pstruct.Value_StringValue{
429							StringValue: s,
430						},
431					},
432				},
433			},
434		},
435	}
436}
437
438// AddSubsetToMetadata will build a new core.Metadata struct containing the
439// subset name supplied. This is used for telemetry reporting. A new core.Metadata
440// is created to prevent modification to shared base Metadata across subsets, etc.
441// This should be called after the initial "istio" metadata has been created for the
442// cluster. If the "istio" metadata field is not already defined, the subset information will
443// not be added (to prevent adding this information where not needed).
444func AddSubsetToMetadata(md *core.Metadata, subset string) *core.Metadata {
445	updatedMeta := &core.Metadata{}
446	proto.Merge(updatedMeta, md)
447	if istioMeta, ok := updatedMeta.FilterMetadata[IstioMetadataKey]; ok {
448		istioMeta.Fields["subset"] = &pstruct.Value{
449			Kind: &pstruct.Value_StringValue{
450				StringValue: subset,
451			},
452		}
453	}
454	return updatedMeta
455}
456
457// IsHTTPFilterChain returns true if the filter chain contains a HTTP connection manager filter
458func IsHTTPFilterChain(filterChain *listener.FilterChain) bool {
459	for _, f := range filterChain.Filters {
460		if f.Name == xdsutil.HTTPConnectionManager {
461			return true
462		}
463	}
464	return false
465}
466
467// MergeAnyWithStruct merges a given struct into the given Any typed message by dynamically inferring the
468// type of Any, converting the struct into the inferred type, merging the two messages, and then
469// marshaling the merged message back into Any.
470func MergeAnyWithStruct(a *any.Any, pbStruct *pstruct.Struct) (*any.Any, error) {
471	// Assuming that Pilot is compiled with this type [which should always be the case]
472	var err error
473	var x ptypes.DynamicAny
474
475	// First get an object of type used by this message
476	if err = ptypes.UnmarshalAny(a, &x); err != nil {
477		return nil, err
478	}
479
480	// Create a typed copy. We will convert the user's struct to this type
481	temp := proto.Clone(x.Message)
482	temp.Reset()
483	if err = conversion.StructToMessage(pbStruct, temp); err != nil {
484		return nil, err
485	}
486
487	// Merge the two typed protos
488	proto.Merge(x.Message, temp)
489	var retVal *any.Any
490	// Convert the merged proto back to any
491	if retVal, err = ptypes.MarshalAny(x.Message); err != nil {
492		return nil, err
493	}
494
495	return retVal, nil
496}
497
498// MergeAnyWithAny merges a given any typed message into the given Any typed message by dynamically inferring the
499// type of Any
500func MergeAnyWithAny(dst *any.Any, src *any.Any) (*any.Any, error) {
501	// Assuming that Pilot is compiled with this type [which should always be the case]
502	var err error
503	var dstX, srcX ptypes.DynamicAny
504
505	// get an object of type used by this message
506	if err = ptypes.UnmarshalAny(dst, &dstX); err != nil {
507		return nil, err
508	}
509
510	// get an object of type used by this message
511	if err = ptypes.UnmarshalAny(src, &srcX); err != nil {
512		return nil, err
513	}
514
515	// Merge the two typed protos
516	proto.Merge(dstX.Message, srcX.Message)
517	var retVal *any.Any
518	// Convert the merged proto back to dst
519	if retVal, err = ptypes.MarshalAny(dstX.Message); err != nil {
520		return nil, err
521	}
522
523	return retVal, nil
524}
525
526// BuildLbEndpointMetadata adds metadata values to a lb endpoint
527func BuildLbEndpointMetadata(uid string, network string, tlsMode string, push *model.PushContext) *core.Metadata {
528	if !push.IsMixerEnabled() {
529		// Only use UIDs when Mixer is enabled.
530		uid = ""
531	}
532
533	if uid == "" && network == "" && tlsMode == model.DisabledTLSModeLabel {
534		return nil
535	}
536
537	metadata := &core.Metadata{
538		FilterMetadata: map[string]*pstruct.Struct{},
539	}
540
541	if uid != "" || network != "" {
542		metadata.FilterMetadata[IstioMetadataKey] = &pstruct.Struct{
543			Fields: map[string]*pstruct.Value{},
544		}
545
546		if uid != "" {
547			metadata.FilterMetadata[IstioMetadataKey].Fields["uid"] = &pstruct.Value{Kind: &pstruct.Value_StringValue{StringValue: uid}}
548		}
549
550		if network != "" {
551			metadata.FilterMetadata[IstioMetadataKey].Fields["network"] = &pstruct.Value{Kind: &pstruct.Value_StringValue{StringValue: network}}
552		}
553	}
554
555	if tlsMode != "" {
556		metadata.FilterMetadata[EnvoyTransportSocketMetadataKey] = &pstruct.Struct{
557			Fields: map[string]*pstruct.Value{
558				model.TLSModeLabelShortname: {Kind: &pstruct.Value_StringValue{StringValue: tlsMode}},
559			},
560		}
561	}
562
563	return metadata
564}
565
566// IsAllowAnyOutbound checks if allow_any is enabled for outbound traffic
567func IsAllowAnyOutbound(node *model.Proxy) bool {
568	return node.SidecarScope != nil &&
569		node.SidecarScope.OutboundTrafficPolicy != nil &&
570		node.SidecarScope.OutboundTrafficPolicy.Mode == networking.OutboundTrafficPolicy_ALLOW_ANY
571}
572
573// BuildStatPrefix builds a stat prefix based on the stat pattern.
574func BuildStatPrefix(statPattern string, host string, subset string, port *model.Port, attributes model.ServiceAttributes) string {
575	prefix := strings.ReplaceAll(statPattern, serviceStatPattern, shortHostName(host, attributes))
576	prefix = strings.ReplaceAll(prefix, serviceFQDNStatPattern, host)
577	prefix = strings.ReplaceAll(prefix, subsetNameStatPattern, subset)
578	prefix = strings.ReplaceAll(prefix, servicePortStatPattern, strconv.Itoa(port.Port))
579	prefix = strings.ReplaceAll(prefix, servicePortNameStatPattern, port.Name)
580	return prefix
581}
582
583// shortHostName constructs the name from kubernetes hosts based on attributes (name and namespace).
584// For other hosts like VMs, this method does not do any thing - just returns the passed in host as is.
585func shortHostName(host string, attributes model.ServiceAttributes) string {
586	if attributes.ServiceRegistry == string(serviceregistry.Kubernetes) {
587		return attributes.Name + "." + attributes.Namespace
588	}
589	return host
590}
591
592func StringToExactMatch(in []string) []*matcher.StringMatcher {
593	if len(in) == 0 {
594		return nil
595	}
596	res := make([]*matcher.StringMatcher, 0, len(in))
597	for _, s := range in {
598		res = append(res, &matcher.StringMatcher{
599			MatchPattern: &matcher.StringMatcher_Exact{Exact: s},
600		})
601	}
602	return res
603}
604
605func StringSliceEqual(a, b []string) bool {
606	if len(a) != len(b) {
607		return false
608	}
609
610	for i := range a {
611		if a[i] != b[i] {
612			return false
613		}
614	}
615
616	return true
617}
618
619func UInt32SliceEqual(a, b []uint32) bool {
620	if len(a) != len(b) {
621		return false
622	}
623
624	for i := range a {
625		if a[i] != b[i] {
626			return false
627		}
628	}
629
630	return true
631}
632
633func CidrRangeSliceEqual(a, b []*core.CidrRange) bool {
634	if len(a) != len(b) {
635		return false
636	}
637
638	for i := range a {
639		if a[i].GetAddressPrefix() != b[i].GetAddressPrefix() || a[i].GetPrefixLen().GetValue() != b[i].GetPrefixLen().GetValue() {
640			return false
641		}
642	}
643
644	return true
645}
646
647// meshconfig ForwardClientCertDetails and the Envoy config enum are off by 1
648// due to the UNDEFINED in the meshconfig ForwardClientCertDetails
649func MeshConfigToEnvoyForwardClientCertDetails(c meshconfig.Topology_ForwardClientCertDetails) http_conn.HttpConnectionManager_ForwardClientCertDetails {
650	return http_conn.HttpConnectionManager_ForwardClientCertDetails(c - 1)
651}
652