Repository: istio/istio (go)

Issue:
Support matching multiple IPs for one service in waypoint
A SE like `addresses: [240.240.240.239, 2001:2::f0f0:239]` only generates a listener config for `240.240.240.239` on the waypoint. This is because this creates 2 model.Services, and we dedupe by hostname

related: https://github.com/istio/istio/issues/51747, especially https://github.com/istio/istio/issues/51747#issuecomment-2204503610

Pull request:
ambient: match multiple svc VIPs in waypoint
**Please provide a description of this PR:**

Fixes #51886

Istio will store all cluster VIPs for service entries, but these VIPs will be applied only to waypoint listeners for now -  I will fix sidecars in a follow-up PR.

Patch:
diff --git a/manifests/addons/gen.sh b/manifests/addons/gen.sh
index 7ecd18e9d4..4c66d94dbf 100755
--- a/manifests/addons/gen.sh
+++ b/manifests/addons/gen.sh
@@ -33,8 +33,8 @@ GRAFANA_VERSION=${GRAFANA_VERSION:-"8.0.1"}
 {
 helm3 template kiali-server \
   --namespace istio-system \
-  --version 1.86.0 \
-  --set deployment.image_version=v1.86 \
+  --version 1.87.0 \
+  --set deployment.image_version=v1.87 \
   --include-crds \
   --set nameOverride=kiali \
   --set fullnameOverride=kiali \
diff --git a/manifests/charts/ztunnel/templates/daemonset.yaml b/manifests/charts/ztunnel/templates/daemonset.yaml
index 3aaed084a2..a087ab0685 100644
--- a/manifests/charts/ztunnel/templates/daemonset.yaml
+++ b/manifests/charts/ztunnel/templates/daemonset.yaml
@@ -6,7 +6,12 @@ metadata:
   labels:
     {{- .Values.labels | toYaml | nindent 4}}
   annotations:
+{{- if .Values.revision }}
+    {{- $annos := set $.Values.annotations "istio.io/rev" .Values.revision }}
+    {{- toYaml $annos | nindent 4}}
+{{- else }}
     {{- .Values.annotations | toYaml | nindent 4 }}
+{{- end }}
 spec:
   updateStrategy:
     rollingUpdate:
@@ -24,6 +29,9 @@ spec:
 {{ with .Values.podLabels -}}{{ toYaml . | indent 8 }}{{ end }}
       annotations:
         sidecar.istio.io/inject: "false"
+{{- if .Values.revision }}
+        istio.io/rev: {{ .Values.revision }}
+{{- end }}
 {{ with .Values.podAnnotations -}}{{ toYaml . | indent 8 }}{{ end }}
     spec:
       nodeSelector:
diff --git a/manifests/charts/ztunnel/templates/rbac.yaml b/manifests/charts/ztunnel/templates/rbac.yaml
index 9583b200ed..19925cdd4d 100644
--- a/manifests/charts/ztunnel/templates/rbac.yaml
+++ b/manifests/charts/ztunnel/templates/rbac.yaml
@@ -12,7 +12,12 @@ metadata:
   labels:
     {{- .Values.labels | toYaml | nindent 4}}
   annotations:
+{{- if .Values.revision }}
+    {{- $annos := set $.Values.annotations "istio.io/rev" .Values.revision }}
+    {{- toYaml $annos | nindent 4}}
+{{- else }}
     {{- .Values.annotations | toYaml | nindent 4 }}
+{{- end }}
 ---
 {{- if (eq .Values.platform "openshift") }}
 apiVersion: rbac.authorization.k8s.io/v1
@@ -22,8 +27,14 @@ metadata:
   labels:
     app: ztunnel
     release: {{ .Release.Name }}
-    istio.io/rev: {{ .Values.revision | default "default" }}
     install.operator.istio.io/owning-resource: {{ .Values.ownerName | default "unknown" }}
+  annotations:
+{{- if .Values.revision }}
+    {{- $annos := set $.Values.annotations "istio.io/rev" .Values.revision }}
+    {{- toYaml $annos | nindent 4}}
+{{- else }}
+    {{- .Values.annotations | toYaml | nindent 4 }}
+{{- end }}
 rules:
 - apiGroups: ["security.openshift.io"]
   resources: ["securitycontextconstraints"]
@@ -37,8 +48,14 @@ metadata:
   labels:
     app: ztunnel
     release: {{ .Release.Name }}
-    istio.io/rev: {{ .Values.revision | default "default" }}
     install.operator.istio.io/owning-resource: {{ .Values.ownerName | default "unknown" }}
+  annotations:
+{{- if .Values.revision }}
+    {{- $annos := set $.Values.annotations "istio.io/rev" .Values.revision }}
+    {{- toYaml $annos | nindent 4}}
+{{- else }}
+    {{- .Values.annotations | toYaml | nindent 4 }}
+{{- end }}
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: ClusterRole
@@ -48,4 +65,4 @@ subjects:
   name: ztunnel
   namespace: {{ .Release.Namespace }}
 {{- end }}
----
\ No newline at end of file
+---
diff --git a/pilot/pkg/model/service.go b/pilot/pkg/model/service.go
index 3f2bac9d2c..a3a6db60fc 100644
--- a/pilot/pkg/model/service.go
+++ b/pilot/pkg/model/service.go
@@ -1275,7 +1275,7 @@ func (s *Service) GetExtraAddressesForProxy(node *Proxy) []string {
 // GetAllAddressesForProxy returns a k8s service's extra addresses to the cluster where the node resides.
 // Especially for dual stack k8s service to get other IP family addresses.
 func (s *Service) GetAllAddressesForProxy(node *Proxy) []string {
-	if features.EnableDualStack && node.Metadata != nil && node.Metadata.ClusterID != "" {
+	if (features.EnableDualStack || features.EnableAmbient) && node.Metadata != nil && node.Metadata.ClusterID != "" {
 		addresses := s.ClusterVIPs.GetAddressesFor(node.Metadata.ClusterID)
 		if len(addresses) > 0 {
 			return addresses
diff --git a/pilot/pkg/networking/core/listener_waypoint.go b/pilot/pkg/networking/core/listener_waypoint.go
index 7e7fec7486..4170b2e851 100644
--- a/pilot/pkg/networking/core/listener_waypoint.go
+++ b/pilot/pkg/networking/core/listener_waypoint.go
@@ -52,6 +52,7 @@ import (
 	"istio.io/istio/pkg/config/protocol"
 	"istio.io/istio/pkg/log"
 	"istio.io/istio/pkg/proto"
+	"istio.io/istio/pkg/slices"
 	"istio.io/istio/pkg/wellknown"
 )
 
@@ -234,12 +235,15 @@ func (lb *ListenerBuilder) buildWaypointInternal(wls []model.WorkloadInfo, svcs
 			}
 		}
 		if len(portMapper.Map) > 0 {
-			cidr := util.ConvertAddressToCidr(svc.GetAddressForProxy(lb.node))
-			rangeMatcher := &matcher.IPMatcher_IPRangeMatcher{
-				Ranges: []*xds.CidrRange{{
+			ranges := slices.Map(svc.GetAllAddressesForProxy(lb.node), func(vip string) *xds.CidrRange {
+				cidr := util.ConvertAddressToCidr(vip)
+				return &xds.CidrRange{
 					AddressPrefix: cidr.AddressPrefix,
 					PrefixLen:     cidr.PrefixLen,
-				}},
+				}
+			})
+			rangeMatcher := &matcher.IPMatcher_IPRangeMatcher{
+				Ranges:  ranges,
 				OnMatch: match.ToMatcher(portMapper.Matcher),
 			}
 			ipMatcher.RangeMatchers = append(ipMatcher.RangeMatchers, rangeMatcher)
diff --git a/pilot/pkg/serviceregistry/serviceentry/controller.go b/pilot/pkg/serviceregistry/serviceentry/controller.go
index eeba4d1273..df574fb9e8 100644
--- a/pilot/pkg/serviceregistry/serviceentry/controller.go
+++ b/pilot/pkg/serviceregistry/serviceentry/controller.go
@@ -366,7 +366,7 @@ func getUpdatedConfigs(services []*model.Service) sets.Set[model.ConfigKey] {
 func (s *Controller) serviceEntryHandler(old, curr config.Config, event model.Event) {
 	log.Debugf("Handle event %s for service entry %s/%s", event, curr.Namespace, curr.Name)
 	currentServiceEntry := curr.Spec.(*networking.ServiceEntry)
-	cs := convertServices(curr)
+	cs := convertServices(curr, s.clusterID)
 	configsUpdated := sets.New[model.ConfigKey]()
 	key := curr.NamespacedName()
 
diff --git a/pilot/pkg/serviceregistry/serviceentry/conversion.go b/pilot/pkg/serviceregistry/serviceentry/conversion.go
index 133ed19023..9ce05ab736 100644
--- a/pilot/pkg/serviceregistry/serviceentry/conversion.go
+++ b/pilot/pkg/serviceregistry/serviceentry/conversion.go
@@ -17,7 +17,6 @@ package serviceentry
 import (
 	"net/netip"
 	"strings"
-	"time"
 
 	"istio.io/api/label"
 	networking "istio.io/api/networking/v1alpha3"
@@ -139,7 +138,7 @@ func ServiceToServiceEntry(svc *model.Service, proxy *model.Proxy) *config.Confi
 }
 
 // convertServices transforms a ServiceEntry config to a list of internal Service objects.
-func convertServices(cfg config.Config) []*model.Service {
+func convertServices(cfg config.Config, clusterID cluster.ID) []*model.Service {
 	serviceEntry := cfg.Spec.(*networking.ServiceEntry)
 	creationTime := cfg.CreationTimestamp
 
@@ -201,38 +200,46 @@ func convertServices(cfg config.Config) []*model.Service {
 		}
 	}
 
-	return buildServices(hostAddresses, cfg.Name, cfg.Namespace, svcPorts, serviceEntry.Location, resolution,
-		exportTo, labelSelectors, serviceEntry.SubjectAltNames, creationTime, cfg.Labels, portOverrides)
-}
-
-func buildServices(hostAddresses []*HostAddress, name, namespace string, ports model.PortList, location networking.ServiceEntry_Location,
-	resolution model.Resolution, exportTo sets.Set[visibility.Instance], selectors map[string]string, saccounts []string,
-	ctime time.Time, labels map[string]string, overrides map[uint32]uint32,
-) []*model.Service {
 	out := make([]*model.Service, 0, len(hostAddresses))
-	lbls := labels
-	if features.CanonicalServiceForMeshExternalServiceEntry && location == networking.ServiceEntry_MESH_EXTERNAL {
-		lbls = ensureCanonicalServiceLabels(name, labels)
+	lbls := cfg.Labels
+	if features.CanonicalServiceForMeshExternalServiceEntry && serviceEntry.Location == networking.ServiceEntry_MESH_EXTERNAL {
+		lbls = ensureCanonicalServiceLabels(cfg.Name, cfg.Labels)
 	}
 	for _, ha := range hostAddresses {
-		out = append(out, &model.Service{
-			CreationTime:   ctime,
-			MeshExternal:   location == networking.ServiceEntry_MESH_EXTERNAL,
+		svc := &model.Service{
+			CreationTime:   creationTime,
+			MeshExternal:   serviceEntry.Location == networking.ServiceEntry_MESH_EXTERNAL,
 			Hostname:       host.Name(ha.host),
 			DefaultAddress: ha.address,
-			Ports:          ports,
+			Ports:          svcPorts,
 			Resolution:     resolution,
 			Attributes: model.ServiceAttributes{
 				ServiceRegistry:        provider.External,
-				PassthroughTargetPorts: overrides,
+				PassthroughTargetPorts: portOverrides,
 				Name:                   ha.host,
-				Namespace:              namespace,
+				Namespace:              cfg.Namespace,
 				Labels:                 lbls,
 				ExportTo:               exportTo,
-				LabelSelectors:         selectors,
+				LabelSelectors:         labelSelectors,
 			},
-			ServiceAccounts: saccounts,
-		})
+			ServiceAccounts: serviceEntry.SubjectAltNames,
+		}
+		addresses := serviceEntry.Addresses
+		if len(addresses) == 0 {
+			addresses = []string{constants.UnspecifiedIP}
+		}
+		// This logic ensures backward compatibility for non-ambient proxies.
+		// It makes sure that the default address is always the first VIP in the list, so there is no difference
+		// between using DefaultAddress or ClusterVIPs[0] to create a listener.
+		notDefaultAddresses := sets.New[string](addresses...).Delete(ha.address)
+		addresses = []string{ha.address}
+		addresses = append(addresses, sets.SortedList(notDefaultAddresses)...)
+		svc.ClusterVIPs = model.AddressMap{
+			Addresses: map[cluster.ID][]string{
+				clusterID: addresses,
+			},
+		}
+		out = append(out, svc)
 	}
 	return out
 }
@@ -326,7 +333,7 @@ func (s *Controller) convertServiceEntryToInstances(cfg config.Config, services
 		return nil
 	}
 	if services == nil {
-		services = convertServices(cfg)
+		services = convertServices(cfg, s.clusterID)
 	}
 
 	endpointsNum := len(serviceEntry.Endpoints)
diff --git a/pilot/pkg/xds/filters/filters.go b/pilot/pkg/xds/filters/filters.go
index d964591529..f2379190ee 100644
--- a/pilot/pkg/xds/filters/filters.go
+++ b/pilot/pkg/xds/filters/filters.go
@@ -113,7 +113,9 @@ var (
 	TLSInspector = &listener.ListenerFilter{
 		Name: wellknown.TLSInspector,
 		ConfigType: &listener.ListenerFilter_TypedConfig{
-			TypedConfig: protoconv.MessageToAny(&tlsinspector.TlsInspector{}),
+			TypedConfig: protoconv.MessageToAny(&tlsinspector.TlsInspector{
+				InitialReadBufferSize: &wrapperspb.UInt32Value{Value: 512}, // Default is 64KB.
+			}),
 		},
 	}
 	HTTPInspector = &listener.ListenerFilter{
diff --git a/releasenotes/notes/51692.yaml b/releasenotes/notes/51692.yaml
new file mode 100644
index 0000000000..11d1c56554
--- /dev/null
+++ b/releasenotes/notes/51692.yaml
@@ -0,0 +1,7 @@
+apiVersion: release-notes/v2
+kind: bug-fix
+area: installation
+
+releaseNotes:
+  - |
+    **Fixed** `istio.io/rev` is not user intent for ztunnel, should be an annotation. Also, consistently propagate custom annotations in the ztunnel chart
diff --git a/releasenotes/notes/51939.yaml b/releasenotes/notes/51939.yaml
new file mode 100644
index 0000000000..8dc71d0340
--- /dev/null
+++ b/releasenotes/notes/51939.yaml
@@ -0,0 +1,8 @@
+apiVersion: release-notes/v2
+kind: feature
+area: traffic-management
+issue:
+  - 51886
+releaseNotes:
+  - |
+    **Added** support for matching multiple service VIPs in waypoint.
diff --git a/releasenotes/notes/kiali-update-v1.87.yaml b/releasenotes/notes/kiali-update-v1.87.yaml
new file mode 100644
index 0000000000..6d0d57faab
--- /dev/null
+++ b/releasenotes/notes/kiali-update-v1.87.yaml
@@ -0,0 +1,7 @@
+apiVersion: release-notes/v2
+kind: feature
+area: installation
+
+releaseNotes:
+  - |
+    **Updated** Kiali addon to version v1.87.0.
diff --git a/samples/addons/kiali.yaml b/samples/addons/kiali.yaml
index 1b858788e9..98f68853b4 100644
--- a/samples/addons/kiali.yaml
+++ b/samples/addons/kiali.yaml
@@ -6,12 +6,12 @@ metadata:
   name: kiali
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.86.0
+    helm.sh/chart: kiali-server-1.87.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.86.0"
-    app.kubernetes.io/version: "v1.86.0"
+    version: "v1.87.0"
+    app.kubernetes.io/version: "v1.87.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 ...
@@ -23,12 +23,12 @@ metadata:
   name: kiali
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.86.0
+    helm.sh/chart: kiali-server-1.87.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.86.0"
-    app.kubernetes.io/version: "v1.86.0"
+    version: "v1.87.0"
+    app.kubernetes.io/version: "v1.87.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 data:
@@ -68,7 +68,7 @@ data:
       image_name: quay.io/kiali/kiali
       image_pull_policy: IfNotPresent
       image_pull_secrets: []
-      image_version: v1.86
+      image_version: v1.87
       ingress:
         additional_labels: {}
         class_name: nginx
@@ -99,7 +99,7 @@ data:
       service_annotations: {}
       service_type: ""
       tolerations: []
-      version_label: v1.86.0
+      version_label: v1.87.0
       view_only_mode: false
     external_services:
       custom_dashboards:
@@ -139,12 +139,12 @@ kind: ClusterRole
 metadata:
   name: kiali
   labels:
-    helm.sh/chart: kiali-server-1.86.0
+    helm.sh/chart: kiali-server-1.87.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.86.0"
-    app.kubernetes.io/version: "v1.86.0"
+    version: "v1.87.0"
+    app.kubernetes.io/version: "v1.87.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 rules:
@@ -246,12 +246,12 @@ kind: ClusterRoleBinding
 metadata:
   name: kiali
   labels:
-    helm.sh/chart: kiali-server-1.86.0
+    helm.sh/chart: kiali-server-1.87.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.86.0"
-    app.kubernetes.io/version: "v1.86.0"
+    version: "v1.87.0"
+    app.kubernetes.io/version: "v1.87.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 roleRef:
@@ -271,12 +271,12 @@ metadata:
   name: kiali-controlplane
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.86.0
+    helm.sh/chart: kiali-server-1.87.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.86.0"
-    app.kubernetes.io/version: "v1.86.0"
+    version: "v1.87.0"
+    app.kubernetes.io/version: "v1.87.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 rules:
@@ -299,12 +299,12 @@ metadata:
   name: kiali-controlplane
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.86.0
+    helm.sh/chart: kiali-server-1.87.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.86.0"
-    app.kubernetes.io/version: "v1.86.0"
+    version: "v1.87.0"
+    app.kubernetes.io/version: "v1.87.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 roleRef:
@@ -324,12 +324,12 @@ metadata:
   name: kiali
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.86.0
+    helm.sh/chart: kiali-server-1.87.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.86.0"
-    app.kubernetes.io/version: "v1.86.0"
+    version: "v1.87.0"
+    app.kubernetes.io/version: "v1.87.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
   annotations:
@@ -355,12 +355,12 @@ metadata:
   name: kiali
   namespace: istio-system
   labels:
-    helm.sh/chart: kiali-server-1.86.0
+    helm.sh/chart: kiali-server-1.87.0
     app: kiali
     app.kubernetes.io/name: kiali
     app.kubernetes.io/instance: kiali
-    version: "v1.86.0"
-    app.kubernetes.io/version: "v1.86.0"
+    version: "v1.87.0"
+    app.kubernetes.io/version: "v1.87.0"
     app.kubernetes.io/managed-by: Helm
     app.kubernetes.io/part-of: "kiali"
 spec:
@@ -378,24 +378,24 @@ spec:
     metadata:
       name: kiali
       labels:
-        helm.sh/chart: kiali-server-1.86.0
+        helm.sh/chart: kiali-server-1.87.0
         app: kiali
         app.kubernetes.io/name: kiali
         app.kubernetes.io/instance: kiali
-        version: "v1.86.0"
-        app.kubernetes.io/version: "v1.86.0"
+        version: "v1.87.0"
+        app.kubernetes.io/version: "v1.87.0"
         app.kubernetes.io/managed-by: Helm
         app.kubernetes.io/part-of: "kiali"
         sidecar.istio.io/inject: "false"
       annotations:
-        checksum/config: 9efbf6282c51e399ad65a678a1e3d3184883868386703f6909db1692166e2cf9
+        checksum/config: 12d7579aa3de1df2ebb8812a826d78d94a8fd2e4a20b51a64dab57151bc28902
         prometheus.io/scrape: "true"
         prometheus.io/port: "9090"
         kiali.io/dashboards: go,kiali
     spec:
       serviceAccountName: kiali
       containers:
-      - image: "quay.io/kiali/kiali:v1.86"
+      - image: "quay.io/kiali/kiali:v1.87"
         imagePullPolicy: IfNotPresent
         name: kiali
         command:


Test patch:
diff --git a/pilot/pkg/serviceregistry/serviceentry/controller_test.go b/pilot/pkg/serviceregistry/serviceentry/controller_test.go
index 84abf0eb8c..93770b1092 100644
--- a/pilot/pkg/serviceregistry/serviceentry/controller_test.go
+++ b/pilot/pkg/serviceregistry/serviceentry/controller_test.go
@@ -132,9 +132,10 @@ func initServiceDiscoveryWithOpts(t test.Failer, workloadOnly bool, opts ...Opti
 func TestServiceDiscoveryServices(t *testing.T) {
 	store, sd, fx := initServiceDiscovery(t)
 	expectedServices := []*model.Service{
-		makeService("*.istio.io", "httpDNSRR", constants.UnspecifiedIP, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSRoundRobinLB),
-		makeService("*.google.com", "httpDNS", constants.UnspecifiedIP, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSLB),
-		makeService("tcpstatic.com", "tcpStatic", "172.217.0.1", map[string]int{"tcp-444": 444}, true, model.ClientSideLB),
+		makeService(
+			"*.istio.io", "httpDNSRR", []string{constants.UnspecifiedIP}, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSRoundRobinLB),
+		makeService("*.google.com", "httpDNS", []string{constants.UnspecifiedIP}, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSLB),
+		makeService("tcpstatic.com", "tcpStatic", []string{"172.217.0.1"}, map[string]int{"tcp-444": 444}, true, model.ClientSideLB),
 	}
 
 	createConfigs([]*config.Config{httpDNS, httpDNSRR, tcpStatic}, store, t)
@@ -1493,7 +1494,7 @@ func expectEvents(t testing.TB, ch *xdsfake.Updater, events ...Event) {
 
 func expectServiceInstances(t testing.TB, sd *Controller, cfg *config.Config, port int, expected ...[]*model.ServiceInstance) {
 	t.Helper()
-	svcs := convertServices(*cfg)
+	svcs := convertServices(*cfg, "")
 	if len(svcs) != len(expected) {
 		t.Fatalf("got more services than expected: %v vs %v", len(svcs), len(expected))
 	}
@@ -1727,8 +1728,8 @@ func TestServicesDiff(t *testing.T) {
 
 	for _, tt := range cases {
 		t.Run(tt.name, func(t *testing.T) {
-			as := convertServices(*tt.current)
-			bs := convertServices(*tt.new)
+			as := convertServices(*tt.current, "")
+			bs := convertServices(*tt.new, "")
 			added, deleted, updated, unchanged := servicesDiff(as, bs)
 			for i, item := range []struct {
 				hostnames []host.Name
diff --git a/pilot/pkg/serviceregistry/serviceentry/conversion_test.go b/pilot/pkg/serviceregistry/serviceentry/conversion_test.go
index 7e415f4dfb..509fe1f222 100644
--- a/pilot/pkg/serviceregistry/serviceentry/conversion_test.go
+++ b/pilot/pkg/serviceregistry/serviceentry/conversion_test.go
@@ -503,13 +503,13 @@ func convertPortNameToProtocol(name string) protocol.Instance {
 	return protocol.Parse(prefix)
 }
 
-func makeService(hostname host.Name, configNamespace, address string, ports map[string]int,
+func makeService(hostname host.Name, configNamespace string, addresses []string, ports map[string]int,
 	external bool, resolution model.Resolution, serviceAccounts ...string,
 ) *model.Service {
 	svc := &model.Service{
 		CreationTime:    GlobalTime,
 		Hostname:        hostname,
-		DefaultAddress:  address,
+		DefaultAddress:  addresses[0],
 		MeshExternal:    external,
 		Resolution:      resolution,
 		ServiceAccounts: serviceAccounts,
@@ -518,6 +518,9 @@ func makeService(hostname host.Name, configNamespace, address string, ports map[
 			Name:            string(hostname),
 			Namespace:       configNamespace,
 		},
+		ClusterVIPs: model.AddressMap{
+			Addresses: map[cluster.ID][]string{"": addresses},
+		},
 	}
 
 	if external && features.CanonicalServiceForMeshExternalServiceEntry {
@@ -566,7 +569,7 @@ func makeInstanceWithServiceAccount(cfg *config.Config, address string, port int
 func makeTarget(cfg *config.Config, address string, port int,
 	svcPort *networking.ServicePort, svcLabels map[string]string, mtlsMode MTLSMode,
 ) model.ServiceTarget {
-	services := convertServices(*cfg)
+	services := convertServices(*cfg, "")
 	svc := services[0] // default
 	for _, s := range services {
 		if string(s.Hostname) == address {
@@ -597,7 +600,7 @@ func makeTarget(cfg *config.Config, address string, port int,
 func makeInstance(cfg *config.Config, address string, port int,
 	svcPort *networking.ServicePort, svcLabels map[string]string, mtlsMode MTLSMode,
 ) *model.ServiceInstance {
-	services := convertServices(*cfg)
+	services := convertServices(*cfg, "")
 	svc := services[0] // default
 	for _, s := range services {
 		if string(s.Hostname) == address {
@@ -650,7 +653,7 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry http
 			externalSvc: httpNone,
 			services: []*model.Service{
-				makeService("*.google.com", "httpNone", constants.UnspecifiedIP,
+				makeService("*.google.com", "httpNone", []string{constants.UnspecifiedIP},
 					map[string]int{"http-number": 80, "http2-number": 8080}, true, model.Passthrough),
 			},
 		},
@@ -658,7 +661,7 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry tcp
 			externalSvc: tcpNone,
 			services: []*model.Service{
-				makeService("tcpnone.com", "tcpNone", "172.217.0.0/16",
+				makeService("tcpnone.com", "tcpNone", []string{"172.217.0.0/16"},
 					map[string]int{"tcp-444": 444}, true, model.Passthrough),
 			},
 		},
@@ -666,7 +669,7 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry http  static
 			externalSvc: httpStatic,
 			services: []*model.Service{
-				makeService("*.google.com", "httpStatic", constants.UnspecifiedIP,
+				makeService("*.google.com", "httpStatic", []string{constants.UnspecifiedIP},
 					map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.ClientSideLB),
 			},
 		},
@@ -674,9 +677,9 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry DNS with no endpoints
 			externalSvc: httpDNSnoEndpoints,
 			services: []*model.Service{
-				makeService("google.com", "httpDNSnoEndpoints", constants.UnspecifiedIP,
+				makeService("google.com", "httpDNSnoEndpoints", []string{constants.UnspecifiedIP},
 					map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSLB, "google.com"),
-				makeService("www.wikipedia.org", "httpDNSnoEndpoints", constants.UnspecifiedIP,
+				makeService("www.wikipedia.org", "httpDNSnoEndpoints", []string{constants.UnspecifiedIP},
 					map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSLB, "google.com"),
 			},
 		},
@@ -684,7 +687,7 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry dns
 			externalSvc: httpDNS,
 			services: []*model.Service{
-				makeService("*.google.com", "httpDNS", constants.UnspecifiedIP,
+				makeService("*.google.com", "httpDNS", []string{constants.UnspecifiedIP},
 					map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSLB),
 			},
 		},
@@ -692,7 +695,7 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry dns with target port
 			externalSvc: dnsTargetPort,
 			services: []*model.Service{
-				makeService("google.com", "dnsTargetPort", constants.UnspecifiedIP,
+				makeService("google.com", "dnsTargetPort", []string{constants.UnspecifiedIP},
 					map[string]int{"http-port": 80}, true, model.DNSLB),
 			},
 		},
@@ -700,7 +703,7 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry tcp DNS
 			externalSvc: tcpDNS,
 			services: []*model.Service{
-				makeService("tcpdns.com", "tcpDNS", constants.UnspecifiedIP,
+				makeService("tcpdns.com", "tcpDNS", []string{constants.UnspecifiedIP},
 					map[string]int{"tcp-444": 444}, true, model.DNSLB),
 			},
 		},
@@ -708,7 +711,7 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry tcp static
 			externalSvc: tcpStatic,
 			services: []*model.Service{
-				makeService("tcpstatic.com", "tcpStatic", "172.217.0.1",
+				makeService("tcpstatic.com", "tcpStatic", []string{"172.217.0.1"},
 					map[string]int{"tcp-444": 444}, true, model.ClientSideLB),
 			},
 		},
@@ -716,7 +719,7 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry http internal
 			externalSvc: httpNoneInternal,
 			services: []*model.Service{
-				makeService("*.google.com", "httpNoneInternal", constants.UnspecifiedIP,
+				makeService("*.google.com", "httpNoneInternal", []string{constants.UnspecifiedIP},
 					map[string]int{"http-number": 80, "http2-number": 8080}, false, model.Passthrough),
 			},
 		},
@@ -724,7 +727,7 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry tcp internal
 			externalSvc: tcpNoneInternal,
 			services: []*model.Service{
-				makeService("tcpinternal.com", "tcpNoneInternal", "172.217.0.0/16",
+				makeService("tcpinternal.com", "tcpNoneInternal", []string{"172.217.0.0/16"},
 					map[string]int{"tcp-444": 444}, false, model.Passthrough),
 			},
 		},
@@ -732,19 +735,19 @@ func testConvertServiceBody(t *testing.T) {
 			// service entry multiAddrInternal
 			externalSvc: multiAddrInternal,
 			services: []*model.Service{
-				makeService("tcp1.com", "multiAddrInternal", "1.1.1.0/16",
+				makeService("tcp1.com", "multiAddrInternal", []string{"1.1.1.0/16", "2.2.2.0/16"},
 					map[string]int{"tcp-444": 444}, false, model.Passthrough),
-				makeService("tcp1.com", "multiAddrInternal", "2.2.2.0/16",
+				makeService("tcp1.com", "multiAddrInternal", []string{"2.2.2.0/16", "1.1.1.0/16"},
 					map[string]int{"tcp-444": 444}, false, model.Passthrough),
-				makeService("tcp2.com", "multiAddrInternal", "1.1.1.0/16",
+				makeService("tcp2.com", "multiAddrInternal", []string{"1.1.1.0/16", "2.2.2.0/16"},
 					map[string]int{"tcp-444": 444}, false, model.Passthrough),
-				makeService("tcp2.com", "multiAddrInternal", "2.2.2.0/16",
+				makeService("tcp2.com", "multiAddrInternal", []string{"2.2.2.0/16", "1.1.1.0/16"},
 					map[string]int{"tcp-444": 444}, false, model.Passthrough),
 			},
 		},
 	}
 
-	selectorSvc := makeService("selector.com", "selector", "0.0.0.0",
+	selectorSvc := makeService("selector.com", "selector", []string{constants.UnspecifiedIP},
 		map[string]int{"tcp-444": 444, "http-445": 445}, true, model.ClientSideLB)
 	selectorSvc.Attributes.LabelSelectors = map[string]string{"app": "wle"}
 
@@ -757,7 +760,7 @@ func testConvertServiceBody(t *testing.T) {
 	})
 
 	for _, tt := range serviceTests {
-		services := convertServices(*tt.externalSvc)
+		services := convertServices(*tt.externalSvc, "")
 		if err := compare(t, services, tt.services); err != nil {
 			t.Errorf("testcase: %v\n%v ", tt.externalSvc.Name, err)
 		}
@@ -944,7 +947,7 @@ func TestConvertWorkloadEntryToServiceInstances(t *testing.T) {
 
 	for _, tt := range serviceInstanceTests {
 		t.Run(tt.name, func(t *testing.T) {
-			services := convertServices(*tt.se)
+			services := convertServices(*tt.se, "")
 			s := &Controller{meshWatcher: mesh.NewFixedWatcher(mesh.DefaultMeshConfig())}
 			instances := s.convertWorkloadEntryToServiceInstances(tt.wle, services, tt.se.Spec.(*networking.ServiceEntry), &configKey{}, tt.clusterID)
 			sortServiceInstances(instances)
diff --git a/pilot/pkg/serviceregistry/serviceentry/store_test.go b/pilot/pkg/serviceregistry/serviceentry/store_test.go
index 814d52c92a..b33a000d69 100644
--- a/pilot/pkg/serviceregistry/serviceentry/store_test.go
+++ b/pilot/pkg/serviceregistry/serviceentry/store_test.go
@@ -114,8 +114,9 @@ func TestServiceStore(t *testing.T) {
 	}
 
 	expectedServices := []*model.Service{
-		makeService("*.istio.io", "httpDNSRR", constants.UnspecifiedIP, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSRoundRobinLB),
-		makeService("*.istio.io", "httpDNSRR", constants.UnspecifiedIP, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSLB),
+		makeService(
+			"*.istio.io", "httpDNSRR", []string{constants.UnspecifiedIP}, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSRoundRobinLB),
+		makeService("*.istio.io", "httpDNSRR", []string{constants.UnspecifiedIP}, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSLB),
 	}
 
 	store.updateServices(httpDNSRR.NamespacedName(), expectedServices)
diff --git a/pkg/test/framework/components/echo/common/deployment/namespace.go b/pkg/test/framework/components/echo/common/deployment/namespace.go
index 0e453b3d41..7f6c8a0914 100644
--- a/pkg/test/framework/components/echo/common/deployment/namespace.go
+++ b/pkg/test/framework/components/echo/common/deployment/namespace.go
@@ -156,7 +156,7 @@ metadata:
   name: external-service
 spec:
 {{- if .ManuallyAllocate }}
-  addresses: [240.240.240.239] # Semi-random address for the range Istio allocates in
+  addresses: [240.240.240.239, 2001:2::f0f0:239] # Semi-random addresses for the range Istio allocates in
 {{- end }}
   exportTo: [.]
   hosts:
diff --git a/tests/integration/ambient/waypoint_test.go b/tests/integration/ambient/waypoint_test.go
index 63dab3fe1d..40422f25c4 100644
--- a/tests/integration/ambient/waypoint_test.go
+++ b/tests/integration/ambient/waypoint_test.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"net/http"
 	"strings"
 	"testing"
 	"time"
@@ -36,7 +37,6 @@ import (
 	"istio.io/istio/pkg/test/framework/components/echo/check"
 	"istio.io/istio/pkg/test/framework/components/istioctl"
 	"istio.io/istio/pkg/test/framework/components/namespace"
-	"istio.io/istio/pkg/test/framework/label"
 	"istio.io/istio/pkg/test/framework/resource/config/apply"
 	kubetest "istio.io/istio/pkg/test/kube"
 	"istio.io/istio/pkg/test/scopes"
@@ -381,20 +381,26 @@ func TestWaypointDNS(t *testing.T) {
 				if src.Config().HasSidecar() {
 					t.Skip("TODO: sidecars don't properly handle use-waypoint")
 				}
+				address := "240.240.240.239"
+				if _, v6 := getSupportedIPFamilies(t); v6 {
+					address = "2001:2::f0f0:239"
+				}
 				src.CallOrFail(t, echo.CallOptions{
 					To:      apps.MockExternal,
-					Address: apps.MockExternal.Config().DefaultHostHeader,
-					Port:    echo.Port{Name: "http"},
-					Scheme:  scheme.HTTP,
-					Count:   1,
-					Check:   check,
+					Address: address,
+					HTTP: echo.HTTP{
+						Headers: http.Header{"Host": []string{apps.MockExternal.Config().DefaultHostHeader}},
+					},
+					Port:   echo.Port{Name: "http"},
+					Scheme: scheme.HTTP,
+					Count:  1,
+					Check:  check,
 				})
 			})
 		}
 	}
 	framework.
 		NewTest(t).
-		Label(label.IPv4). // TODO(https://github.com/istio/istio/issues/51886)
 		Run(func(t framework.TestContext) {
 			t.NewSubTest("without waypoint").Run(func(t framework.TestContext) {
 				runTest(t, check.OK())