Repository: scionproto/scion
Pull request #3442 (2019-12-02 10:23:09)

Add snet.{SVC,UDP}Addr to replace snet.Addr
In this PR the snet.Addr is being deprecated and being replaced with two new ones snet.{SVC,UDP}Addr. This is required because we want to remove the usage of addr.AppAddr overall
in our code base and make the API towards SVC or UDP scion addresses cleaner.

As from now, snet.Addr should not be used. In the code the net.Addr interface should be the way to communicate between packages, and a switch statement should always take place.

For example

```
//  public API should ideally use net.Addr not the type
func MyPublicRead( ..., address net.Addr, ...) (int,err){
	switch a:=a.(type) {
        case *snet.Addr:
	     panic("we should never use that")
 	case *snet.UDPAddr:
	            doStuffWithUDP(a)
	case *snet.SVCAddr:
                     doStuffWithSVC(a)
       default:
	         return nil, fmt.Errorf("unsupported type  a(%T)", a, ,a)
}

// not public API should ideally use the type not the addr
func doStuff(a snet.UDPaddr) {
  if a.IA == mylocal IA { ...}
}

```
Currently {SVC,UDP}addr are using snet.Addr because they share functionality (same fields, some common functions but in the future that will change.

In this PR:
- modified BS,PS,CS to use new addrs type
- messenger to support new addrs
- make internals to work with SVCAddr if possible
- Copy the path when creating the legacy address
- Always print the acceptance log line, needed for tests

Contributes scionproto/scion#3344

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/scionproto/scion/3442)
<!-- Reviewable:end -->



Patch:
diff --git a/go/beacon_srv/internal/beaconing/handler.go b/go/beacon_srv/internal/beaconing/handler.go
index c27c3479f..682293d75 100644
--- a/go/beacon_srv/internal/beaconing/handler.go
+++ b/go/beacon_srv/internal/beaconing/handler.go
@@ -199,11 +199,7 @@ func (h *handler) verifySegment(segment *seg.PathSegment) error {
 	if err != nil {
 		return common.NewBasicError("path error", err)
 	}
-	svcToQuery := &snet.Addr{
-		IA:      snetPeer.IA,
-		Path:    peerPath.Path(),
-		NextHop: peerPath.OverlayNextHop(),
-		Host:    addr.NewSVCUDPAppAddr(addr.SvcBS),
-	}
+	svcToQuery := snet.NewSVCAddr(snetPeer.IA, peerPath.Path(),
+		peerPath.OverlayNextHop(), addr.SvcBS)
 	return segverifier.VerifySegment(h.request.Context(), h.verifier, svcToQuery, segment)
 }
