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  }