Repository: scionproto/scion
Pull request #4789 (2025-06-18 14:29:20)

controlplane: do less SVC resolution
This PR adds a new beacon extension that contains the control and discovery service addresses of the AS.
All code has been adapted to use the address out of the extension if it's available.

The only place that still relies on SVC resolution in this new stack is the beacon sending,
because we don't know the control service address of the receiving AS.

This implements a variation of #4388

Co-authored-by: JordiSubira <jordi.subira.nieto@gmail.com>


Patch:
diff --git a/control/BUILD.bazel b/control/BUILD.bazel
index 53ce80db9..c25537572 100644
--- a/control/BUILD.bazel
+++ b/control/BUILD.bazel
@@ -38,6 +38,7 @@ go_library(
         "//pkg/scrypto:go_default_library",
         "//pkg/scrypto/cppki:go_default_library",
         "//pkg/segment:go_default_library",
+        "//pkg/segment/extensions/discovery:go_default_library",
         "//pkg/snet:go_default_library",
         "//pkg/snet/addrutil:go_default_library",
         "//pkg/snet/metrics:go_default_library",
diff --git a/control/beaconing/BUILD.bazel b/control/beaconing/BUILD.bazel
index ce783e3c4..88130ff7a 100644
--- a/control/beaconing/BUILD.bazel
+++ b/control/beaconing/BUILD.bazel
@@ -31,6 +31,7 @@ go_library(
         "//pkg/scrypto/cppki:go_default_library",
         "//pkg/segment:go_default_library",
         "//pkg/segment/extensions/digest:go_default_library",
+        "//pkg/segment/extensions/discovery:go_default_library",
         "//pkg/segment/extensions/epic:go_default_library",
         "//pkg/segment/extensions/staticinfo:go_default_library",
         "//pkg/segment/iface:go_default_library",
@@ -73,6 +74,7 @@ go_test(
         "//pkg/scrypto/cppki:go_default_library",
         "//pkg/scrypto/signed:go_default_library",
         "//pkg/segment:go_default_library",
+        "//pkg/segment/extensions/discovery:go_default_library",
         "//pkg/segment/extensions/staticinfo:go_default_library",
         "//pkg/segment/iface:go_default_library",
         "//pkg/slayers/path:go_default_library",
diff --git a/control/beaconing/extender.go b/control/beaconing/extender.go
index ee5160d88..a8e8d68e1 100644
--- a/control/beaconing/extender.go
+++ b/control/beaconing/extender.go
@@ -30,6 +30,7 @@ import (
 	"github.com/scionproto/scion/pkg/scrypto/cppki"
 	seg "github.com/scionproto/scion/pkg/segment"
 	"github.com/scionproto/scion/pkg/segment/extensions/digest"
+	"github.com/scionproto/scion/pkg/segment/extensions/discovery"
 	"github.com/scionproto/scion/pkg/segment/extensions/epic"
 	"github.com/scionproto/scion/pkg/slayers/path"
 	"github.com/scionproto/scion/private/trust"
@@ -79,6 +80,9 @@ type DefaultExtender struct {
 	Task string
 	// StaticInfo contains the configuration used for the StaticInfo Extension.
 	StaticInfo func() *StaticInfoCfg
+	// DiscoveryInformation contains the discovery information to be added to
+	// the segment.
+	DiscoveryInformation func() *discovery.Extension
 	// EPIC defines whether the EPIC authenticators should be added when the segment is extended.
 	EPIC bool
 
@@ -176,6 +180,9 @@ func (s *DefaultExtender) Extend(
 	if static := s.StaticInfo(); static != nil {
 		asEntry.Extensions.StaticInfo = static.Generate(s.Intfs, ingress, egress)
 	}
+	if discovery := s.DiscoveryInformation(); discovery != nil {
+		asEntry.Extensions.Discovery = discovery
+	}
 
 	// Add the detachable Epic extension
 	if s.EPIC {
diff --git a/control/beaconing/handler.go b/control/beaconing/handler.go
index 6fc2d27c2..7655f32a7 100644
--- a/control/beaconing/handler.go
+++ b/control/beaconing/handler.go
@@ -16,6 +16,7 @@ package beaconing
 
 import (
 	"context"
+	"net"
 	"strconv"
 
 	"github.com/opentracing/opentracing-go"
@@ -127,13 +128,24 @@ func (h Handler) verifySegment(ctx context.Context, segment *seg.PathSegment,
 	if err != nil {
 		return err
 	}
-	svcToQuery := &snet.SVCAddr{
+	var remoteAddr net.Addr = &snet.SVCAddr{
 		IA:      peer.IA,
 		Path:    peerPath.Dataplane(),
 		NextHop: peerPath.UnderlayNextHop(),
 		SVC:     addr.SvcCS,
 	}
-	return segverifier.VerifySegment(ctx, h.Verifier, svcToQuery, segment)
+
+	if disco := segment.ASEntries[segment.MaxIdx()].Extensions.Discovery; disco != nil &&
+		len(disco.ControlServices) > 0 {
+		remoteAddr = &snet.UDPAddr{
+			IA:      peer.IA,
+			Path:    peerPath.Dataplane(),
+			NextHop: peerPath.UnderlayNextHop(),
+			Host:    net.UDPAddrFromAddrPort(disco.ControlServices[0]),
+		}
+	}
+
+	return segverifier.VerifySegment(ctx, h.Verifier, remoteAddr, segment)
 }
 
 func (h Handler) updateMetric(span opentracing.Span, l handlerLabels, err error) {
diff --git a/control/beaconing/writer.go b/control/beaconing/writer.go
index ef1552c0c..1d0ff6ddc 100644
--- a/control/beaconing/writer.go
+++ b/control/beaconing/writer.go
@@ -29,7 +29,6 @@ import (
 	"github.com/scionproto/scion/pkg/private/prom"
 	"github.com/scionproto/scion/pkg/private/serrors"
 	seg "github.com/scionproto/scion/pkg/segment"
-	"github.com/scionproto/scion/pkg/snet"
 	"github.com/scionproto/scion/private/periodic"
 	"github.com/scionproto/scion/private/segment/seghandler"
 	"github.com/scionproto/scion/private/topology"
@@ -37,7 +36,7 @@ import (
 
 // Pather computes the remote address with a path based on the provided segment.
 type Pather interface {
-	GetPath(svc addr.SVC, ps *seg.PathSegment) (*snet.SVCAddr, error)
+	GetPath(svc addr.SVC, ps *seg.PathSegment) (net.Addr, error)
 }
 
 // SegmentProvider provides segments to register for the specified type.
diff --git a/control/cmd/control/BUILD.bazel b/control/cmd/control/BUILD.bazel
index e4780cae1..02c471bc8 100644
--- a/control/cmd/control/BUILD.bazel
+++ b/control/cmd/control/BUILD.bazel
@@ -44,6 +44,7 @@ go_library(
         "//pkg/proto/discovery:go_default_library",
         "//pkg/scrypto:go_default_library",
         "//pkg/scrypto/cppki:go_default_library",
+        "//pkg/segment/extensions/discovery:go_default_library",
         "//pkg/segment/iface:go_default_library",
         "//pkg/snet:go_default_library",
         "//pkg/snet/squic:go_default_library",
diff --git a/control/cmd/control/main.go b/control/cmd/control/main.go
index ca750478d..1c8ec2bc4 100644
--- a/control/cmd/control/main.go
+++ b/control/cmd/control/main.go
@@ -76,6 +76,7 @@ import (
 	dconnect "github.com/scionproto/scion/pkg/proto/discovery/v1/discoveryconnect"
 	"github.com/scionproto/scion/pkg/scrypto"
 	"github.com/scionproto/scion/pkg/scrypto/cppki"
+	discoveryext "github.com/scionproto/scion/pkg/segment/extensions/discovery"
 	"github.com/scionproto/scion/pkg/segment/iface"
 	"github.com/scionproto/scion/pkg/snet"
 	"github.com/scionproto/scion/pkg/snet/squic"
@@ -955,6 +956,18 @@ func realMain(ctx context.Context) error {
 		NextHopper:  topo,
 		StaticInfo:  func() *beaconing.StaticInfoCfg { return staticInfo },
 
+		DiscoveryInfo: func() *discoveryext.Extension {
+			cses := topo.ControlServiceAddresses()
+			addrs := make([]netip.AddrPort, 0, len(cses))
+			for _, cs := range cses {
+				addrs = append(addrs, cs.AddrPort())
+			}
+			return &discoveryext.Extension{
+				ControlServices:   addrs,
+				DiscoveryServices: addrs,
+			}
+		},
+
 		OriginationInterval:       globalCfg.BS.OriginationInterval.Duration,
 		PropagationInterval:       globalCfg.BS.PropagationInterval.Duration,
 		RegistrationInterval:      globalCfg.BS.RegistrationInterval.Duration,
diff --git a/control/drkey/grpc/BUILD.bazel b/control/drkey/grpc/BUILD.bazel
index 0ac50194f..570b5a596 100644
--- a/control/drkey/grpc/BUILD.bazel
+++ b/control/drkey/grpc/BUILD.bazel
@@ -21,6 +21,7 @@ go_library(
         "//pkg/proto/control_plane:go_default_library",
         "//pkg/proto/drkey:go_default_library",
         "//pkg/snet:go_default_library",
+        "//pkg/snet/addrutil:go_default_library",
         "@org_go4_netipx//:go_default_library",
         "@org_golang_google_grpc//credentials:go_default_library",
         "@org_golang_google_grpc//peer:go_default_library",
diff --git a/control/drkey/grpc/fetcher.go b/control/drkey/grpc/fetcher.go
index 29fb3c5c2..fc05ce9fa 100644
--- a/control/drkey/grpc/fetcher.go
+++ b/control/drkey/grpc/fetcher.go
@@ -26,6 +26,7 @@ import (
 	"github.com/scionproto/scion/pkg/private/serrors"
 	cppb "github.com/scionproto/scion/pkg/proto/control_plane"
 	"github.com/scionproto/scion/pkg/snet"
+	"github.com/scionproto/scion/pkg/snet/addrutil"
 )
 
 const (
@@ -106,12 +107,7 @@ func (f *Fetcher) getLevel1Key(
 	if err != nil {
 		return nil, err
 	}
-	remote := &snet.SVCAddr{
-		IA:      srcIA,
-		Path:    path.Dataplane(),
-		NextHop: path.UnderlayNextHop(),
-		SVC:     addr.SvcCS,
-	}
+	remote := addrutil.ExtractDestinationServiceAddress(addr.SvcCS, path)
 	dialCtx, cancelF := context.WithTimeout(ctx, defaultRPCDialTimeout)
 	defer cancelF()
 	conn, err := f.Dialer.Dial(dialCtx, remote)
diff --git a/control/segreg/grpc/registration_server.go b/control/segreg/grpc/registration_server.go
index ef6260466..61eaccaa0 100644
--- a/control/segreg/grpc/registration_server.go
+++ b/control/segreg/grpc/registration_server.go
@@ -17,6 +17,7 @@ package grpc
 import (
 	"context"
 	"fmt"
+	"net"
 
 	"github.com/opentracing/opentracing-go"
 	"google.golang.org/grpc/codes"
@@ -88,16 +89,36 @@ func (s *RegistrationServer) SegmentsRegistration(ctx context.Context,
 		}
 	}
 
+	var remoteAddr net.Addr = &snet.SVCAddr{
+		IA:      peer.IA,
+		Path:    peer.Path,
+		NextHop: peer.NextHop,
+		SVC:     addr.SvcCS,
+	}
+
+	// Let's see if any of the segments has the remote CS address and if there
+	// is one, pick it.
+	for _, seg := range segs {
+		for _, asE := range seg.Segment.ASEntries {
+			if asE.Local != peer.IA {
+				continue
+			}
+			if disco := asE.Extensions.Discovery; disco != nil && len(disco.ControlServices) > 0 {
+				remoteAddr = &snet.UDPAddr{
+					IA:      peer.IA,
+					Path:    peer.Path,
+					NextHop: peer.NextHop,
+					Host:    net.UDPAddrFromAddrPort(disco.ControlServices[0]),
+				}
+			}
+		}
+	}
+
 	res := s.SegHandler.Handle(ctx,
 		seghandler.Segments{
 			Segs: segs,
 		},
-		&snet.SVCAddr{
-			IA:      peer.IA,
-			Path:    peer.Path,
-			NextHop: peer.NextHop,
-			SVC:     addr.SvcCS,
-		},
+		remoteAddr,
 	)
 	if err := res.Err(); err != nil {
 		s.failMetric(span, labels.WithResult(prom.ErrProcess), err)
diff --git a/control/segreq/fetcher.go b/control/segreq/fetcher.go
index 7d6a1fb14..58292b394 100644
--- a/control/segreq/fetcher.go
+++ b/control/segreq/fetcher.go
@@ -182,7 +182,7 @@ func (p *dstProvider) Dst(ctx context.Context, req segfetcher.Request) (net.Addr
 		if err != nil {
 			return nil, serrors.JoinNoStack(segfetcher.ErrNotReachable, err)
 		}
-		path = up
+		return up, nil
 	case seg.TypeDown:
 		// Select a random path (just like we choose a random segment above)
 		// Avoids potentially being stuck with a broken but not revoked path;
@@ -195,6 +195,8 @@ func (p *dstProvider) Dst(ctx context.Context, req segfetcher.Request) (net.Addr
 			return nil, segfetcher.ErrNotReachable
 		}
 		path = paths[rand.IntN(len(paths))]
+		addr := addrutil.ExtractDestinationServiceAddress(addr.SvcCS, path)
+		return addr, nil
 	default:
 		panic(
 			"unsupported segment type for request forwarding: " +
@@ -202,16 +204,10 @@ func (p *dstProvider) Dst(ctx context.Context, req segfetcher.Request) (net.Addr
 				req.SegType.String(),
 		)
 	}
-	addr := &snet.SVCAddr{
-		IA:      path.Destination(),
-		Path:    path.Dataplane(),
-		NextHop: path.UnderlayNextHop(),
-		SVC:     addr.SvcCS,
-	}
-	return addr, nil
+
 }
 
-func (p *dstProvider) upPath(ctx context.Context, dst addr.IA) (snet.Path, error) {
+func (p *dstProvider) upPath(ctx context.Context, dst addr.IA) (net.Addr, error) {
 	return p.segSelector.SelectSeg(ctx, &query.Params{
 		StartsAt: []addr.IA{dst},
 		EndsAt:   []addr.IA{p.localIA},
diff --git a/control/segreq/helpers.go b/control/segreq/helpers.go
index 7692e144c..47b9e60db 100644
--- a/control/segreq/helpers.go
+++ b/control/segreq/helpers.go
@@ -17,11 +17,11 @@ package segreq
 import (
 	"context"
 	"math/rand/v2"
+	"net"
 
 	"github.com/scionproto/scion/pkg/addr"
 	"github.com/scionproto/scion/pkg/private/serrors"
 	seg "github.com/scionproto/scion/pkg/segment"
-	"github.com/scionproto/scion/pkg/snet"
 	"github.com/scionproto/scion/private/pathdb"
 	"github.com/scionproto/scion/private/pathdb/query"
 	"github.com/scionproto/scion/private/revcache"
@@ -30,7 +30,7 @@ import (
 
 // Pather computes the remote address with a path based on the provided segment.
 type Pather interface {
-	GetPath(svc addr.SVC, ps *seg.PathSegment) (*snet.SVCAddr, error)
+	GetPath(svc addr.SVC, ps *seg.PathSegment) (net.Addr, error)
 }
 
 // CoreChecker checks whether a given ia is core.
@@ -54,8 +54,10 @@ type SegSelector struct {
 }
 
 // SelectSeg selects a suitable segment for the given path db query.
-func (s *SegSelector) SelectSeg(ctx context.Context,
-	params *query.Params) (snet.Path, error) {
+func (s *SegSelector) SelectSeg(
+	ctx context.Context,
+	params *query.Params,
+) (net.Addr, error) {
 
 	res, err := s.PathDB.Get(ctx, params)
 	if err != nil {
@@ -73,10 +75,5 @@ func (s *SegSelector) SelectSeg(ctx context.Context,
 	}
 	seg := segs[rand.IntN(len(segs))]
 
-	svcaddr, err := s.Pather.GetPath(addr.SvcCS, seg)
-	// odd interface, builds address not path. Use GetPath to convert to snet.Path
-	if err != nil {
-		return nil, err
-	}
-	return svcaddr.GetPath()
+	return s.Pather.GetPath(addr.SvcCS, seg)
 }
diff --git a/control/tasks.go b/control/tasks.go
index 3484fd174..71da29405 100644
--- a/control/tasks.go
+++ b/control/tasks.go
@@ -28,6 +28,7 @@ import (
 	"github.com/scionproto/scion/pkg/experimental/hiddenpath"
 	"github.com/scionproto/scion/pkg/metrics"
 	seg "github.com/scionproto/scion/pkg/segment"
+	"github.com/scionproto/scion/pkg/segment/extensions/discovery"
 	"github.com/scionproto/scion/pkg/snet"
 	"github.com/scionproto/scion/pkg/snet/addrutil"
 	"github.com/scionproto/scion/private/pathdb"
@@ -61,8 +62,9 @@ type TasksConfig struct {
 	Metrics               *Metrics
 	DRKeyEngine           *drkey.ServiceEngine
 
-	MACGen     func() hash.Hash
-	StaticInfo func() *beaconing.StaticInfoCfg
+	MACGen        func() hash.Hash
+	StaticInfo    func() *beaconing.StaticInfoCfg
+	DiscoveryInfo func() *discovery.Extension
 
 	OriginationInterval  time.Duration
 	PropagationInterval  time.Duration
@@ -213,15 +215,16 @@ func (t *TasksConfig) extender(
 ) beaconing.Extender {
 
 	return &beaconing.DefaultExtender{
-		IA:         ia,
-		SignerGen:  t.SignerGen,
-		MAC:        t.MACGen,
-		Intfs:      t.AllInterfaces,
-		MTU:        mtu,
-		MaxExpTime: func() uint8 { return maxExp() },
-		StaticInfo: t.StaticInfo,
-		Task:       task,
-		EPIC:       t.EPIC,
+		IA:                   ia,
+		SignerGen:            t.SignerGen,
+		MAC:                  t.MACGen,
+		Intfs:                t.AllInterfaces,
+		MTU:                  mtu,
+		MaxExpTime:           func() uint8 { return maxExp() },
+		StaticInfo:           t.StaticInfo,
+		DiscoveryInformation: t.DiscoveryInfo,
+		Task:                 task,
+		EPIC:                 t.EPIC,
 		SegmentExpirationDeficient: func() metrics.Gauge {
 			if t.Metrics == nil {
 				return nil
diff --git a/daemon/internal/servers/BUILD.bazel b/daemon/internal/servers/BUILD.bazel
index 50eed2252..ec1ae2a4e 100644
--- a/daemon/internal/servers/BUILD.bazel
+++ b/daemon/internal/servers/BUILD.bazel
@@ -22,6 +22,7 @@ go_library(
         "//pkg/private/util:go_default_library",
         "//pkg/proto/daemon:go_default_library",
         "//pkg/segment/iface:go_default_library",
+        "//pkg/slices:go_default_library",
         "//pkg/snet:go_default_library",
         "//pkg/snet/path:go_default_library",
         "//private/revcache:go_default_library",
diff --git a/daemon/internal/servers/grpc.go b/daemon/internal/servers/grpc.go
index 36dfadbcc..06edc3b73 100644
--- a/daemon/internal/servers/grpc.go
+++ b/daemon/internal/servers/grpc.go
@@ -18,6 +18,7 @@ import (
 	"context"
 	"fmt"
 	"net"
+	"net/netip"
 	"time"
 
 	"github.com/opentracing/opentracing-go"
@@ -38,6 +39,7 @@ import (
 	"github.com/scionproto/scion/pkg/private/util"
 	"github.com/scionproto/scion/pkg/proto/daemon"
 	"github.com/scionproto/scion/pkg/segment/iface"
+	"github.com/scionproto/scion/pkg/slices"
 	"github.com/scionproto/scion/pkg/snet"
 	snetpath "github.com/scionproto/scion/pkg/snet/path"
 	"github.com/scionproto/scion/private/revcache"
@@ -169,21 +171,39 @@ func pathToPB(path snet.Path) *daemon.Path {
 		AuthLhvf: append([]byte(nil), meta.EpicAuths.AuthLHVF...),
 	}
 
+	var discovery map[uint64]*daemon.DiscoveryInformation
+	if di := meta.DiscoveryInformation; di != nil {
+		discovery = make(map[uint64]*daemon.DiscoveryInformation, len(di))
+		for ia, info := range di {
+			discovery[uint64(ia)] = &daemon.DiscoveryInformation{
+				ControlServiceAddresses: slices.Transform(
+					info.ControlServices,
+					netip.AddrPort.String,
+				),
+				DiscoveryServiceAddresses: slices.Transform(
+					info.DiscoveryServices,
+					netip.AddrPort.String,
+				),
+			}
+		}
+	}
+
 	return &daemon.Path{
 		Raw: raw,
 		Interface: &daemon.Interface{
 			Address: &daemon.Underlay{Address: nextHopStr},
 		},
-		Interfaces:   interfaces,
-		Mtu:          uint32(meta.MTU),
-		Expiration:   &timestamppb.Timestamp{Seconds: meta.Expiry.Unix()},
-		Latency:      latency,
-		Bandwidth:    meta.Bandwidth,
-		Geo:          geo,
-		LinkType:     linkType,
-		InternalHops: meta.InternalHops,
-		Notes:        meta.Notes,
-		EpicAuths:    epicAuths,
+		Interfaces:           interfaces,
+		Mtu:                  uint32(meta.MTU),
+		Expiration:           &timestamppb.Timestamp{Seconds: meta.Expiry.Unix()},
+		Latency:              latency,
+		Bandwidth:            meta.Bandwidth,
+		Geo:                  geo,
+		LinkType:             linkType,
+		InternalHops:         meta.InternalHops,
+		Notes:                meta.Notes,
+		EpicAuths:            epicAuths,
+		DiscoveryInformation: discovery,
 	}
 }
 
diff --git a/gateway/control/grpc/BUILD.bazel b/gateway/control/grpc/BUILD.bazel
index 009833771..f84dcb252 100644
--- a/gateway/control/grpc/BUILD.bazel
+++ b/gateway/control/grpc/BUILD.bazel
@@ -22,6 +22,7 @@ go_library(
         "//pkg/proto/discovery:go_default_library",
         "//pkg/proto/gateway:go_default_library",
         "//pkg/snet:go_default_library",
+        "//pkg/snet/addrutil:go_default_library",
         "@org_golang_google_grpc//codes:go_default_library",
         "@org_golang_google_grpc//peer:go_default_library",
         "@org_golang_google_grpc//status:go_default_library",
diff --git a/gateway/control/grpc/discoverer.go b/gateway/control/grpc/discoverer.go
index 5111290aa..31bc59b9c 100644
--- a/gateway/control/grpc/discoverer.go
+++ b/gateway/control/grpc/discoverer.go
@@ -23,7 +23,7 @@ import (
 	"github.com/scionproto/scion/pkg/grpc"
 	"github.com/scionproto/scion/pkg/private/serrors"
 	dpb "github.com/scionproto/scion/pkg/proto/discovery"
-	"github.com/scionproto/scion/pkg/snet"
+	"github.com/scionproto/scion/pkg/snet/addrutil"
 )
 
 // Discoverer discovers the gateways for a specific remote AS.
@@ -38,12 +38,7 @@ func (d Discoverer) Gateways(ctx context.Context) ([]control.Gateway, error) {
 	if len(paths) == 0 {
 		return nil, serrors.New("no path available")
 	}
-	ds := &snet.SVCAddr{
-		IA:      d.Remote,
-		Path:    paths[0].Dataplane(),
-		NextHop: paths[0].UnderlayNextHop(),
-		SVC:     addr.SvcDS,
-	}
+	ds := addrutil.ExtractDestinationServiceAddress(addr.SvcDS, paths[0])
 	conn, err := d.Dialer.Dial(ctx, ds)
 	if err != nil {
 		return nil, err
diff --git a/pkg/daemon/grpc.go b/pkg/daemon/grpc.go
index d27d8e61d..ce28397d4 100644
--- a/pkg/daemon/grpc.go
+++ b/pkg/daemon/grpc.go
@@ -300,6 +300,35 @@ func convertPath(p *sdpb.Path, dst addr.IA) (path.Path, error) {
 		},
 	}
 
+	if p.DiscoveryInformation != nil {
+		res.Meta.DiscoveryInformation = make(map[addr.IA]snet.DiscoveryInformation)
+		for ia, di := range p.DiscoveryInformation {
+			cses := make([]netip.AddrPort, 0, len(di.ControlServiceAddresses))
+			dses := make([]netip.AddrPort, 0, len(di.DiscoveryServiceAddresses))
+			for _, cs := range di.ControlServiceAddresses {
+				ap, err := netip.ParseAddrPort(cs)
+				if err != nil {
+					return path.Path{}, serrors.Wrap("parsing control service address", err,
+						"address", cs, "ia", ia)
+				}
+				cses = append(cses, ap)
+			}
+			for _, ds := range di.DiscoveryServiceAddresses {
+				ap, err := netip.ParseAddrPort(ds)
+				if err != nil {
+					return path.Path{}, serrors.Wrap("parsing discovery service address", err,
+						"address", ds, "ia", ia)
+				}
+				dses = append(dses, ap)
+
+			}
+			res.Meta.DiscoveryInformation[addr.IA(ia)] = snet.DiscoveryInformation{
+				ControlServices:   cses,
+				DiscoveryServices: dses,
+			}
+		}
+	}
+
 	if p.EpicAuths == nil {
 		return res, nil
 	}
diff --git a/pkg/experimental/hiddenpath/BUILD.bazel b/pkg/experimental/hiddenpath/BUILD.bazel
index 456511085..0885a14ca 100644
--- a/pkg/experimental/hiddenpath/BUILD.bazel
+++ b/pkg/experimental/hiddenpath/BUILD.bazel
@@ -26,7 +26,7 @@ go_library(
         "//pkg/private/serrors:go_default_library",
         "//pkg/segment:go_default_library",
         "//pkg/snet:go_default_library",
-        "//pkg/snet/path:go_default_library",
+        "//pkg/snet/addrutil:go_default_library",
         "//private/config:go_default_library",
         "//private/pathdb:go_default_library",
         "//private/pathdb/query:go_default_library",
@@ -64,6 +64,7 @@ go_test(
         "//pkg/scrypto/cppki:go_default_library",
         "//pkg/scrypto/signed:go_default_library",
         "//pkg/segment:go_default_library",
+        "//pkg/segment/extensions/discovery:go_default_library",
         "//pkg/segment/iface:go_default_library",
         "//pkg/slayers/path:go_default_library",
         "//pkg/slayers/path/scion:go_default_library",
diff --git a/pkg/experimental/hiddenpath/discovery.go b/pkg/experimental/hiddenpath/discovery.go
index bec3bb5cc..5086a4e25 100644
--- a/pkg/experimental/hiddenpath/discovery.go
+++ b/pkg/experimental/hiddenpath/discovery.go
@@ -21,7 +21,7 @@ import (
 	"github.com/scionproto/scion/pkg/addr"
 	"github.com/scionproto/scion/pkg/private/serrors"
 	"github.com/scionproto/scion/pkg/snet"
-	"github.com/scionproto/scion/pkg/snet/path"
+	"github.com/scionproto/scion/pkg/snet/addrutil"
 )
 
 // Servers is a list of discovered remote hidden segment server.
@@ -88,15 +88,8 @@ func resolve(ctx context.Context, ia addr.IA, discoverer Discoverer, router snet
 	if p == nil {
 		return nil, serrors.Wrap("no path found to remote", err)
 	}
-	dsAddr := &snet.SVCAddr{
-		IA:      ia,
-		NextHop: p.UnderlayNextHop(),
-		Path:    p.Dataplane(),
-		SVC:     addr.SvcDS,
-	}
-	if dsAddr.Path == nil {
-		dsAddr.Path = path.Empty{}
-	}
+
+	dsAddr := addrutil.ExtractDestinationServiceAddress(addr.SvcDS, p)
 	hps, err := discoverer.Discover(ctx, dsAddr)
 	if err != nil {
 		return nil, serrors.Wrap("discovering hidden path server", err)
@@ -108,7 +101,7 @@ func resolve(ctx context.Context, ia addr.IA, discoverer Discoverer, router snet
 	return &snet.UDPAddr{
 		IA:      ia,
 		Host:    a,
-		NextHop: dsAddr.NextHop,
-		Path:    dsAddr.Path,
+		NextHop: p.UnderlayNextHop(),
+		Path:    p.Dataplane(),
 	}, nil
 }
diff --git a/pkg/grpc/BUILD.bazel b/pkg/grpc/BUILD.bazel
index 42f76da70..c56bccdbc 100644
--- a/pkg/grpc/BUILD.bazel
+++ b/pkg/grpc/BUILD.bazel
@@ -13,7 +13,6 @@ go_library(
     deps = [
         "//pkg/addr:go_default_library",
         "//pkg/log:go_default_library",
-        "//pkg/private/common:go_default_library",
         "//pkg/private/serrors:go_default_library",
         "//pkg/snet:go_default_library",
         "@com_github_grpc_ecosystem_go_grpc_middleware//retry:go_default_library",
diff --git a/pkg/grpc/dialer.go b/pkg/grpc/dialer.go
index f758f9f6a..1b97c62ae 100644
--- a/pkg/grpc/dialer.go
+++ b/pkg/grpc/dialer.go
@@ -26,7 +26,6 @@ import (
 	"google.golang.org/grpc/resolver/manual"
 
 	"github.com/scionproto/scion/pkg/addr"
-	"github.com/scionproto/scion/pkg/private/common"
 	"github.com/scionproto/scion/pkg/private/serrors"
 	"github.com/scionproto/scion/pkg/snet"
 )
@@ -152,10 +151,6 @@ func (d *QUICDialer) Dial(ctx context.Context, addr net.Addr) (*grpc.ClientConn,
 	if err != nil {
 		return nil, serrors.Wrap("resolving SVC address", err)
 	}
-	if _, ok := addr.(*snet.UDPAddr); !ok {
-		return nil, serrors.New("wrong address type after svc resolution",
-			"type", common.TypeOf(addr))
-	}
 	dialer := func(context.Context, string) (net.Conn, error) {
 		return d.Dialer.Dial(ctx, addr)
 	}
diff --git a/pkg/proto/control_plane/seg_extensions.pb.go b/pkg/proto/control_plane/seg_extensions.pb.go
index 5ab4a4fd4..c55a7017a 100644
--- a/pkg/proto/control_plane/seg_extensions.pb.go
+++ b/pkg/proto/control_plane/seg_extensions.pb.go
@@ -78,6 +78,7 @@ type PathSegmentExtensions struct {
 	state         protoimpl.MessageState `protogen:"open.v1"`
 	StaticInfo    *StaticInfoExtension   `protobuf:"bytes,1,opt,name=static_info,json=staticInfo,proto3" json:"static_info,omitempty"`
 	HiddenPath    *HiddenPathExtension   `protobuf:"bytes,2,opt,name=hidden_path,json=hiddenPath,proto3" json:"hidden_path,omitempty"`
+	Discovery     *DiscoveryExtension    `protobuf:"bytes,3,opt,name=discovery,proto3" json:"discovery,omitempty"`
 	Digests       *DigestExtension       `protobuf:"bytes,1000,opt,name=digests,proto3" json:"digests,omitempty"`
 	unknownFields protoimpl.UnknownFields
 	sizeCache     protoimpl.SizeCache
@@ -127,6 +128,13 @@ func (x *PathSegmentExtensions) GetHiddenPath() *HiddenPathExtension {
 	return nil
 }
 
+func (x *PathSegmentExtensions) GetDiscovery() *DiscoveryExtension {
+	if x != nil {
+		return x.Discovery
+	}
+	return nil
+}
+
 func (x *PathSegmentExtensions) GetDigests() *DigestExtension {
 	if x != nil {
 		return x.Digests
@@ -262,6 +270,66 @@ func (x *StaticInfoExtension) GetNote() string {
 	return ""
 }
 
+type DiscoveryExtension struct {
+	state                     protoimpl.MessageState `protogen:"open.v1"`
+	Version                   uint32                 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	ControlServiceAddresses   []string               `protobuf:"bytes,2,rep,name=control_service_addresses,json=controlServiceAddresses,proto3" json:"control_service_addresses,omitempty"`
+	DiscoveryServiceAddresses []string               `protobuf:"bytes,3,rep,name=discovery_service_addresses,json=discoveryServiceAddresses,proto3" json:"discovery_service_addresses,omitempty"`
+	unknownFields             protoimpl.UnknownFields
+	sizeCache                 protoimpl.SizeCache
+}
+
+func (x *DiscoveryExtension) Reset() {
+	*x = DiscoveryExtension{}
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[3]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *DiscoveryExtension) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DiscoveryExtension) ProtoMessage() {}
+
+func (x *DiscoveryExtension) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[3]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DiscoveryExtension.ProtoReflect.Descriptor instead.
+func (*DiscoveryExtension) Descriptor() ([]byte, []int) {
+	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *DiscoveryExtension) GetVersion() uint32 {
+	if x != nil {
+		return x.Version
+	}
+	return 0
+}
+
+func (x *DiscoveryExtension) GetControlServiceAddresses() []string {
+	if x != nil {
+		return x.ControlServiceAddresses
+	}
+	return nil
+}
+
+func (x *DiscoveryExtension) GetDiscoveryServiceAddresses() []string {
+	if x != nil {
+		return x.DiscoveryServiceAddresses
+	}
+	return nil
+}
+
 type LatencyInfo struct {
 	state         protoimpl.MessageState `protogen:"open.v1"`
 	Intra         map[uint64]uint32      `protobuf:"bytes,1,rep,name=intra,proto3" json:"intra,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
@@ -272,7 +340,7 @@ type LatencyInfo struct {
 
 func (x *LatencyInfo) Reset() {
 	*x = LatencyInfo{}
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[3]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[4]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -284,7 +352,7 @@ func (x *LatencyInfo) String() string {
 func (*LatencyInfo) ProtoMessage() {}
 
 func (x *LatencyInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[3]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[4]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -297,7 +365,7 @@ func (x *LatencyInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use LatencyInfo.ProtoReflect.Descriptor instead.
 func (*LatencyInfo) Descriptor() ([]byte, []int) {
-	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{3}
+	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{4}
 }
 
 func (x *LatencyInfo) GetIntra() map[uint64]uint32 {
@@ -324,7 +392,7 @@ type BandwidthInfo struct {
 
 func (x *BandwidthInfo) Reset() {
 	*x = BandwidthInfo{}
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[4]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[5]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -336,7 +404,7 @@ func (x *BandwidthInfo) String() string {
 func (*BandwidthInfo) ProtoMessage() {}
 
 func (x *BandwidthInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[4]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[5]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -349,7 +417,7 @@ func (x *BandwidthInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use BandwidthInfo.ProtoReflect.Descriptor instead.
 func (*BandwidthInfo) Descriptor() ([]byte, []int) {
-	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{4}
+	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{5}
 }
 
 func (x *BandwidthInfo) GetIntra() map[uint64]uint64 {
@@ -377,7 +445,7 @@ type GeoCoordinates struct {
 
 func (x *GeoCoordinates) Reset() {
 	*x = GeoCoordinates{}
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[5]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[6]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -389,7 +457,7 @@ func (x *GeoCoordinates) String() string {
 func (*GeoCoordinates) ProtoMessage() {}
 
 func (x *GeoCoordinates) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[5]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[6]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -402,7 +470,7 @@ func (x *GeoCoordinates) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use GeoCoordinates.ProtoReflect.Descriptor instead.
 func (*GeoCoordinates) Descriptor() ([]byte, []int) {
-	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{5}
+	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{6}
 }
 
 func (x *GeoCoordinates) GetLatitude() float32 {
@@ -435,7 +503,7 @@ type DigestExtension struct {
 
 func (x *DigestExtension) Reset() {
 	*x = DigestExtension{}
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[6]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[7]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -447,7 +515,7 @@ func (x *DigestExtension) String() string {
 func (*DigestExtension) ProtoMessage() {}
 
 func (x *DigestExtension) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[6]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[7]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -460,7 +528,7 @@ func (x *DigestExtension) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DigestExtension.ProtoReflect.Descriptor instead.
 func (*DigestExtension) Descriptor() ([]byte, []int) {
-	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{6}
+	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{7}
 }
 
 func (x *DigestExtension) GetEpic() *DigestExtension_Digest {
@@ -479,7 +547,7 @@ type PathSegmentUnsignedExtensions struct {
 
 func (x *PathSegmentUnsignedExtensions) Reset() {
 	*x = PathSegmentUnsignedExtensions{}
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[7]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[8]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -491,7 +559,7 @@ func (x *PathSegmentUnsignedExtensions) String() string {
 func (*PathSegmentUnsignedExtensions) ProtoMessage() {}
 
 func (x *PathSegmentUnsignedExtensions) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[7]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[8]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -504,7 +572,7 @@ func (x *PathSegmentUnsignedExtensions) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use PathSegmentUnsignedExtensions.ProtoReflect.Descriptor instead.
 func (*PathSegmentUnsignedExtensions) Descriptor() ([]byte, []int) {
-	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{7}
+	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{8}
 }
 
 func (x *PathSegmentUnsignedExtensions) GetEpic() *experimental.EPICDetachedExtension {
@@ -523,7 +591,7 @@ type DigestExtension_Digest struct {
 
 func (x *DigestExtension_Digest) Reset() {
 	*x = DigestExtension_Digest{}
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[15]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[16]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -535,7 +603,7 @@ func (x *DigestExtension_Digest) String() string {
 func (*DigestExtension_Digest) ProtoMessage() {}
 
 func (x *DigestExtension_Digest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[15]
+	mi := &file_proto_control_plane_v1_seg_extensions_proto_msgTypes[16]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -548,7 +616,7 @@ func (x *DigestExtension_Digest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DigestExtension_Digest.ProtoReflect.Descriptor instead.
 func (*DigestExtension_Digest) Descriptor() ([]byte, []int) {
-	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{6, 0}
+	return file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP(), []int{7, 0}
 }
 
 func (x *DigestExtension_Digest) GetDigest() []byte {
@@ -562,12 +630,13 @@ var File_proto_control_plane_v1_seg_extensions_proto protoreflect.FileDescriptor
 
 const file_proto_control_plane_v1_seg_extensions_proto_rawDesc = "" +
 	"\n" +
-	"+proto/control_plane/v1/seg_extensions.proto\x12\x16proto.control_plane.v1\x1aAproto/control_plane/experimental/v1/seg_detached_extensions.proto\"\xf7\x01\n" +
+	"+proto/control_plane/v1/seg_extensions.proto\x12\x16proto.control_plane.v1\x1aAproto/control_plane/experimental/v1/seg_detached_extensions.proto\"\xc1\x02\n" +
 	"\x15PathSegmentExtensions\x12L\n" +
 	"\vstatic_info\x18\x01 \x01(\v2+.proto.control_plane.v1.StaticInfoExtensionR\n" +
 	"staticInfo\x12L\n" +
 	"\vhidden_path\x18\x02 \x01(\v2+.proto.control_plane.v1.HiddenPathExtensionR\n" +
-	"hiddenPath\x12B\n" +
+	"hiddenPath\x12H\n" +
+	"\tdiscovery\x18\x03 \x01(\v2*.proto.control_plane.v1.DiscoveryExtensionR\tdiscovery\x12B\n" +
 	"\adigests\x18\xe8\a \x01(\v2'.proto.control_plane.v1.DigestExtensionR\adigests\"2\n" +
 	"\x13HiddenPathExtension\x12\x1b\n" +
 	"\tis_hidden\x18\x01 \x01(\bR\bisHidden\"\xb1\x05\n" +
@@ -586,7 +655,11 @@ const file_proto_control_plane_v1_seg_extensions_proto_rawDesc = "" +
 	"\x05value\x18\x02 \x01(\x0e2 .proto.control_plane.v1.LinkTypeR\x05value:\x028\x01\x1a?\n" +
 	"\x11InternalHopsEntry\x12\x10\n" +
 	"\x03key\x18\x01 \x01(\x04R\x03key\x12\x14\n" +
-	"\x05value\x18\x02 \x01(\rR\x05value:\x028\x01\"\x8d\x02\n" +
+	"\x05value\x18\x02 \x01(\rR\x05value:\x028\x01\"\xaa\x01\n" +
+	"\x12DiscoveryExtension\x12\x18\n" +
+	"\aversion\x18\x01 \x01(\rR\aversion\x12:\n" +
+	"\x19control_service_addresses\x18\x02 \x03(\tR\x17controlServiceAddresses\x12>\n" +
+	"\x1bdiscovery_service_addresses\x18\x03 \x03(\tR\x19discoveryServiceAddresses\"\x8d\x02\n" +
 	"\vLatencyInfo\x12D\n" +
 	"\x05intra\x18\x01 \x03(\v2..proto.control_plane.v1.LatencyInfo.IntraEntryR\x05intra\x12D\n" +
 	"\x05inter\x18\x02 \x03(\v2..proto.control_plane.v1.LatencyInfo.InterEntryR\x05inter\x1a8\n" +
@@ -638,49 +711,51 @@ func file_proto_control_plane_v1_seg_extensions_proto_rawDescGZIP() []byte {
 }
 
 var file_proto_control_plane_v1_seg_extensions_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_proto_control_plane_v1_seg_extensions_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
+var file_proto_control_plane_v1_seg_extensions_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
 var file_proto_control_plane_v1_seg_extensions_proto_goTypes = []any{
 	(LinkType)(0),                         // 0: proto.control_plane.v1.LinkType
 	(*PathSegmentExtensions)(nil),         // 1: proto.control_plane.v1.PathSegmentExtensions
 	(*HiddenPathExtension)(nil),           // 2: proto.control_plane.v1.HiddenPathExtension
 	(*StaticInfoExtension)(nil),           // 3: proto.control_plane.v1.StaticInfoExtension
-	(*LatencyInfo)(nil),                   // 4: proto.control_plane.v1.LatencyInfo
-	(*BandwidthInfo)(nil),                 // 5: proto.control_plane.v1.BandwidthInfo
-	(*GeoCoordinates)(nil),                // 6: proto.control_plane.v1.GeoCoordinates
-	(*DigestExtension)(nil),               // 7: proto.control_plane.v1.DigestExtension
-	(*PathSegmentUnsignedExtensions)(nil), // 8: proto.control_plane.v1.PathSegmentUnsignedExtensions
-	nil,                                   // 9: proto.control_plane.v1.StaticInfoExtension.GeoEntry
-	nil,                                   // 10: proto.control_plane.v1.StaticInfoExtension.LinkTypeEntry
-	nil,                                   // 11: proto.control_plane.v1.StaticInfoExtension.InternalHopsEntry
-	nil,                                   // 12: proto.control_plane.v1.LatencyInfo.IntraEntry
-	nil,                                   // 13: proto.control_plane.v1.LatencyInfo.InterEntry
-	nil,                                   // 14: proto.control_plane.v1.BandwidthInfo.IntraEntry
-	nil,                                   // 15: proto.control_plane.v1.BandwidthInfo.InterEntry
-	(*DigestExtension_Digest)(nil),        // 16: proto.control_plane.v1.DigestExtension.Digest
-	(*experimental.EPICDetachedExtension)(nil), // 17: proto.control_plane.experimental.v1.EPICDetachedExtension
+	(*DiscoveryExtension)(nil),            // 4: proto.control_plane.v1.DiscoveryExtension
+	(*LatencyInfo)(nil),                   // 5: proto.control_plane.v1.LatencyInfo
+	(*BandwidthInfo)(nil),                 // 6: proto.control_plane.v1.BandwidthInfo
+	(*GeoCoordinates)(nil),                // 7: proto.control_plane.v1.GeoCoordinates
+	(*DigestExtension)(nil),               // 8: proto.control_plane.v1.DigestExtension
+	(*PathSegmentUnsignedExtensions)(nil), // 9: proto.control_plane.v1.PathSegmentUnsignedExtensions
+	nil,                                   // 10: proto.control_plane.v1.StaticInfoExtension.GeoEntry
+	nil,                                   // 11: proto.control_plane.v1.StaticInfoExtension.LinkTypeEntry
+	nil,                                   // 12: proto.control_plane.v1.StaticInfoExtension.InternalHopsEntry
+	nil,                                   // 13: proto.control_plane.v1.LatencyInfo.IntraEntry
+	nil,                                   // 14: proto.control_plane.v1.LatencyInfo.InterEntry
+	nil,                                   // 15: proto.control_plane.v1.BandwidthInfo.IntraEntry
+	nil,                                   // 16: proto.control_plane.v1.BandwidthInfo.InterEntry
+	(*DigestExtension_Digest)(nil),        // 17: proto.control_plane.v1.DigestExtension.Digest
+	(*experimental.EPICDetachedExtension)(nil), // 18: proto.control_plane.experimental.v1.EPICDetachedExtension
 }
 var file_proto_control_plane_v1_seg_extensions_proto_depIdxs = []int32{
 	3,  // 0: proto.control_plane.v1.PathSegmentExtensions.static_info:type_name -> proto.control_plane.v1.StaticInfoExtension
 	2,  // 1: proto.control_plane.v1.PathSegmentExtensions.hidden_path:type_name -> proto.control_plane.v1.HiddenPathExtension
-	7,  // 2: proto.control_plane.v1.PathSegmentExtensions.digests:type_name -> proto.control_plane.v1.DigestExtension
-	4,  // 3: proto.control_plane.v1.StaticInfoExtension.latency:type_name -> proto.control_plane.v1.LatencyInfo
-	5,  // 4: proto.control_plane.v1.StaticInfoExtension.bandwidth:type_name -> proto.control_plane.v1.BandwidthInfo
-	9,  // 5: proto.control_plane.v1.StaticInfoExtension.geo:type_name -> proto.control_plane.v1.StaticInfoExtension.GeoEntry
-	10, // 6: proto.control_plane.v1.StaticInfoExtension.link_type:type_name -> proto.control_plane.v1.StaticInfoExtension.LinkTypeEntry
-	11, // 7: proto.control_plane.v1.StaticInfoExtension.internal_hops:type_name -> proto.control_plane.v1.StaticInfoExtension.InternalHopsEntry
-	12, // 8: proto.control_plane.v1.LatencyInfo.intra:type_name -> proto.control_plane.v1.LatencyInfo.IntraEntry
-	13, // 9: proto.control_plane.v1.LatencyInfo.inter:type_name -> proto.control_plane.v1.LatencyInfo.InterEntry
-	14, // 10: proto.control_plane.v1.BandwidthInfo.intra:type_name -> proto.control_plane.v1.BandwidthInfo.IntraEntry
-	15, // 11: proto.control_plane.v1.BandwidthInfo.inter:type_name -> proto.control_plane.v1.BandwidthInfo.InterEntry
-	16, // 12: proto.control_plane.v1.DigestExtension.epic:type_name -> proto.control_plane.v1.DigestExtension.Digest
-	17, // 13: proto.control_plane.v1.PathSegmentUnsignedExtensions.epic:type_name -> proto.control_plane.experimental.v1.EPICDetachedExtension
-	6,  // 14: proto.control_plane.v1.StaticInfoExtension.GeoEntry.value:type_name -> proto.control_plane.v1.GeoCoordinates
-	0,  // 15: proto.control_plane.v1.StaticInfoExtension.LinkTypeEntry.value:type_name -> proto.control_plane.v1.LinkType
-	16, // [16:16] is the sub-list for method output_type
-	16, // [16:16] is the sub-list for method input_type
-	16, // [16:16] is the sub-list for extension type_name
-	16, // [16:16] is the sub-list for extension extendee
-	0,  // [0:16] is the sub-list for field type_name
+	4,  // 2: proto.control_plane.v1.PathSegmentExtensions.discovery:type_name -> proto.control_plane.v1.DiscoveryExtension
+	8,  // 3: proto.control_plane.v1.PathSegmentExtensions.digests:type_name -> proto.control_plane.v1.DigestExtension
+	5,  // 4: proto.control_plane.v1.StaticInfoExtension.latency:type_name -> proto.control_plane.v1.LatencyInfo
+	6,  // 5: proto.control_plane.v1.StaticInfoExtension.bandwidth:type_name -> proto.control_plane.v1.BandwidthInfo
+	10, // 6: proto.control_plane.v1.StaticInfoExtension.geo:type_name -> proto.control_plane.v1.StaticInfoExtension.GeoEntry
+	11, // 7: proto.control_plane.v1.StaticInfoExtension.link_type:type_name -> proto.control_plane.v1.StaticInfoExtension.LinkTypeEntry
+	12, // 8: proto.control_plane.v1.StaticInfoExtension.internal_hops:type_name -> proto.control_plane.v1.StaticInfoExtension.InternalHopsEntry
+	13, // 9: proto.control_plane.v1.LatencyInfo.intra:type_name -> proto.control_plane.v1.LatencyInfo.IntraEntry
+	14, // 10: proto.control_plane.v1.LatencyInfo.inter:type_name -> proto.control_plane.v1.LatencyInfo.InterEntry
+	15, // 11: proto.control_plane.v1.BandwidthInfo.intra:type_name -> proto.control_plane.v1.BandwidthInfo.IntraEntry
+	16, // 12: proto.control_plane.v1.BandwidthInfo.inter:type_name -> proto.control_plane.v1.BandwidthInfo.InterEntry
+	17, // 13: proto.control_plane.v1.DigestExtension.epic:type_name -> proto.control_plane.v1.DigestExtension.Digest
+	18, // 14: proto.control_plane.v1.PathSegmentUnsignedExtensions.epic:type_name -> proto.control_plane.experimental.v1.EPICDetachedExtension
+	7,  // 15: proto.control_plane.v1.StaticInfoExtension.GeoEntry.value:type_name -> proto.control_plane.v1.GeoCoordinates
+	0,  // 16: proto.control_plane.v1.StaticInfoExtension.LinkTypeEntry.value:type_name -> proto.control_plane.v1.LinkType
+	17, // [17:17] is the sub-list for method output_type
+	17, // [17:17] is the sub-list for method input_type
+	17, // [17:17] is the sub-list for extension type_name
+	17, // [17:17] is the sub-list for extension extendee
+	0,  // [0:17] is the sub-list for field type_name
 }
 
 func init() { file_proto_control_plane_v1_seg_extensions_proto_init() }
@@ -694,7 +769,7 @@ func file_proto_control_plane_v1_seg_extensions_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_control_plane_v1_seg_extensions_proto_rawDesc), len(file_proto_control_plane_v1_seg_extensions_proto_rawDesc)),
 			NumEnums:      1,
-			NumMessages:   16,
+			NumMessages:   17,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/pkg/proto/daemon/daemon.pb.go b/pkg/proto/daemon/daemon.pb.go
index fe02a5887..bcb28c88b 100644
--- a/pkg/proto/daemon/daemon.pb.go
+++ b/pkg/proto/daemon/daemon.pb.go
@@ -194,21 +194,22 @@ func (x *PathsResponse) GetPaths() []*Path {
 }
 
 type Path struct {
-	state         protoimpl.MessageState `protogen:"open.v1"`
-	Raw           []byte                 `protobuf:"bytes,1,opt,name=raw,proto3" json:"raw,omitempty"`
-	Interface     *Interface             `protobuf:"bytes,2,opt,name=interface,proto3" json:"interface,omitempty"`
-	Interfaces    []*PathInterface       `protobuf:"bytes,3,rep,name=interfaces,proto3" json:"interfaces,omitempty"`
-	Mtu           uint32                 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
-	Expiration    *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=expiration,proto3" json:"expiration,omitempty"`
-	Latency       []*durationpb.Duration `protobuf:"bytes,6,rep,name=latency,proto3" json:"latency,omitempty"`
-	Bandwidth     []uint64               `protobuf:"varint,7,rep,packed,name=bandwidth,proto3" json:"bandwidth,omitempty"`
-	Geo           []*GeoCoordinates      `protobuf:"bytes,8,rep,name=geo,proto3" json:"geo,omitempty"`
-	LinkType      []LinkType             `protobuf:"varint,9,rep,packed,name=link_type,json=linkType,proto3,enum=proto.daemon.v1.LinkType" json:"link_type,omitempty"`
-	InternalHops  []uint32               `protobuf:"varint,10,rep,packed,name=internal_hops,json=internalHops,proto3" json:"internal_hops,omitempty"`
-	Notes         []string               `protobuf:"bytes,11,rep,name=notes,proto3" json:"notes,omitempty"`
-	EpicAuths     *EpicAuths             `protobuf:"bytes,12,opt,name=epic_auths,json=epicAuths,proto3" json:"epic_auths,omitempty"`
-	unknownFields protoimpl.UnknownFields
-	sizeCache     protoimpl.SizeCache
+	state                protoimpl.MessageState           `protogen:"open.v1"`
+	Raw                  []byte                           `protobuf:"bytes,1,opt,name=raw,proto3" json:"raw,omitempty"`
+	Interface            *Interface                       `protobuf:"bytes,2,opt,name=interface,proto3" json:"interface,omitempty"`
+	Interfaces           []*PathInterface                 `protobuf:"bytes,3,rep,name=interfaces,proto3" json:"interfaces,omitempty"`
+	Mtu                  uint32                           `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"`
+	Expiration           *timestamppb.Timestamp           `protobuf:"bytes,5,opt,name=expiration,proto3" json:"expiration,omitempty"`
+	Latency              []*durationpb.Duration           `protobuf:"bytes,6,rep,name=latency,proto3" json:"latency,omitempty"`
+	Bandwidth            []uint64                         `protobuf:"varint,7,rep,packed,name=bandwidth,proto3" json:"bandwidth,omitempty"`
+	Geo                  []*GeoCoordinates                `protobuf:"bytes,8,rep,name=geo,proto3" json:"geo,omitempty"`
+	LinkType             []LinkType                       `protobuf:"varint,9,rep,packed,name=link_type,json=linkType,proto3,enum=proto.daemon.v1.LinkType" json:"link_type,omitempty"`
+	InternalHops         []uint32                         `protobuf:"varint,10,rep,packed,name=internal_hops,json=internalHops,proto3" json:"internal_hops,omitempty"`
+	Notes                []string                         `protobuf:"bytes,11,rep,name=notes,proto3" json:"notes,omitempty"`
+	EpicAuths            *EpicAuths                       `protobuf:"bytes,12,opt,name=epic_auths,json=epicAuths,proto3" json:"epic_auths,omitempty"`
+	DiscoveryInformation map[uint64]*DiscoveryInformation `protobuf:"bytes,13,rep,name=discovery_information,json=discoveryInformation,proto3" json:"discovery_information,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+	unknownFields        protoimpl.UnknownFields
+	sizeCache            protoimpl.SizeCache
 }
 
 func (x *Path) Reset() {
@@ -325,6 +326,65 @@ func (x *Path) GetEpicAuths() *EpicAuths {
 	return nil
 }
 
+func (x *Path) GetDiscoveryInformation() map[uint64]*DiscoveryInformation {
+	if x != nil {
+		return x.DiscoveryInformation
+	}
+	return nil
+}
+
+type DiscoveryInformation struct {
+	state                     protoimpl.MessageState `protogen:"open.v1"`
+	ControlServiceAddresses   []string               `protobuf:"bytes,1,rep,name=control_service_addresses,json=controlServiceAddresses,proto3" json:"control_service_addresses,omitempty"`
+	DiscoveryServiceAddresses []string               `protobuf:"bytes,2,rep,name=discovery_service_addresses,json=discoveryServiceAddresses,proto3" json:"discovery_service_addresses,omitempty"`
+	unknownFields             protoimpl.UnknownFields
+	sizeCache                 protoimpl.SizeCache
+}
+
+func (x *DiscoveryInformation) Reset() {
+	*x = DiscoveryInformation{}
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[3]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *DiscoveryInformation) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DiscoveryInformation) ProtoMessage() {}
+
+func (x *DiscoveryInformation) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[3]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DiscoveryInformation.ProtoReflect.Descriptor instead.
+func (*DiscoveryInformation) Descriptor() ([]byte, []int) {
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *DiscoveryInformation) GetControlServiceAddresses() []string {
+	if x != nil {
+		return x.ControlServiceAddresses
+	}
+	return nil
+}
+
+func (x *DiscoveryInformation) GetDiscoveryServiceAddresses() []string {
+	if x != nil {
+		return x.DiscoveryServiceAddresses
+	}
+	return nil
+}
+
 type EpicAuths struct {
 	state         protoimpl.MessageState `protogen:"open.v1"`
 	AuthPhvf      []byte                 `protobuf:"bytes,1,opt,name=auth_phvf,json=authPhvf,proto3" json:"auth_phvf,omitempty"`
@@ -335,7 +395,7 @@ type EpicAuths struct {
 
 func (x *EpicAuths) Reset() {
 	*x = EpicAuths{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[3]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[4]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -347,7 +407,7 @@ func (x *EpicAuths) String() string {
 func (*EpicAuths) ProtoMessage() {}
 
 func (x *EpicAuths) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[3]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[4]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -360,7 +420,7 @@ func (x *EpicAuths) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use EpicAuths.ProtoReflect.Descriptor instead.
 func (*EpicAuths) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{3}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{4}
 }
 
 func (x *EpicAuths) GetAuthPhvf() []byte {
@@ -387,7 +447,7 @@ type PathInterface struct {
 
 func (x *PathInterface) Reset() {
 	*x = PathInterface{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[4]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[5]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -399,7 +459,7 @@ func (x *PathInterface) String() string {
 func (*PathInterface) ProtoMessage() {}
 
 func (x *PathInterface) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[4]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[5]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -412,7 +472,7 @@ func (x *PathInterface) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use PathInterface.ProtoReflect.Descriptor instead.
 func (*PathInterface) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{4}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{5}
 }
 
 func (x *PathInterface) GetIsdAs() uint64 {
@@ -440,7 +500,7 @@ type GeoCoordinates struct {
 
 func (x *GeoCoordinates) Reset() {
 	*x = GeoCoordinates{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[5]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[6]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -452,7 +512,7 @@ func (x *GeoCoordinates) String() string {
 func (*GeoCoordinates) ProtoMessage() {}
 
 func (x *GeoCoordinates) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[5]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[6]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -465,7 +525,7 @@ func (x *GeoCoordinates) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use GeoCoordinates.ProtoReflect.Descriptor instead.
 func (*GeoCoordinates) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{5}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{6}
 }
 
 func (x *GeoCoordinates) GetLatitude() float32 {
@@ -498,7 +558,7 @@ type ASRequest struct {
 
 func (x *ASRequest) Reset() {
 	*x = ASRequest{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[6]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[7]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -510,7 +570,7 @@ func (x *ASRequest) String() string {
 func (*ASRequest) ProtoMessage() {}
 
 func (x *ASRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[6]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[7]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -523,7 +583,7 @@ func (x *ASRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ASRequest.ProtoReflect.Descriptor instead.
 func (*ASRequest) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{6}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{7}
 }
 
 func (x *ASRequest) GetIsdAs() uint64 {
@@ -544,7 +604,7 @@ type ASResponse struct {
 
 func (x *ASResponse) Reset() {
 	*x = ASResponse{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[7]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[8]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -556,7 +616,7 @@ func (x *ASResponse) String() string {
 func (*ASResponse) ProtoMessage() {}
 
 func (x *ASResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[7]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[8]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -569,7 +629,7 @@ func (x *ASResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ASResponse.ProtoReflect.Descriptor instead.
 func (*ASResponse) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{7}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{8}
 }
 
 func (x *ASResponse) GetIsdAs() uint64 {
@@ -601,7 +661,7 @@ type InterfacesRequest struct {
 
 func (x *InterfacesRequest) Reset() {
 	*x = InterfacesRequest{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[8]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[9]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -613,7 +673,7 @@ func (x *InterfacesRequest) String() string {
 func (*InterfacesRequest) ProtoMessage() {}
 
 func (x *InterfacesRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[8]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[9]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -626,7 +686,7 @@ func (x *InterfacesRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use InterfacesRequest.ProtoReflect.Descriptor instead.
 func (*InterfacesRequest) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{8}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{9}
 }
 
 type InterfacesResponse struct {
@@ -638,7 +698,7 @@ type InterfacesResponse struct {
 
 func (x *InterfacesResponse) Reset() {
 	*x = InterfacesResponse{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[9]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[10]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -650,7 +710,7 @@ func (x *InterfacesResponse) String() string {
 func (*InterfacesResponse) ProtoMessage() {}
 
 func (x *InterfacesResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[9]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[10]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -663,7 +723,7 @@ func (x *InterfacesResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use InterfacesResponse.ProtoReflect.Descriptor instead.
 func (*InterfacesResponse) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{9}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{10}
 }
 
 func (x *InterfacesResponse) GetInterfaces() map[uint64]*Interface {
@@ -682,7 +742,7 @@ type Interface struct {
 
 func (x *Interface) Reset() {
 	*x = Interface{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[10]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[11]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -694,7 +754,7 @@ func (x *Interface) String() string {
 func (*Interface) ProtoMessage() {}
 
 func (x *Interface) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[10]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[11]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -707,7 +767,7 @@ func (x *Interface) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Interface.ProtoReflect.Descriptor instead.
 func (*Interface) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{10}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{11}
 }
 
 func (x *Interface) GetAddress() *Underlay {
@@ -725,7 +785,7 @@ type ServicesRequest struct {
 
 func (x *ServicesRequest) Reset() {
 	*x = ServicesRequest{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[11]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[12]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -737,7 +797,7 @@ func (x *ServicesRequest) String() string {
 func (*ServicesRequest) ProtoMessage() {}
 
 func (x *ServicesRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[11]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[12]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -750,7 +810,7 @@ func (x *ServicesRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ServicesRequest.ProtoReflect.Descriptor instead.
 func (*ServicesRequest) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{11}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{12}
 }
 
 type ServicesResponse struct {
@@ -762,7 +822,7 @@ type ServicesResponse struct {
 
 func (x *ServicesResponse) Reset() {
 	*x = ServicesResponse{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[12]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[13]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -774,7 +834,7 @@ func (x *ServicesResponse) String() string {
 func (*ServicesResponse) ProtoMessage() {}
 
 func (x *ServicesResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[12]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[13]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -787,7 +847,7 @@ func (x *ServicesResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ServicesResponse.ProtoReflect.Descriptor instead.
 func (*ServicesResponse) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{12}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{13}
 }
 
 func (x *ServicesResponse) GetServices() map[string]*ListService {
@@ -806,7 +866,7 @@ type ListService struct {
 
 func (x *ListService) Reset() {
 	*x = ListService{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[13]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[14]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -818,7 +878,7 @@ func (x *ListService) String() string {
 func (*ListService) ProtoMessage() {}
 
 func (x *ListService) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[13]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[14]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -831,7 +891,7 @@ func (x *ListService) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ListService.ProtoReflect.Descriptor instead.
 func (*ListService) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{13}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{14}
 }
 
 func (x *ListService) GetServices() []*Service {
@@ -850,7 +910,7 @@ type Service struct {
 
 func (x *Service) Reset() {
 	*x = Service{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[14]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[15]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -862,7 +922,7 @@ func (x *Service) String() string {
 func (*Service) ProtoMessage() {}
 
 func (x *Service) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[14]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[15]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -875,7 +935,7 @@ func (x *Service) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Service.ProtoReflect.Descriptor instead.
 func (*Service) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{14}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{15}
 }
 
 func (x *Service) GetUri() string {
@@ -894,7 +954,7 @@ type Underlay struct {
 
 func (x *Underlay) Reset() {
 	*x = Underlay{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[15]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[16]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -906,7 +966,7 @@ func (x *Underlay) String() string {
 func (*Underlay) ProtoMessage() {}
 
 func (x *Underlay) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[15]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[16]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -919,7 +979,7 @@ func (x *Underlay) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Underlay.ProtoReflect.Descriptor instead.
 func (*Underlay) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{15}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{16}
 }
 
 func (x *Underlay) GetAddress() string {
@@ -939,7 +999,7 @@ type NotifyInterfaceDownRequest struct {
 
 func (x *NotifyInterfaceDownRequest) Reset() {
 	*x = NotifyInterfaceDownRequest{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[16]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[17]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -951,7 +1011,7 @@ func (x *NotifyInterfaceDownRequest) String() string {
 func (*NotifyInterfaceDownRequest) ProtoMessage() {}
 
 func (x *NotifyInterfaceDownRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[16]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[17]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -964,7 +1024,7 @@ func (x *NotifyInterfaceDownRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use NotifyInterfaceDownRequest.ProtoReflect.Descriptor instead.
 func (*NotifyInterfaceDownRequest) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{16}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{17}
 }
 
 func (x *NotifyInterfaceDownRequest) GetIsdAs() uint64 {
@@ -989,7 +1049,7 @@ type NotifyInterfaceDownResponse struct {
 
 func (x *NotifyInterfaceDownResponse) Reset() {
 	*x = NotifyInterfaceDownResponse{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[17]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -1001,7 +1061,7 @@ func (x *NotifyInterfaceDownResponse) String() string {
 func (*NotifyInterfaceDownResponse) ProtoMessage() {}
 
 func (x *NotifyInterfaceDownResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[17]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1014,7 +1074,7 @@ func (x *NotifyInterfaceDownResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use NotifyInterfaceDownResponse.ProtoReflect.Descriptor instead.
 func (*NotifyInterfaceDownResponse) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{17}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{18}
 }
 
 type PortRangeResponse struct {
@@ -1027,7 +1087,7 @@ type PortRangeResponse struct {
 
 func (x *PortRangeResponse) Reset() {
 	*x = PortRangeResponse{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -1039,7 +1099,7 @@ func (x *PortRangeResponse) String() string {
 func (*PortRangeResponse) ProtoMessage() {}
 
 func (x *PortRangeResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1052,7 +1112,7 @@ func (x *PortRangeResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use PortRangeResponse.ProtoReflect.Descriptor instead.
 func (*PortRangeResponse) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{18}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{19}
 }
 
 func (x *PortRangeResponse) GetDispatchedPortStart() uint32 {
@@ -1082,7 +1142,7 @@ type DRKeyHostASRequest struct {
 
 func (x *DRKeyHostASRequest) Reset() {
 	*x = DRKeyHostASRequest{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -1094,7 +1154,7 @@ func (x *DRKeyHostASRequest) String() string {
 func (*DRKeyHostASRequest) ProtoMessage() {}
 
 func (x *DRKeyHostASRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1107,7 +1167,7 @@ func (x *DRKeyHostASRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DRKeyHostASRequest.ProtoReflect.Descriptor instead.
 func (*DRKeyHostASRequest) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{19}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{20}
 }
 
 func (x *DRKeyHostASRequest) GetValTime() *timestamppb.Timestamp {
@@ -1156,7 +1216,7 @@ type DRKeyHostASResponse struct {
 
 func (x *DRKeyHostASResponse) Reset() {
 	*x = DRKeyHostASResponse{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -1168,7 +1228,7 @@ func (x *DRKeyHostASResponse) String() string {
 func (*DRKeyHostASResponse) ProtoMessage() {}
 
 func (x *DRKeyHostASResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1181,7 +1241,7 @@ func (x *DRKeyHostASResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DRKeyHostASResponse.ProtoReflect.Descriptor instead.
 func (*DRKeyHostASResponse) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{20}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{21}
 }
 
 func (x *DRKeyHostASResponse) GetEpochBegin() *timestamppb.Timestamp {
@@ -1218,7 +1278,7 @@ type DRKeyASHostRequest struct {
 
 func (x *DRKeyASHostRequest) Reset() {
 	*x = DRKeyASHostRequest{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -1230,7 +1290,7 @@ func (x *DRKeyASHostRequest) String() string {
 func (*DRKeyASHostRequest) ProtoMessage() {}
 
 func (x *DRKeyASHostRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1243,7 +1303,7 @@ func (x *DRKeyASHostRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DRKeyASHostRequest.ProtoReflect.Descriptor instead.
 func (*DRKeyASHostRequest) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{21}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{22}
 }
 
 func (x *DRKeyASHostRequest) GetValTime() *timestamppb.Timestamp {
@@ -1292,7 +1352,7 @@ type DRKeyASHostResponse struct {
 
 func (x *DRKeyASHostResponse) Reset() {
 	*x = DRKeyASHostResponse{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -1304,7 +1364,7 @@ func (x *DRKeyASHostResponse) String() string {
 func (*DRKeyASHostResponse) ProtoMessage() {}
 
 func (x *DRKeyASHostResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1317,7 +1377,7 @@ func (x *DRKeyASHostResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DRKeyASHostResponse.ProtoReflect.Descriptor instead.
 func (*DRKeyASHostResponse) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{22}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{23}
 }
 
 func (x *DRKeyASHostResponse) GetEpochBegin() *timestamppb.Timestamp {
@@ -1355,7 +1415,7 @@ type DRKeyHostHostRequest struct {
 
 func (x *DRKeyHostHostRequest) Reset() {
 	*x = DRKeyHostHostRequest{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[24]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -1367,7 +1427,7 @@ func (x *DRKeyHostHostRequest) String() string {
 func (*DRKeyHostHostRequest) ProtoMessage() {}
 
 func (x *DRKeyHostHostRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[24]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1380,7 +1440,7 @@ func (x *DRKeyHostHostRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DRKeyHostHostRequest.ProtoReflect.Descriptor instead.
 func (*DRKeyHostHostRequest) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{23}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{24}
 }
 
 func (x *DRKeyHostHostRequest) GetValTime() *timestamppb.Timestamp {
@@ -1436,7 +1496,7 @@ type DRKeyHostHostResponse struct {
 
 func (x *DRKeyHostHostResponse) Reset() {
 	*x = DRKeyHostHostResponse{}
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[24]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[25]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -1448,7 +1508,7 @@ func (x *DRKeyHostHostResponse) String() string {
 func (*DRKeyHostHostResponse) ProtoMessage() {}
 
 func (x *DRKeyHostHostResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[24]
+	mi := &file_proto_daemon_v1_daemon_proto_msgTypes[25]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1461,7 +1521,7 @@ func (x *DRKeyHostHostResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DRKeyHostHostResponse.ProtoReflect.Descriptor instead.
 func (*DRKeyHostHostResponse) Descriptor() ([]byte, []int) {
-	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{24}
+	return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{25}
 }
 
 func (x *DRKeyHostHostResponse) GetEpochBegin() *timestamppb.Timestamp {
@@ -1496,7 +1556,7 @@ const file_proto_daemon_v1_daemon_proto_rawDesc = "" +
 	"\arefresh\x18\x03 \x01(\bR\arefresh\x12\x16\n" +
 	"\x06hidden\x18\x04 \x01(\bR\x06hidden\"<\n" +
 	"\rPathsResponse\x12+\n" +
-	"\x05paths\x18\x01 \x03(\v2\x15.proto.daemon.v1.PathR\x05paths\"\x94\x04\n" +
+	"\x05paths\x18\x01 \x03(\v2\x15.proto.daemon.v1.PathR\x05paths\"\xea\x05\n" +
 	"\x04Path\x12\x10\n" +
 	"\x03raw\x18\x01 \x01(\fR\x03raw\x128\n" +
 	"\tinterface\x18\x02 \x01(\v2\x1a.proto.daemon.v1.InterfaceR\tinterface\x12>\n" +
@@ -1515,7 +1575,14 @@ const file_proto_daemon_v1_daemon_proto_rawDesc = "" +
 	" \x03(\rR\finternalHops\x12\x14\n" +
 	"\x05notes\x18\v \x03(\tR\x05notes\x129\n" +
 	"\n" +
-	"epic_auths\x18\f \x01(\v2\x1a.proto.daemon.v1.EpicAuthsR\tepicAuths\"E\n" +
+	"epic_auths\x18\f \x01(\v2\x1a.proto.daemon.v1.EpicAuthsR\tepicAuths\x12d\n" +
+	"\x15discovery_information\x18\r \x03(\v2/.proto.daemon.v1.Path.DiscoveryInformationEntryR\x14discoveryInformation\x1an\n" +
+	"\x19DiscoveryInformationEntry\x12\x10\n" +
+	"\x03key\x18\x01 \x01(\x04R\x03key\x12;\n" +
+	"\x05value\x18\x02 \x01(\v2%.proto.daemon.v1.DiscoveryInformationR\x05value:\x028\x01\"\x92\x01\n" +
+	"\x14DiscoveryInformation\x12:\n" +
+	"\x19control_service_addresses\x18\x01 \x03(\tR\x17controlServiceAddresses\x12>\n" +
+	"\x1bdiscovery_service_addresses\x18\x02 \x03(\tR\x19discoveryServiceAddresses\"E\n" +
 	"\tEpicAuths\x12\x1b\n" +
 	"\tauth_phvf\x18\x01 \x01(\fR\bauthPhvf\x12\x1b\n" +
 	"\tauth_lhvf\x18\x02 \x01(\fR\bauthLhvf\"6\n" +
@@ -1629,91 +1696,95 @@ func file_proto_daemon_v1_daemon_proto_rawDescGZIP() []byte {
 }
 
 var file_proto_daemon_v1_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_proto_daemon_v1_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 27)
+var file_proto_daemon_v1_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 29)
 var file_proto_daemon_v1_daemon_proto_goTypes = []any{
 	(LinkType)(0),                       // 0: proto.daemon.v1.LinkType
 	(*PathsRequest)(nil),                // 1: proto.daemon.v1.PathsRequest
 	(*PathsResponse)(nil),               // 2: proto.daemon.v1.PathsResponse
 	(*Path)(nil),                        // 3: proto.daemon.v1.Path
-	(*EpicAuths)(nil),                   // 4: proto.daemon.v1.EpicAuths
-	(*PathInterface)(nil),               // 5: proto.daemon.v1.PathInterface
-	(*GeoCoordinates)(nil),              // 6: proto.daemon.v1.GeoCoordinates
-	(*ASRequest)(nil),                   // 7: proto.daemon.v1.ASRequest
-	(*ASResponse)(nil),                  // 8: proto.daemon.v1.ASResponse
-	(*InterfacesRequest)(nil),           // 9: proto.daemon.v1.InterfacesRequest
-	(*InterfacesResponse)(nil),          // 10: proto.daemon.v1.InterfacesResponse
-	(*Interface)(nil),                   // 11: proto.daemon.v1.Interface
-	(*ServicesRequest)(nil),             // 12: proto.daemon.v1.ServicesRequest
-	(*ServicesResponse)(nil),            // 13: proto.daemon.v1.ServicesResponse
-	(*ListService)(nil),                 // 14: proto.daemon.v1.ListService
-	(*Service)(nil),                     // 15: proto.daemon.v1.Service
-	(*Underlay)(nil),                    // 16: proto.daemon.v1.Underlay
-	(*NotifyInterfaceDownRequest)(nil),  // 17: proto.daemon.v1.NotifyInterfaceDownRequest
-	(*NotifyInterfaceDownResponse)(nil), // 18: proto.daemon.v1.NotifyInterfaceDownResponse
-	(*PortRangeResponse)(nil),           // 19: proto.daemon.v1.PortRangeResponse
-	(*DRKeyHostASRequest)(nil),          // 20: proto.daemon.v1.DRKeyHostASRequest
-	(*DRKeyHostASResponse)(nil),         // 21: proto.daemon.v1.DRKeyHostASResponse
-	(*DRKeyASHostRequest)(nil),          // 22: proto.daemon.v1.DRKeyASHostRequest
-	(*DRKeyASHostResponse)(nil),         // 23: proto.daemon.v1.DRKeyASHostResponse
-	(*DRKeyHostHostRequest)(nil),        // 24: proto.daemon.v1.DRKeyHostHostRequest
-	(*DRKeyHostHostResponse)(nil),       // 25: proto.daemon.v1.DRKeyHostHostResponse
-	nil,                                 // 26: proto.daemon.v1.InterfacesResponse.InterfacesEntry
-	nil,                                 // 27: proto.daemon.v1.ServicesResponse.ServicesEntry
-	(*timestamppb.Timestamp)(nil),       // 28: google.protobuf.Timestamp
-	(*durationpb.Duration)(nil),         // 29: google.protobuf.Duration
-	(drkey.Protocol)(0),                 // 30: proto.drkey.v1.Protocol
-	(*emptypb.Empty)(nil),               // 31: google.protobuf.Empty
+	(*DiscoveryInformation)(nil),        // 4: proto.daemon.v1.DiscoveryInformation
+	(*EpicAuths)(nil),                   // 5: proto.daemon.v1.EpicAuths
+	(*PathInterface)(nil),               // 6: proto.daemon.v1.PathInterface
+	(*GeoCoordinates)(nil),              // 7: proto.daemon.v1.GeoCoordinates
+	(*ASRequest)(nil),                   // 8: proto.daemon.v1.ASRequest
+	(*ASResponse)(nil),                  // 9: proto.daemon.v1.ASResponse
+	(*InterfacesRequest)(nil),           // 10: proto.daemon.v1.InterfacesRequest
+	(*InterfacesResponse)(nil),          // 11: proto.daemon.v1.InterfacesResponse
+	(*Interface)(nil),                   // 12: proto.daemon.v1.Interface
+	(*ServicesRequest)(nil),             // 13: proto.daemon.v1.ServicesRequest
+	(*ServicesResponse)(nil),            // 14: proto.daemon.v1.ServicesResponse
+	(*ListService)(nil),                 // 15: proto.daemon.v1.ListService
+	(*Service)(nil),                     // 16: proto.daemon.v1.Service
+	(*Underlay)(nil),                    // 17: proto.daemon.v1.Underlay
+	(*NotifyInterfaceDownRequest)(nil),  // 18: proto.daemon.v1.NotifyInterfaceDownRequest
+	(*NotifyInterfaceDownResponse)(nil), // 19: proto.daemon.v1.NotifyInterfaceDownResponse
+	(*PortRangeResponse)(nil),           // 20: proto.daemon.v1.PortRangeResponse
+	(*DRKeyHostASRequest)(nil),          // 21: proto.daemon.v1.DRKeyHostASRequest
+	(*DRKeyHostASResponse)(nil),         // 22: proto.daemon.v1.DRKeyHostASResponse
+	(*DRKeyASHostRequest)(nil),          // 23: proto.daemon.v1.DRKeyASHostRequest
+	(*DRKeyASHostResponse)(nil),         // 24: proto.daemon.v1.DRKeyASHostResponse
+	(*DRKeyHostHostRequest)(nil),        // 25: proto.daemon.v1.DRKeyHostHostRequest
+	(*DRKeyHostHostResponse)(nil),       // 26: proto.daemon.v1.DRKeyHostHostResponse
+	nil,                                 // 27: proto.daemon.v1.Path.DiscoveryInformationEntry
+	nil,                                 // 28: proto.daemon.v1.InterfacesResponse.InterfacesEntry
+	nil,                                 // 29: proto.daemon.v1.ServicesResponse.ServicesEntry
+	(*timestamppb.Timestamp)(nil),       // 30: google.protobuf.Timestamp
+	(*durationpb.Duration)(nil),         // 31: google.protobuf.Duration
+	(drkey.Protocol)(0),                 // 32: proto.drkey.v1.Protocol
+	(*emptypb.Empty)(nil),               // 33: google.protobuf.Empty
 }
 var file_proto_daemon_v1_daemon_proto_depIdxs = []int32{
 	3,  // 0: proto.daemon.v1.PathsResponse.paths:type_name -> proto.daemon.v1.Path
-	11, // 1: proto.daemon.v1.Path.interface:type_name -> proto.daemon.v1.Interface
-	5,  // 2: proto.daemon.v1.Path.interfaces:type_name -> proto.daemon.v1.PathInterface
-	28, // 3: proto.daemon.v1.Path.expiration:type_name -> google.protobuf.Timestamp
-	29, // 4: proto.daemon.v1.Path.latency:type_name -> google.protobuf.Duration
-	6,  // 5: proto.daemon.v1.Path.geo:type_name -> proto.daemon.v1.GeoCoordinates
+	12, // 1: proto.daemon.v1.Path.interface:type_name -> proto.daemon.v1.Interface
+	6,  // 2: proto.daemon.v1.Path.interfaces:type_name -> proto.daemon.v1.PathInterface
+	30, // 3: proto.daemon.v1.Path.expiration:type_name -> google.protobuf.Timestamp
+	31, // 4: proto.daemon.v1.Path.latency:type_name -> google.protobuf.Duration
+	7,  // 5: proto.daemon.v1.Path.geo:type_name -> proto.daemon.v1.GeoCoordinates
 	0,  // 6: proto.daemon.v1.Path.link_type:type_name -> proto.daemon.v1.LinkType
-	4,  // 7: proto.daemon.v1.Path.epic_auths:type_name -> proto.daemon.v1.EpicAuths
-	26, // 8: proto.daemon.v1.InterfacesResponse.interfaces:type_name -> proto.daemon.v1.InterfacesResponse.InterfacesEntry
-	16, // 9: proto.daemon.v1.Interface.address:type_name -> proto.daemon.v1.Underlay
-	27, // 10: proto.daemon.v1.ServicesResponse.services:type_name -> proto.daemon.v1.ServicesResponse.ServicesEntry
-	15, // 11: proto.daemon.v1.ListService.services:type_name -> proto.daemon.v1.Service
-	28, // 12: proto.daemon.v1.DRKeyHostASRequest.val_time:type_name -> google.protobuf.Timestamp
-	30, // 13: proto.daemon.v1.DRKeyHostASRequest.protocol_id:type_name -> proto.drkey.v1.Protocol
-	28, // 14: proto.daemon.v1.DRKeyHostASResponse.epoch_begin:type_name -> google.protobuf.Timestamp
-	28, // 15: proto.daemon.v1.DRKeyHostASResponse.epoch_end:type_name -> google.protobuf.Timestamp
-	28, // 16: proto.daemon.v1.DRKeyASHostRequest.val_time:type_name -> google.protobuf.Timestamp
-	30, // 17: proto.daemon.v1.DRKeyASHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol
-	28, // 18: proto.daemon.v1.DRKeyASHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp
-	28, // 19: proto.daemon.v1.DRKeyASHostResponse.epoch_end:type_name -> google.protobuf.Timestamp
-	28, // 20: proto.daemon.v1.DRKeyHostHostRequest.val_time:type_name -> google.protobuf.Timestamp
-	30, // 21: proto.daemon.v1.DRKeyHostHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol
-	28, // 22: proto.daemon.v1.DRKeyHostHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp
-	28, // 23: proto.daemon.v1.DRKeyHostHostResponse.epoch_end:type_name -> google.protobuf.Timestamp
-	11, // 24: proto.daemon.v1.InterfacesResponse.InterfacesEntry.value:type_name -> proto.daemon.v1.Interface
-	14, // 25: proto.daemon.v1.ServicesResponse.ServicesEntry.value:type_name -> proto.daemon.v1.ListService
-	1,  // 26: proto.daemon.v1.DaemonService.Paths:input_type -> proto.daemon.v1.PathsRequest
-	7,  // 27: proto.daemon.v1.DaemonService.AS:input_type -> proto.daemon.v1.ASRequest
-	9,  // 28: proto.daemon.v1.DaemonService.Interfaces:input_type -> proto.daemon.v1.InterfacesRequest
-	12, // 29: proto.daemon.v1.DaemonService.Services:input_type -> proto.daemon.v1.ServicesRequest
-	17, // 30: proto.daemon.v1.DaemonService.NotifyInterfaceDown:input_type -> proto.daemon.v1.NotifyInterfaceDownRequest
-	31, // 31: proto.daemon.v1.DaemonService.PortRange:input_type -> google.protobuf.Empty
-	22, // 32: proto.daemon.v1.DaemonService.DRKeyASHost:input_type -> proto.daemon.v1.DRKeyASHostRequest
-	20, // 33: proto.daemon.v1.DaemonService.DRKeyHostAS:input_type -> proto.daemon.v1.DRKeyHostASRequest
-	24, // 34: proto.daemon.v1.DaemonService.DRKeyHostHost:input_type -> proto.daemon.v1.DRKeyHostHostRequest
-	2,  // 35: proto.daemon.v1.DaemonService.Paths:output_type -> proto.daemon.v1.PathsResponse
-	8,  // 36: proto.daemon.v1.DaemonService.AS:output_type -> proto.daemon.v1.ASResponse
-	10, // 37: proto.daemon.v1.DaemonService.Interfaces:output_type -> proto.daemon.v1.InterfacesResponse
-	13, // 38: proto.daemon.v1.DaemonService.Services:output_type -> proto.daemon.v1.ServicesResponse
-	18, // 39: proto.daemon.v1.DaemonService.NotifyInterfaceDown:output_type -> proto.daemon.v1.NotifyInterfaceDownResponse
-	19, // 40: proto.daemon.v1.DaemonService.PortRange:output_type -> proto.daemon.v1.PortRangeResponse
-	23, // 41: proto.daemon.v1.DaemonService.DRKeyASHost:output_type -> proto.daemon.v1.DRKeyASHostResponse
-	21, // 42: proto.daemon.v1.DaemonService.DRKeyHostAS:output_type -> proto.daemon.v1.DRKeyHostASResponse
-	25, // 43: proto.daemon.v1.DaemonService.DRKeyHostHost:output_type -> proto.daemon.v1.DRKeyHostHostResponse
-	35, // [35:44] is the sub-list for method output_type
-	26, // [26:35] is the sub-list for method input_type
-	26, // [26:26] is the sub-list for extension type_name
-	26, // [26:26] is the sub-list for extension extendee
-	0,  // [0:26] is the sub-list for field type_name
+	5,  // 7: proto.daemon.v1.Path.epic_auths:type_name -> proto.daemon.v1.EpicAuths
+	27, // 8: proto.daemon.v1.Path.discovery_information:type_name -> proto.daemon.v1.Path.DiscoveryInformationEntry
+	28, // 9: proto.daemon.v1.InterfacesResponse.interfaces:type_name -> proto.daemon.v1.InterfacesResponse.InterfacesEntry
+	17, // 10: proto.daemon.v1.Interface.address:type_name -> proto.daemon.v1.Underlay
+	29, // 11: proto.daemon.v1.ServicesResponse.services:type_name -> proto.daemon.v1.ServicesResponse.ServicesEntry
+	16, // 12: proto.daemon.v1.ListService.services:type_name -> proto.daemon.v1.Service
+	30, // 13: proto.daemon.v1.DRKeyHostASRequest.val_time:type_name -> google.protobuf.Timestamp
+	32, // 14: proto.daemon.v1.DRKeyHostASRequest.protocol_id:type_name -> proto.drkey.v1.Protocol
+	30, // 15: proto.daemon.v1.DRKeyHostASResponse.epoch_begin:type_name -> google.protobuf.Timestamp
+	30, // 16: proto.daemon.v1.DRKeyHostASResponse.epoch_end:type_name -> google.protobuf.Timestamp
+	30, // 17: proto.daemon.v1.DRKeyASHostRequest.val_time:type_name -> google.protobuf.Timestamp
+	32, // 18: proto.daemon.v1.DRKeyASHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol
+	30, // 19: proto.daemon.v1.DRKeyASHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp
+	30, // 20: proto.daemon.v1.DRKeyASHostResponse.epoch_end:type_name -> google.protobuf.Timestamp
+	30, // 21: proto.daemon.v1.DRKeyHostHostRequest.val_time:type_name -> google.protobuf.Timestamp
+	32, // 22: proto.daemon.v1.DRKeyHostHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol
+	30, // 23: proto.daemon.v1.DRKeyHostHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp
+	30, // 24: proto.daemon.v1.DRKeyHostHostResponse.epoch_end:type_name -> google.protobuf.Timestamp
+	4,  // 25: proto.daemon.v1.Path.DiscoveryInformationEntry.value:type_name -> proto.daemon.v1.DiscoveryInformation
+	12, // 26: proto.daemon.v1.InterfacesResponse.InterfacesEntry.value:type_name -> proto.daemon.v1.Interface
+	15, // 27: proto.daemon.v1.ServicesResponse.ServicesEntry.value:type_name -> proto.daemon.v1.ListService
+	1,  // 28: proto.daemon.v1.DaemonService.Paths:input_type -> proto.daemon.v1.PathsRequest
+	8,  // 29: proto.daemon.v1.DaemonService.AS:input_type -> proto.daemon.v1.ASRequest
+	10, // 30: proto.daemon.v1.DaemonService.Interfaces:input_type -> proto.daemon.v1.InterfacesRequest
+	13, // 31: proto.daemon.v1.DaemonService.Services:input_type -> proto.daemon.v1.ServicesRequest
+	18, // 32: proto.daemon.v1.DaemonService.NotifyInterfaceDown:input_type -> proto.daemon.v1.NotifyInterfaceDownRequest
+	33, // 33: proto.daemon.v1.DaemonService.PortRange:input_type -> google.protobuf.Empty
+	23, // 34: proto.daemon.v1.DaemonService.DRKeyASHost:input_type -> proto.daemon.v1.DRKeyASHostRequest
+	21, // 35: proto.daemon.v1.DaemonService.DRKeyHostAS:input_type -> proto.daemon.v1.DRKeyHostASRequest
+	25, // 36: proto.daemon.v1.DaemonService.DRKeyHostHost:input_type -> proto.daemon.v1.DRKeyHostHostRequest
+	2,  // 37: proto.daemon.v1.DaemonService.Paths:output_type -> proto.daemon.v1.PathsResponse
+	9,  // 38: proto.daemon.v1.DaemonService.AS:output_type -> proto.daemon.v1.ASResponse
+	11, // 39: proto.daemon.v1.DaemonService.Interfaces:output_type -> proto.daemon.v1.InterfacesResponse
+	14, // 40: proto.daemon.v1.DaemonService.Services:output_type -> proto.daemon.v1.ServicesResponse
+	19, // 41: proto.daemon.v1.DaemonService.NotifyInterfaceDown:output_type -> proto.daemon.v1.NotifyInterfaceDownResponse
+	20, // 42: proto.daemon.v1.DaemonService.PortRange:output_type -> proto.daemon.v1.PortRangeResponse
+	24, // 43: proto.daemon.v1.DaemonService.DRKeyASHost:output_type -> proto.daemon.v1.DRKeyASHostResponse
+	22, // 44: proto.daemon.v1.DaemonService.DRKeyHostAS:output_type -> proto.daemon.v1.DRKeyHostASResponse
+	26, // 45: proto.daemon.v1.DaemonService.DRKeyHostHost:output_type -> proto.daemon.v1.DRKeyHostHostResponse
+	37, // [37:46] is the sub-list for method output_type
+	28, // [28:37] is the sub-list for method input_type
+	28, // [28:28] is the sub-list for extension type_name
+	28, // [28:28] is the sub-list for extension extendee
+	0,  // [0:28] is the sub-list for field type_name
 }
 
 func init() { file_proto_daemon_v1_daemon_proto_init() }
@@ -1727,7 +1798,7 @@ func file_proto_daemon_v1_daemon_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_daemon_v1_daemon_proto_rawDesc), len(file_proto_daemon_v1_daemon_proto_rawDesc)),
 			NumEnums:      1,
-			NumMessages:   27,
+			NumMessages:   29,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
diff --git a/pkg/segment/BUILD.bazel b/pkg/segment/BUILD.bazel
index 002ede57f..a53ffede5 100644
--- a/pkg/segment/BUILD.bazel
+++ b/pkg/segment/BUILD.bazel
@@ -24,6 +24,7 @@ go_library(
         "//pkg/proto/crypto:go_default_library",
         "//pkg/scrypto/signed:go_default_library",
         "//pkg/segment/extensions/digest:go_default_library",
+        "//pkg/segment/extensions/discovery:go_default_library",
         "//pkg/segment/extensions/epic:go_default_library",
         "//pkg/segment/extensions/staticinfo:go_default_library",
         "//pkg/slayers/path:go_default_library",
@@ -34,7 +35,6 @@ go_library(
 go_test(
     name = "go_default_test",
     srcs = [
-        "export_test.go",
         "extensions_test.go",
         "seg_test.go",
         "segs_test.go",
@@ -44,7 +44,6 @@ go_test(
     deps = [
         "//pkg/addr:go_default_library",
         "//pkg/private/serrors:go_default_library",
-        "//pkg/proto/control_plane:go_default_library",
         "//pkg/proto/crypto:go_default_library",
         "//pkg/scrypto/signed:go_default_library",
         "//pkg/segment/extensions/digest:go_default_library",
diff --git a/pkg/segment/as.go b/pkg/segment/as.go
index 7394c506e..c5356ec0f 100644
--- a/pkg/segment/as.go
+++ b/pkg/segment/as.go
@@ -86,7 +86,10 @@ func ASEntryFromPB(pb *cppb.ASEntry) (ASEntry, error) {
 		peerEntries = append(peerEntries, peerEntry)
 	}
 
-	extensions := extensionsFromPB(entry.Extensions)
+	extensions, err := extensionsFromPB(entry.Extensions)
+	if err != nil {
+		return ASEntry{}, err
+	}
 	unsignedExtensions := UnsignedExtensionsFromPB(pb.Unsigned)
 
 	return ASEntry{
diff --git a/pkg/segment/extensions.go b/pkg/segment/extensions.go
index 6e8c67912..7bc10c8c2 100644
--- a/pkg/segment/extensions.go
+++ b/pkg/segment/extensions.go
@@ -17,30 +17,37 @@ package segment
 import (
 	cppb "github.com/scionproto/scion/pkg/proto/control_plane"
 	"github.com/scionproto/scion/pkg/segment/extensions/digest"
+	"github.com/scionproto/scion/pkg/segment/extensions/discovery"
 	"github.com/scionproto/scion/pkg/segment/extensions/staticinfo"
 )
 
 type Extensions struct {
 	HiddenPath HiddenPathExtension
 	StaticInfo *staticinfo.Extension
+	Discovery  *discovery.Extension
 	Digests    *digest.Extension
 }
 
-func extensionsFromPB(pb *cppb.PathSegmentExtensions) Extensions {
+func extensionsFromPB(pb *cppb.PathSegmentExtensions) (Extensions, error) {
 	if pb == nil {
-		return Extensions{}
+		return Extensions{}, nil
 	}
 
 	hiddenPath := HiddenPathExtension{
 		IsHidden: pb.HiddenPath != nil && pb.HiddenPath.IsHidden,
 	}
 	staticInfo := staticinfo.FromPB(pb.StaticInfo)
+	discovery, err := discovery.FromPB(pb.Discovery)
+	if err != nil {
+		return Extensions{}, err
+	}
 	digest := digest.ExtensionFromPB(pb.Digests)
 	return Extensions{
 		HiddenPath: hiddenPath,
 		StaticInfo: staticInfo,
+		Discovery:  discovery,
 		Digests:    digest,
-	}
+	}, nil
 }
 
 func extensionsToPB(ext Extensions) *cppb.PathSegmentExtensions {
@@ -49,12 +56,14 @@ func extensionsToPB(ext Extensions) *cppb.PathSegmentExtensions {
 		hiddenPath = &cppb.HiddenPathExtension{IsHidden: true}
 	}
 	staticInfo := staticinfo.ToPB(ext.StaticInfo)
+	discovery := discovery.ToPB(ext.Discovery)
 	digest := digest.ExtensionToPB(ext.Digests)
 
-	if hiddenPath != nil || staticInfo != nil || digest != nil {
+	if hiddenPath != nil || staticInfo != nil || discovery != nil || digest != nil {
 		return &cppb.PathSegmentExtensions{
 			HiddenPath: hiddenPath,
 			StaticInfo: staticInfo,
+			Discovery:  discovery,
 			Digests:    digest,
 		}
 	}
diff --git a/pkg/segment/extensions/discovery/BUILD.bazel b/pkg/segment/extensions/discovery/BUILD.bazel
new file mode 100644
index 000000000..156a68d47
--- /dev/null
+++ b/pkg/segment/extensions/discovery/BUILD.bazel
@@ -0,0 +1,12 @@
+load("@rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["discovery.go"],
+    importpath = "github.com/scionproto/scion/pkg/segment/extensions/discovery",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//pkg/proto/control_plane:go_default_library",
+        "//pkg/slices:go_default_library",
+    ],
+)
diff --git a/pkg/segment/extensions/discovery/discovery.go b/pkg/segment/extensions/discovery/discovery.go
new file mode 100644
index 000000000..08975cf84
--- /dev/null
+++ b/pkg/segment/extensions/discovery/discovery.go
@@ -0,0 +1,73 @@
+// Copyright 2025 Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package discovery
+
+import (
+	"errors"
+	"net/netip"
+
+	cppb "github.com/scionproto/scion/pkg/proto/control_plane"
+	"github.com/scionproto/scion/pkg/slices"
+)
+
+// Extension is the discovery extension for SCION segments.
+type Extension struct {
+	ControlServices   []netip.AddrPort
+	DiscoveryServices []netip.AddrPort
+}
+
+func FromPB(pb *cppb.DiscoveryExtension) (*Extension, error) {
+	if pb == nil {
+		return nil, nil
+	}
+	var cses, dses []netip.AddrPort
+	var parseErrors []error
+	for _, a := range pb.ControlServiceAddresses {
+		cs, err := netip.ParseAddrPort(a)
+		if err == nil {
+			cses = append(cses, cs)
+			continue
+		}
+		parseErrors = append(parseErrors, err)
+	}
+	for _, a := range pb.DiscoveryServiceAddresses {
+		ds, err := netip.ParseAddrPort(a)
+		if err == nil {
+			dses = append(dses, ds)
+			continue
+		}
+		parseErrors = append(parseErrors, err)
+	}
+	if (len(pb.ControlServiceAddresses) > 0 && len(cses) == 0) ||
+		(len(pb.DiscoveryServiceAddresses) > 0 && len(dses) == 0) {
+		// If there are addresses in the pb, but none could be parsed
+		// successfully, we return all parsing errors.
+		return nil, errors.Join(parseErrors...)
+	}
+	return &Extension{
+		ControlServices:   cses,
+		DiscoveryServices: dses,
+	}, nil
+}
+
+func ToPB(ext *Extension) *cppb.DiscoveryExtension {
+	if ext == nil {
+		return nil
+	}
+	return &cppb.DiscoveryExtension{
+		ControlServiceAddresses:   slices.Transform(ext.ControlServices, netip.AddrPort.String),
+		DiscoveryServiceAddresses: slices.Transform(ext.DiscoveryServices, netip.AddrPort.String),
+	}
+}
diff --git a/pkg/slices/BUILD.bazel b/pkg/slices/BUILD.bazel
new file mode 100644
index 000000000..f5beed85b
--- /dev/null
+++ b/pkg/slices/BUILD.bazel
@@ -0,0 +1,18 @@
+load("@rules_go//go:def.bzl", "go_library")
+load("//tools:go.bzl", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["transform.go"],
+    importpath = "github.com/scionproto/scion/pkg/slices",
+    visibility = ["//visibility:public"],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["transform_test.go"],
+    deps = [
+        ":go_default_library",
+        "@com_github_stretchr_testify//assert:go_default_library",
+    ],
+)
diff --git a/pkg/snet/addrutil/BUILD.bazel b/pkg/snet/addrutil/BUILD.bazel
index d3ab77c20..ef0f6fbfc 100644
--- a/pkg/snet/addrutil/BUILD.bazel
+++ b/pkg/snet/addrutil/BUILD.bazel
@@ -1,4 +1,5 @@
 load("@rules_go//go:def.bzl", "go_library")
+load("//tools:go.bzl", "go_test")
 
 go_library(
     name = "go_default_library",
@@ -16,3 +17,17 @@ go_library(
         "//pkg/snet/path:go_default_library",
     ],
 )
+
+go_test(
+    name = "go_default_test",
+    srcs = ["addrutil_test.go"],
+    deps = [
+        ":go_default_library",
+        "//pkg/addr:go_default_library",
+        "//pkg/snet:go_default_library",
+        "//pkg/snet/mock_snet:go_default_library",
+        "//pkg/snet/path:go_default_library",
+        "@com_github_golang_mock//gomock:go_default_library",
+        "@com_github_stretchr_testify//assert:go_default_library",
+    ],
+)
diff --git a/pkg/snet/addrutil/addrutil.go b/pkg/snet/addrutil/addrutil.go
index c72508716..3df167384 100644
--- a/pkg/snet/addrutil/addrutil.go
+++ b/pkg/snet/addrutil/addrutil.go
@@ -17,6 +17,7 @@ package addrutil
 import (
 	"context"
 	"encoding/binary"
+	"math/rand/v2"
 	"net"
 
 	"github.com/scionproto/scion/pkg/addr"
@@ -38,8 +39,13 @@ type Pather struct {
 	}
 }
 
-// GetPath computes the remote address with a path based on the provided segment.
-func (p Pather) GetPath(svc addr.SVC, ps *seg.PathSegment) (*snet.SVCAddr, error) {
+// GetPath computes the remote address with a path based on the provided
+// segment. If the provided path segment contains discovery information, it will
+// use a random available control or discovery service address based on the
+// provided service type (addr.SvcCS or addr.SvcDS). If no discovery information
+// is available, it will return a SVC address with the destination IA and the
+// path's underlay next hop.
+func (p Pather) GetPath(svc addr.SVC, ps *seg.PathSegment) (net.Addr, error) {
 	if len(ps.ASEntries) == 0 {
 		return nil, serrors.New("empty path")
 	}
@@ -87,13 +93,35 @@ func (p Pather) GetPath(svc addr.SVC, ps *seg.PathSegment) (*snet.SVCAddr, error
 	if nextHop == nil {
 		return nil, serrors.New("first-hop border router not found", "intf_id", ifID)
 	}
-	return &snet.SVCAddr{
-		IA:      ps.FirstIA(),
-		Path:    path,
-		NextHop: nextHop,
-		SVC:     svc,
-	}, nil
-
+	switch disco := ps.ASEntries[0].Extensions.Discovery; {
+	case disco != nil && svc == addr.SvcCS && len(disco.ControlServices) > 0:
+		// take any control service address
+		return &snet.UDPAddr{
+			IA:      ps.FirstIA(),
+			Path:    path,
+			NextHop: nextHop,
+			Host: net.UDPAddrFromAddrPort(
+				disco.ControlServices[rand.IntN(len(disco.ControlServices))],
+			),
+		}, nil
+	case disco != nil && svc == addr.SvcDS && len(disco.DiscoveryServices) > 0:
+		// take any discovery service address
+		return &snet.UDPAddr{
+			IA:      ps.FirstIA(),
+			Path:    path,
+			NextHop: nextHop,
+			Host: net.UDPAddrFromAddrPort(
+				disco.DiscoveryServices[rand.IntN(len(disco.DiscoveryServices))],
+			),
+		}, nil
+	default:
+		return &snet.SVCAddr{
+			IA:      ps.FirstIA(),
+			Path:    path,
+			NextHop: nextHop,
+			SVC:     svc,
+		}, nil
+	}
 }
 
 // TopoQuerier can be used to get topology information from the SCION Daemon.
@@ -131,3 +159,55 @@ func ResolveLocal(dst net.IP) (net.IP, error) {
 	srcIP := udpConn.LocalAddr().(*net.UDPAddr).IP
 	return srcIP, nil
 }
+
+// ExtractDestinationServiceAddress extracts the destination service address
+// from the provided path. If the path contains discovery information, it will
+// use a random available control or discovery service address based on the
+// provided service type (addr.SvcCS or addr.SvcDS). If no discovery information
+// is available, it will return a SVC address with the destination IA and the
+// path's underlay next hop.
+// The caller must ensure that the path is not nil.
+func ExtractDestinationServiceAddress(a addr.SVC, path snet.Path) net.Addr {
+	if path == nil {
+		panic("path is nil")
+	}
+
+	destination := path.Destination()
+	if metadata := path.Metadata(); metadata != nil {
+		disco, hasDiscoveryInfo := metadata.DiscoveryInformation[destination]
+		switch {
+		case a == addr.SvcCS && hasDiscoveryInfo && len(disco.ControlServices) > 0:
+			// Use any control service if available
+			cs := disco.ControlServices[rand.IntN(len(disco.ControlServices))]
+			ret := &snet.UDPAddr{
+				IA:      destination,
+				Path:    path.Dataplane(),
+				NextHop: path.UnderlayNextHop(),
+				Host: &net.UDPAddr{
+					IP:   cs.Addr().AsSlice(),
+					Port: int(cs.Port()),
+				},
+			}
+			return ret
+		case a == addr.SvcDS && hasDiscoveryInfo && len(disco.DiscoveryServices) > 0:
+			// Use any discovery service if available
+			ds := disco.DiscoveryServices[rand.IntN(len(disco.DiscoveryServices))]
+			ret := &snet.UDPAddr{
+				IA:      destination,
+				Path:    path.Dataplane(),
+				NextHop: path.UnderlayNextHop(),
+				Host: &net.UDPAddr{
+					IP:   ds.Addr().AsSlice(),
+					Port: int(ds.Port()),
+				},
+			}
+			return ret
+		}
+	}
+	return &snet.SVCAddr{
+		IA:      destination,
+		Path:    path.Dataplane(),
+		NextHop: path.UnderlayNextHop(),
+		SVC:     a,
+	}
+}
diff --git a/pkg/snet/path.go b/pkg/snet/path.go
index 9fcd36723..d0b5500ee 100644
--- a/pkg/snet/path.go
+++ b/pkg/snet/path.go
@@ -18,7 +18,9 @@ import (
 	"crypto/sha256"
 	"encoding/binary"
 	"fmt"
+	"maps"
 	"net"
+	"net/netip"
 	"time"
 
 	"github.com/scionproto/scion/pkg/addr"
@@ -140,6 +142,12 @@ type PathMetadata struct {
 
 	// EpicAuths contains the EPIC authenticators.
 	EpicAuths EpicAuths
+
+	DiscoveryInformation map[addr.IA]DiscoveryInformation
+}
+type DiscoveryInformation struct {
+	ControlServices   []netip.AddrPort
+	DiscoveryServices []netip.AddrPort
 }
 
 func (pm *PathMetadata) Copy() *PathMetadata {
@@ -161,6 +169,7 @@ func (pm *PathMetadata) Copy() *PathMetadata {
 			AuthPHVF: append([]byte(nil), pm.EpicAuths.AuthPHVF...),
 			AuthLHVF: append([]byte(nil), pm.EpicAuths.AuthLHVF...),
 		},
+		DiscoveryInformation: maps.Clone(pm.DiscoveryInformation),
 	}
 }
 
diff --git a/private/path/combinator/graph.go b/private/path/combinator/graph.go
index 242c71f59..0146ae2ae 100644
--- a/private/path/combinator/graph.go
+++ b/private/path/combinator/graph.go
@@ -445,15 +445,16 @@ func (solution *pathSolution) Path(hashState hashState) Path {
 	path := Path{
 		SCIONPath: segments.ScionPath(),
 		Metadata: snet.PathMetadata{
-			Interfaces:   interfaces,
-			MTU:          mtu,
-			Expiry:       segments.ComputeExpTime(),
-			Latency:      staticInfo.Latency,
-			Bandwidth:    staticInfo.Bandwidth,
-			Geo:          staticInfo.Geo,
-			LinkType:     staticInfo.LinkType,
-			InternalHops: staticInfo.InternalHops,
-			Notes:        staticInfo.Notes,
+			Interfaces:           interfaces,
+			MTU:                  mtu,
+			Expiry:               segments.ComputeExpTime(),
+			Latency:              staticInfo.Latency,
+			Bandwidth:            staticInfo.Bandwidth,
+			Geo:                  staticInfo.Geo,
+			LinkType:             staticInfo.LinkType,
+			InternalHops:         staticInfo.InternalHops,
+			Notes:                staticInfo.Notes,
+			DiscoveryInformation: staticInfo.DiscoveryInformation,
 		},
 		Weight:      solution.cost,
 		Fingerprint: fingerprint(interfaces, hashState),
diff --git a/private/path/combinator/staticinfo_accumulator.go b/private/path/combinator/staticinfo_accumulator.go
index df9132872..1fba36d5e 100644
--- a/private/path/combinator/staticinfo_accumulator.go
+++ b/private/path/combinator/staticinfo_accumulator.go
@@ -16,6 +16,7 @@ package combinator
 
 import (
 	"fmt"
+	"slices"
 	"strings"
 	"time"
 
@@ -63,12 +64,13 @@ func collectMetadata(interfaces []snet.PathInterface, asEntries []seg.ASEntry) s
 
 	path := pathInfo{interfaces, asEntries, remoteIF}
 	return snet.PathMetadata{
-		Latency:      collectLatency(path),
-		Bandwidth:    collectBandwidth(path),
-		Geo:          collectGeo(path),
-		LinkType:     collectLinkType(path),
-		InternalHops: collectInternalHops(path),
-		Notes:        collectNotes(path),
+		Latency:              collectLatency(path),
+		Bandwidth:            collectBandwidth(path),
+		Geo:                  collectGeo(path),
+		LinkType:             collectLinkType(path),
+		InternalHops:         collectInternalHops(path),
+		Notes:                collectNotes(path),
+		DiscoveryInformation: collectDiscoverInformation(asEntries),
 	}
 }
 
@@ -347,6 +349,42 @@ func deduplicateStrings(elements []string) []string {
 	return result
 }
 
+func collectDiscoverInformation(asEntries []seg.ASEntry) map[addr.IA]snet.DiscoveryInformation {
+	// Collect the discovery information for each AS.
+	discoveryInfo := make(map[addr.IA]snet.DiscoveryInformation)
+	for _, asEntry := range asEntries {
+		disco := asEntry.Extensions.Discovery
+		if disco == nil {
+			continue
+		}
+		ia := asEntry.Local
+
+		// if the entry already exists we merge the information
+		if info, exists := discoveryInfo[ia]; exists {
+			for _, cs := range disco.ControlServices {
+				if !slices.Contains(info.ControlServices, cs) {
+					info.ControlServices = append(info.ControlServices, cs)
+				}
+			}
+			for _, ds := range disco.DiscoveryServices {
+				if !slices.Contains(info.DiscoveryServices, ds) {
+					info.DiscoveryServices = append(info.DiscoveryServices, ds)
+				}
+			}
+			discoveryInfo[ia] = info
+		} else {
+			discoveryInfo[ia] = snet.DiscoveryInformation{
+				ControlServices:   disco.ControlServices,
+				DiscoveryServices: disco.DiscoveryServices,
+			}
+		}
+	}
+	if len(discoveryInfo) == 0 {
+		return nil
+	}
+	return discoveryInfo
+}
+
 // hopKey is a map key for looking up information about a hop, a pair of
 // snet.PathInterface.
 type hopKey struct {
diff --git a/private/trust/BUILD.bazel b/private/trust/BUILD.bazel
index c15987db0..fdb69cb37 100644
--- a/private/trust/BUILD.bazel
+++ b/private/trust/BUILD.bazel
@@ -35,6 +35,7 @@ go_library(
         "//pkg/scrypto/cppki:go_default_library",
         "//pkg/scrypto/signed:go_default_library",
         "//pkg/snet:go_default_library",
+        "//pkg/snet/addrutil:go_default_library",
         "//private/tracing:go_default_library",
         "//private/trust/internal/metrics:go_default_library",
         "@com_github_opentracing_opentracing_go//:go_default_library",
diff --git a/private/trust/router.go b/private/trust/router.go
index 392813769..052b2c134 100644
--- a/private/trust/router.go
+++ b/private/trust/router.go
@@ -24,6 +24,7 @@ import (
 	"github.com/scionproto/scion/pkg/private/serrors"
 	"github.com/scionproto/scion/pkg/scrypto/cppki"
 	"github.com/scionproto/scion/pkg/snet"
+	"github.com/scionproto/scion/pkg/snet/addrutil"
 )
 
 // Router builds the CS address for crypto material with the subject in a given ISD.
@@ -72,12 +73,7 @@ func (r AuthRouter) ChooseServer(ctx context.Context, subjectISD addr.ISD) (net.
 	if err != nil || path == nil {
 		return nil, serrors.Wrap("unable to find path to any core AS", err, "isd", dstISD)
 	}
-	ret := &snet.SVCAddr{
-		IA:      path.Destination(),
-		Path:    path.Dataplane(),
-		NextHop: path.UnderlayNextHop(),
-		SVC:     addr.SvcCS,
-	}
+	ret := addrutil.ExtractDestinationServiceAddress(addr.SvcCS, path)
 	return ret, nil
 }
 
diff --git a/proto/control_plane/v1/seg_extensions.proto b/proto/control_plane/v1/seg_extensions.proto
index 8ed4765a2..98ee30bc5 100644
--- a/proto/control_plane/v1/seg_extensions.proto
+++ b/proto/control_plane/v1/seg_extensions.proto
@@ -25,6 +25,8 @@ message PathSegmentExtensions {
     StaticInfoExtension static_info = 1;
     // Optional hidden path extension.
     HiddenPathExtension hidden_path = 2;
+    // Optional discovery extension.
+    DiscoveryExtension discovery = 3;
 
     // Optional digests of detached extensions.
     DigestExtension digests = 1000;
@@ -58,6 +60,22 @@ message StaticInfoExtension {
     string note = 6;
 }
 
+// DiscoveryExtension contains information about reachable endpoints in the AS.
+// It should at least contain the endpoints that are directly rechable over the
+// local interface on which the beacon is sent out on. Note that it can also,
+// but not necessarily does, contain endpoints that are reachable over other
+// interfaces in the AS.
+message DiscoveryExtension {
+    // Version of the discovery extension. This is set to 1 for now.
+    uint32 version = 1;
+    // The address of the control services for this AS. The format is ip:port
+    // or [ip]:port for IPv6 addresses.
+    repeated string control_service_addresses = 2;
+    // The address of the control services for this AS. The format is ip:port
+    // or [ip]:port for IPv6 addresses.
+    repeated string discovery_service_addresses = 3;
+}
+
 // LatencyInfo specifies approximate lower-bound latency values.
 // The separate intra and inter AS latency information allows to reconstruct
 // latency values for all paths (cross-over, shortcut, peering) based on this
diff --git a/proto/daemon/v1/daemon.proto b/proto/daemon/v1/daemon.proto
index fff158a11..e0d0a32ec 100644
--- a/proto/daemon/v1/daemon.proto
+++ b/proto/daemon/v1/daemon.proto
@@ -107,6 +107,19 @@ message Path {
     repeated string notes = 11;
     // EpicAuths contains the EPIC authenticators used to calculate the PHVF and LHVF.
     EpicAuths epic_auths = 12;
+    // DiscoveryInformation contains the discovery information of each ISD-AS on
+    // the path. Note that depending on the path, the information for a given
+    // ISD-AS might be differnt.
+    map<uint64, DiscoveryInformation> discovery_information =13;
+}
+
+message DiscoveryInformation {
+    // The control service addresses of an AS. Note that this might not be a
+    // complete view of all control services in the AS.
+    repeated string control_service_addresses = 1;
+    // The discovery sevice addresses of an AS. Note that this might not be a
+    // complete view of all discovery services in the AS.
+    repeated string discovery_service_addresses = 2;
 }
 
 message EpicAuths {
diff --git a/scion-pki/certs/renew.go b/scion-pki/certs/renew.go
index 2069ec1b0..d2edd54ba 100644
--- a/scion-pki/certs/renew.go
+++ b/scion-pki/certs/renew.go
@@ -708,12 +708,7 @@ func (r *renewer) requestRemote(
 			NextHop: path.UnderlayNextHop(),
 		}
 	case *snet.SVCAddr:
-		dst = &snet.SVCAddr{
-			IA:      ca,
-			SVC:     r.SVC,
-			Path:    path.Dataplane(),
-			NextHop: path.UnderlayNextHop(),
-		}
+		dst = addrutil.ExtractDestinationServiceAddress(addr.SvcCS, path)
 	default:
 		panic(fmt.Sprintf("unsupported remote address: %#v", remote))
 	}
diff --git a/scion/showpaths/BUILD.bazel b/scion/showpaths/BUILD.bazel
index 66697d0aa..892930288 100644
--- a/scion/showpaths/BUILD.bazel
+++ b/scion/showpaths/BUILD.bazel
@@ -13,6 +13,7 @@ go_library(
         "//pkg/daemon:go_default_library",
         "//pkg/private/serrors:go_default_library",
         "//pkg/segment/iface:go_default_library",
+        "//pkg/slices:go_default_library",
         "//pkg/snet:go_default_library",
         "//private/app/path:go_default_library",
         "//private/app/path/pathprobe:go_default_library",
diff --git a/scion/showpaths/showpaths.go b/scion/showpaths/showpaths.go
index 3006b8e69..2bdc39737 100644
--- a/scion/showpaths/showpaths.go
+++ b/scion/showpaths/showpaths.go
@@ -28,6 +28,7 @@ import (
 	"github.com/scionproto/scion/pkg/daemon"
 	"github.com/scionproto/scion/pkg/private/serrors"
 	"github.com/scionproto/scion/pkg/segment/iface"
+	"github.com/scionproto/scion/pkg/slices"
 	"github.com/scionproto/scion/pkg/snet"
 	"github.com/scionproto/scion/private/app/path"
 	"github.com/scionproto/scion/private/app/path/pathprobe"
@@ -43,17 +44,18 @@ type Result struct {
 
 // Path holds information about the discovered path.
 type Path struct {
-	FullPath    snet.Path       `json:"-" yaml:"-"`
-	Fingerprint string          `json:"fingerprint" yaml:"fingerprint"`
-	Hops        []Hop           `json:"hops" yaml:"hops"`
-	Sequence    string          `json:"sequence" yaml:"sequence"`
-	NextHop     string          `json:"next_hop" yaml:"next_hop"`
-	Expiry      time.Time       `json:"expiry" yaml:"expiry"`
-	MTU         uint16          `json:"mtu" yaml:"mtu"`
-	Latency     []time.Duration `json:"latency" yaml:"latency"`
-	Status      string          `json:"status,omitempty" yaml:"status,omitempty"`
-	StatusInfo  string          `json:"status_info,omitempty" yaml:"status_info,omitempty"`
-	Local       netip.Addr      `json:"local_ip,omitempty" yaml:"local_ip,omitempty"`
+	FullPath    snet.Path                        `json:"-" yaml:"-"`
+	Fingerprint string                           `json:"fingerprint" yaml:"fingerprint"`
+	Hops        []Hop                            `json:"hops" yaml:"hops"`
+	Sequence    string                           `json:"sequence" yaml:"sequence"`
+	NextHop     string                           `json:"next_hop" yaml:"next_hop"`
+	Expiry      time.Time                        `json:"expiry" yaml:"expiry"`
+	MTU         uint16                           `json:"mtu" yaml:"mtu"`
+	Latency     []time.Duration                  `json:"latency" yaml:"latency"`
+	Status      string                           `json:"status,omitempty" yaml:"status,omitempty"`
+	StatusInfo  string                           `json:"status_info,omitempty" yaml:"status_info,omitempty"`
+	Local       netip.Addr                       `json:"local_ip,omitempty" yaml:"local_ip,omitempty"`
+	Discovery   map[addr.IA]DiscoveryInformation `json:"discovery,omitempty" yaml:"discovery,omitempty"`
 }
 
 // Hop represents an hop on the path.
@@ -62,6 +64,11 @@ type Hop struct {
 	IA   addr.IA  `json:"isd_as"`
 }
 
+type DiscoveryInformation struct {
+	ControlServices   []netip.AddrPort `json:"control_services,omitempty" yaml:"control_services,omitempty"`
+	DiscoveryServices []netip.AddrPort `json:"discovery_services,omitempty" yaml:"discovery_services,omitempty"`
+}
+
 // Human writes human readable output to the writer.
 func (r Result) Human(w io.Writer, showExtendedMetadata, colored bool) {
 	cs := path.DefaultColorScheme(!colored)
@@ -108,6 +115,7 @@ func (r Result) Human(w io.Writer, showExtendedMetadata, colored bool) {
 				"InternalHops", humanInternalHops(meta),
 				"Notes", humanNotes(meta),
 				"SupportsEPIC", strconv.FormatBool(meta.EpicAuths.SupportsEpic()),
+				"Discovery", humanDiscovery(meta.DiscoveryInformation),
 			)...)
 		}
 		if path.Status != "" {
@@ -273,6 +281,24 @@ func humanNotes(p *snet.PathMetadata) string {
 	return fmt.Sprintf("[%s]", strings.Join(notes, ", "))
 }
 
+func humanDiscovery(di map[addr.IA]snet.DiscoveryInformation) string {
+	if len(di) == 0 {
+		return ""
+	}
+	discovery := make([]string, 0, len(di))
+	for ia, info := range di {
+		if len(info.ControlServices) == 0 && len(info.DiscoveryServices) == 0 {
+			continue
+		}
+		discovery = append(discovery, fmt.Sprintf("%s: {Control: %s, Discovery: %s}",
+			ia,
+			strings.Join(slices.Transform(info.ControlServices, netip.AddrPort.String), ", "),
+			strings.Join(slices.Transform(info.DiscoveryServices, netip.AddrPort.String), ", "),
+		))
+	}
+	return fmt.Sprintf("{%s}", strings.Join(discovery, ", "))
+}
+
 // sanitizeString returns a trimmed single line representation of the string,
 // with any control characters or quotation marks removed.
 func sanitizeString(str string) string {
@@ -391,6 +417,18 @@ func Run(ctx context.Context, dst addr.IA, cfg Config) (*Result, error) {
 		for _, hop := range path.Metadata().Interfaces {
 			rpath.Hops = append(rpath.Hops, Hop{IA: hop.IA, IfID: hop.ID})
 		}
+		for ia, info := range pathMeta.DiscoveryInformation {
+			if len(info.ControlServices) == 0 && len(info.DiscoveryServices) == 0 {
+				continue
+			}
+			if rpath.Discovery == nil {
+				rpath.Discovery = make(map[addr.IA]DiscoveryInformation)
+			}
+			rpath.Discovery[ia] = DiscoveryInformation{
+				ControlServices:   info.ControlServices,
+				DiscoveryServices: info.DiscoveryServices,
+			}
+		}
 		if status, ok := statuses[pathprobe.PathKey(path)]; ok {
 			rpath.Status = strings.ToLower(string(status.Status))
 			rpath.StatusInfo = status.AdditionalInfo


Test patch:
diff --git a/control/beaconing/extender_test.go b/control/beaconing/extender_test.go
index bfb9bbae2..48cbca70b 100644
--- a/control/beaconing/extender_test.go
+++ b/control/beaconing/extender_test.go
@@ -35,6 +35,7 @@ import (
 	cryptopb "github.com/scionproto/scion/pkg/proto/crypto"
 	"github.com/scionproto/scion/pkg/scrypto"
 	seg "github.com/scionproto/scion/pkg/segment"
+	"github.com/scionproto/scion/pkg/segment/extensions/discovery"
 	"github.com/scionproto/scion/pkg/slayers/path"
 	"github.com/scionproto/scion/private/topology"
 	"github.com/scionproto/scion/private/trust"
@@ -104,10 +105,11 @@ func TestDefaultExtenderExtend(t *testing.T) {
 					require.NoError(t, err)
 					return mac
 				},
-				Intfs:      intfs,
-				MTU:        1337,
-				MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-				StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+				Intfs:                intfs,
+				MTU:                  1337,
+				MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+				StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+				DiscoveryInformation: func() *discovery.Extension { return nil },
 			}
 			pseg, err := seg.CreateSegment(time.Time{}, 0)
 			require.NoError(t, err)
@@ -175,10 +177,11 @@ func TestDefaultExtenderExtend(t *testing.T) {
 				require.NoError(t, err)
 				return mac
 			},
-			Intfs:      intfs,
-			MTU:        1337,
-			MaxExpTime: func() uint8 { return 1 },
-			StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+			Intfs:                intfs,
+			MTU:                  1337,
+			MaxExpTime:           func() uint8 { return 1 },
+			StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+			DiscoveryInformation: func() *discovery.Extension { return nil },
 		}
 		require.NoError(t, err)
 		pseg, err := seg.CreateSegment(time.Now(), uint16(mrand.Int()))
@@ -267,10 +270,11 @@ func TestDefaultExtenderExtend(t *testing.T) {
 						require.NoError(t, err)
 						return mac
 					},
-					Intfs:      intfs,
-					MTU:        1337,
-					MaxExpTime: tc.MaxExpTime,
-					StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+					Intfs:                intfs,
+					MTU:                  1337,
+					MaxExpTime:           tc.MaxExpTime,
+					StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+					DiscoveryInformation: func() *discovery.Extension { return nil },
 				}
 				pseg, err := seg.CreateSegment(ts, uint16(mrand.Int()))
 				require.NoError(t, err)
@@ -337,10 +341,11 @@ func TestDefaultExtenderExtend(t *testing.T) {
 						require.NoError(t, err)
 						return mac
 					},
-					Intfs:      intfs,
-					MTU:        1337,
-					MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-					StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+					Intfs:                intfs,
+					MTU:                  1337,
+					MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+					StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+					DiscoveryInformation: func() *discovery.Extension { return nil },
 				}
 				pseg, err := seg.CreateSegment(time.Now(), uint16(mrand.Int()))
 				require.NoError(t, err)
diff --git a/control/beaconing/handler_test.go b/control/beaconing/handler_test.go
index da6527415..fac9d7afb 100644
--- a/control/beaconing/handler_test.go
+++ b/control/beaconing/handler_test.go
@@ -84,7 +84,7 @@ func TestHandlerHandleBeacon(t *testing.T) {
 			},
 			Peer: func() *snet.UDPAddr {
 				return &snet.UDPAddr{
-					IA:   0,
+					IA:   addr.MustParseIA("2-ff00:0:220"),
 					Path: path.SCION{},
 				}
 			},
@@ -108,7 +108,7 @@ func TestHandlerHandleBeacon(t *testing.T) {
 			},
 			Peer: func() *snet.UDPAddr {
 				return &snet.UDPAddr{
-					IA:   0,
+					IA:   addr.MustParseIA("2-ff00:0:220"),
 					Path: path.SCION{},
 				}
 			},
@@ -134,7 +134,7 @@ func TestHandlerHandleBeacon(t *testing.T) {
 			},
 			Peer: func() *snet.UDPAddr {
 				return &snet.UDPAddr{
-					IA:   0,
+					IA:   addr.MustParseIA("2-ff00:0:220"),
 					Path: path.SCION{},
 				}
 			},
@@ -160,6 +160,34 @@ func TestHandlerHandleBeacon(t *testing.T) {
 				b.Segment.ASEntries[b.Segment.MaxIdx()].Local = addr.MustParseIA("1-ff00:0:111")
 				return b
 			},
+			Peer: func() *snet.UDPAddr {
+				return &snet.UDPAddr{
+					IA:   addr.MustParseIA("2-ff00:0:220"),
+					Path: path.SCION{},
+				}
+			},
+			Assertion: assert.Error,
+		},
+		"invalid peer ISD-AS": {
+			Inserter: func(mctrl *gomock.Controller) *mock_beaconing.MockBeaconInserter {
+				inserter := mock_beaconing.NewMockBeaconInserter(mctrl)
+				inserter.EXPECT().PreFilter(gomock.Any()).Return(nil)
+				return inserter
+			},
+			Verifier: func(mctrl *gomock.Controller) *mock_infra.MockVerifier {
+				return mock_infra.NewMockVerifier(mctrl)
+			},
+			Beacon: func(t *testing.T, mctrl *gomock.Controller) beacon.Beacon {
+				g := graph.NewDefaultGraph(mctrl)
+				b := beacon.Beacon{
+					Segment: testSegment(g, []uint16{
+						graph.If_220_X_120_B, graph.If_120_A_110_X,
+					}),
+					InIfID: localIF,
+				}
+				b.Segment.ASEntries[b.Segment.MaxIdx()].Next = addr.MustParseIA("1-ff00:0:111")
+				return b
+			},
 			Peer: func() *snet.UDPAddr {
 				return &snet.UDPAddr{
 					IA:   0,
@@ -190,7 +218,7 @@ func TestHandlerHandleBeacon(t *testing.T) {
 			},
 			Peer: func() *snet.UDPAddr {
 				return &snet.UDPAddr{
-					IA:   0,
+					IA:   addr.MustParseIA("2-ff00:0:220"),
 					Path: path.SCION{},
 				}
 			},
@@ -216,7 +244,7 @@ func TestHandlerHandleBeacon(t *testing.T) {
 			},
 			Peer: func() *snet.UDPAddr {
 				return &snet.UDPAddr{
-					IA:   0,
+					IA:   addr.MustParseIA("2-ff00:0:220"),
 					Path: path.SCION{},
 				}
 			},
@@ -245,7 +273,7 @@ func TestHandlerHandleBeacon(t *testing.T) {
 			},
 			Peer: func() *snet.UDPAddr {
 				return &snet.UDPAddr{
-					IA:   0,
+					IA:   addr.MustParseIA("2-ff00:0:220"),
 					Path: path.SCION{},
 				}
 			},
diff --git a/control/beaconing/originator_test.go b/control/beaconing/originator_test.go
index 44d04d0d7..bb3acd4b1 100644
--- a/control/beaconing/originator_test.go
+++ b/control/beaconing/originator_test.go
@@ -38,6 +38,7 @@ import (
 	cryptopb "github.com/scionproto/scion/pkg/proto/crypto"
 	"github.com/scionproto/scion/pkg/scrypto/signed"
 	seg "github.com/scionproto/scion/pkg/segment"
+	"github.com/scionproto/scion/pkg/segment/extensions/discovery"
 	"github.com/scionproto/scion/private/topology"
 	"github.com/scionproto/scion/private/trust"
 )
@@ -68,13 +69,14 @@ func TestOriginatorRun(t *testing.T) {
 		senderFactory := mock_beaconing.NewMockSenderFactory(mctrl)
 		o := beaconing.Originator{
 			Extender: &beaconing.DefaultExtender{
-				IA:         topo.IA(),
-				MTU:        topo.MTU(),
-				SignerGen:  testSignerGen{Signers: []trust.Signer{signer}},
-				Intfs:      intfs,
-				MAC:        macFactory,
-				MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-				StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+				IA:                   topo.IA(),
+				MTU:                  topo.MTU(),
+				SignerGen:            testSignerGen{Signers: []trust.Signer{signer}},
+				Intfs:                intfs,
+				MAC:                  macFactory,
+				MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+				StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+				DiscoveryInformation: func() *discovery.Extension { return nil },
 			},
 			SenderFactory: senderFactory,
 			IA:            topo.IA(),
@@ -131,13 +133,14 @@ func TestOriginatorRun(t *testing.T) {
 		sender := mock_beaconing.NewMockSender(mctrl)
 		o := beaconing.Originator{
 			Extender: &beaconing.DefaultExtender{
-				IA:         topo.IA(),
-				MTU:        topo.MTU(),
-				SignerGen:  testSignerGen{Signers: []trust.Signer{signer}},
-				Intfs:      intfs,
-				MAC:        macFactory,
-				MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-				StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+				IA:                   topo.IA(),
+				MTU:                  topo.MTU(),
+				SignerGen:            testSignerGen{Signers: []trust.Signer{signer}},
+				Intfs:                intfs,
+				MAC:                  macFactory,
+				MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+				StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+				DiscoveryInformation: func() *discovery.Extension { return nil },
 			},
 			SenderFactory: senderFactory,
 			IA:            topo.IA(),
diff --git a/control/beaconing/propagator_test.go b/control/beaconing/propagator_test.go
index 75b1b2ba0..a6048ac19 100644
--- a/control/beaconing/propagator_test.go
+++ b/control/beaconing/propagator_test.go
@@ -37,6 +37,7 @@ import (
 	"github.com/scionproto/scion/pkg/private/serrors"
 	"github.com/scionproto/scion/pkg/private/xtest/graph"
 	seg "github.com/scionproto/scion/pkg/segment"
+	"github.com/scionproto/scion/pkg/segment/extensions/discovery"
 	"github.com/scionproto/scion/private/topology"
 	"github.com/scionproto/scion/private/trust"
 )
@@ -63,13 +64,16 @@ func TestPropagatorRunNonCore(t *testing.T) {
 	}
 	p := beaconing.Propagator{
 		Extender: &beaconing.DefaultExtender{
-			IA:         topo.IA(),
-			MTU:        topo.MTU(),
-			SignerGen:  testSignerGen{Signers: []trust.Signer{testSigner(t, priv, topo.IA())}},
-			Intfs:      intfs,
-			MAC:        macFactory,
-			MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-			StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+			IA:  topo.IA(),
+			MTU: topo.MTU(),
+			SignerGen: testSignerGen{
+				Signers: []trust.Signer{testSigner(t, priv, topo.IA())},
+			},
+			Intfs:                intfs,
+			MAC:                  macFactory,
+			MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+			StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+			DiscoveryInformation: func() *discovery.Extension { return nil },
 		},
 		SenderFactory: senderFactory,
 		IA:            topo.IA(),
@@ -136,13 +140,16 @@ func TestPropagatorRunCore(t *testing.T) {
 	}
 	p := beaconing.Propagator{
 		Extender: &beaconing.DefaultExtender{
-			IA:         topo.IA(),
-			MTU:        topo.MTU(),
-			SignerGen:  testSignerGen{Signers: []trust.Signer{testSigner(t, priv, topo.IA())}},
-			Intfs:      intfs,
-			MAC:        macFactory,
-			MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-			StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+			IA:  topo.IA(),
+			MTU: topo.MTU(),
+			SignerGen: testSignerGen{
+				Signers: []trust.Signer{testSigner(t, priv, topo.IA())},
+			},
+			Intfs:                intfs,
+			MAC:                  macFactory,
+			MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+			StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+			DiscoveryInformation: func() *discovery.Extension { return nil },
 		},
 		SenderFactory: senderFactory,
 		IA:            topo.IA(),
@@ -223,13 +230,16 @@ func TestPropagatorFastRecovery(t *testing.T) {
 
 	p := beaconing.Propagator{
 		Extender: &beaconing.DefaultExtender{
-			IA:         topo.IA(),
-			MTU:        topo.MTU(),
-			SignerGen:  testSignerGen{Signers: []trust.Signer{testSigner(t, priv, topo.IA())}},
-			Intfs:      intfs,
-			MAC:        macFactory,
-			MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-			StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+			IA:  topo.IA(),
+			MTU: topo.MTU(),
+			SignerGen: testSignerGen{
+				Signers: []trust.Signer{testSigner(t, priv, topo.IA())},
+			},
+			Intfs:                intfs,
+			MAC:                  macFactory,
+			MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+			StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+			DiscoveryInformation: func() *discovery.Extension { return nil },
 		},
 		SenderFactory: senderFactory,
 		IA:            topo.IA(),
diff --git a/control/beaconing/writer_test.go b/control/beaconing/writer_test.go
index c868a3fbd..042d0ba91 100644
--- a/control/beaconing/writer_test.go
+++ b/control/beaconing/writer_test.go
@@ -42,6 +42,7 @@ import (
 	"github.com/scionproto/scion/pkg/scrypto/cppki"
 	"github.com/scionproto/scion/pkg/scrypto/signed"
 	seg "github.com/scionproto/scion/pkg/segment"
+	"github.com/scionproto/scion/pkg/segment/extensions/discovery"
 	"github.com/scionproto/scion/pkg/segment/iface"
 	"github.com/scionproto/scion/pkg/slayers/path/scion"
 	"github.com/scionproto/scion/pkg/snet"
@@ -101,10 +102,11 @@ func TestRegistrarRun(t *testing.T) {
 						SignerGen: testSignerGen{
 							Signers: []trust.Signer{testSigner(t, priv, topo.IA())},
 						},
-						Intfs:      intfs,
-						MAC:        macFactory,
-						MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-						StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+						Intfs:                intfs,
+						MAC:                  macFactory,
+						MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+						StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+						DiscoveryInformation: func() *discovery.Extension { return nil },
 					},
 					Intfs: intfs,
 					Store: segStore,
@@ -188,10 +190,11 @@ func TestRegistrarRun(t *testing.T) {
 						SignerGen: testSignerGen{
 							Signers: []trust.Signer{testSigner(t, priv, topo.IA())},
 						},
-						Intfs:      intfs,
-						MAC:        macFactory,
-						MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-						StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+						Intfs:                intfs,
+						MAC:                  macFactory,
+						MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+						StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+						DiscoveryInformation: func() *discovery.Extension { return nil },
 					},
 					Pather: addrutil.Pather{
 						NextHopper: topoWrap{Topo: topo},
@@ -288,10 +291,11 @@ func TestRegistrarRun(t *testing.T) {
 					SignerGen: testSignerGen{
 						Signers: []trust.Signer{testSigner(t, priv, topo.IA())},
 					},
-					Intfs:      intfs,
-					MAC:        macFactory,
-					MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-					StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+					Intfs:                intfs,
+					MAC:                  macFactory,
+					MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+					StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+					DiscoveryInformation: func() *discovery.Extension { return nil },
 				},
 				Pather: addrutil.Pather{
 					NextHopper: topoWrap{Topo: topo},
diff --git a/control/drkey/grpc/fetcher_test.go b/control/drkey/grpc/fetcher_test.go
index 2667bf014..5f23ac18c 100644
--- a/control/drkey/grpc/fetcher_test.go
+++ b/control/drkey/grpc/fetcher_test.go
@@ -52,6 +52,7 @@ func TestLevel1KeyFetching(t *testing.T) {
 	// This test uses grpc's TLS stack, unlike the  real system, where the TLS of
 	// the QUIC session is used.
 	dir := genCrypto(t)
+	dstIA := addr.MustParseIA("1-ff00:0:111")
 
 	trc := xtest.LoadTRC(t, filepath.Join(dir, "trcs/ISD1-B1-S1.trc"))
 	tlsCert, err := tls.LoadX509KeyPair(
@@ -76,6 +77,7 @@ func TestLevel1KeyFetching(t *testing.T) {
 		Interfaces: []snet.PathInterface{},
 	})
 	path.EXPECT().Dataplane().AnyTimes().Return(nil)
+	path.EXPECT().Destination().AnyTimes().Return(dstIA)
 	path.EXPECT().UnderlayNextHop().AnyTimes().Return(&net.UDPAddr{})
 
 	router := mock_snet.NewMockRouter(ctrl)
@@ -138,7 +140,7 @@ func TestLevel1KeyFetching(t *testing.T) {
 			meta := drkey.Level1Meta{
 				ProtoId:  drkey.Generic,
 				Validity: time.Now(),
-				SrcIA:    addr.MustParseIA("1-ff00:0:111"),
+				SrcIA:    dstIA,
 			}
 			_, err = fetcher.Level1(context.Background(), meta)
 			tc.assertErr(t, err)
diff --git a/pkg/experimental/hiddenpath/beaconwriter_test.go b/pkg/experimental/hiddenpath/beaconwriter_test.go
index 36a690019..dda4f56f9 100644
--- a/pkg/experimental/hiddenpath/beaconwriter_test.go
+++ b/pkg/experimental/hiddenpath/beaconwriter_test.go
@@ -44,6 +44,7 @@ import (
 	"github.com/scionproto/scion/pkg/scrypto/cppki"
 	"github.com/scionproto/scion/pkg/scrypto/signed"
 	seg "github.com/scionproto/scion/pkg/segment"
+	"github.com/scionproto/scion/pkg/segment/extensions/discovery"
 	"github.com/scionproto/scion/pkg/segment/iface"
 	"github.com/scionproto/scion/pkg/slayers/path/scion"
 	"github.com/scionproto/scion/pkg/snet"
@@ -195,13 +196,14 @@ func TestRemoteBeaconWriterWrite(t *testing.T) {
 			w := &hiddenpath.BeaconWriter{
 				Intfs: intfs,
 				Extender: &beaconing.DefaultExtender{
-					IA:         topo.IA(),
-					MTU:        topo.MTU(),
-					SignerGen:  testSignerGen{Signer: testSigner(t, priv, topo.IA())},
-					Intfs:      intfs,
-					MAC:        macFactory,
-					MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime },
-					StaticInfo: func() *beaconing.StaticInfoCfg { return nil },
+					IA:                   topo.IA(),
+					MTU:                  topo.MTU(),
+					SignerGen:            testSignerGen{Signer: testSigner(t, priv, topo.IA())},
+					Intfs:                intfs,
+					MAC:                  macFactory,
+					MaxExpTime:           func() uint8 { return beacon.DefaultMaxExpTime },
+					StaticInfo:           func() *beaconing.StaticInfoCfg { return nil },
+					DiscoveryInformation: func() *discovery.Extension { return nil },
 				},
 				RPC: tc.createRPC(t, ctrl),
 				Pather: addrutil.Pather{
diff --git a/pkg/experimental/hiddenpath/discovery_test.go b/pkg/experimental/hiddenpath/discovery_test.go
index 9a3e91e15..51a68ae63 100644
--- a/pkg/experimental/hiddenpath/discovery_test.go
+++ b/pkg/experimental/hiddenpath/discovery_test.go
@@ -72,6 +72,8 @@ func TestRegistrationResolverResolve(t *testing.T) {
 				path.EXPECT().Dataplane().Return(snetpath.SCION{Raw: []byte("path")}).AnyTimes()
 				path.EXPECT().UnderlayNextHop().AnyTimes().Return(
 					xtest.MustParseUDPAddr(t, "10.1.0.1:404"))
+				path.EXPECT().Destination().AnyTimes().Return(addr.MustParseIA("1-ff00:0:110"))
+				path.EXPECT().Metadata().AnyTimes().Return(nil)
 				router.EXPECT().Route(gomock.Any(), addr.MustParseIA("1-ff00:0:110")).
 					Return(path, nil)
 				return router
@@ -96,6 +98,8 @@ func TestRegistrationResolverResolve(t *testing.T) {
 				path.EXPECT().Dataplane().Return(snetpath.SCION{Raw: []byte("path")}).AnyTimes()
 				path.EXPECT().UnderlayNextHop().AnyTimes().Return(
 					xtest.MustParseUDPAddr(t, "10.1.0.1:404"))
+				path.EXPECT().Destination().AnyTimes().Return(addr.MustParseIA("1-ff00:0:110"))
+				path.EXPECT().Metadata().AnyTimes().Return(nil)
 				router.EXPECT().Route(gomock.Any(), addr.MustParseIA("1-ff00:0:110")).
 					Return(path, nil)
 				return router
@@ -120,6 +124,8 @@ func TestRegistrationResolverResolve(t *testing.T) {
 				path.EXPECT().Dataplane().Return(snetpath.SCION{Raw: []byte("path")}).AnyTimes()
 				path.EXPECT().UnderlayNextHop().AnyTimes().Return(
 					xtest.MustParseUDPAddr(t, "10.1.0.1:404"))
+				path.EXPECT().Destination().AnyTimes().Return(addr.MustParseIA("1-ff00:0:110"))
+				path.EXPECT().Metadata().AnyTimes().Return(nil)
 				router.EXPECT().Route(gomock.Any(), addr.MustParseIA("1-ff00:0:110")).
 					Return(path, nil)
 				return router
diff --git a/pkg/segment/extensions_test.go b/pkg/segment/extensions_test.go
index 48b10a465..32ea666f4 100644
--- a/pkg/segment/extensions_test.go
+++ b/pkg/segment/extensions_test.go
@@ -32,6 +32,7 @@ func TestDecodeEncodeEpicDigest(t *testing.T) {
 	ext := Extensions{
 		Digests: dig,
 	}
-	ext2 := ExtensionsFromPB(ExtensionsToPB(ext))
+	ext2, err := extensionsFromPB(extensionsToPB(ext))
+	assert.NoError(t, err)
 	assert.Equal(t, ext, ext2)
 }
diff --git a/pkg/segment/export_test.go b/pkg/slices/transform.go
similarity index 64%
rename from pkg/segment/export_test.go
rename to pkg/slices/transform.go
index 357e97243..57cf9e90d 100644
--- a/pkg/segment/export_test.go
+++ b/pkg/slices/transform.go
@@ -1,4 +1,4 @@
-// Copyright 2020 ETH Zurich
+// Copyright 2025 Anapaya Systems
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,16 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package segment
+package slices
 
-import (
-	cppb "github.com/scionproto/scion/pkg/proto/control_plane"
-)
-
-func ExtensionsFromPB(pb *cppb.PathSegmentExtensions) Extensions {
-	return extensionsFromPB(pb)
-}
-
-func ExtensionsToPB(ext Extensions) *cppb.PathSegmentExtensions {
-	return extensionsToPB(ext)
+func Transform[S ~[]I, I any, O any](s S, transform func(I) O) []O {
+	if s == nil {
+		return nil
+	}
+	out := make([]O, len(s))
+	for i, v := range s {
+		out[i] = transform(v)
+	}
+	return out
 }
diff --git a/pkg/slices/transform_test.go b/pkg/slices/transform_test.go
new file mode 100644
index 000000000..1719f91cb
--- /dev/null
+++ b/pkg/slices/transform_test.go
@@ -0,0 +1,43 @@
+// Copyright 2025 Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package slices_test
+
+import (
+	"strconv"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/scionproto/scion/pkg/slices"
+)
+
+func TestTransform(t *testing.T) {
+	t.Parallel()
+
+	t.Run("nil slice", func(t *testing.T) {
+		t.Parallel()
+
+		var in []int
+		assert.Nil(t, slices.Transform(in, func(i int) string { return "" }))
+	})
+
+	t.Run("int to string", func(t *testing.T) {
+		t.Parallel()
+
+		in := []int{1, 2, 3}
+		out := slices.Transform(in, strconv.Itoa)
+		assert.Equal(t, []string{"1", "2", "3"}, out)
+	})
+}
diff --git a/pkg/snet/addrutil/addrutil_test.go b/pkg/snet/addrutil/addrutil_test.go
new file mode 100644
index 000000000..79ab1d79d
--- /dev/null
+++ b/pkg/snet/addrutil/addrutil_test.go
@@ -0,0 +1,110 @@
+// Copyright 2025 Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package addrutil_test
+
+import (
+	"net"
+	"net/netip"
+	"testing"
+
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/scionproto/scion/pkg/addr"
+	"github.com/scionproto/scion/pkg/snet"
+	"github.com/scionproto/scion/pkg/snet/addrutil"
+	"github.com/scionproto/scion/pkg/snet/mock_snet"
+	snetpath "github.com/scionproto/scion/pkg/snet/path"
+)
+
+func TestExtractServiceAddress(t *testing.T) {
+	t.Run("valid Control Service with discovery info, returns no error and UDPAddr",
+		func(t *testing.T) {
+			ctrl := gomock.NewController(t)
+			dummyIA := addr.MustParseIA("1-ff00:0:2")
+
+			path := mock_snet.NewMockPath(ctrl)
+
+			addrPort := netip.AddrPortFrom(netip.MustParseAddr("192.168.2.100"), 30652)
+			discoveryInfo := make(map[addr.IA]snet.DiscoveryInformation)
+			discoveryInfo[dummyIA] = snet.DiscoveryInformation{
+				ControlServices: []netip.AddrPort{addrPort},
+			}
+			metadata := &snet.PathMetadata{
+				Interfaces:           make([]snet.PathInterface, 1), // just non-empty
+				DiscoveryInformation: discoveryInfo,
+			}
+
+			path.EXPECT().Metadata().Return(metadata)
+			path.EXPECT().Dataplane().Return(snetpath.SCION{})
+			path.EXPECT().Destination().Return(dummyIA)
+			path.EXPECT().UnderlayNextHop().Return(
+				&net.UDPAddr{IP: netip.MustParseAddr("10.1.1.1").AsSlice()},
+			)
+
+			want := &snet.UDPAddr{
+				IA:      dummyIA,
+				Path:    snetpath.SCION{},
+				NextHop: &net.UDPAddr{IP: netip.MustParseAddr("10.1.1.1").AsSlice()},
+				Host: &net.UDPAddr{
+					IP:   netip.MustParseAddr("192.168.2.100").AsSlice(),
+					Port: 30652,
+				},
+			}
+
+			retrievedAddr := addrutil.ExtractDestinationServiceAddress(addr.SvcCS, path)
+
+			assert.Equal(t, want, retrievedAddr)
+		})
+
+	t.Run("valid Discovery Service with discovery info, returns no error and UDPAddr",
+		func(t *testing.T) {
+			ctrl := gomock.NewController(t)
+			dummyIA := addr.MustParseIA("1-ff00:0:2")
+
+			path := mock_snet.NewMockPath(ctrl)
+
+			addrPort := netip.AddrPortFrom(netip.MustParseAddr("192.168.2.100"), 30652)
+			discoveryInfo := make(map[addr.IA]snet.DiscoveryInformation)
+			discoveryInfo[dummyIA] = snet.DiscoveryInformation{
+				DiscoveryServices: []netip.AddrPort{addrPort},
+			}
+			metadata := &snet.PathMetadata{
+				Interfaces:           make([]snet.PathInterface, 1), // just non-empty
+				DiscoveryInformation: discoveryInfo,
+			}
+
+			path.EXPECT().Metadata().Return(metadata)
+			path.EXPECT().Dataplane().Return(snetpath.SCION{})
+			path.EXPECT().Destination().Return(dummyIA)
+			path.EXPECT().UnderlayNextHop().Return(
+				&net.UDPAddr{IP: netip.MustParseAddr("10.1.1.1").AsSlice()},
+			)
+
+			want := &snet.UDPAddr{
+				IA:      dummyIA,
+				Path:    snetpath.SCION{},
+				NextHop: &net.UDPAddr{IP: netip.MustParseAddr("10.1.1.1").AsSlice()},
+				Host: &net.UDPAddr{
+					IP:   netip.MustParseAddr("192.168.2.100").AsSlice(),
+					Port: 30652,
+				},
+			}
+
+			retrievedAddr := addrutil.ExtractDestinationServiceAddress(addr.SvcDS, path)
+
+			assert.Equal(t, want, retrievedAddr)
+		})
+}
diff --git a/private/trust/router_test.go b/private/trust/router_test.go
index 985155b27..ed8f5098b 100644
--- a/private/trust/router_test.go
+++ b/private/trust/router_test.go
@@ -70,6 +70,7 @@ func TestCSRouterChooseServer(t *testing.T) {
 				p.EXPECT().Dataplane().AnyTimes().Return(path.SCION{Raw: []byte("isd local path")})
 				p.EXPECT().Destination().AnyTimes().Return(ia110)
 				p.EXPECT().UnderlayNextHop().AnyTimes().Return(nil)
+				p.EXPECT().Metadata().AnyTimes().Return(nil)
 				r.EXPECT().Route(gomock.Any(), addr.MustIAFrom(1, 0)).Return(p, nil)
 			},
 		},
@@ -94,6 +95,7 @@ func TestCSRouterChooseServer(t *testing.T) {
 				p.EXPECT().Dataplane().AnyTimes().Return(path.SCION{Raw: []byte("remote ISD path")})
 				p.EXPECT().Destination().AnyTimes().Return(ia210)
 				p.EXPECT().UnderlayNextHop().AnyTimes().Return(nil)
+				p.EXPECT().Metadata().AnyTimes().Return(nil)
 				r.EXPECT().Route(gomock.Any(), addr.MustIAFrom(2, 0)).Return(p, nil)
 			},
 		},
@@ -107,6 +109,7 @@ func TestCSRouterChooseServer(t *testing.T) {
 				p.EXPECT().Dataplane().AnyTimes().Return(path.SCION{Raw: []byte("isd local path")})
 				p.EXPECT().Destination().AnyTimes().Return(ia110)
 				p.EXPECT().UnderlayNextHop().AnyTimes().Return(nil)
+				p.EXPECT().Metadata().AnyTimes().Return(nil)
 				r.EXPECT().Route(gomock.Any(), addr.MustIAFrom(1, 0)).Return(p, nil)
 			},
 		},
@@ -122,6 +125,7 @@ func TestCSRouterChooseServer(t *testing.T) {
 				p.EXPECT().Dataplane().AnyTimes().Return(path.SCION{Raw: []byte("isd local path")})
 				p.EXPECT().Destination().AnyTimes().Return(ia110)
 				p.EXPECT().UnderlayNextHop().AnyTimes().Return(nil)
+				p.EXPECT().Metadata().AnyTimes().Return(nil)
 				r.EXPECT().Route(gomock.Any(), addr.MustIAFrom(1, 0)).Return(p, nil)
 			},
 		},