diff --git a/go/beacon_srv/internal/beaconing/registrar.go b/go/beacon_srv/internal/beaconing/registrar.go
index b83f8853b..4c85895b5 100644
--- a/go/beacon_srv/internal/beaconing/registrar.go
+++ b/go/beacon_srv/internal/beaconing/registrar.go
@@ -247,7 +247,7 @@ func (r *segmentRegistrar) onSuccess() {
 func (r *segmentRegistrar) chooseServer(pseg *seg.PathSegment) (net.Addr, error) {
 	if r.segType != proto.PathSegType_down {
 		topo := r.topoProvider.Get()
-		return &snet.Addr{IA: topo.IA(), Host: addr.NewSVCUDPAppAddr(addr.SvcPS)}, nil
+		return snet.NewSVCAddr(topo.IA(), nil, nil, addr.SvcPS), nil
 	}
 	return addrutil.GetPath(addr.SvcPS, pseg, r.topoProvider)
 }
diff --git a/go/beacon_srv/internal/ifstate/revoker.go b/go/beacon_srv/internal/ifstate/revoker.go
index f5716429f..682609d97 100644
--- a/go/beacon_srv/internal/ifstate/revoker.go
+++ b/go/beacon_srv/internal/ifstate/revoker.go
@@ -170,7 +170,8 @@ func (r *Revoker) pushRevocationsToPS(ctx context.Context,
 
 	topo := r.cfg.TopoProvider.Get()
 	labels := metrics.SentLabels{Dst: metrics.DstPS}
-	a := &snet.Addr{IA: topo.IA(), Host: addr.NewSVCUDPAppAddr(addr.SvcPS)}
+
+	a := snet.NewSVCAddr(topo.IA(), nil, nil, addr.SvcPS)
 	for ifid, srev := range revs {
 		if err := r.cfg.Msgr.SendRev(ctx, srev, a, messenger.NextId()); err != nil {
 			log.FromCtx(ctx).Error("[ifstate.Revoker] Failed to send revocation to PS",
@@ -189,7 +190,8 @@ func (p *brPusher) sendIfStateToAllBRs(ctx context.Context, msg *path_mgmt.IFSta
 	topo topology.Topology, wg *sync.WaitGroup) {
 
 	for _, br := range topo.BRNames() {
-		p.sendIfStateToBr(ctx, msg, br, topo.SBRAddress(br), wg)
+		t := topo.SBRAddress(br)
+		p.sendIfStateToBr(ctx, msg, br, t.ToXAddr(), wg)
 	}
 }
 
@@ -202,7 +204,7 @@ func (p *brPusher) sendIfStateToBr(ctx context.Context, msg *path_mgmt.IFStateIn
 		defer wg.Done()
 		if err := p.msgr.SendIfStateInfos(ctx, msg, a, messenger.NextId()); err != nil {
 			log.FromCtx(ctx).Error("Failed to send interface state to BR",
-				"br", id, "mode", p.mode, "err", err)
+				"br", id, "mode", p.mode, "err", err, "address", a)
 		}
 	}()
 }
diff --git a/go/beacon_srv/internal/onehop/sender.go b/go/beacon_srv/internal/onehop/sender.go
index b9f6a293a..01e938a1e 100644
--- a/go/beacon_srv/internal/onehop/sender.go
+++ b/go/beacon_srv/internal/onehop/sender.go
@@ -172,13 +172,8 @@ func (s *BeaconSender) attemptQUIC(ctx context.Context, ia addr.IA, path *spath.
 		return false, nil
 	}
 
-	newAddr, redirect, err := s.AddressRewriter.RedirectToQUIC(ctx,
-		&snet.Addr{
-			IA:      ia,
-			Path:    path,
-			NextHop: nextHop,
-			Host:    addr.NewSVCUDPAppAddr(addr.SvcBS),
-		})
+	t := snet.NewSVCAddr(ia, path, nextHop, addr.SvcBS)
+	newAddr, redirect, err := s.AddressRewriter.RedirectToQUIC(ctx, t)
 
 	if err != nil || !redirect {
 		log.Trace("Beaconing could not be upgraded to QUIC, using UDP", "remote", newAddr)
diff --git a/go/cert_srv/internal/reiss/corepush.go b/go/cert_srv/internal/reiss/corepush.go
index 9fd679d39..0f7422176 100644
--- a/go/cert_srv/internal/reiss/corepush.go
+++ b/go/cert_srv/internal/reiss/corepush.go
@@ -151,8 +151,8 @@ func (p *CorePusher) hasChain(ctx context.Context, coreAS addr.IA,
 		Version:   ver,
 		CacheOnly: true,
 	}
-	coreAddr := &snet.Addr{IA: coreAS, Host: addr.NewSVCUDPAppAddr(addr.SvcCS)}
-	reply, err := p.Msger.GetCertChain(ctx, req, coreAddr, messenger.NextId())
+	a := snet.NewSVCAddr(coreAS, nil, nil, addr.SvcCS)
+	reply, err := p.Msger.GetCertChain(ctx, req, a, messenger.NextId())
 	if err != nil {
 		return false, common.NewBasicError("Error during fetch", err)
 	}
@@ -168,9 +168,9 @@ func (p *CorePusher) sendChain(ctx context.Context, coreAS addr.IA, chain *cert.
 	msg := &cert_mgmt.Chain{
 		RawChain: rawChain,
 	}
-	coreAddr := &snet.Addr{IA: coreAS, Host: addr.NewSVCUDPAppAddr(addr.SvcCS)}
+	a := snet.NewSVCAddr(coreAS, nil, nil, addr.SvcCS)
 	// TODO(lukedirtwalker): Expect Acks.
-	return p.Msger.SendCertChain(ctx, msg, coreAddr, messenger.NextId())
+	return p.Msger.SendCertChain(ctx, msg, a, messenger.NextId())
 }
 
 type iaList struct {
diff --git a/go/cert_srv/internal/reiss/handler.go b/go/cert_srv/internal/reiss/handler.go
index de5384975..be3e5fef7 100644
--- a/go/cert_srv/internal/reiss/handler.go
+++ b/go/cert_srv/internal/reiss/handler.go
@@ -57,7 +57,16 @@ type Handler struct {
 }
 
 func (h *Handler) Handle(r *infra.Request) *infra.HandlerResult {
-	addr := r.Peer.(*snet.Addr)
+	var addr *snet.Addr
+	switch peer := r.Peer.(type) {
+	case *snet.Addr:
+		addr = peer
+	case *snet.UDPAddr:
+		addr = peer.ToAddr()
+	case *snet.SVCAddr:
+		addr = peer.ToAddr()
+	}
+
 	req := r.Message.(*cert_mgmt.ChainIssReq)
 	if err := h.handle(r, addr, req); err != nil {
 		log.Error("[reiss.Handler] Dropping certificate reissue request",
diff --git a/go/cert_srv/internal/reiss/requester.go b/go/cert_srv/internal/reiss/requester.go
index cae742031..e4f553a69 100644
--- a/go/cert_srv/internal/reiss/requester.go
+++ b/go/cert_srv/internal/reiss/requester.go
@@ -101,7 +101,7 @@ func (r *Requester) sendReq(ctx context.Context, chain *cert.Chain) (bool, error
 		return false, common.NewBasicError("Unable to pack certificate", err)
 	}
 	req := &cert_mgmt.ChainIssReq{RawCert: raw}
-	a := &snet.Addr{IA: c.Issuer, Host: addr.NewSVCUDPAppAddr(addr.SvcCS)}
+	a := snet.NewSVCAddr(c.Issuer, nil, nil, addr.SvcCS)
 	rep, err := r.Msgr.RequestChainIssue(ctx, req, a, messenger.NextId())
 	if err != nil {
 		return false, common.NewBasicError("Unable to request reissued certificate chain", err)
diff --git a/go/lib/infra/disp/disp.go b/go/lib/infra/disp/disp.go
index 29ffd2c8e..e02d1d436 100644
--- a/go/lib/infra/disp/disp.go
+++ b/go/lib/infra/disp/disp.go
@@ -138,7 +138,7 @@ func (d *Dispatcher) Notify(ctx context.Context, msg proto.Cerealizable, address
 		return common.NewBasicError(infra.ErrAdapter, err, "op", "MsgToRaw")
 	}
 	if _, err := d.conn.WriteTo(b, address); err != nil {
-		return common.NewBasicError(infra.ErrTransport, err, "op", "WriteTo")
+		return common.NewBasicError(infra.ErrTransport, err, "op", "WriteTo", "address", address)
 	}
 	return nil
 }
diff --git a/go/lib/infra/messenger/addr.go b/go/lib/infra/messenger/addr.go
index fb36d33bb..989eac370 100644
--- a/go/lib/infra/messenger/addr.go
+++ b/go/lib/infra/messenger/addr.go
@@ -16,6 +16,7 @@ package messenger
 
 import (
 	"context"
+	"fmt"
 	"net"
 	"time"
 
@@ -69,92 +70,91 @@ type AddressRewriter struct {
 // QUIC-compatible and we have successfully discovered its address.
 //
 // If the address is already unicast, no redirection to QUIC is attempted.
-func (r AddressRewriter) RedirectToQUIC(ctx context.Context, a net.Addr) (net.Addr, bool, error) {
+func (r AddressRewriter) RedirectToQUIC(ctx context.Context,
+	address net.Addr) (net.Addr, bool, error) {
+	logger := log.FromCtx(ctx)
 
 	// FIXME(scrye): This is not legitimate use. It's only included for
 	// compatibility with older unit tests. See
 	// https://github.com/scionproto/scion/issues/2611.
-	if a == nil || r.SVCResolutionFraction <= 0.0 {
-		return a, false, nil
+	if address == nil || r.SVCResolutionFraction <= 0.0 {
+		return address, false, nil
 	}
 
-	// If already of type UDPAddr then return
-	if v, ok := a.(*snet.Addr); ok && v.Host.L3.Type() != addr.HostTypeSVC {
+	switch a := address.(type) {
+	case *snet.UDPAddr:
 		return a, false, nil
-	}
+	case *snet.Addr:
+		logger.Trace("Legacy snet.Addr is still being used", "addr", fmt.Sprintf("%v(%T)", a, a))
+		return r.RedirectToQUIC(ctx, a.ToXAddr())
+	case *snet.SVCAddr:
 
-	t, err := r.buildFullAddress(ctx, a)
-	if err != nil {
-		return nil, false, err
-	}
+		fa, err := r.buildFullAddress(ctx, a)
+		if err != nil {
+			return nil, false, err
+		}
 
-	fullAddress, ok := t.(*snet.Addr)
-	if !ok {
-		return nil, false, common.NewBasicError("address type not supported", nil, "addr", a)
-	}
-	path, err := fullAddress.GetPath()
-	if err != nil {
-		return nil, false, common.NewBasicError("bad path", err)
-	}
+		path, err := fa.GetPath()
+		if err != nil {
+			return nil, false, common.NewBasicError("bad path", err)
+		}
 
-	v, ok := fullAddress.Host.L3.(addr.HostSVC)
-	if !ok { //if not SVC
-		return fullAddress, false, nil
-	}
+		// During One-Hop Path operation, use SVC resolution to also bootstrap the path.
+		p, u, quicRedirect, err := r.resolveSVC(ctx, path, fa.SVC)
+		if err != nil {
+			if r.SVCResolutionFraction < 1.0 {
+				// SVC resolution failed but we allow legacy behavior and have some
+				// fraction of the timeout left for data transfers, so return
+				// address with SVC destination still set
+				logger.Trace("Falling back to legacy mode, ignore error", "err", err)
+				return fa, false, nil
+			}
+			return a, false, err
+		}
 
-	// During One-Hop Path operation, use SVC resolution to also bootstrap the path.
-	p, u, quicRedirect, err := r.resolveSVC(ctx, path, v)
-	if p != nil {
-		fullAddress.Path = p.Path()
-	}
-	if u != nil {
-		fullAddress.Host = addr.AppAddrFromUDP(u)
+		ret := snet.NewUDPAddr(fa.IA, p.Path(), fa.NextHop, u)
+		return ret, quicRedirect, err
 	}
 
-	return fullAddress, quicRedirect, err
+	return nil, false, common.NewBasicError("address type not supported", nil,
+		"addr", fmt.Sprintf("%v(%T)", address, address))
 }
 
 // buildFullAddress checks that a is a well-formed address (all fields set,
 // non-nil, only supported protocols). If the path is missing, the path and
 // next-hop are added by performing a routing lookup. The returned address is
 // always a copy, and the input address is guaranteed to not change.
-func (r AddressRewriter) buildFullAddress(ctx context.Context, a net.Addr) (net.Addr, error) {
-	snetAddr, ok := a.(*snet.Addr)
-	if !ok {
-		return nil, common.NewBasicError("address type not supported", nil, "addr", a)
-	}
-	if snetAddr.Host == nil {
-		return nil, common.NewBasicError("host address not specified", nil, "addr", snetAddr)
-	}
-	if snetAddr.Host.L3 == nil {
-		return nil, common.NewBasicError("host address missing L3 address", nil, "addr", snetAddr)
+func (r AddressRewriter) buildFullAddress(ctx context.Context,
+	s *snet.SVCAddr) (*snet.SVCAddr, error) {
+
+	if s.Path != nil {
+		ret := snet.NewSVCAddr(s.IA, s.Path.Copy(), snet.CopyUDPAddr(s.NextHop), s.SVC)
+		log.Trace("[Acceptance]", "overlay", ret.NextHop)
+		return ret, nil
 	}
-	if t := snetAddr.Host.L3.Type(); !addr.HostTypeCheck(t) {
-		return nil, common.NewBasicError("host address L3 address not supported", nil, "type", t)
+
+	ret := snet.NewSVCAddr(s.IA, nil, nil, s.SVC)
+	p, err := r.Router.Route(ctx, s.IA)
+	if err != nil {
+		return nil, err
 	}
-	newAddr := snetAddr.Copy()
 
+	ret.Path = p.Path()
+	ret.NextHop = p.OverlayNextHop()
 	defer func() {
-		log.Trace("[Acceptance]", "overlay", newAddr.NextHop)
+		log.Trace("[Acceptance]", "overlay", ret.NextHop)
 	}()
-	if newAddr.Path == nil {
-		p, err := r.Router.Route(ctx, newAddr.IA)
+
+	// SVC addresses in the local AS get resolved via topology lookup
+	if p.Fingerprint() == "" { //when local AS
+		ov, err := r.SVCRouter.GetOverlay(s.SVC)
 		if err != nil {
-			return nil, err
-		}
-		newAddr.Path = p.Path()
-		newAddr.NextHop = p.OverlayNextHop()
-		// SVC addresses in the local AS get resolved via topology lookup
-		if svc, ok := newAddr.Host.L3.(addr.HostSVC); ok && p.Fingerprint() == "" {
-			ov, err := r.SVCRouter.GetOverlay(svc)
-			if err != nil {
-				return nil, common.NewBasicError("Unable to resolve overlay", err)
-			}
-			newAddr.NextHop = ov
-			return newAddr, nil
+			return nil, common.NewBasicError("Unable to resolve overlay", err)
 		}
+		ret.NextHop = ov
 	}
-	return newAddr, nil
+
+	return ret, nil
 }
 
 // resolveSVC performs SVC resolution and returns an UDP/IP address. If the UDP/IP
@@ -179,15 +179,6 @@ func (r AddressRewriter) resolveSVC(ctx context.Context, p snet.Path,
 	reply, err := r.Resolver.LookupSVC(ctx, p, s)
 	if err != nil {
 		logger.Trace("SVC resolution failed", "err", err)
-		if r.SVCResolutionFraction < 1.0 {
-			// SVC resolution failed but we allow legacy behavior and have some
-			// fraction of the timeout left for data transfers, so return
-			// address with SVC destination still set
-			logger.Trace("Falling back to legacy mode, ignore error", "err", err)
-			return nil, nil, false, nil
-		}
-		// Legacy behavior is disallowed, so propagate a hard failure back to the app.
-		logger.Trace("Legacy mode disabled, propagate error", "err", err)
 		return nil, nil, false, err
 	}
 
diff --git a/go/lib/infra/messenger/messenger.go b/go/lib/infra/messenger/messenger.go
index df7b0e219..00b0e4d5c 100644
--- a/go/lib/infra/messenger/messenger.go
+++ b/go/lib/infra/messenger/messenger.go
@@ -347,7 +347,8 @@ func (m *Messenger) SendIfStateInfos(ctx context.Context, msg *path_mgmt.IFState
 	}
 	// FIXME(scrye): Use only UDP because the BR doesn't support QUIC.
 	logger := log.FromCtx(ctx)
-	logger.Trace("[Messenger] Sending Notify", "type", infra.IfStateInfos, "to", a, "id", id)
+	logger.Trace("[Messenger] Sending Notify", "type", infra.IfStateInfos,
+		"to", a, "id", id)
 	return m.getFallbackRequester(infra.SegReply).Notify(ctx, pld, a)
 }
 
@@ -675,12 +676,17 @@ func (m *Messenger) SendChainIssueReply(ctx context.Context, msg *cert_mgmt.Chai
 }
 
 func (m *Messenger) SendBeacon(ctx context.Context, msg *seg.Beacon, a net.Addr, id uint64) error {
-	if svc, ok := a.(*snet.Addr).Host.L3.(addr.HostSVC); ok {
+	logger := log.FromCtx(ctx)
+	switch a.(type) {
+	case *snet.Addr:
+	case *snet.UDPAddr:
+	case *snet.SVCAddr:
 		return common.NewBasicError("[Messenger] Cannot send to SVC address on QUIC-only RPC", nil,
-			"svc", svc)
+			"svc", a.(*snet.SVCAddr).Host)
+	default:
+		return common.NewBasicError("[Messenger] Cannot send to unknown address", nil)
 	}
 
-	logger := log.FromCtx(ctx)
 	pld, err := ctrl.NewPld(msg, &ctrl.Data{ReqId: id, TraceId: traceId(ctx)})
 	if err != nil {
 		return err
diff --git a/go/lib/infra/modules/trust/trust.go b/go/lib/infra/modules/trust/trust.go
index bf60d5306..d1eb51a16 100644
--- a/go/lib/infra/modules/trust/trust.go
+++ b/go/lib/infra/modules/trust/trust.go
@@ -752,7 +752,8 @@ func (store *Store) isLocal(address net.Addr) error {
 func (store *Store) ChooseServer(ctx context.Context, destination addr.IA) (net.Addr, error) {
 	topo := store.config.TopoProvider.Get()
 	if store.config.ServiceType != proto.ServiceType_cs {
-		return &snet.Addr{IA: store.ia, Host: addr.NewSVCUDPAppAddr(addr.SvcCS)}, nil
+		ret := snet.NewSVCAddr(store.ia, nil, nil, addr.SvcCS)
+		return ret, nil
 	}
 	destISD, err := store.chooseDestCSIsd(ctx, destination, topo)
 	if err != nil {
@@ -763,13 +764,8 @@ func (store *Store) ChooseServer(ctx context.Context, destination addr.IA) (net.
 		return nil, common.NewBasicError("Unable to find path to any core AS", err,
 			"isd", destISD)
 	}
-	a := &snet.Addr{
-		IA:      path.Destination(),
-		Host:    addr.NewSVCUDPAppAddr(addr.SvcCS),
-		Path:    path.Path(),
-		NextHop: path.OverlayNextHop(),
-	}
-	return a, nil
+	ret := snet.NewSVCAddr(path.Destination(), path.Path(), path.OverlayNextHop(), addr.SvcCS)
+	return ret, nil
 }
 
 // chooseDestCSIsd selects the CS to ask for crypto material, using the following strategy:
diff --git a/go/lib/infra/rpc/rpc.go b/go/lib/infra/rpc/rpc.go
index 2197f6026..9ff8f1c58 100644
--- a/go/lib/infra/rpc/rpc.go
+++ b/go/lib/infra/rpc/rpc.go
@@ -188,9 +188,8 @@ func (c *Client) sendRequest() error {
 // computeAddressStr returns a parseable version of the SCION address for use
 // with QUIC SNI.
 func computeAddressStr(address net.Addr) string {
-	snetAddr, ok := address.(*snet.Addr)
-	if ok {
-		return fmt.Sprintf("%s:%d", snetAddr.Host.L3.String(), snetAddr.Host.L4)
+	if v, ok := address.(*snet.UDPAddr); ok {
+		return fmt.Sprintf("%s:%d", v.Host.IP, v.Host.Port)
 	}
 	return address.String()
 }
diff --git a/go/lib/snet/BUILD.bazel b/go/lib/snet/BUILD.bazel
index 6e7b400c2..79562c811 100644
--- a/go/lib/snet/BUILD.bazel
+++ b/go/lib/snet/BUILD.bazel
@@ -13,6 +13,8 @@ go_library(
         "reader.go",
         "router.go",
         "snet.go",
+        "svcaddr.go",
+        "udpaddr.go",
         "writer.go",
     ],
     importpath = "github.com/scionproto/scion/go/lib/snet",
@@ -42,6 +44,8 @@ go_test(
         "export_test.go",
         "raw_test.go",
         "router_test.go",
+        "svcaddr_test.go",
+        "udpaddr_test.go",
         "writer_test.go",
     ],
     embed = [":go_default_library"],
diff --git a/go/lib/snet/addr.go b/go/lib/snet/addr.go
index 2b0a46187..7868a9f18 100644
--- a/go/lib/snet/addr.go
+++ b/go/lib/snet/addr.go
@@ -48,6 +48,27 @@ func (a *Addr) ToNetUDPAddr() *net.UDPAddr {
 	return nil
 }
 
+func newAddr(ia addr.IA, p *spath.Path, nh *net.UDPAddr) *Addr {
+	return &Addr{
+		IA:      ia,
+		Path:    p,
+		NextHop: nh,
+	}
+}
+
+// ToXAddr returns an snet.{SVC,UDP}addr.
+// TODO(karampok): Remove this once snet.Addr is removed.
+func (a *Addr) ToXAddr() net.Addr {
+	switch a.Host.L3.Type() {
+	case addr.HostTypeSVC:
+		return NewSVCAddr(a.IA, a.Path, a.NextHop, a.Host.L3.(addr.HostSVC))
+	case addr.HostTypeIPv4, addr.HostTypeIPv6:
+		u := &net.UDPAddr{IP: a.Host.L3.IP(), Port: int(a.Host.L4)}
+		return NewUDPAddr(a.IA, a.Path, a.NextHop, u)
+	}
+	return &net.TCPAddr{} //TODO(karampok). discuss: return nil
+}
+
 func (a *Addr) Network() string {
 	return "scion"
 }
diff --git a/go/lib/snet/addrutil/addrutil.go b/go/lib/snet/addrutil/addrutil.go
index 2f553412f..3e82cf120 100644
--- a/go/lib/snet/addrutil/addrutil.go
+++ b/go/lib/snet/addrutil/addrutil.go
@@ -49,10 +49,5 @@ func GetPath(svc addr.HostSVC, ps *seg.PathSegment, topoProv topology.Provider)
 	if !ok {
 		return nil, common.NewBasicError("unable to find first-hop BR for path", nil, "ifID", ifID)
 	}
-	return &snet.Addr{
-		IA:      ps.FirstIA(),
-		Host:    addr.NewSVCUDPAppAddr(svc),
-		Path:    p,
-		NextHop: overlayNextHop,
-	}, nil
+	return snet.NewSVCAddr(ps.FirstIA(), p, overlayNextHop, svc), nil
 }
diff --git a/go/lib/snet/svcaddr.go b/go/lib/snet/svcaddr.go
new file mode 100644
index 000000000..fc8371bd1
--- /dev/null
+++ b/go/lib/snet/svcaddr.go
@@ -0,0 +1,47 @@
+package snet
+
+import (
+	"fmt"
+	"net"
+
+	"github.com/scionproto/scion/go/lib/addr"
+	"github.com/scionproto/scion/go/lib/spath"
+)
+
+// SVCAddr is the address type for SVC destinations.
+type SVCAddr struct {
+	Addr
+
+	SVC addr.HostSVC
+}
+
+// NewSVCAddr returns a new instance of SVCAddr.
+// TODO(karampok). constructor to be removed once snet.Addr is removed.
+func NewSVCAddr(ia addr.IA, p *spath.Path, nh *net.UDPAddr, svc addr.HostSVC) *SVCAddr {
+	a := newAddr(ia, p, nh)
+	return &SVCAddr{*a, svc}
+}
+
+// Network implements net.Addr interface.
+func (a *SVCAddr) Network() string {
+	return "scion"
+}
+
+// String implements net.Addr interface.
+func (a *SVCAddr) String() string {
+	return fmt.Sprintf("%v,%v", a.IA, a.SVC)
+}
+
+// ToAddr returns a legacy snet.Addr.
+func (a *SVCAddr) ToAddr() *Addr {
+	ret := &Addr{
+		IA:      a.IA,
+		NextHop: CopyUDPAddr(a.NextHop),
+		Host:    &addr.AppAddr{L3: a.SVC},
+	}
+	if a.Path != nil {
+		ret.Path = a.Path.Copy()
+	}
+	return ret
+
+}
diff --git a/go/lib/snet/udpaddr.go b/go/lib/snet/udpaddr.go
new file mode 100644
index 000000000..15ff4e879
--- /dev/null
+++ b/go/lib/snet/udpaddr.go
@@ -0,0 +1,50 @@
+package snet
+
+import (
+	"fmt"
+	"net"
+
+	"github.com/scionproto/scion/go/lib/addr"
+	"github.com/scionproto/scion/go/lib/spath"
+)
+
+// UDPAddr to be used when UDP host.
+type UDPAddr struct {
+	Addr
+
+	Host *net.UDPAddr
+}
+
+// NewUDPAddr returns an instance of UDPAddr.
+// TODO(karampok). constructor to be removed once snet.Addr is removed.
+func NewUDPAddr(ia addr.IA, p *spath.Path, nh *net.UDPAddr, t *net.UDPAddr) *UDPAddr {
+	a := newAddr(ia, p, nh)
+	return &UDPAddr{*a, t}
+}
+
+// Network implements net.Addr interface.
+func (a *UDPAddr) Network() string {
+	return "scion"
+}
+
+// String implements net.Addr interface.
+func (a *UDPAddr) String() string {
+	return fmt.Sprintf("%v,[%v]:%v", a.IA, a.Host.IP, a.Host.Port)
+}
+
+// ToAddr returns a legacy snet.Addr.
+func (a *UDPAddr) ToAddr() *Addr {
+	ret := &Addr{
+		IA:      a.IA,
+		NextHop: CopyUDPAddr(a.NextHop),
+		Host: &addr.AppAddr{
+			L3: addr.HostFromIP(a.Host.IP),
+			L4: uint16(a.Host.Port),
+		},
+	}
+	if a.Path != nil {
+		ret.Path = a.Path.Copy()
+	}
+	return ret
+
+}
diff --git a/go/lib/snet/writer.go b/go/lib/snet/writer.go
index 1aa5f1e1e..72ac90768 100644
--- a/go/lib/snet/writer.go
+++ b/go/lib/snet/writer.go
@@ -17,6 +17,7 @@ package snet
 
 import (
 	"context"
+	"fmt"
 	"net"
 	"sync"
 	"time"
@@ -76,12 +77,18 @@ func (c *scionConnWriter) WriteToSCION(b []byte, raddr *Addr) (int, error) {
 	return c.write(b, raddr)
 }
 
-func (c *scionConnWriter) WriteTo(b []byte, raddr net.Addr) (int, error) {
-	sraddr, ok := raddr.(*Addr)
-	if !ok {
-		return 0, common.NewBasicError("Unable to write to non-SCION address", nil, "addr", raddr)
+func (c *scionConnWriter) WriteTo(b []byte, a net.Addr) (int, error) {
+	switch addr := a.(type) {
+	case *Addr:
+		return c.WriteToSCION(b, addr)
+	case *UDPAddr:
+		return c.WriteToSCION(b, addr.ToAddr())
+	case *SVCAddr:
+		return c.WriteToSCION(b, addr.ToAddr())
+	default:
+		return 0, common.NewBasicError("Unable to write to non-SCION address", nil,
+			"addr", fmt.Sprintf("%v(%T)", a, a))
 	}
-	return c.WriteToSCION(b, sraddr)
 }
 
 // Write sends b through a connection with fixed remote address. If the remote
diff --git a/go/path_srv/internal/cryptosyncer/cryptosyncer.go b/go/path_srv/internal/cryptosyncer/cryptosyncer.go
index aae14b255..0ac4db796 100644
--- a/go/path_srv/internal/cryptosyncer/cryptosyncer.go
+++ b/go/path_srv/internal/cryptosyncer/cryptosyncer.go
@@ -49,7 +49,7 @@ func (c *Syncer) Run(ctx context.Context) {
 		logger.Error("[cryptosyncer.Syncer] Failed to read TRCs", "err", err)
 		return
 	}
-	cs := &snet.Addr{IA: c.IA, Host: addr.NewSVCUDPAppAddr(addr.SvcCS)}
+	cs := snet.NewSVCAddr(c.IA, nil, nil, addr.SvcCS)
 	trcCount := c.sendTRCs(ctx, trcChan, cs)
 	chainChan, err := c.DB.GetAllChains(ctx)
 	if err != nil {
diff --git a/go/path_srv/internal/handlers/segreg.go b/go/path_srv/internal/handlers/segreg.go
index 154ea4d06..2ea52203c 100644
--- a/go/path_srv/internal/handlers/segreg.go
+++ b/go/path_srv/internal/handlers/segreg.go
@@ -92,12 +92,8 @@ func (h *segRegHandler) Handle() *infra.HandlerResult {
 		sendAck(proto.Ack_ErrCode_reject, messenger.AckRejectFailedToParse)
 		return infra.MetricsErrInvalid
 	}
-	svcToQuery := &snet.Addr{
-		IA:      snetPeer.IA,
-		Path:    peerPath.Path(),
-		NextHop: peerPath.OverlayNextHop(),
-		Host:    addr.NewSVCUDPAppAddr(addr.SvcBS),
-	}
+	svcToQuery := snet.NewSVCAddr(snetPeer.IA, peerPath.Path(),
+		peerPath.OverlayNextHop(), addr.SvcBS)
 	segs := seghandler.Segments{
 		Segs:      segReg.Recs,
 		SRevInfos: segReg.SRevInfos,
diff --git a/go/path_srv/internal/handlers/segsync.go b/go/path_srv/internal/handlers/segsync.go
index 47f6e46e4..7d454453d 100644
--- a/go/path_srv/internal/handlers/segsync.go
+++ b/go/path_srv/internal/handlers/segsync.go
@@ -89,12 +89,9 @@ func (h *syncHandler) Handle() *infra.HandlerResult {
 		metrics.Sync.Registrations(labels.WithResult(metrics.ErrParse)).Inc()
 		return infra.MetricsErrInvalid
 	}
-	svcToQuery := &snet.Addr{
-		IA:      snetPeer.IA,
-		Path:    peerPath.Path(),
-		NextHop: peerPath.OverlayNextHop(),
-		Host:    addr.NewSVCUDPAppAddr(addr.SvcPS),
-	}
+	svcToQuery := snet.NewSVCAddr(snetPeer.IA, peerPath.Path(),
+		peerPath.OverlayNextHop(), addr.SvcPS)
+
 	segs := seghandler.Segments{
 		Segs:      segSync.Recs,
 		SRevInfos: segSync.SRevInfos,
diff --git a/go/sciond/internal/fetcher/fetcher.go b/go/sciond/internal/fetcher/fetcher.go
index b6993c39a..9dfe608c6 100644
--- a/go/sciond/internal/fetcher/fetcher.go
+++ b/go/sciond/internal/fetcher/fetcher.go
@@ -322,7 +322,7 @@ type dstProvider struct {
 }
 
 func (r *dstProvider) Dst(_ context.Context, _ segfetcher.Request) (net.Addr, error) {
-	return &snet.Addr{IA: r.IA, Host: addr.NewSVCUDPAppAddr(addr.SvcPS)}, nil
+	return snet.NewSVCAddr(r.IA, nil, nil, addr.SvcPS), nil
 }
 
 type neverLocal struct{}


Test patch:
diff --git a/go/beacon_srv/internal/beaconing/registrar_test.go b/go/beacon_srv/internal/beaconing/registrar_test.go
index a04f7c732..3e021ae33 100644
--- a/go/beacon_srv/internal/beaconing/registrar_test.go
+++ b/go/beacon_srv/internal/beaconing/registrar_test.go
@@ -125,7 +125,7 @@ func TestRegistrarRun(t *testing.T) {
 				})
 			type regMsg struct {
 				Reg  *path_mgmt.SegReg
-				Addr *snet.Addr
+				Addr *snet.SVCAddr
 			}
 			segMu := sync.Mutex{}
 			var sent []regMsg
@@ -137,7 +137,7 @@ func TestRegistrarRun(t *testing.T) {
 					defer segMu.Unlock()
 					sent = append(sent, regMsg{
 						Reg:  isegreg.(*path_mgmt.SegReg),
-						Addr: iaddr.(*snet.Addr),
+						Addr: iaddr.(*snet.SVCAddr),
 					})
 					return nil
 				},
@@ -165,12 +165,11 @@ func TestRegistrarRun(t *testing.T) {
 				Convey(fmt.Sprintf("Segment %d is sent to the PS", segIdx), func() {
 					if !test.remotePS {
 						SoMsg("IA", s.Addr.IA, ShouldResemble, topoProvider.Get().IA())
-						a := addr.NewSVCUDPAppAddr(addr.SvcPS)
-						SoMsg("Host", s.Addr.Host, ShouldResemble, a)
+						SoMsg("Host", s.Addr.SVC, ShouldResemble, addr.SvcPS)
 						return
 					}
 					SoMsg("IA", s.Addr.IA, ShouldResemble, pseg.FirstIA())
-					SoMsg("Host", s.Addr.Host.L3, ShouldResemble, addr.SvcPS)
+					SoMsg("Host", s.Addr.SVC, ShouldResemble, addr.SvcPS)
 					hopF, err := s.Addr.Path.GetHopField(s.Addr.Path.HopOff)
 					SoMsg("err", err, ShouldBeNil)
 					SoMsg("HopField", []uint8(hopF.Pack()), ShouldResemble,
diff --git a/go/beacon_srv/internal/ifstate/pusher_test.go b/go/beacon_srv/internal/ifstate/pusher_test.go
index d5078cebb..acc96dd0d 100644
--- a/go/beacon_srv/internal/ifstate/pusher_test.go
+++ b/go/beacon_srv/internal/ifstate/pusher_test.go
@@ -59,7 +59,7 @@ func TestPusherPush(t *testing.T) {
 				for _, br := range topoProvider.Get().BRNames() {
 					a := topoProvider.Get().SBRAddress(br)
 					msgr.EXPECT().SendIfStateInfos(gomock.Any(), gomock.Eq(expectedMsg),
-						gomock.Eq(a), gomock.Any())
+						gomock.Eq(a.ToXAddr()), gomock.Any())
 				}
 			}
 			p.Push(context.Background(), 101)
diff --git a/go/beacon_srv/internal/ifstate/revoker_test.go b/go/beacon_srv/internal/ifstate/revoker_test.go
index 67a8db056..da93dcd82 100644
--- a/go/beacon_srv/internal/ifstate/revoker_test.go
+++ b/go/beacon_srv/internal/ifstate/revoker_test.go
@@ -264,17 +264,16 @@ func expectMessengerCalls(msger *mock_infra.MockMessenger,
 				ShouldEqual, len(topoProvider.Get().BRNames()))
 			sentBRs := expectedBRs(topoProvider)
 			for _, brMsg := range brMsgs {
-				brName := brId(t, topoProvider, brMsg.a.(*snet.Addr))
+				brName := brId(t, topoProvider, brMsg.a.(*snet.UDPAddr))
 				delete(sentBRs, brName)
 				checkBRMessage(t, brName, brMsg.msg, revokedIfId, verifier, topoProvider)
 			}
 			SoMsg("Should have sent to all brs", sentBRs, ShouldBeEmpty)
 		})
 		Convey("Check sent PS message", func() {
-			saddr := psAddr.(*snet.Addr)
+			saddr := psAddr.(*snet.SVCAddr)
 			SoMsg("Should send to local IA", saddr.IA, ShouldResemble, ia)
-			a := addr.NewSVCUDPAppAddr(addr.SvcPS)
-			SoMsg("Should send to PS", saddr.Host, ShouldResemble, a)
+			SoMsg("Should send to PS", saddr.SVC, ShouldResemble, addr.SvcPS)
 			checkRevocation(t, psMsg, revokedIfId, verifier, topoProvider)
 		})
 	}
@@ -320,13 +319,15 @@ func checkInterfaces(intfs *Interfaces, nonActive map[common.IFIDType]State) {
 	})
 }
 
-func brId(t *testing.T, topoProvider topology.Provider, saddr *snet.Addr) string {
+func brId(t *testing.T, topoProvider topology.Provider, saddr *snet.UDPAddr) string {
 	topo := topoProvider.Get()
 	for _, brID := range topo.BRNames() {
-		if topo.SBRAddress(brID).Host.Equal(saddr.Host) {
+		leg := topo.SBRAddress(brID)
+		if leg.Host.L3.String() == saddr.Host.IP.String() && leg.IA == saddr.IA {
 			return brID
 		}
 	}
+
 	t.Fatalf("Didn't find br ID for %s", saddr.Host)
 	return "" // meh, makes the compiler happy.
 }
diff --git a/go/lib/infra/messenger/addr_test.go b/go/lib/infra/messenger/addr_test.go
index 8564e9b04..9b4fd3b19 100644
--- a/go/lib/infra/messenger/addr_test.go
+++ b/go/lib/infra/messenger/addr_test.go
@@ -36,6 +36,7 @@ import (
 )
 
 func TestRedirectQUIC(t *testing.T) {
+	dummyIA := xtest.MustParseIA("1-ff00:0:2")
 	testCases := map[string]struct {
 		input                 net.Addr
 		wantAddr              net.Addr
@@ -48,9 +49,9 @@ func TestRedirectQUIC(t *testing.T) {
 			wantAddr:  nil,
 			assertErr: assert.NoError,
 		},
-		"disabling SVC resolution, returns unchanged": {
-			input:                 &snet.Addr{Host: newSVCAppAddr(addr.SvcBS)},
-			wantAddr:              &snet.Addr{Host: newSVCAppAddr(addr.SvcBS)},
+		"disabling SVC resolution, ": {
+			input:                 &snet.SVCAddr{},
+			wantAddr:              &snet.SVCAddr{},
 			SVCResolutionFraction: 0.0,
 			assertErr:             assert.NoError,
 		},
@@ -61,22 +62,11 @@ func TestRedirectQUIC(t *testing.T) {
 			SVCResolutionFraction: 1.0,
 			assertErr:             assert.Error,
 		},
-		"valid empty UDP, ?error": {
-			input:                 newUDPAppAddr(&net.UDPAddr{}),
-			wantAddr:              nil,
-			wantRedirect:          false,
-			SVCResolutionFraction: 1.0,
-			assertErr:             assert.Error,
-		},
 		"valid UDPAddr, returns unchanged": {
-			input: &snet.Addr{
-				Host: newUDPAppAddr(&net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1}),
-			},
-			wantAddr: &snet.Addr{
-				Host: newUDPAppAddr(&net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1}),
-			},
-			SVCResolutionFraction: 1.0,
+			input:                 snet.NewUDPAddr(dummyIA, nil, nil, &net.UDPAddr{}),
+			wantAddr:              snet.NewUDPAddr(dummyIA, nil, nil, &net.UDPAddr{}),
 			wantRedirect:          false,
+			SVCResolutionFraction: 1.0,
 			assertErr:             assert.NoError,
 		},
 	}
@@ -101,53 +91,68 @@ func TestRedirectQUIC(t *testing.T) {
 			assert.Equal(t, r, tc.wantRedirect)
 		})
 	}
-}
 
-func TestBuildFullAddress(t *testing.T) {
-	testCases := map[string]struct {
-		input     net.Addr
-		want      net.Addr
-		assertErr assert.ErrorAssertionFunc
-	}{
-		"non-snet address": {
-			input:     &net.UDPAddr{},
-			assertErr: assert.Error,
-		},
-		"snet address without host": {
-			input:     &snet.Addr{},
-			assertErr: assert.Error,
-		},
-		"snet address without L3": {
-			input:     &snet.Addr{Host: &addr.AppAddr{}},
-			assertErr: assert.Error,
-		},
-		"snet address with bad L3 type": {
-			input: &snet.Addr{
-				Host: &addr.AppAddr{
-					L3: &addr.HostNone{},
-					L4: 5,
-				},
-			},
-			assertErr: assert.Error,
-		},
-	}
-	for tn, tc := range testCases {
-		t.Run(tn, func(t *testing.T) {
-			ctrl := gomock.NewController(t)
-			defer ctrl.Finish()
-			router := mock_snet.NewMockRouter(ctrl)
-			resolver := mock_messenger.NewMockResolver(ctrl)
-			aw := messenger.AddressRewriter{
-				Resolver: resolver,
-				Router:   router,
-			}
+	t.Run("Fraction 0.5, returns no error", func(t *testing.T) {
+		t.Log("svc address, half time allowed for resolution, lookup fails")
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		router := mock_snet.NewMockRouter(ctrl)
+		resolver := mock_messenger.NewMockResolver(ctrl)
+		resolver.EXPECT().LookupSVC(gomock.Any(), gomock.Any(), gomock.Any()).
+			Return(nil, fmt.Errorf("lookups errors"))
+		path := mock_snet.NewMockPath(ctrl)
+		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
+		path.EXPECT().Path().Return(nil)
+		path.EXPECT().OverlayNextHop().Return(&net.UDPAddr{IP: net.ParseIP("10.1.1.1")})
+		path.EXPECT().Fingerprint().Return(snet.PathFingerprint("foo"))
 
-			a, err := aw.BuildFullAddress(context.Background(), tc.input)
-			assert.Equal(t, a, tc.want)
-			tc.assertErr(t, err)
-		})
-	}
+		aw := messenger.AddressRewriter{
+			Router:                router,
+			Resolver:              resolver,
+			SVCResolutionFraction: 0.5,
+		}
+
+		input := snet.NewSVCAddr(dummyIA, nil, nil, addr.SvcBS)
+		want := snet.NewSVCAddr(dummyIA, nil, &net.UDPAddr{IP: net.ParseIP("10.1.1.1")}, addr.SvcBS)
+		a, r, err := aw.RedirectToQUIC(context.Background(), input)
+		assert.NoError(t, err)
+		assert.Equal(t, a, want)
+		assert.False(t, r)
+	})
+
+	t.Run("valid SVCAddr, returns no error and UPDAddr", func(t *testing.T) {
+		ctrl := gomock.NewController(t)
+		defer ctrl.Finish()
+		router := mock_snet.NewMockRouter(ctrl)
+		resolver := mock_messenger.NewMockResolver(ctrl)
+		resolver.EXPECT().LookupSVC(gomock.Any(), gomock.Any(), gomock.Any()).
+			Return(&svc.Reply{
+				Transports: map[svc.Transport]string{svc.QUIC: "192.168.1.1:8000"},
+				ReturnPath: &testPath{},
+			}, nil)
+		path := mock_snet.NewMockPath(ctrl)
+		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
+		path.EXPECT().Path().Return(nil)
+		path.EXPECT().OverlayNextHop().Return(&net.UDPAddr{IP: net.ParseIP("10.1.1.1")})
+		path.EXPECT().Fingerprint().Return(snet.PathFingerprint("foo"))
 
+		aw := messenger.AddressRewriter{
+			Router:                router,
+			Resolver:              resolver,
+			SVCResolutionFraction: 1,
+		}
+
+		input := snet.NewSVCAddr(dummyIA, nil, nil, addr.SvcBS)
+		want := snet.NewUDPAddr(dummyIA, &spath.Path{}, &net.UDPAddr{IP: net.ParseIP("10.1.1.1")},
+			&net.UDPAddr{IP: net.ParseIP("192.168.1.1"), Port: 8000})
+		a, r, err := aw.RedirectToQUIC(context.Background(), input)
+		assert.NoError(t, err)
+		assert.Equal(t, a, want)
+		assert.True(t, r)
+	})
+}
+
+func TestBuildFullAddress(t *testing.T) {
 	t.Run("snet address without path, error retrieving path", func(t *testing.T) {
 		ctrl := gomock.NewController(t)
 		defer ctrl.Finish()
@@ -158,11 +163,7 @@ func TestBuildFullAddress(t *testing.T) {
 			Router:    router,
 			SVCRouter: svcRouter,
 		}
-
-		input := &snet.Addr{
-			IA:   remoteIA,
-			Host: newSVCAppAddr(addr.SvcBS),
-		}
+		input := snet.NewSVCAddr(remoteIA, nil, nil, addr.SvcBS)
 		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("err"))
 		_, err := aw.BuildFullAddress(context.Background(), input)
 		assert.Error(t, err)
@@ -179,11 +180,7 @@ func TestBuildFullAddress(t *testing.T) {
 			SVCRouter: svcRouter,
 		}
 
-		input := &snet.Addr{
-			IA:   remoteIA,
-			Host: newSVCAppAddr(addr.SvcBS),
-			Path: &spath.Path{},
-		}
+		input := snet.NewSVCAddr(remoteIA, &spath.Path{}, nil, addr.SvcBS)
 		a, err := aw.BuildFullAddress(context.Background(), input)
 		assert.Equal(t, a, input)
 		assert.NoError(t, err)
@@ -205,17 +202,10 @@ func TestBuildFullAddress(t *testing.T) {
 		path.EXPECT().OverlayNextHop().Return(&net.UDPAddr{})
 		path.EXPECT().Fingerprint().Return(snet.PathFingerprint("foo"))
 		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
-		input := &snet.Addr{
-			IA:   remoteIA,
-			Host: newSVCAppAddr(addr.SvcBS),
-		}
+		input := snet.NewSVCAddr(remoteIA, nil, nil, addr.SvcBS)
 		a, err := aw.BuildFullAddress(context.Background(), input)
-		assert.Equal(t, a, &snet.Addr{
-			IA:      remoteIA,
-			Host:    newSVCAppAddr(addr.SvcBS),
-			Path:    &spath.Path{},
-			NextHop: &net.UDPAddr{},
-		})
+		want := snet.NewSVCAddr(remoteIA, &spath.Path{}, &net.UDPAddr{}, addr.SvcBS)
+		assert.Equal(t, a, want)
 		assert.NoError(t, err)
 	})
 
@@ -242,17 +232,11 @@ func TestBuildFullAddress(t *testing.T) {
 		path.EXPECT().OverlayNextHop()
 		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
 
-		input := &snet.Addr{
-			IA:   localIA,
-			Host: newSVCAppAddr(addr.SvcBS),
-		}
-
+		input := snet.NewSVCAddr(localIA, nil, nil, addr.SvcBS)
 		a, err := aw.BuildFullAddress(context.Background(), input)
-		assert.Equal(t, a, &snet.Addr{
-			IA:      localIA,
-			Host:    newSVCAppAddr(addr.SvcBS),
-			NextHop: overlayAddr,
-		})
+
+		want := snet.NewSVCAddr(localIA, nil, overlayAddr, addr.SvcBS)
+		assert.Equal(t, a, want)
 		assert.NoError(t, err)
 	})
 
@@ -275,11 +259,7 @@ func TestBuildFullAddress(t *testing.T) {
 		path.EXPECT().OverlayNextHop()
 		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
 
-		input := &snet.Addr{
-			IA:   localIA,
-			Host: newSVCAppAddr(addr.SvcBS),
-		}
-
+		input := snet.NewSVCAddr(localIA, nil, nil, addr.SvcBS)
 		a, err := aw.BuildFullAddress(context.Background(), input)
 		assert.Nil(t, a)
 		assert.Error(t, err)
@@ -305,16 +285,6 @@ func TestResolve(t *testing.T) {
 			SVCResolutionFraction: 1.0,
 			assertErr:             assert.Error,
 		},
-		"svc address, half time allowed for resolution, lookup fails": {
-			input: addr.SvcBS,
-			ResolverSetup: func(r *mock_messenger.MockResolver) {
-				r.EXPECT().LookupSVC(gomock.Any(), gomock.Any(), gomock.Any()).
-					Return(nil, fmt.Errorf("err"))
-			},
-			SVCResolutionFraction: 0.5,
-			want:                  nil,
-			assertErr:             assert.NoError,
-		},
 		"svc address, lookup succeeds": {
 			input: addr.SvcBS,
 			ResolverSetup: func(r *mock_messenger.MockResolver) {
@@ -450,7 +420,7 @@ func (t *testPath) OverlayNextHop() *net.UDPAddr {
 }
 
 func (t *testPath) Path() *spath.Path {
-	panic("not implemented")
+	return &spath.Path{}
 }
 
 func (t *testPath) Interfaces() []snet.PathInterface {
@@ -472,15 +442,3 @@ func (t *testPath) Expiry() time.Time {
 func (t *testPath) Copy() snet.Path {
 	panic("not implemented")
 }
-
-func newSVCAppAddr(svc addr.HostSVC) *addr.AppAddr {
-	return &addr.AppAddr{
-		L3: svc,
-	}
-}
-func newUDPAppAddr(u *net.UDPAddr) *addr.AppAddr {
-	return &addr.AppAddr{
-		L3: addr.HostFromIP(u.IP),
-		L4: uint16(u.Port),
-	}
-}
diff --git a/go/lib/infra/messenger/export_test.go b/go/lib/infra/messenger/export_test.go
index ececfa239..b3e170bda 100644
--- a/go/lib/infra/messenger/export_test.go
+++ b/go/lib/infra/messenger/export_test.go
@@ -9,7 +9,8 @@ import (
 	"github.com/scionproto/scion/go/lib/svc"
 )
 
-func (r AddressRewriter) BuildFullAddress(ctx context.Context, a net.Addr) (net.Addr, error) {
+func (r AddressRewriter) BuildFullAddress(ctx context.Context,
+	a *snet.SVCAddr) (*snet.SVCAddr, error) {
 	return r.buildFullAddress(ctx, a)
 }
 
diff --git a/go/lib/snet/svcaddr_test.go b/go/lib/snet/svcaddr_test.go
new file mode 100644
index 000000000..d2356da3d
--- /dev/null
+++ b/go/lib/snet/svcaddr_test.go
@@ -0,0 +1,34 @@
+package snet_test
+
+import (
+	"net"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/scionproto/scion/go/lib/snet"
+)
+
+func TestSVCAddrInterface(t *testing.T) {
+	var x interface{} = &snet.SVCAddr{}
+	_, ok := x.(net.Addr)
+	assert.True(t, ok, "should implement net interface")
+}
+
+func TestSVCAddrString(t *testing.T) {
+	tests := map[string]struct {
+		input *snet.SVCAddr
+		want  string
+	}{
+		"nil": {
+			input: &snet.SVCAddr{},
+			want:  "0-0,BS A (0x0000)",
+		},
+	}
+	for n, tc := range tests {
+		t.Run(n, func(t *testing.T) {
+			a := tc.input.String()
+			assert.Equal(t, a, tc.want)
+		})
+	}
+}
diff --git a/go/lib/snet/udpaddr_test.go b/go/lib/snet/udpaddr_test.go
new file mode 100644
index 000000000..4552913a4
--- /dev/null
+++ b/go/lib/snet/udpaddr_test.go
@@ -0,0 +1,34 @@
+package snet_test
+
+import (
+	"net"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/scionproto/scion/go/lib/snet"
+)
+
+func TestUDPAddrInterface(t *testing.T) {
+	var x interface{} = &snet.UDPAddr{}
+	_, ok := x.(net.Addr)
+	assert.True(t, ok, "should implement net interface")
+}
+
+func TestUDPAddrString(t *testing.T) {
+	tests := map[string]struct {
+		input *snet.UDPAddr
+		want  string
+	}{
+		"empty": {
+			input: &snet.UDPAddr{Host: &net.UDPAddr{}},
+			want:  "0-0,[<nil>]:0",
+		},
+	}
+	for n, tc := range tests {
+		t.Run(n, func(t *testing.T) {
+			a := tc.input.String()
+			assert.Equal(t, a, tc.want)
+		})
+	}
+}
diff --git a/go/lib/xtest/matchers/matchers.go b/go/lib/xtest/matchers/matchers.go
index dcf091ada..0ebda4a5f 100644
--- a/go/lib/xtest/matchers/matchers.go
+++ b/go/lib/xtest/matchers/matchers.go
@@ -44,7 +44,7 @@ func IsSnetAddrWithIA(ia addr.IA) gomock.Matcher {
 }
 
 func (m *addrIAMatcher) Matches(x interface{}) bool {
-	sAddr, ok := x.(*snet.Addr)
+	sAddr, ok := x.(*snet.SVCAddr)
 	if !ok {
 		return false
 	}