istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/service.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  // This file describes the abstract model of services (and their instances) as
    16  // represented in Istio. This model is independent of the underlying platform
    17  // (Kubernetes, Mesos, etc.). Platform specific adapters found populate the
    18  // model object with various fields, from the metadata found in the platform.
    19  // The platform independent proxy code uses the representation in the model to
    20  // generate the configuration files for the Layer 7 proxy sidecar. The proxy
    21  // code is specific to individual proxy implementations
    22  
    23  package model
    24  
    25  import (
    26  	"fmt"
    27  	"net/netip"
    28  	"sort"
    29  	"strconv"
    30  	"strings"
    31  	"time"
    32  
    33  	"github.com/google/go-cmp/cmp"
    34  	"github.com/google/go-cmp/cmp/cmpopts"
    35  	"github.com/mitchellh/copystructure"
    36  	"google.golang.org/protobuf/proto"
    37  	"k8s.io/apimachinery/pkg/types"
    38  
    39  	"istio.io/api/label"
    40  	"istio.io/istio/pilot/pkg/features"
    41  	"istio.io/istio/pilot/pkg/serviceregistry/provider"
    42  	"istio.io/istio/pkg/cluster"
    43  	"istio.io/istio/pkg/config/constants"
    44  	"istio.io/istio/pkg/config/host"
    45  	"istio.io/istio/pkg/config/labels"
    46  	"istio.io/istio/pkg/config/protocol"
    47  	"istio.io/istio/pkg/config/schema/kind"
    48  	"istio.io/istio/pkg/config/visibility"
    49  	"istio.io/istio/pkg/maps"
    50  	pm "istio.io/istio/pkg/model"
    51  	"istio.io/istio/pkg/network"
    52  	"istio.io/istio/pkg/slices"
    53  	"istio.io/istio/pkg/util/sets"
    54  	"istio.io/istio/pkg/workloadapi"
    55  	"istio.io/istio/pkg/workloadapi/security"
    56  )
    57  
    58  // Service describes an Istio service (e.g., catalog.mystore.com:8080)
    59  // Each service has a fully qualified domain name (FQDN) and one or more
    60  // ports where the service is listening for connections. *Optionally*, a
    61  // service can have a single load balancer/virtual IP address associated
    62  // with it, such that the DNS queries for the FQDN resolves to the virtual
    63  // IP address (a load balancer IP).
    64  //
    65  // E.g., in kubernetes, a service foo is associated with
    66  // foo.default.svc.cluster.local hostname, has a virtual IP of 10.0.1.1 and
    67  // listens on ports 80, 8080
    68  type Service struct {
    69  	// Attributes contains additional attributes associated with the service
    70  	// used mostly by RBAC for policy enforcement purposes.
    71  	Attributes ServiceAttributes
    72  
    73  	// Ports is the set of network ports where the service is listening for
    74  	// connections
    75  	Ports PortList `json:"ports,omitempty"`
    76  
    77  	// ServiceAccounts specifies the service accounts that run the service.
    78  	ServiceAccounts []string `json:"serviceAccounts,omitempty"`
    79  
    80  	// CreationTime records the time this service was created, if available.
    81  	CreationTime time.Time `json:"creationTime,omitempty"`
    82  
    83  	// Name of the service, e.g. "catalog.mystore.com"
    84  	Hostname host.Name `json:"hostname"`
    85  
    86  	// ClusterVIPs specifies the service address of the load balancer
    87  	// in each of the clusters where the service resides
    88  	ClusterVIPs AddressMap `json:"clusterVIPs,omitempty"`
    89  
    90  	// DefaultAddress specifies the default service IP of the load balancer.
    91  	// Do not access directly. Use GetAddressForProxy
    92  	DefaultAddress string `json:"defaultAddress,omitempty"`
    93  
    94  	// AutoAllocatedIPv4Address and AutoAllocatedIPv6Address specifies
    95  	// the automatically allocated IPv4/IPv6 address out of the reserved
    96  	// Class E subnet (240.240.0.0/16) or reserved Benchmarking IP range
    97  	// (2001:2::/48) in RFC5180.for service entries with non-wildcard
    98  	// hostnames. The IPs assigned to services are not
    99  	// synchronized across istiod replicas as the DNS resolution
   100  	// for these service entries happens completely inside a pod
   101  	// whose proxy is managed by one istiod. That said, the algorithm
   102  	// to allocate IPs is pretty deterministic that at stable state, two
   103  	// istiods will allocate the exact same set of IPs for a given set of
   104  	// service entries.
   105  	AutoAllocatedIPv4Address string `json:"autoAllocatedIPv4Address,omitempty"`
   106  	AutoAllocatedIPv6Address string `json:"autoAllocatedIPv6Address,omitempty"`
   107  
   108  	// Resolution indicates how the service instances need to be resolved before routing
   109  	// traffic. Most services in the service registry will use static load balancing wherein
   110  	// the proxy will decide the service instance that will receive the traffic. Service entries
   111  	// could either use DNS load balancing (i.e. proxy will query DNS server for the IP of the service)
   112  	// or use the passthrough model (i.e. proxy will forward the traffic to the network endpoint requested
   113  	// by the caller)
   114  	Resolution Resolution
   115  
   116  	// MeshExternal (if true) indicates that the service is external to the mesh.
   117  	// These services are defined using Istio's ServiceEntry spec.
   118  	MeshExternal bool
   119  
   120  	// ResourceVersion represents the internal version of this object.
   121  	ResourceVersion string
   122  }
   123  
   124  func (s *Service) NamespacedName() types.NamespacedName {
   125  	return types.NamespacedName{Name: s.Attributes.Name, Namespace: s.Attributes.Namespace}
   126  }
   127  
   128  func (s *Service) Key() string {
   129  	if s == nil {
   130  		return ""
   131  	}
   132  
   133  	return s.Attributes.Namespace + "/" + string(s.Hostname)
   134  }
   135  
   136  var serviceCmpOpts = []cmp.Option{cmpopts.IgnoreFields(AddressMap{}, "mutex")}
   137  
   138  func (s *Service) CmpOpts() []cmp.Option {
   139  	return serviceCmpOpts
   140  }
   141  
   142  // Resolution indicates how the service instances need to be resolved before routing traffic.
   143  type Resolution int
   144  
   145  const (
   146  	// ClientSideLB implies that the proxy will decide the endpoint from its local lb pool
   147  	ClientSideLB Resolution = iota
   148  	// DNSLB implies that the proxy will resolve a DNS address and forward to the resolved address
   149  	DNSLB
   150  	// Passthrough implies that the proxy should forward traffic to the destination IP requested by the caller
   151  	Passthrough
   152  	// DNSRoundRobinLB implies that the proxy will resolve a DNS address and forward to the resolved address
   153  	DNSRoundRobinLB
   154  	// Alias defines a Service that is an alias for another.
   155  	Alias
   156  )
   157  
   158  // String converts Resolution in to String.
   159  func (resolution Resolution) String() string {
   160  	switch resolution {
   161  	case ClientSideLB:
   162  		return "ClientSide"
   163  	case DNSLB:
   164  		return "DNS"
   165  	case DNSRoundRobinLB:
   166  		return "DNSRoundRobin"
   167  	case Passthrough:
   168  		return "Passthrough"
   169  	default:
   170  		return fmt.Sprintf("%d", int(resolution))
   171  	}
   172  }
   173  
   174  const (
   175  	// LocalityLabel indicates the region/zone/subzone of an instance. It is used to override the native
   176  	// registry's value.
   177  	//
   178  	// Note: because k8s labels does not support `/`, so we use `.` instead in k8s.
   179  	LocalityLabel = pm.LocalityLabel
   180  )
   181  
   182  const (
   183  	// TunnelLabel defines the label workloads describe to indicate that they support tunneling.
   184  	// Values are expected to be a CSV list, sorted by preference, of protocols supported.
   185  	// Currently supported values:
   186  	// * "http": indicates tunneling over HTTP over TCP. HTTP/2 vs HTTP/1.1 may be supported by ALPN negotiation.
   187  	// Planned future values:
   188  	// * "http3": indicates tunneling over HTTP over QUIC. This is distinct from "http", since we cannot do ALPN
   189  	//   negotiation for QUIC vs TCP.
   190  	// Users should appropriately parse the full list rather than doing a string literal check to
   191  	// ensure future-proofing against new protocols being added.
   192  	TunnelLabel = "networking.istio.io/tunnel"
   193  	// TunnelLabelShortName is a short name for TunnelLabel to be used in optimized scenarios.
   194  	TunnelLabelShortName = "tunnel"
   195  	// TunnelHTTP indicates tunneling over HTTP over TCP. HTTP/2 vs HTTP/1.1 may be supported by ALPN
   196  	// negotiation. Note: ALPN negotiation is not currently implemented; HTTP/2 will always be used.
   197  	// This is future-proofed, however, because only the `h2` ALPN is exposed.
   198  	TunnelHTTP = "http"
   199  )
   200  
   201  const (
   202  	// TLSModeLabelShortname name used for determining endpoint level tls transport socket configuration
   203  	TLSModeLabelShortname = "tlsMode"
   204  
   205  	// DisabledTLSModeLabel implies that this endpoint should receive traffic as is (mostly plaintext)
   206  	DisabledTLSModeLabel = "disabled"
   207  
   208  	// IstioMutualTLSModeLabel implies that the endpoint is ready to receive Istio mTLS connections.
   209  	IstioMutualTLSModeLabel = "istio"
   210  
   211  	// IstioCanonicalServiceLabelName is the name of label for the Istio Canonical Service for a workload instance.
   212  	IstioCanonicalServiceLabelName = pm.IstioCanonicalServiceLabelName
   213  
   214  	// IstioCanonicalServiceRevisionLabelName is the name of label for the Istio Canonical Service revision for a workload instance.
   215  	IstioCanonicalServiceRevisionLabelName = pm.IstioCanonicalServiceRevisionLabelName
   216  )
   217  
   218  func SupportsTunnel(labels map[string]string, tunnelType string) bool {
   219  	return sets.New(strings.Split(labels[TunnelLabel], ",")...).Contains(tunnelType)
   220  }
   221  
   222  // Port represents a network port where a service is listening for
   223  // connections. The port should be annotated with the type of protocol
   224  // used by the port.
   225  type Port struct {
   226  	// Name ascribes a human readable name for the port object. When a
   227  	// service has multiple ports, the name field is mandatory
   228  	Name string `json:"name,omitempty"`
   229  
   230  	// Port number where the service can be reached. Does not necessarily
   231  	// map to the corresponding port numbers for the instances behind the
   232  	// service.
   233  	Port int `json:"port"`
   234  
   235  	// Protocol to be used for the port.
   236  	Protocol protocol.Instance `json:"protocol,omitempty"`
   237  }
   238  
   239  func (p Port) String() string {
   240  	return fmt.Sprintf("Name:%s Port:%d Protocol:%v", p.Name, p.Port, p.Protocol)
   241  }
   242  
   243  // PortList is a set of ports
   244  type PortList []*Port
   245  
   246  // TrafficDirection defines whether traffic exists a service instance or enters a service instance
   247  type TrafficDirection string
   248  
   249  const (
   250  	// TrafficDirectionInbound indicates inbound traffic
   251  	TrafficDirectionInbound TrafficDirection = "inbound"
   252  	// TrafficDirectionInboundVIP indicates inbound traffic for vip
   253  	TrafficDirectionInboundVIP TrafficDirection = "inbound-vip"
   254  	// TrafficDirectionOutbound indicates outbound traffic
   255  	TrafficDirectionOutbound TrafficDirection = "outbound"
   256  
   257  	// trafficDirectionOutboundSrvPrefix the prefix for a DNS SRV type subset key
   258  	trafficDirectionOutboundSrvPrefix = string(TrafficDirectionOutbound) + "_"
   259  	// trafficDirectionInboundSrvPrefix the prefix for a DNS SRV type subset key
   260  	trafficDirectionInboundSrvPrefix = string(TrafficDirectionInbound) + "_"
   261  )
   262  
   263  // ServiceInstance represents an individual instance of a specific version
   264  // of a service. It binds a network endpoint (ip:port), the service
   265  // description (which is oblivious to various versions) and a set of labels
   266  // that describe the service version associated with this instance.
   267  //
   268  // Since a ServiceInstance has a single IstioEndpoint, which has a single port,
   269  // multiple ServiceInstances are required to represent a workload that listens
   270  // on multiple ports.
   271  //
   272  // The labels associated with a service instance are unique per a network endpoint.
   273  // There is one well defined set of labels for each service instance network endpoint.
   274  //
   275  // For example, the set of service instances associated with catalog.mystore.com
   276  // are modeled like this
   277  //
   278  //	--> IstioEndpoint(172.16.0.1:8888), Service(catalog.myservice.com), Labels(foo=bar)
   279  //	--> IstioEndpoint(172.16.0.2:8888), Service(catalog.myservice.com), Labels(foo=bar)
   280  //	--> IstioEndpoint(172.16.0.3:8888), Service(catalog.myservice.com), Labels(kitty=cat)
   281  //	--> IstioEndpoint(172.16.0.4:8888), Service(catalog.myservice.com), Labels(kitty=cat)
   282  type ServiceInstance struct {
   283  	Service     *Service       `json:"service,omitempty"`
   284  	ServicePort *Port          `json:"servicePort,omitempty"`
   285  	Endpoint    *IstioEndpoint `json:"endpoint,omitempty"`
   286  }
   287  
   288  func (instance *ServiceInstance) CmpOpts() []cmp.Option {
   289  	res := []cmp.Option{}
   290  	res = append(res, istioEndpointCmpOpts...)
   291  	res = append(res, serviceCmpOpts...)
   292  	return res
   293  }
   294  
   295  // ServiceTarget includes a Service object, along with a specific service port
   296  // and target port. This is basically a smaller version of ServiceInstance,
   297  // intended to avoid the need to have the full object when only port information
   298  // is needed.
   299  type ServiceTarget struct {
   300  	Service *Service
   301  	Port    ServiceInstancePort
   302  }
   303  
   304  func (st ServiceTarget) NamespacedName() types.NamespacedName {
   305  	return st.Service.NamespacedName()
   306  }
   307  
   308  type (
   309  	ServicePort = *Port
   310  	// ServiceInstancePort defines a port that has both a port and targetPort (which distinguishes it from model.Port)
   311  	// Note: ServiceInstancePort only makes sense in the context of a specific ServiceInstance, because TargetPort depends on a specific instance.
   312  	ServiceInstancePort struct {
   313  		ServicePort
   314  		TargetPort uint32
   315  	}
   316  )
   317  
   318  func ServiceInstanceToTarget(e *ServiceInstance) ServiceTarget {
   319  	return ServiceTarget{
   320  		Service: e.Service,
   321  		Port: ServiceInstancePort{
   322  			ServicePort: e.ServicePort,
   323  			TargetPort:  e.Endpoint.EndpointPort,
   324  		},
   325  	}
   326  }
   327  
   328  // DeepCopy creates a copy of ServiceInstance.
   329  func (instance *ServiceInstance) DeepCopy() *ServiceInstance {
   330  	return &ServiceInstance{
   331  		Service:  instance.Service.DeepCopy(),
   332  		Endpoint: instance.Endpoint.DeepCopy(),
   333  		ServicePort: &Port{
   334  			Name:     instance.ServicePort.Name,
   335  			Port:     instance.ServicePort.Port,
   336  			Protocol: instance.ServicePort.Protocol,
   337  		},
   338  	}
   339  }
   340  
   341  type workloadKind int
   342  
   343  const (
   344  	// PodKind indicates the workload is from pod
   345  	PodKind workloadKind = iota
   346  	// WorkloadEntryKind indicates the workload is from workloadentry
   347  	WorkloadEntryKind
   348  )
   349  
   350  func (k workloadKind) String() string {
   351  	if k == PodKind {
   352  		return "Pod"
   353  	}
   354  
   355  	if k == WorkloadEntryKind {
   356  		return "WorkloadEntry"
   357  	}
   358  	return ""
   359  }
   360  
   361  type WorkloadInstance struct {
   362  	Name      string `json:"name,omitempty"`
   363  	Namespace string `json:"namespace,omitempty"`
   364  	// Where the workloadInstance come from, valid values are`Pod` or `WorkloadEntry`
   365  	Kind     workloadKind      `json:"kind"`
   366  	Endpoint *IstioEndpoint    `json:"endpoint,omitempty"`
   367  	PortMap  map[string]uint32 `json:"portMap,omitempty"`
   368  	// Can only be selected by service entry of DNS type.
   369  	DNSServiceEntryOnly bool `json:"dnsServiceEntryOnly,omitempty"`
   370  }
   371  
   372  func (instance *WorkloadInstance) CmpOpts() []cmp.Option {
   373  	return istioEndpointCmpOpts
   374  }
   375  
   376  // DeepCopy creates a copy of WorkloadInstance.
   377  func (instance *WorkloadInstance) DeepCopy() *WorkloadInstance {
   378  	pmap := map[string]uint32{}
   379  	for k, v := range instance.PortMap {
   380  		pmap[k] = v
   381  	}
   382  	return &WorkloadInstance{
   383  		Name:      instance.Name,
   384  		Namespace: instance.Namespace,
   385  		Kind:      instance.Kind,
   386  		PortMap:   pmap,
   387  		Endpoint:  instance.Endpoint.DeepCopy(),
   388  	}
   389  }
   390  
   391  // WorkloadInstancesEqual is a custom comparison of workload instances based on the fields that we need.
   392  // Returns true if equal, false otherwise.
   393  func WorkloadInstancesEqual(first, second *WorkloadInstance) bool {
   394  	if first.Endpoint == nil || second.Endpoint == nil {
   395  		return first.Endpoint == second.Endpoint
   396  	}
   397  	if first.Endpoint.Address != second.Endpoint.Address {
   398  		return false
   399  	}
   400  	if first.Endpoint.Network != second.Endpoint.Network {
   401  		return false
   402  	}
   403  	if first.Endpoint.TLSMode != second.Endpoint.TLSMode {
   404  		return false
   405  	}
   406  	if !first.Endpoint.Labels.Equals(second.Endpoint.Labels) {
   407  		return false
   408  	}
   409  	if first.Endpoint.ServiceAccount != second.Endpoint.ServiceAccount {
   410  		return false
   411  	}
   412  	if first.Endpoint.Locality != second.Endpoint.Locality {
   413  		return false
   414  	}
   415  	if first.Endpoint.GetLoadBalancingWeight() != second.Endpoint.GetLoadBalancingWeight() {
   416  		return false
   417  	}
   418  	if first.Namespace != second.Namespace {
   419  		return false
   420  	}
   421  	if first.Name != second.Name {
   422  		return false
   423  	}
   424  	if first.Kind != second.Kind {
   425  		return false
   426  	}
   427  	if !maps.Equal(first.PortMap, second.PortMap) {
   428  		return false
   429  	}
   430  	return true
   431  }
   432  
   433  // GetLocalityLabel returns the locality from the supplied label. Because Kubernetes
   434  // labels don't support `/`, we replace "." with "/" in the supplied label as a workaround.
   435  func GetLocalityLabel(label string) string {
   436  	return pm.GetLocalityLabel(label)
   437  }
   438  
   439  // Locality information for an IstioEndpoint
   440  type Locality struct {
   441  	// Label for locality on the endpoint. This is a "/" separated string.
   442  	Label string
   443  
   444  	// ClusterID where the endpoint is located
   445  	ClusterID cluster.ID
   446  }
   447  
   448  // Endpoint health status.
   449  type HealthStatus int32
   450  
   451  const (
   452  	// Healthy.
   453  	Healthy HealthStatus = 1
   454  	// Unhealthy.
   455  	UnHealthy HealthStatus = 2
   456  	// Draining - the constant matches envoy
   457  	Draining HealthStatus = 3
   458  )
   459  
   460  // IstioEndpoint defines a network address (IP:port) associated with an instance of the
   461  // service. A service has one or more instances each running in a
   462  // container/VM/pod. If a service has multiple ports, then the same
   463  // instance IP is expected to be listening on multiple ports (one per each
   464  // service port). Note that the port associated with an instance does not
   465  // have to be the same as the port associated with the service. Depending
   466  // on the network setup (NAT, overlays), this could vary.
   467  //
   468  // For e.g., if catalog.mystore.com is accessible through port 80 and 8080,
   469  // and it maps to an instance with IP 172.16.0.1, such that connections to
   470  // port 80 are forwarded to port 55446, and connections to port 8080 are
   471  // forwarded to port 33333,
   472  //
   473  // then internally, we have two endpoint structs for the
   474  // service catalog.mystore.com
   475  //
   476  //	--> 172.16.0.1:55446 (with ServicePort pointing to 80) and
   477  //	--> 172.16.0.1:33333 (with ServicePort pointing to 8080)
   478  //
   479  // TODO: Investigate removing ServiceInstance entirely.
   480  type IstioEndpoint struct {
   481  	// Labels points to the workload or deployment labels.
   482  	Labels labels.Instance
   483  
   484  	// Address is the address of the endpoint, using envoy proto.
   485  	Address string
   486  
   487  	// ServicePortName tracks the name of the port, this is used to select the IstioEndpoint by service port.
   488  	ServicePortName string
   489  	// LegacyClusterPortKey provides an alternative key from ServicePortName to support legacy quirks in the API.
   490  	// Basically, EDS merges by port name, but CDS historically ignored port name and matched on number.
   491  	// Note that for Kubernetes Service, this is identical - its only ServiceEntry where these checks can differ
   492  	LegacyClusterPortKey int
   493  
   494  	// ServiceAccount holds the associated service account.
   495  	ServiceAccount string
   496  
   497  	// Network holds the network where this endpoint is present
   498  	Network network.ID
   499  
   500  	// The locality where the endpoint is present.
   501  	Locality Locality
   502  
   503  	// EndpointPort is the port where the workload is listening, can be different
   504  	// from the service port.
   505  	EndpointPort uint32
   506  
   507  	// The load balancing weight associated with this endpoint.
   508  	LbWeight uint32
   509  
   510  	// TLSMode endpoint is injected with istio sidecar and ready to configure Istio mTLS
   511  	TLSMode string
   512  
   513  	// Namespace that this endpoint belongs to. This is for telemetry purpose.
   514  	Namespace string
   515  
   516  	// Name of the workload that this endpoint belongs to. This is for telemetry purpose.
   517  	WorkloadName string
   518  
   519  	// Specifies the hostname of the Pod, empty for vm workload.
   520  	HostName string
   521  
   522  	// If specified, the fully qualified Pod hostname will be "<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>".
   523  	SubDomain string
   524  
   525  	// Determines the discoverability of this endpoint throughout the mesh.
   526  	DiscoverabilityPolicy EndpointDiscoverabilityPolicy `json:"-"`
   527  
   528  	// Indicates the endpoint health status.
   529  	HealthStatus HealthStatus
   530  
   531  	// If in k8s, the node where the pod resides
   532  	NodeName string
   533  }
   534  
   535  func (ep *IstioEndpoint) SupportsTunnel(tunnelType string) bool {
   536  	return SupportsTunnel(ep.Labels, tunnelType)
   537  }
   538  
   539  // GetLoadBalancingWeight returns the weight for this endpoint, normalized to always be > 0.
   540  func (ep *IstioEndpoint) GetLoadBalancingWeight() uint32 {
   541  	if ep.LbWeight > 0 {
   542  		return ep.LbWeight
   543  	}
   544  	return 1
   545  }
   546  
   547  // IsDiscoverableFromProxy indicates whether this endpoint is discoverable from the given Proxy.
   548  func (ep *IstioEndpoint) IsDiscoverableFromProxy(p *Proxy) bool {
   549  	if ep == nil || ep.DiscoverabilityPolicy == nil {
   550  		// If no policy was assigned, default to discoverable mesh-wide.
   551  		// TODO(nmittler): Will need to re-think this default when cluster.local is actually cluster-local.
   552  		return true
   553  	}
   554  	return ep.DiscoverabilityPolicy.IsDiscoverableFromProxy(ep, p)
   555  }
   556  
   557  // MetadataClone returns the cloned endpoint metadata used for telemetry purposes.
   558  // This should be used when the endpoint labels should be updated.
   559  func (ep *IstioEndpoint) MetadataClone() *EndpointMetadata {
   560  	return &EndpointMetadata{
   561  		Network:      ep.Network,
   562  		TLSMode:      ep.TLSMode,
   563  		WorkloadName: ep.WorkloadName,
   564  		Namespace:    ep.Namespace,
   565  		Labels:       maps.Clone(ep.Labels),
   566  		ClusterID:    ep.Locality.ClusterID,
   567  	}
   568  }
   569  
   570  // Metadata returns the endpoint metadata used for telemetry purposes.
   571  func (ep *IstioEndpoint) Metadata() *EndpointMetadata {
   572  	return &EndpointMetadata{
   573  		Network:      ep.Network,
   574  		TLSMode:      ep.TLSMode,
   575  		WorkloadName: ep.WorkloadName,
   576  		Namespace:    ep.Namespace,
   577  		Labels:       ep.Labels,
   578  		ClusterID:    ep.Locality.ClusterID,
   579  	}
   580  }
   581  
   582  var istioEndpointCmpOpts = []cmp.Option{cmpopts.IgnoreUnexported(IstioEndpoint{}), endpointDiscoverabilityPolicyImplCmpOpt, cmp.AllowUnexported()}
   583  
   584  func (ep *IstioEndpoint) CmpOpts() []cmp.Option {
   585  	return istioEndpointCmpOpts
   586  }
   587  
   588  // EndpointMetadata represents metadata set on Envoy LbEndpoint used for telemetry purposes.
   589  type EndpointMetadata struct {
   590  	// Network holds the network where this endpoint is present
   591  	Network network.ID
   592  
   593  	// TLSMode endpoint is injected with istio sidecar and ready to configure Istio mTLS
   594  	TLSMode string
   595  
   596  	// Name of the workload that this endpoint belongs to. This is for telemetry purpose.
   597  	WorkloadName string
   598  
   599  	// Namespace that this endpoint belongs to. This is for telemetry purpose.
   600  	Namespace string
   601  
   602  	// Labels points to the workload or deployment labels.
   603  	Labels labels.Instance
   604  
   605  	// ClusterID where the endpoint is located
   606  	ClusterID cluster.ID
   607  }
   608  
   609  // EndpointDiscoverabilityPolicy determines the discoverability of an endpoint throughout the mesh.
   610  type EndpointDiscoverabilityPolicy interface {
   611  	// IsDiscoverableFromProxy indicates whether an endpoint is discoverable from the given Proxy.
   612  	IsDiscoverableFromProxy(*IstioEndpoint, *Proxy) bool
   613  
   614  	// String returns name of this policy.
   615  	String() string
   616  }
   617  
   618  type endpointDiscoverabilityPolicyImpl struct {
   619  	name string
   620  	f    func(*IstioEndpoint, *Proxy) bool
   621  }
   622  
   623  func (p *endpointDiscoverabilityPolicyImpl) IsDiscoverableFromProxy(ep *IstioEndpoint, proxy *Proxy) bool {
   624  	return p.f(ep, proxy)
   625  }
   626  
   627  func (p *endpointDiscoverabilityPolicyImpl) String() string {
   628  	return p.name
   629  }
   630  
   631  var endpointDiscoverabilityPolicyImplCmpOpt = cmp.Comparer(func(x, y endpointDiscoverabilityPolicyImpl) bool {
   632  	return x.String() == y.String()
   633  })
   634  
   635  func (p *endpointDiscoverabilityPolicyImpl) CmpOpts() []cmp.Option {
   636  	return []cmp.Option{endpointDiscoverabilityPolicyImplCmpOpt}
   637  }
   638  
   639  // AlwaysDiscoverable is an EndpointDiscoverabilityPolicy that allows an endpoint to be discoverable throughout the mesh.
   640  var AlwaysDiscoverable EndpointDiscoverabilityPolicy = &endpointDiscoverabilityPolicyImpl{
   641  	name: "AlwaysDiscoverable",
   642  	f: func(*IstioEndpoint, *Proxy) bool {
   643  		return true
   644  	},
   645  }
   646  
   647  // DiscoverableFromSameCluster is an EndpointDiscoverabilityPolicy that only allows an endpoint to be discoverable
   648  // from proxies within the same cluster.
   649  var DiscoverableFromSameCluster EndpointDiscoverabilityPolicy = &endpointDiscoverabilityPolicyImpl{
   650  	name: "DiscoverableFromSameCluster",
   651  	f: func(ep *IstioEndpoint, p *Proxy) bool {
   652  		return p.InCluster(ep.Locality.ClusterID)
   653  	},
   654  }
   655  
   656  // ServiceAttributes represents a group of custom attributes of the service.
   657  type ServiceAttributes struct {
   658  	// ServiceRegistry indicates the backing service registry system where this service
   659  	// was sourced from.
   660  	// TODO: move the ServiceRegistry type from platform.go to model
   661  	ServiceRegistry provider.ID
   662  	// Name is "destination.service.name" attribute
   663  	Name string
   664  	// Namespace is "destination.service.namespace" attribute
   665  	Namespace string
   666  	// Labels applied to the service
   667  	Labels map[string]string
   668  	// ExportTo defines the visibility of Service in
   669  	// a namespace when the namespace is imported.
   670  	ExportTo sets.Set[visibility.Instance]
   671  
   672  	// LabelSelectors are the labels used by the service to select workloads.
   673  	// Applicable to both Kubernetes and ServiceEntries.
   674  	LabelSelectors map[string]string
   675  
   676  	// Aliases is the resolved set of aliases for this service. This is computed based on a global view of all Service's `AliasFor`
   677  	// fields.
   678  	// For example, if I had two Services with `externalName: foo`, "a" and "b", then the "foo" service would have Aliases=[a,b].
   679  	Aliases []NamespacedHostname
   680  
   681  	// For Kubernetes platform
   682  
   683  	// ClusterExternalAddresses is a mapping between a cluster name and the external
   684  	// address(es) to access the service from outside the cluster.
   685  	// Used by the aggregator to aggregate the Attributes.ClusterExternalAddresses
   686  	// for clusters where the service resides
   687  	ClusterExternalAddresses *AddressMap
   688  
   689  	// ClusterExternalPorts is a mapping between a cluster name and the service port
   690  	// to node port mappings for a given service. When accessing the service via
   691  	// node port IPs, we need to use the kubernetes assigned node ports of the service
   692  	// The port that the user provides in the meshNetworks config is the service port.
   693  	// We translate that to the appropriate node port here.
   694  	ClusterExternalPorts map[cluster.ID]map[uint32]uint32
   695  
   696  	PassthroughTargetPorts map[uint32]uint32
   697  
   698  	K8sAttributes
   699  }
   700  
   701  type NamespacedHostname struct {
   702  	Hostname  host.Name
   703  	Namespace string
   704  }
   705  
   706  type K8sAttributes struct {
   707  	// Type holds the value of the corev1.Type of the Kubernetes service
   708  	// spec.Type
   709  	Type string
   710  
   711  	// spec.ExternalName
   712  	ExternalName string
   713  
   714  	// NodeLocal means the proxy will only forward traffic to node local endpoints
   715  	// spec.InternalTrafficPolicy == Local
   716  	NodeLocal bool
   717  }
   718  
   719  // DeepCopy creates a deep copy of ServiceAttributes, but skips internal mutexes.
   720  func (s *ServiceAttributes) DeepCopy() ServiceAttributes {
   721  	// AddressMap contains a mutex, which is safe to copy in this case.
   722  	// nolint: govet
   723  	out := *s
   724  
   725  	if s.Labels != nil {
   726  		out.Labels = make(map[string]string, len(s.Labels))
   727  		for k, v := range s.Labels {
   728  			out.Labels[k] = v
   729  		}
   730  	}
   731  
   732  	if s.ExportTo != nil {
   733  		out.ExportTo = s.ExportTo.Copy()
   734  	}
   735  
   736  	if s.LabelSelectors != nil {
   737  		out.LabelSelectors = make(map[string]string, len(s.LabelSelectors))
   738  		for k, v := range s.LabelSelectors {
   739  			out.LabelSelectors[k] = v
   740  		}
   741  	}
   742  
   743  	out.ClusterExternalAddresses = s.ClusterExternalAddresses.DeepCopy()
   744  
   745  	if s.ClusterExternalPorts != nil {
   746  		out.ClusterExternalPorts = make(map[cluster.ID]map[uint32]uint32, len(s.ClusterExternalPorts))
   747  		for k, m := range s.ClusterExternalPorts {
   748  			if m == nil {
   749  				out.ClusterExternalPorts[k] = nil
   750  				continue
   751  			}
   752  
   753  			out.ClusterExternalPorts[k] = make(map[uint32]uint32, len(m))
   754  			for sp, np := range m {
   755  				out.ClusterExternalPorts[k][sp] = np
   756  			}
   757  		}
   758  	}
   759  
   760  	out.Aliases = slices.Clone(s.Aliases)
   761  
   762  	// AddressMap contains a mutex, which is safe to return a copy in this case.
   763  	// nolint: govet
   764  	return out
   765  }
   766  
   767  // Equals checks whether the attributes are equal from the passed in service.
   768  func (s *ServiceAttributes) Equals(other *ServiceAttributes) bool {
   769  	if s == nil {
   770  		return other == nil
   771  	}
   772  	if other == nil {
   773  		return s == nil
   774  	}
   775  
   776  	if !maps.Equal(s.Labels, other.Labels) {
   777  		return false
   778  	}
   779  
   780  	if !maps.Equal(s.LabelSelectors, other.LabelSelectors) {
   781  		return false
   782  	}
   783  
   784  	if !maps.Equal(s.ExportTo, other.ExportTo) {
   785  		return false
   786  	}
   787  
   788  	if !slices.Equal(s.Aliases, other.Aliases) {
   789  		return false
   790  	}
   791  
   792  	if s.ClusterExternalAddresses.Len() != other.ClusterExternalAddresses.Len() {
   793  		return false
   794  	}
   795  
   796  	for k, v1 := range s.ClusterExternalAddresses.GetAddresses() {
   797  		if v2, ok := other.ClusterExternalAddresses.Addresses[k]; !ok || !slices.Equal(v1, v2) {
   798  			return false
   799  		}
   800  	}
   801  
   802  	if len(s.ClusterExternalPorts) != len(other.ClusterExternalPorts) {
   803  		return false
   804  	}
   805  
   806  	for k, v1 := range s.ClusterExternalPorts {
   807  		if v2, ok := s.ClusterExternalPorts[k]; !ok || !maps.Equal(v1, v2) {
   808  			return false
   809  		}
   810  	}
   811  	return s.Name == other.Name && s.Namespace == other.Namespace &&
   812  		s.ServiceRegistry == other.ServiceRegistry && s.K8sAttributes == other.K8sAttributes
   813  }
   814  
   815  // ServiceDiscovery enumerates Istio service instances.
   816  // nolint: lll
   817  type ServiceDiscovery interface {
   818  	NetworkGatewaysWatcher
   819  
   820  	// Services list declarations of all services in the system
   821  	Services() []*Service
   822  
   823  	// GetService retrieves a service by host name if it exists
   824  	GetService(hostname host.Name) *Service
   825  
   826  	// GetProxyServiceTargets returns the service targets that co-located with a given Proxy
   827  	//
   828  	// Co-located generally means running in the same network namespace and security context.
   829  	//
   830  	// A Proxy operating as a Sidecar will return a non-empty slice.  A stand-alone Proxy
   831  	// will return an empty slice.
   832  	//
   833  	// There are two reasons why this returns multiple ServiceTargets instead of one:
   834  	// - A ServiceTargets has a single Port.  But a Service
   835  	//   may have many ports.  So a workload implementing such a Service would need
   836  	//   multiple ServiceTargets, one for each port.
   837  	// - A single workload may implement multiple logical Services.
   838  	//
   839  	// In the second case, multiple services may be implemented by the same physical port number,
   840  	// though with a different ServicePort and IstioEndpoint for each.  If any of these overlapping
   841  	// services are not HTTP or H2-based, behavior is undefined, since the listener may not be able to
   842  	// determine the intended destination of a connection without a Host header on the request.
   843  	GetProxyServiceTargets(*Proxy) []ServiceTarget
   844  	GetProxyWorkloadLabels(*Proxy) labels.Instance
   845  
   846  	// MCSServices returns information about the services that have been exported/imported via the
   847  	// Kubernetes Multi-Cluster Services (MCS) ServiceExport API. Only applies to services in
   848  	// Kubernetes clusters.
   849  	MCSServices() []MCSServiceInfo
   850  	AmbientIndexes
   851  }
   852  
   853  type AmbientIndexes interface {
   854  	AddressInformation(addresses sets.String) ([]AddressInfo, sets.String)
   855  	AdditionalPodSubscriptions(
   856  		proxy *Proxy,
   857  		allAddresses sets.String,
   858  		currentSubs sets.String,
   859  	) sets.String
   860  	Policies(requested sets.Set[ConfigKey]) []WorkloadAuthorization
   861  	ServicesForWaypoint(WaypointKey) []ServiceInfo
   862  	WorkloadsForWaypoint(WaypointKey) []WorkloadInfo
   863  }
   864  
   865  // WaypointKey is a multi-address extension of NetworkAddress which is commonly used for lookups in AmbientIndex
   866  // We likely need to consider alternative keying options internally such as hostname as we look to expand beyong istio-waypoint
   867  // This extension can ideally support that type of lookup in the interface without introducing scope creep into things
   868  // like NetworkAddress
   869  type WaypointKey struct {
   870  	Network   string
   871  	Addresses []string
   872  }
   873  
   874  // WaypointKey contains all of the VIPs that the Proxy serves.
   875  func WaypointKeyForProxy(node *Proxy) WaypointKey {
   876  	// TODO IP based lookup should switch to looking up services by name/ns
   877  	key := WaypointKey{
   878  		Network: node.Metadata.Network.String(),
   879  	}
   880  	for _, svct := range node.ServiceTargets {
   881  		ips := svct.Service.ClusterVIPs.GetAddressesFor(node.GetClusterID())
   882  		key.Addresses = append(key.Addresses, ips...)
   883  	}
   884  	return key
   885  }
   886  
   887  // NoopAmbientIndexes provides an implementation of AmbientIndexes that always returns nil, to easily "skip" it.
   888  type NoopAmbientIndexes struct{}
   889  
   890  func (u NoopAmbientIndexes) AddressInformation(sets.String) ([]AddressInfo, sets.String) {
   891  	return nil, nil
   892  }
   893  
   894  func (u NoopAmbientIndexes) AdditionalPodSubscriptions(
   895  	*Proxy,
   896  	sets.String,
   897  	sets.String,
   898  ) sets.String {
   899  	return nil
   900  }
   901  
   902  func (u NoopAmbientIndexes) Policies(sets.Set[ConfigKey]) []WorkloadAuthorization {
   903  	return nil
   904  }
   905  
   906  func (u NoopAmbientIndexes) ServicesForWaypoint(WaypointKey) []ServiceInfo {
   907  	return nil
   908  }
   909  
   910  func (u NoopAmbientIndexes) Waypoint(string, string) []netip.Addr {
   911  	return nil
   912  }
   913  
   914  func (u NoopAmbientIndexes) WorkloadsForWaypoint(WaypointKey) []WorkloadInfo {
   915  	return nil
   916  }
   917  
   918  var _ AmbientIndexes = NoopAmbientIndexes{}
   919  
   920  type AddressInfo struct {
   921  	*workloadapi.Address
   922  }
   923  
   924  func (i AddressInfo) Aliases() []string {
   925  	switch addr := i.Type.(type) {
   926  	case *workloadapi.Address_Workload:
   927  		aliases := make([]string, 0, len(addr.Workload.Addresses))
   928  		network := addr.Workload.Network
   929  		for _, workloadAddr := range addr.Workload.Addresses {
   930  			ip, _ := netip.AddrFromSlice(workloadAddr)
   931  			aliases = append(aliases, network+"/"+ip.String())
   932  		}
   933  		return aliases
   934  	case *workloadapi.Address_Service:
   935  		aliases := make([]string, 0, len(addr.Service.Addresses))
   936  		for _, networkAddr := range addr.Service.Addresses {
   937  			ip, _ := netip.AddrFromSlice(networkAddr.Address)
   938  			aliases = append(aliases, networkAddr.Network+"/"+ip.String())
   939  		}
   940  		return aliases
   941  	}
   942  	return nil
   943  }
   944  
   945  func (i AddressInfo) ResourceName() string {
   946  	var name string
   947  	switch addr := i.Type.(type) {
   948  	case *workloadapi.Address_Workload:
   949  		name = workloadResourceName(addr.Workload)
   950  	case *workloadapi.Address_Service:
   951  		name = serviceResourceName(addr.Service)
   952  	}
   953  	return name
   954  }
   955  
   956  type ServicePortName struct {
   957  	PortName       string
   958  	TargetPortName string
   959  }
   960  
   961  type ServiceInfo struct {
   962  	*workloadapi.Service
   963  	// LabelSelectors for the Service. Note these are only used internally, not sent over XDS
   964  	LabelSelector
   965  	// PortNames provides a mapping of ServicePort -> port names. Note these are only used internally, not sent over XDS
   966  	PortNames map[int32]ServicePortName
   967  	// Source is the type that introduced this service.
   968  	Source kind.Kind
   969  	// Waypoint that clients should use when addressing traffic to this Service.
   970  	Waypoint string
   971  }
   972  
   973  func (i ServiceInfo) NamespacedName() types.NamespacedName {
   974  	return types.NamespacedName{Name: i.Name, Namespace: i.Namespace}
   975  }
   976  
   977  func (i ServiceInfo) Equals(other ServiceInfo) bool {
   978  	return proto.Equal(i.Service, other.Service) &&
   979  		maps.Equal(i.LabelSelector.Labels, other.LabelSelector.Labels) &&
   980  		maps.Equal(i.PortNames, other.PortNames) &&
   981  		i.Source == other.Source
   982  }
   983  
   984  func (i ServiceInfo) ResourceName() string {
   985  	return serviceResourceName(i.Service)
   986  }
   987  
   988  func serviceResourceName(s *workloadapi.Service) string {
   989  	return s.Namespace + "/" + s.Hostname
   990  }
   991  
   992  type WorkloadSource string
   993  
   994  type WorkloadInfo struct {
   995  	*workloadapi.Workload
   996  	// Labels for the workload. Note these are only used internally, not sent over XDS
   997  	Labels map[string]string
   998  	// Source is the type that introduced this workload.
   999  	Source kind.Kind
  1000  	// CreationTime is the time when the workload was created. Note this is used internally only.
  1001  	CreationTime time.Time
  1002  }
  1003  
  1004  func (i WorkloadInfo) Equals(other WorkloadInfo) bool {
  1005  	return proto.Equal(i.Workload, other.Workload) &&
  1006  		maps.Equal(i.Labels, other.Labels) &&
  1007  		i.Source == other.Source &&
  1008  		i.CreationTime == other.CreationTime
  1009  }
  1010  
  1011  func workloadResourceName(w *workloadapi.Workload) string {
  1012  	return w.Uid
  1013  }
  1014  
  1015  func (i *WorkloadInfo) Clone() *WorkloadInfo {
  1016  	return &WorkloadInfo{
  1017  		Workload:     proto.Clone(i).(*workloadapi.Workload),
  1018  		Labels:       maps.Clone(i.Labels),
  1019  		Source:       i.Source,
  1020  		CreationTime: i.CreationTime,
  1021  	}
  1022  }
  1023  
  1024  func (i WorkloadInfo) ResourceName() string {
  1025  	return workloadResourceName(i.Workload)
  1026  }
  1027  
  1028  type WorkloadAuthorization struct {
  1029  	// LabelSelectors for the workload. Note these are only used internally, not sent over XDS
  1030  	LabelSelector
  1031  	Authorization *security.Authorization
  1032  }
  1033  
  1034  func (i WorkloadAuthorization) Equals(other WorkloadAuthorization) bool {
  1035  	return maps.Equal(i.LabelSelector.Labels, other.LabelSelector.Labels) &&
  1036  		proto.Equal(i.Authorization, other.Authorization)
  1037  }
  1038  
  1039  func (i WorkloadAuthorization) ResourceName() string {
  1040  	return i.Authorization.GetNamespace() + "/" + i.Authorization.GetName()
  1041  }
  1042  
  1043  type LabelSelector struct {
  1044  	Labels map[string]string
  1045  }
  1046  
  1047  func NewSelector(l map[string]string) LabelSelector {
  1048  	return LabelSelector{l}
  1049  }
  1050  
  1051  func (l LabelSelector) GetLabelSelector() map[string]string {
  1052  	return l.Labels
  1053  }
  1054  
  1055  func ExtractWorkloadsFromAddresses(addrs []AddressInfo) []WorkloadInfo {
  1056  	return slices.MapFilter(addrs, func(a AddressInfo) *WorkloadInfo {
  1057  		switch addr := a.Type.(type) {
  1058  		case *workloadapi.Address_Workload:
  1059  			return &WorkloadInfo{Workload: addr.Workload}
  1060  		default:
  1061  			return nil
  1062  		}
  1063  	})
  1064  }
  1065  
  1066  func SortWorkloadsByCreationTime(workloads []WorkloadInfo) []WorkloadInfo {
  1067  	sort.SliceStable(workloads, func(i, j int) bool {
  1068  		if workloads[i].CreationTime.Equal(workloads[j].CreationTime) {
  1069  			return workloads[i].Uid < workloads[j].Uid
  1070  		}
  1071  		return workloads[i].CreationTime.Before(workloads[j].CreationTime)
  1072  	})
  1073  	return workloads
  1074  }
  1075  
  1076  // MCSServiceInfo combines the name of a service with a particular Kubernetes cluster. This
  1077  // is used for debug information regarding the state of Kubernetes Multi-Cluster Services (MCS).
  1078  type MCSServiceInfo struct {
  1079  	Cluster         cluster.ID
  1080  	Name            string
  1081  	Namespace       string
  1082  	Exported        bool
  1083  	Imported        bool
  1084  	ClusterSetVIP   string
  1085  	Discoverability map[host.Name]string
  1086  }
  1087  
  1088  // GetNames returns port names
  1089  func (ports PortList) GetNames() []string {
  1090  	names := make([]string, 0, len(ports))
  1091  	for _, port := range ports {
  1092  		names = append(names, port.Name)
  1093  	}
  1094  	return names
  1095  }
  1096  
  1097  // Get retrieves a port declaration by name
  1098  func (ports PortList) Get(name string) (*Port, bool) {
  1099  	for _, port := range ports {
  1100  		if port.Name == name {
  1101  			return port, true
  1102  		}
  1103  	}
  1104  	return nil, false
  1105  }
  1106  
  1107  // GetByPort retrieves a port declaration by port value
  1108  func (ports PortList) GetByPort(num int) (*Port, bool) {
  1109  	for _, port := range ports {
  1110  		if port.Port == num && port.Protocol != protocol.UDP {
  1111  			return port, true
  1112  		}
  1113  	}
  1114  	return nil, false
  1115  }
  1116  
  1117  func (p *Port) Equals(other *Port) bool {
  1118  	if p == nil {
  1119  		return other == nil
  1120  	}
  1121  	if other == nil {
  1122  		return p == nil
  1123  	}
  1124  	return p.Name == other.Name && p.Port == other.Port && p.Protocol == other.Protocol
  1125  }
  1126  
  1127  func (ports PortList) Equals(other PortList) bool {
  1128  	return slices.EqualFunc(ports, other, func(a, b *Port) bool {
  1129  		return a.Equals(b)
  1130  	})
  1131  }
  1132  
  1133  func (ports PortList) String() string {
  1134  	sp := make([]string, 0, len(ports))
  1135  	for _, p := range ports {
  1136  		sp = append(sp, p.String())
  1137  	}
  1138  	return strings.Join(sp, ", ")
  1139  }
  1140  
  1141  // External predicate checks whether the service is external
  1142  func (s *Service) External() bool {
  1143  	return s.MeshExternal
  1144  }
  1145  
  1146  // BuildSubsetKey generates a unique string referencing service instances for a given service name, a subset and a port.
  1147  // The proxy queries Pilot with this key to obtain the list of instances in a subset.
  1148  func BuildSubsetKey(direction TrafficDirection, subsetName string, hostname host.Name, port int) string {
  1149  	return string(direction) + "|" + strconv.Itoa(port) + "|" + subsetName + "|" + string(hostname)
  1150  }
  1151  
  1152  // BuildInboundSubsetKey generates a unique string referencing service instances with port.
  1153  func BuildInboundSubsetKey(port int) string {
  1154  	return BuildSubsetKey(TrafficDirectionInbound, "", "", port)
  1155  }
  1156  
  1157  // BuildDNSSrvSubsetKey generates a unique string referencing service instances for a given service name, a subset and a port.
  1158  // The proxy queries Pilot with this key to obtain the list of instances in a subset.
  1159  // This is used only for the SNI-DNAT router. Do not use for other purposes.
  1160  // The DNS Srv format of the cluster is also used as the default SNI string for Istio mTLS connections
  1161  func BuildDNSSrvSubsetKey(direction TrafficDirection, subsetName string, hostname host.Name, port int) string {
  1162  	return string(direction) + "_." + strconv.Itoa(port) + "_." + subsetName + "_." + string(hostname)
  1163  }
  1164  
  1165  // IsValidSubsetKey checks if a string is valid for subset key parsing.
  1166  func IsValidSubsetKey(s string) bool {
  1167  	return strings.Count(s, "|") == 3
  1168  }
  1169  
  1170  // IsDNSSrvSubsetKey checks whether the given key is a DNSSrv key (built by BuildDNSSrvSubsetKey).
  1171  func IsDNSSrvSubsetKey(s string) bool {
  1172  	if strings.HasPrefix(s, trafficDirectionOutboundSrvPrefix) ||
  1173  		strings.HasPrefix(s, trafficDirectionInboundSrvPrefix) {
  1174  		return true
  1175  	}
  1176  	return false
  1177  }
  1178  
  1179  // ParseSubsetKeyHostname is an optimized specialization of ParseSubsetKey that only returns the hostname.
  1180  // This is created as this is used in some hot paths and is about 2x faster than ParseSubsetKey; for typical use ParseSubsetKey is sufficient (and zero-alloc).
  1181  func ParseSubsetKeyHostname(s string) (hostname string) {
  1182  	idx := strings.LastIndex(s, "|")
  1183  	if idx == -1 {
  1184  		// Could be DNS SRV format.
  1185  		// Do not do LastIndex("_."), as those are valid characters in the hostname (unlike |)
  1186  		// Fallback to the full parser.
  1187  		_, _, hostname, _ := ParseSubsetKey(s)
  1188  		return string(hostname)
  1189  	}
  1190  	return s[idx+1:]
  1191  }
  1192  
  1193  // ParseSubsetKey is the inverse of the BuildSubsetKey method
  1194  func ParseSubsetKey(s string) (direction TrafficDirection, subsetName string, hostname host.Name, port int) {
  1195  	sep := "|"
  1196  	// This could be the DNS srv form of the cluster that uses outbound_.port_.subset_.hostname
  1197  	// Since we do not want every callsite to implement the logic to differentiate between the two forms
  1198  	// we add an alternate parser here.
  1199  	if strings.HasPrefix(s, trafficDirectionOutboundSrvPrefix) ||
  1200  		strings.HasPrefix(s, trafficDirectionInboundSrvPrefix) {
  1201  		sep = "_."
  1202  	}
  1203  
  1204  	// Format: dir|port|subset|hostname
  1205  	dir, s, ok := strings.Cut(s, sep)
  1206  	if !ok {
  1207  		return
  1208  	}
  1209  	direction = TrafficDirection(dir)
  1210  
  1211  	p, s, ok := strings.Cut(s, sep)
  1212  	if !ok {
  1213  		return
  1214  	}
  1215  	port, _ = strconv.Atoi(p)
  1216  
  1217  	ss, s, ok := strings.Cut(s, sep)
  1218  	if !ok {
  1219  		return
  1220  	}
  1221  	subsetName = ss
  1222  
  1223  	// last part. No | remains -- verify this
  1224  	if strings.Contains(s, sep) {
  1225  		return
  1226  	}
  1227  	hostname = host.Name(s)
  1228  	return
  1229  }
  1230  
  1231  // GetAddresses returns a Service's addresses.
  1232  // This method returns all the VIPs of a service if the ClusterID is explicitly set to "", otherwise only return the VIP
  1233  // specific to the cluster where the node resides
  1234  func (s *Service) GetAddresses(node *Proxy) []string {
  1235  	if node.Metadata != nil && node.Metadata.ClusterID == "" {
  1236  		return s.getAllAddresses()
  1237  	}
  1238  
  1239  	return []string{s.GetAddressForProxy(node)}
  1240  }
  1241  
  1242  // GetAddressForProxy returns a Service's address specific to the cluster where the node resides
  1243  func (s *Service) GetAddressForProxy(node *Proxy) string {
  1244  	if node.Metadata != nil {
  1245  		if node.Metadata.ClusterID != "" {
  1246  			addresses := s.ClusterVIPs.GetAddressesFor(node.Metadata.ClusterID)
  1247  			if len(addresses) > 0 {
  1248  				return addresses[0]
  1249  			}
  1250  		}
  1251  
  1252  		if node.Metadata.DNSCapture && node.Metadata.DNSAutoAllocate && s.DefaultAddress == constants.UnspecifiedIP {
  1253  			if node.SupportsIPv4() && s.AutoAllocatedIPv4Address != "" {
  1254  				return s.AutoAllocatedIPv4Address
  1255  			}
  1256  			if node.SupportsIPv6() && s.AutoAllocatedIPv6Address != "" {
  1257  				return s.AutoAllocatedIPv6Address
  1258  			}
  1259  		}
  1260  	}
  1261  
  1262  	return s.DefaultAddress
  1263  }
  1264  
  1265  // GetExtraAddressesForProxy returns a k8s service's extra addresses to the cluster where the node resides.
  1266  // Especially for dual stack k8s service to get other IP family addresses.
  1267  func (s *Service) GetExtraAddressesForProxy(node *Proxy) []string {
  1268  	if features.EnableDualStack && node.Metadata != nil {
  1269  		if node.Metadata.ClusterID != "" {
  1270  			addresses := s.ClusterVIPs.GetAddressesFor(node.Metadata.ClusterID)
  1271  			if len(addresses) > 1 {
  1272  				return addresses[1:]
  1273  			}
  1274  		}
  1275  	}
  1276  	return nil
  1277  }
  1278  
  1279  // getAllAddresses returns a Service's all addresses.
  1280  func (s *Service) getAllAddresses() []string {
  1281  	var addresses []string
  1282  	addressMap := s.ClusterVIPs.GetAddresses()
  1283  	for _, clusterAddresses := range addressMap {
  1284  		addresses = append(addresses, clusterAddresses...)
  1285  	}
  1286  
  1287  	return addresses
  1288  }
  1289  
  1290  // GetTLSModeFromEndpointLabels returns the value of the label
  1291  // security.istio.io/tlsMode if set. Do not return Enums or constants
  1292  // from this function as users could provide values other than istio/disabled
  1293  // and apply custom transport socket matchers here.
  1294  func GetTLSModeFromEndpointLabels(labels map[string]string) string {
  1295  	if labels != nil {
  1296  		if val, exists := labels[label.SecurityTlsMode.Name]; exists {
  1297  			return val
  1298  		}
  1299  	}
  1300  	return DisabledTLSModeLabel
  1301  }
  1302  
  1303  // DeepCopy creates a clone of Service.
  1304  func (s *Service) DeepCopy() *Service {
  1305  	// nolint: govet
  1306  	out := *s
  1307  	out.Attributes = s.Attributes.DeepCopy()
  1308  	if s.Ports != nil {
  1309  		out.Ports = make(PortList, len(s.Ports))
  1310  		for i, port := range s.Ports {
  1311  			if port != nil {
  1312  				out.Ports[i] = &Port{
  1313  					Name:     port.Name,
  1314  					Port:     port.Port,
  1315  					Protocol: port.Protocol,
  1316  				}
  1317  			} else {
  1318  				out.Ports[i] = nil
  1319  			}
  1320  		}
  1321  	}
  1322  
  1323  	if s.ServiceAccounts != nil {
  1324  		out.ServiceAccounts = make([]string, len(s.ServiceAccounts))
  1325  		copy(out.ServiceAccounts, s.ServiceAccounts)
  1326  	}
  1327  	out.ClusterVIPs = *s.ClusterVIPs.DeepCopy()
  1328  	return &out
  1329  }
  1330  
  1331  // Equals compares two service objects.
  1332  func (s *Service) Equals(other *Service) bool {
  1333  	if s == nil {
  1334  		return other == nil
  1335  	}
  1336  	if other == nil {
  1337  		return s == nil
  1338  	}
  1339  
  1340  	if !s.Attributes.Equals(&other.Attributes) {
  1341  		return false
  1342  	}
  1343  
  1344  	if !s.Ports.Equals(other.Ports) {
  1345  		return false
  1346  	}
  1347  	if !slices.Equal(s.ServiceAccounts, other.ServiceAccounts) {
  1348  		return false
  1349  	}
  1350  
  1351  	if len(s.ClusterVIPs.Addresses) != len(other.ClusterVIPs.Addresses) {
  1352  		return false
  1353  	}
  1354  	for k, v1 := range s.ClusterVIPs.Addresses {
  1355  		if v2, ok := other.ClusterVIPs.Addresses[k]; !ok || !slices.Equal(v1, v2) {
  1356  			return false
  1357  		}
  1358  	}
  1359  
  1360  	return s.DefaultAddress == other.DefaultAddress && s.AutoAllocatedIPv4Address == other.AutoAllocatedIPv4Address &&
  1361  		s.AutoAllocatedIPv6Address == other.AutoAllocatedIPv6Address && s.Hostname == other.Hostname &&
  1362  		s.Resolution == other.Resolution && s.MeshExternal == other.MeshExternal
  1363  }
  1364  
  1365  // DeepCopy creates a clone of IstioEndpoint.
  1366  func (ep *IstioEndpoint) DeepCopy() *IstioEndpoint {
  1367  	return copyInternal(ep).(*IstioEndpoint)
  1368  }
  1369  
  1370  // ShallowCopy creates a shallow clone of IstioEndpoint.
  1371  func (ep *IstioEndpoint) ShallowCopy() *IstioEndpoint {
  1372  	// nolint: govet
  1373  	cpy := *ep
  1374  	return &cpy
  1375  }
  1376  
  1377  // Equals checks whether the attributes are equal from the passed in service.
  1378  func (ep *IstioEndpoint) Equals(other *IstioEndpoint) bool {
  1379  	if ep == nil {
  1380  		return other == nil
  1381  	}
  1382  	if other == nil {
  1383  		return ep == nil
  1384  	}
  1385  
  1386  	// Check things we can directly compare...
  1387  	eq := ep.Address == other.Address &&
  1388  		ep.ServicePortName == other.ServicePortName &&
  1389  		ep.LegacyClusterPortKey == other.LegacyClusterPortKey &&
  1390  		ep.ServiceAccount == other.ServiceAccount &&
  1391  		ep.Network == other.Network &&
  1392  		ep.Locality == other.Locality &&
  1393  		ep.EndpointPort == other.EndpointPort &&
  1394  		ep.LbWeight == other.LbWeight &&
  1395  		ep.TLSMode == other.TLSMode &&
  1396  		ep.Namespace == other.Namespace &&
  1397  		ep.WorkloadName == other.WorkloadName &&
  1398  		ep.HostName == other.HostName &&
  1399  		ep.SubDomain == other.SubDomain &&
  1400  		ep.HealthStatus == other.HealthStatus &&
  1401  		ep.NodeName == other.NodeName
  1402  	if !eq {
  1403  		return false
  1404  	}
  1405  
  1406  	// check everything else
  1407  	if !maps.Equal(ep.Labels, other.Labels) {
  1408  		return false
  1409  	}
  1410  
  1411  	// Compare discoverability by name
  1412  	var epp string
  1413  	if ep.DiscoverabilityPolicy != nil {
  1414  		epp = ep.DiscoverabilityPolicy.String()
  1415  	}
  1416  	var op string
  1417  	if other.DiscoverabilityPolicy != nil {
  1418  		op = other.DiscoverabilityPolicy.String()
  1419  	}
  1420  	if epp != op {
  1421  		return false
  1422  	}
  1423  
  1424  	return true
  1425  }
  1426  
  1427  func copyInternal(v any) any {
  1428  	copied, err := copystructure.Copy(v)
  1429  	if err != nil {
  1430  		// There are 2 locations where errors are generated in copystructure.Copy:
  1431  		//  * The reflection walk over the structure fails, which should never happen
  1432  		//  * A configurable copy function returns an error. This is only used for copying times, which never returns an error.
  1433  		// Therefore, this should never happen
  1434  		panic(err)
  1435  	}
  1436  	return copied
  1437  }