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