istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/util/util.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 util 16 17 import ( 18 "bytes" 19 "fmt" 20 "net" 21 "net/netip" 22 "sort" 23 "strconv" 24 "strings" 25 26 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 27 endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 28 listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 29 route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 30 statefulsession "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/stateful_session/v3" 31 hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" 32 cookiev3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/stateful_session/cookie/v3" 33 headerv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/stateful_session/header/v3" 34 httpv3 "github.com/envoyproxy/go-control-plane/envoy/type/http/v3" 35 matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" 36 "google.golang.org/protobuf/types/known/anypb" 37 "google.golang.org/protobuf/types/known/structpb" 38 "google.golang.org/protobuf/types/known/wrapperspb" 39 40 meshconfig "istio.io/api/mesh/v1alpha1" 41 networking "istio.io/api/networking/v1alpha3" 42 "istio.io/istio/pilot/pkg/features" 43 "istio.io/istio/pilot/pkg/model" 44 istionetworking "istio.io/istio/pilot/pkg/networking" 45 "istio.io/istio/pilot/pkg/serviceregistry/util/label" 46 "istio.io/istio/pilot/pkg/util/protoconv" 47 "istio.io/istio/pkg/config" 48 kubelabels "istio.io/istio/pkg/kube/labels" 49 "istio.io/istio/pkg/log" 50 pm "istio.io/istio/pkg/model" 51 "istio.io/istio/pkg/proto/merge" 52 "istio.io/istio/pkg/util/strcase" 53 "istio.io/istio/pkg/wellknown" 54 ) 55 56 const ( 57 // BlackHoleCluster to catch traffic from routes with unresolved clusters. Traffic arriving here goes nowhere. 58 BlackHoleCluster = "BlackHoleCluster" 59 // BlackHole is the name of the virtual host and route name used to block all traffic 60 BlackHole = "block_all" 61 // PassthroughCluster to forward traffic to the original destination requested. This cluster is used when 62 // traffic does not match any listener in envoy. 63 PassthroughCluster = "PassthroughCluster" 64 // Passthrough is the name of the virtual host used to forward traffic to the 65 // PassthroughCluster 66 Passthrough = "allow_any" 67 68 // PassthroughFilterChain to catch traffic that doesn't match other filter chains. 69 PassthroughFilterChain = "PassthroughFilterChain" 70 71 // Inbound pass through cluster need to the bind the loopback ip address for the security and loop avoidance. 72 InboundPassthroughClusterIpv4 = "InboundPassthroughClusterIpv4" 73 InboundPassthroughClusterIpv6 = "InboundPassthroughClusterIpv6" 74 75 // IstioMetadataKey is the key under which metadata is added to a route or cluster 76 // regarding the virtual service or destination rule used for each 77 IstioMetadataKey = "istio" 78 79 // EnvoyTransportSocketMetadataKey is the key under which metadata is added to an endpoint 80 // which determines the endpoint level transport socket configuration. 81 EnvoyTransportSocketMetadataKey = "envoy.transport_socket_match" 82 83 // Well-known header names 84 AltSvcHeader = "alt-svc" 85 86 // Envoy Stateful Session Filter 87 // TODO: Move to well known. 88 StatefulSessionFilter = "envoy.filters.http.stateful_session" 89 90 // AlpnOverrideMetadataKey is the key under which metadata is added 91 // to indicate whether Istio rewrite the ALPN headers 92 AlpnOverrideMetadataKey = "alpn_override" 93 ) 94 95 // ALPNH2Only advertises that Proxy is going to use HTTP/2 when talking to the cluster. 96 var ALPNH2Only = pm.ALPNH2Only 97 98 // ALPNInMeshH2 advertises that Proxy is going to use HTTP/2 when talking to the in-mesh cluster. 99 // The custom "istio" value indicates in-mesh traffic and it's going to be used for routing decisions. 100 // Once Envoy supports client-side ALPN negotiation, this should be {"istio", "h2", "http/1.1"}. 101 var ALPNInMeshH2 = pm.ALPNInMeshH2 102 103 // ALPNInMeshH2WithMxc advertises that Proxy is going to use HTTP/2 when talking to the in-mesh cluster. 104 // The custom "istio" value indicates in-mesh traffic and it's going to be used for routing decisions. 105 // The custom "istio-peer-exchange" value indicates, metadata exchange is enabled for TCP. 106 var ALPNInMeshH2WithMxc = []string{"istio-peer-exchange", "istio", "h2"} 107 108 // ALPNInMesh advertises that Proxy is going to talk to the in-mesh cluster. 109 // The custom "istio" value indicates in-mesh traffic and it's going to be used for routing decisions. 110 var ALPNInMesh = []string{"istio"} 111 112 // ALPNInMeshWithMxc advertises that Proxy is going to talk to the in-mesh cluster and has metadata exchange enabled for 113 // TCP. The custom "istio-peer-exchange" value indicates, metadata exchange is enabled for TCP. The custom "istio" value 114 // indicates in-mesh traffic and it's going to be used for routing decisions. 115 var ALPNInMeshWithMxc = []string{"istio-peer-exchange", "istio"} 116 117 // ALPNHttp advertises that Proxy is going to talking either http2 or http 1.1. 118 var ALPNHttp = []string{"h2", "http/1.1"} 119 120 // ALPNHttp3OverQUIC advertises that Proxy is going to talk HTTP/3 over QUIC 121 var ALPNHttp3OverQUIC = []string{"h3"} 122 123 // ALPNDownstreamWithMxc advertises that Proxy is going to talk either tcp(for metadata exchange), http2 or http 1.1. 124 var ALPNDownstreamWithMxc = []string{"istio-peer-exchange", "h2", "http/1.1"} 125 126 // ALPNDownstream advertises that Proxy is going to talk either http2 or http 1.1. 127 var ALPNDownstream = []string{"h2", "http/1.1"} 128 129 // ConvertAddressToCidr converts from string to CIDR proto 130 func ConvertAddressToCidr(addr string) *core.CidrRange { 131 cidr, err := AddrStrToCidrRange(addr) 132 if err != nil { 133 log.Errorf("failed to convert address %s to CidrRange: %v", addr, err) 134 return nil 135 } 136 137 return cidr 138 } 139 140 // AddrStrToCidrRange converts from string to CIDR prefix 141 func AddrStrToPrefix(addr string) (netip.Prefix, error) { 142 if len(addr) == 0 { 143 return netip.Prefix{}, fmt.Errorf("empty address") 144 } 145 146 // Already a CIDR, just parse it. 147 if strings.Contains(addr, "/") { 148 return netip.ParsePrefix(addr) 149 } 150 151 // Otherwise it is a raw IP. Make it a /32 or /128 depending on family 152 ipa, err := netip.ParseAddr(addr) 153 if err != nil { 154 return netip.Prefix{}, err 155 } 156 157 return netip.PrefixFrom(ipa, ipa.BitLen()), nil 158 } 159 160 // AddrStrToCidrRange converts from string to CIDR proto 161 func AddrStrToCidrRange(addr string) (*core.CidrRange, error) { 162 prefix, err := AddrStrToPrefix(addr) 163 if err != nil { 164 return nil, err 165 } 166 return &core.CidrRange{ 167 AddressPrefix: prefix.Addr().String(), 168 PrefixLen: &wrapperspb.UInt32Value{ 169 Value: uint32(prefix.Bits()), 170 }, 171 }, nil 172 } 173 174 // BuildAddress returns a SocketAddress with the given ip and port or uds. 175 func BuildAddress(bind string, port uint32) *core.Address { 176 address := BuildNetworkAddress(bind, port, istionetworking.TransportProtocolTCP) 177 if address != nil { 178 return address 179 } 180 181 return &core.Address{ 182 Address: &core.Address_Pipe{ 183 Pipe: &core.Pipe{ 184 Path: strings.TrimPrefix(bind, model.UnixAddressPrefix), 185 }, 186 }, 187 } 188 } 189 190 // BuildAdditionalAddresses can add extra addresses to additional addresses for a listener 191 func BuildAdditionalAddresses(extrAddresses []string, listenPort uint32) []*listener.AdditionalAddress { 192 var additionalAddresses []*listener.AdditionalAddress 193 if len(extrAddresses) > 0 { 194 for _, exbd := range extrAddresses { 195 if exbd == "" { 196 continue 197 } 198 extraAddress := &listener.AdditionalAddress{ 199 Address: BuildAddress(exbd, listenPort), 200 } 201 additionalAddresses = append(additionalAddresses, extraAddress) 202 } 203 } 204 return additionalAddresses 205 } 206 207 func BuildNetworkAddress(bind string, port uint32, transport istionetworking.TransportProtocol) *core.Address { 208 if port == 0 { 209 return nil 210 } 211 return &core.Address{ 212 Address: &core.Address_SocketAddress{ 213 SocketAddress: &core.SocketAddress{ 214 Address: bind, 215 Protocol: transport.ToEnvoySocketProtocol(), 216 PortSpecifier: &core.SocketAddress_PortValue{ 217 PortValue: port, 218 }, 219 }, 220 }, 221 } 222 } 223 224 // SortVirtualHosts sorts a slice of virtual hosts by name. 225 // 226 // Envoy computes a hash of RDS to see if things have changed - hash is affected by order of elements in the filter. Therefore 227 // we sort virtual hosts by name before handing them back so the ordering is stable across HTTP Route Configs. 228 func SortVirtualHosts(hosts []*route.VirtualHost) { 229 if len(hosts) < 2 { 230 return 231 } 232 sort.SliceStable(hosts, func(i, j int) bool { 233 return hosts[i].Name < hosts[j].Name 234 }) 235 } 236 237 // ConvertLocality converts '/' separated locality string to Locality struct. 238 func ConvertLocality(locality string) *core.Locality { 239 return pm.ConvertLocality(locality) 240 } 241 242 // LocalityToString converts Locality struct to '/' separated locality string. 243 func LocalityToString(l *core.Locality) string { 244 if l == nil { 245 return "" 246 } 247 resp := l.Region 248 if l.Zone == "" { 249 return resp 250 } 251 resp += "/" + l.Zone 252 if l.SubZone == "" { 253 return resp 254 } 255 resp += "/" + l.SubZone 256 return resp 257 } 258 259 // GetFailoverPriorityLabels returns a byte array which contains failover priorities of the proxy. 260 func GetFailoverPriorityLabels(proxyLabels map[string]string, priorities []string) []byte { 261 var b bytes.Buffer 262 for _, key := range priorities { 263 b.WriteString(key) 264 b.WriteRune(':') 265 b.WriteString(proxyLabels[key]) 266 b.WriteRune(' ') 267 } 268 return b.Bytes() 269 } 270 271 // IsLocalityEmpty checks if a locality is empty (checking region is good enough, based on how its initialized) 272 func IsLocalityEmpty(locality *core.Locality) bool { 273 if locality == nil || (len(locality.GetRegion()) == 0) { 274 return true 275 } 276 return false 277 } 278 279 func LocalityMatch(proxyLocality *core.Locality, ruleLocality string) bool { 280 ruleRegion, ruleZone, ruleSubzone := label.SplitLocalityLabel(ruleLocality) 281 regionMatch := ruleRegion == "*" || proxyLocality.GetRegion() == ruleRegion 282 zoneMatch := ruleZone == "*" || ruleZone == "" || proxyLocality.GetZone() == ruleZone 283 subzoneMatch := ruleSubzone == "*" || ruleSubzone == "" || proxyLocality.GetSubZone() == ruleSubzone 284 285 if regionMatch && zoneMatch && subzoneMatch { 286 return true 287 } 288 return false 289 } 290 291 func LbPriority(proxyLocality, endpointsLocality *core.Locality) int { 292 if proxyLocality.GetRegion() == endpointsLocality.GetRegion() { 293 if proxyLocality.GetZone() == endpointsLocality.GetZone() { 294 if proxyLocality.GetSubZone() == endpointsLocality.GetSubZone() { 295 return 0 296 } 297 return 1 298 } 299 return 2 300 } 301 return 3 302 } 303 304 // return a shallow copy ClusterLoadAssignment 305 func CloneClusterLoadAssignment(original *endpoint.ClusterLoadAssignment) *endpoint.ClusterLoadAssignment { 306 if original == nil { 307 return nil 308 } 309 out := &endpoint.ClusterLoadAssignment{} 310 311 out.ClusterName = original.ClusterName 312 out.Endpoints = cloneLocalityLbEndpoints(original.Endpoints) 313 out.Policy = original.Policy 314 315 return out 316 } 317 318 // return a shallow copy LocalityLbEndpoints 319 func cloneLocalityLbEndpoints(endpoints []*endpoint.LocalityLbEndpoints) []*endpoint.LocalityLbEndpoints { 320 out := make([]*endpoint.LocalityLbEndpoints, 0, len(endpoints)) 321 for _, ep := range endpoints { 322 clone := CloneLocalityLbEndpoint(ep) 323 out = append(out, clone) 324 } 325 return out 326 } 327 328 // return a shallow copy of LocalityLbEndpoints 329 func CloneLocalityLbEndpoint(ep *endpoint.LocalityLbEndpoints) *endpoint.LocalityLbEndpoints { 330 clone := &endpoint.LocalityLbEndpoints{} 331 clone.Locality = ep.Locality 332 clone.LbEndpoints = ep.LbEndpoints 333 clone.Proximity = ep.Proximity 334 clone.Priority = ep.Priority 335 if ep.LoadBalancingWeight != nil { 336 clone.LoadBalancingWeight = &wrapperspb.UInt32Value{ 337 Value: ep.GetLoadBalancingWeight().GetValue(), 338 } 339 } 340 return clone 341 } 342 343 // BuildConfigInfoMetadata builds core.Metadata struct containing the 344 // name.namespace of the config, the type, etc. 345 func BuildConfigInfoMetadata(config config.Meta) *core.Metadata { 346 return AddConfigInfoMetadata(nil, config) 347 } 348 349 // AddConfigInfoMetadata adds name.namespace of the config, the type, etc 350 // to the given core.Metadata struct, if metadata is not initialized, build a new metadata. 351 func AddConfigInfoMetadata(metadata *core.Metadata, config config.Meta) *core.Metadata { 352 if metadata == nil { 353 metadata = &core.Metadata{ 354 FilterMetadata: map[string]*structpb.Struct{}, 355 } 356 } 357 s := "/apis/" + config.GroupVersionKind.Group + "/" + config.GroupVersionKind.Version + "/namespaces/" + config.Namespace + "/" + 358 strcase.CamelCaseToKebabCase(config.GroupVersionKind.Kind) + "/" + config.Name 359 if _, ok := metadata.FilterMetadata[IstioMetadataKey]; !ok { 360 metadata.FilterMetadata[IstioMetadataKey] = &structpb.Struct{ 361 Fields: map[string]*structpb.Value{}, 362 } 363 } 364 metadata.FilterMetadata[IstioMetadataKey].Fields["config"] = &structpb.Value{ 365 Kind: &structpb.Value_StringValue{ 366 StringValue: s, 367 }, 368 } 369 return metadata 370 } 371 372 // AddSubsetToMetadata will insert the subset name supplied. This should be called after the initial 373 // "istio" metadata has been created for the cluster. If the "istio" metadata field is not already 374 // defined, the subset information will not be added (to prevent adding this information where not 375 // needed). This is used for telemetry reporting. 376 func AddSubsetToMetadata(md *core.Metadata, subset string) { 377 if istioMeta, ok := md.FilterMetadata[IstioMetadataKey]; ok { 378 istioMeta.Fields["subset"] = &structpb.Value{ 379 Kind: &structpb.Value_StringValue{ 380 StringValue: subset, 381 }, 382 } 383 } 384 } 385 386 // AddALPNOverrideToMetadata sets filter metadata `istio.alpn_override: "false"` in the given core.Metadata struct, 387 // when TLS mode is SIMPLE or MUTUAL. If metadata is not initialized, builds a new metadata. 388 func AddALPNOverrideToMetadata(metadata *core.Metadata, tlsMode networking.ClientTLSSettings_TLSmode) *core.Metadata { 389 if tlsMode != networking.ClientTLSSettings_SIMPLE && tlsMode != networking.ClientTLSSettings_MUTUAL { 390 return metadata 391 } 392 393 if metadata == nil { 394 metadata = &core.Metadata{ 395 FilterMetadata: map[string]*structpb.Struct{}, 396 } 397 } 398 399 if _, ok := metadata.FilterMetadata[IstioMetadataKey]; !ok { 400 metadata.FilterMetadata[IstioMetadataKey] = &structpb.Struct{ 401 Fields: map[string]*structpb.Value{}, 402 } 403 } 404 405 metadata.FilterMetadata[IstioMetadataKey].Fields["alpn_override"] = &structpb.Value{ 406 Kind: &structpb.Value_StringValue{ 407 StringValue: "false", 408 }, 409 } 410 411 return metadata 412 } 413 414 // IsHTTPFilterChain returns true if the filter chain contains a HTTP connection manager filter 415 func IsHTTPFilterChain(filterChain *listener.FilterChain) bool { 416 for _, f := range filterChain.Filters { 417 if f.Name == wellknown.HTTPConnectionManager { 418 return true 419 } 420 } 421 return false 422 } 423 424 // MergeAnyWithAny merges a given any typed message into the given Any typed message by dynamically inferring the 425 // type of Any 426 func MergeAnyWithAny(dst *anypb.Any, src *anypb.Any) (*anypb.Any, error) { 427 // Assuming that Pilot is compiled with this type [which should always be the case] 428 var err error 429 430 // get an object of type used by this message 431 dstX, err := dst.UnmarshalNew() 432 if err != nil { 433 return nil, err 434 } 435 436 // get an object of type used by this message 437 srcX, err := src.UnmarshalNew() 438 if err != nil { 439 return nil, err 440 } 441 442 // Merge the two typed protos 443 merge.Merge(dstX, srcX) 444 445 // Convert the merged proto back to dst 446 retVal := protoconv.MessageToAny(dstX) 447 448 return retVal, nil 449 } 450 451 // AppendLbEndpointMetadata adds metadata values to a lb endpoint using the passed in metadata as base. 452 func AppendLbEndpointMetadata(istioMetadata *model.EndpointMetadata, envoyMetadata *core.Metadata, 453 ) { 454 if !features.EndpointTelemetryLabel || !features.EnableTelemetryLabel { 455 return 456 } 457 458 if envoyMetadata.FilterMetadata == nil { 459 envoyMetadata.FilterMetadata = map[string]*structpb.Struct{} 460 } 461 462 if istioMetadata.TLSMode != "" && istioMetadata.TLSMode != model.DisabledTLSModeLabel { 463 envoyMetadata.FilterMetadata[EnvoyTransportSocketMetadataKey] = &structpb.Struct{ 464 Fields: map[string]*structpb.Value{ 465 model.TLSModeLabelShortname: {Kind: &structpb.Value_StringValue{StringValue: istioMetadata.TLSMode}}, 466 }, 467 } 468 } 469 470 // Add compressed telemetry metadata. Note this is a short term solution to make server workload metadata 471 // available at client sidecar, so that telemetry filter could use for metric labels. This is useful for two cases: 472 // server does not have sidecar injected, and request fails to reach server and thus metadata exchange does not happen. 473 // Due to performance concern, telemetry metadata is compressed into a semicolon separated string: 474 // workload-name;namespace;canonical-service-name;canonical-service-revision;cluster-id. 475 if features.EndpointTelemetryLabel { 476 // allow defaulting for non-injected cases 477 canonicalName, canonicalRevision := kubelabels.CanonicalService(istioMetadata.Labels, istioMetadata.WorkloadName) 478 479 // don't bother sending the default value in config 480 if canonicalRevision == "latest" { 481 canonicalRevision = "" 482 } 483 484 var sb strings.Builder 485 sb.WriteString(istioMetadata.WorkloadName) 486 sb.WriteString(";") 487 sb.WriteString(istioMetadata.Namespace) 488 sb.WriteString(";") 489 sb.WriteString(canonicalName) 490 sb.WriteString(";") 491 sb.WriteString(canonicalRevision) 492 sb.WriteString(";") 493 sb.WriteString(istioMetadata.ClusterID.String()) 494 addIstioEndpointLabel(envoyMetadata, "workload", &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: sb.String()}}) 495 } 496 } 497 498 func addIstioEndpointLabel(metadata *core.Metadata, key string, val *structpb.Value) { 499 if _, ok := metadata.FilterMetadata[IstioMetadataKey]; !ok { 500 metadata.FilterMetadata[IstioMetadataKey] = &structpb.Struct{ 501 Fields: map[string]*structpb.Value{}, 502 } 503 } 504 505 metadata.FilterMetadata[IstioMetadataKey].Fields[key] = val 506 } 507 508 // IsAllowAnyOutbound checks if allow_any is enabled for outbound traffic 509 func IsAllowAnyOutbound(node *model.Proxy) bool { 510 return node.SidecarScope != nil && 511 node.SidecarScope.OutboundTrafficPolicy != nil && 512 node.SidecarScope.OutboundTrafficPolicy.Mode == networking.OutboundTrafficPolicy_ALLOW_ANY 513 } 514 515 func StringToExactMatch(in []string) []*matcher.StringMatcher { 516 return pm.StringToExactMatch(in) 517 } 518 519 func StringToPrefixMatch(in []string) []*matcher.StringMatcher { 520 if len(in) == 0 { 521 return nil 522 } 523 res := make([]*matcher.StringMatcher, 0, len(in)) 524 for _, s := range in { 525 res = append(res, &matcher.StringMatcher{ 526 MatchPattern: &matcher.StringMatcher_Prefix{Prefix: s}, 527 }) 528 } 529 return res 530 } 531 532 func ConvertToEnvoyMatches(in []*networking.StringMatch) []*matcher.StringMatcher { 533 res := make([]*matcher.StringMatcher, 0, len(in)) 534 535 for _, im := range in { 536 if em := ConvertToEnvoyMatch(im); em != nil { 537 res = append(res, em) 538 } 539 } 540 541 return res 542 } 543 544 func ConvertToEnvoyMatch(in *networking.StringMatch) *matcher.StringMatcher { 545 switch m := in.MatchType.(type) { 546 case *networking.StringMatch_Exact: 547 return &matcher.StringMatcher{MatchPattern: &matcher.StringMatcher_Exact{Exact: m.Exact}} 548 case *networking.StringMatch_Prefix: 549 return &matcher.StringMatcher{MatchPattern: &matcher.StringMatcher_Prefix{Prefix: m.Prefix}} 550 case *networking.StringMatch_Regex: 551 return &matcher.StringMatcher{ 552 MatchPattern: &matcher.StringMatcher_SafeRegex{ 553 SafeRegex: &matcher.RegexMatcher{ 554 Regex: m.Regex, 555 }, 556 }, 557 } 558 } 559 return nil 560 } 561 562 func CidrRangeSliceEqual(a, b []*core.CidrRange) bool { 563 if len(a) != len(b) { 564 return false 565 } 566 567 for i := range a { 568 netA, err := toMaskedPrefix(a[i]) 569 if err != nil { 570 return false 571 } 572 netB, err := toMaskedPrefix(b[i]) 573 if err != nil { 574 return false 575 } 576 if netA.Addr().String() != netB.Addr().String() { 577 return false 578 } 579 } 580 581 return true 582 } 583 584 func toMaskedPrefix(c *core.CidrRange) (netip.Prefix, error) { 585 ipp, err := netip.ParsePrefix(c.AddressPrefix + "/" + strconv.Itoa(int(c.PrefixLen.GetValue()))) 586 if err != nil { 587 log.Errorf("failed to parse CidrRange %v as IPNet: %v", c, err) 588 } 589 590 return ipp.Masked(), err 591 } 592 593 // meshconfig ForwardClientCertDetails and the Envoy config enum are off by 1 594 // due to the UNDEFINED in the meshconfig ForwardClientCertDetails 595 func MeshConfigToEnvoyForwardClientCertDetails(c meshconfig.ForwardClientCertDetails) hcm.HttpConnectionManager_ForwardClientCertDetails { 596 return hcm.HttpConnectionManager_ForwardClientCertDetails(c - 1) 597 } 598 599 // ByteCount returns a human readable byte format 600 // Inspired by https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/ 601 func ByteCount(b int) string { 602 const unit = 1000 603 if b < unit { 604 return fmt.Sprintf("%dB", b) 605 } 606 div, exp := int64(unit), 0 607 for n := b / unit; n >= unit; n /= unit { 608 div *= unit 609 exp++ 610 } 611 return fmt.Sprintf("%.1f%cB", 612 float64(b)/float64(div), "kMGTPE"[exp]) 613 } 614 615 // IPv6Compliant encloses ipv6 addresses in square brackets followed by port number in Host header/URIs 616 func IPv6Compliant(host string) string { 617 if strings.Contains(host, ":") { 618 return "[" + host + "]" 619 } 620 return host 621 } 622 623 // DomainName builds the domain name for a given host and port 624 func DomainName(host string, port int) string { 625 return net.JoinHostPort(host, strconv.Itoa(port)) 626 } 627 628 // BuildInternalEndpoint builds an lb endpoint pointing to the internal listener named dest. 629 // If the metadata contains "tunnel.destination" that will become the "endpointId" to prevent deduplication. 630 func BuildInternalEndpoint(dest string, meta *core.Metadata) []*endpoint.LocalityLbEndpoints { 631 llb := []*endpoint.LocalityLbEndpoints{{ 632 LbEndpoints: []*endpoint.LbEndpoint{BuildInternalLbEndpoint(dest, meta)}, 633 }} 634 return llb 635 } 636 637 const OriginalDstMetadataKey = "envoy.filters.listener.original_dst" 638 639 // BuildInternalLbEndpoint builds an lb endpoint pointing to the internal listener named dest. 640 // If the metadata contains ORIGINAL_DST destination that will become the "endpointId" to prevent deduplication. 641 func BuildInternalLbEndpoint(dest string, meta *core.Metadata) *endpoint.LbEndpoint { 642 var endpointID string 643 if tunnel, ok := meta.GetFilterMetadata()[OriginalDstMetadataKey]; ok { 644 if dest, ok := tunnel.GetFields()["local"]; ok { 645 endpointID = dest.GetStringValue() 646 } 647 } 648 address := BuildInternalAddressWithIdentifier(dest, endpointID) 649 650 return &endpoint.LbEndpoint{ 651 HostIdentifier: &endpoint.LbEndpoint_Endpoint{ 652 Endpoint: &endpoint.Endpoint{ 653 Address: address, 654 }, 655 }, 656 Metadata: meta, 657 } 658 } 659 660 func BuildInternalAddressWithIdentifier(name, identifier string) *core.Address { 661 return &core.Address{ 662 Address: &core.Address_EnvoyInternalAddress{ 663 EnvoyInternalAddress: &core.EnvoyInternalAddress{ 664 AddressNameSpecifier: &core.EnvoyInternalAddress_ServerListenerName{ 665 ServerListenerName: name, 666 }, 667 EndpointId: identifier, 668 }, 669 }, 670 } 671 } 672 673 func BuildTunnelMetadataStruct(address string, port int) *structpb.Struct { 674 m := map[string]interface{}{ 675 // logical destination behind the tunnel, on which policy and telemetry will be applied 676 "local": net.JoinHostPort(address, strconv.Itoa(port)), 677 } 678 st, _ := structpb.NewStruct(m) 679 return st 680 } 681 682 func BuildStatefulSessionFilter(svc *model.Service) *hcm.HttpFilter { 683 filterConfig := MaybeBuildStatefulSessionFilterConfig(svc) 684 if filterConfig == nil { 685 return nil 686 } 687 688 return &hcm.HttpFilter{ 689 Name: StatefulSessionFilter, 690 ConfigType: &hcm.HttpFilter_TypedConfig{ 691 TypedConfig: protoconv.MessageToAny(filterConfig), 692 }, 693 } 694 } 695 696 func MaybeBuildStatefulSessionFilterConfig(svc *model.Service) *statefulsession.StatefulSession { 697 if svc == nil { 698 return nil 699 } 700 sessionCookie := svc.Attributes.Labels[features.PersistentSessionLabel] 701 sessionHeader := svc.Attributes.Labels[features.PersistentSessionHeaderLabel] 702 703 switch { 704 case sessionCookie != "": 705 cookieName, cookiePath, found := strings.Cut(sessionCookie, ":") 706 if !found { 707 cookiePath = "/" 708 } 709 // The cookie is using TTL=0, which means they are session cookies (browser is not saving the cookie, expires 710 // when the tab is closed). Most pods don't have ability (or code) to actually persist cookies, and expiration 711 // is better handled in the cookie content (and consistently in the header value - which doesn't have 712 // persistence semantics). 713 return &statefulsession.StatefulSession{ 714 SessionState: &core.TypedExtensionConfig{ 715 Name: "envoy.http.stateful_session.cookie", 716 TypedConfig: protoconv.MessageToAny(&cookiev3.CookieBasedSessionState{ 717 Cookie: &httpv3.Cookie{ 718 Path: cookiePath, 719 Name: cookieName, 720 }, 721 }), 722 }, 723 } 724 case sessionHeader != "": 725 return &statefulsession.StatefulSession{ 726 SessionState: &core.TypedExtensionConfig{ 727 Name: "envoy.http.stateful_session.header", 728 TypedConfig: protoconv.MessageToAny(&headerv3.HeaderBasedSessionState{ 729 Name: sessionHeader, 730 }), 731 }, 732 } 733 } 734 return nil 735 } 736 737 // GetPortLevelTrafficPolicy return the port level traffic policy and true if it exists. 738 // Otherwise returns the original policy that applies to all destination ports. 739 func GetPortLevelTrafficPolicy(policy *networking.TrafficPolicy, port *model.Port) (*networking.TrafficPolicy, bool) { 740 if port == nil { 741 return policy, false 742 } 743 if policy == nil { 744 return nil, false 745 } 746 747 var portTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy 748 // Check if port level overrides exist, if yes override with them. 749 for _, p := range policy.PortLevelSettings { 750 if p.Port != nil && uint32(port.Port) == p.Port.Number { 751 // per the docs, port level policies do not inherit and instead to defaults if not provided 752 portTrafficPolicy = p 753 break 754 } 755 } 756 if portTrafficPolicy == nil { 757 return policy, false 758 } 759 760 // Note that port-level settings will override the destination-level settings. 761 // Traffic settings specified at the destination-level will not be inherited when overridden by port-level settings, 762 // i.e. default values will be applied to fields omitted in port-level traffic policies. 763 return shadowCopyPortTrafficPolicy(portTrafficPolicy), true 764 } 765 766 // MergeSubsetTrafficPolicy merges the destination and subset level traffic policy for the given port. 767 func MergeSubsetTrafficPolicy(original, subsetPolicy *networking.TrafficPolicy, port *model.Port) *networking.TrafficPolicy { 768 // First get DR port level traffic policy 769 original, _ = GetPortLevelTrafficPolicy(original, port) 770 if subsetPolicy == nil { 771 return original 772 } 773 subsetPolicy, hasPortLevel := GetPortLevelTrafficPolicy(subsetPolicy, port) 774 if original == nil { 775 return subsetPolicy 776 } 777 778 // merge DR with subset traffic policy 779 // Override with subset values. 780 mergedPolicy := ShallowCopyTrafficPolicy(original) 781 782 return mergeTrafficPolicy(mergedPolicy, subsetPolicy, hasPortLevel) 783 } 784 785 // Note that port-level settings will override the destination-level settings. 786 // Traffic settings specified at the destination-level will not be inherited when overridden by port-level settings, 787 // i.e. default values will be applied to fields omitted in port-level traffic policies. 788 func mergeTrafficPolicy(mergedPolicy, subsetPolicy *networking.TrafficPolicy, hasPortLevel bool) *networking.TrafficPolicy { 789 if subsetPolicy.ConnectionPool != nil || hasPortLevel { 790 mergedPolicy.ConnectionPool = subsetPolicy.ConnectionPool 791 } 792 if subsetPolicy.OutlierDetection != nil || hasPortLevel { 793 mergedPolicy.OutlierDetection = subsetPolicy.OutlierDetection 794 } 795 if subsetPolicy.LoadBalancer != nil || hasPortLevel { 796 mergedPolicy.LoadBalancer = subsetPolicy.LoadBalancer 797 } 798 if subsetPolicy.Tls != nil || hasPortLevel { 799 mergedPolicy.Tls = subsetPolicy.Tls 800 } 801 802 if subsetPolicy.Tunnel != nil { 803 mergedPolicy.Tunnel = subsetPolicy.Tunnel 804 } 805 if subsetPolicy.ProxyProtocol != nil { 806 mergedPolicy.ProxyProtocol = subsetPolicy.ProxyProtocol 807 } 808 return mergedPolicy 809 } 810 811 func shadowCopyPortTrafficPolicy(portTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy) *networking.TrafficPolicy { 812 if portTrafficPolicy == nil { 813 return nil 814 } 815 ret := &networking.TrafficPolicy{} 816 ret.ConnectionPool = portTrafficPolicy.ConnectionPool 817 ret.LoadBalancer = portTrafficPolicy.LoadBalancer 818 ret.OutlierDetection = portTrafficPolicy.OutlierDetection 819 ret.Tls = portTrafficPolicy.Tls 820 return ret 821 } 822 823 // ShallowCopyTrafficPolicy shallow copy a traffic policy, portLevelSettings are ignored. 824 func ShallowCopyTrafficPolicy(original *networking.TrafficPolicy) *networking.TrafficPolicy { 825 if original == nil { 826 return nil 827 } 828 ret := &networking.TrafficPolicy{} 829 ret.ConnectionPool = original.ConnectionPool 830 ret.LoadBalancer = original.LoadBalancer 831 ret.OutlierDetection = original.OutlierDetection 832 ret.Tls = original.Tls 833 ret.Tunnel = original.Tunnel 834 ret.ProxyProtocol = original.ProxyProtocol 835 return ret 836 }