Repository: scionproto/scion
Pull request #3839 (2020-07-28 09:21:47)

snet: move MTU/Expiry to separate snet.PathMetadata interface
Add additional interface for (optional) path metadata on snet.Path.
This new snet.PathMetadata interface will be extended to add support for
the information in the StaticInfo beacon extension (Geo information,
Latency, Bandwidth, etc) as a next step.

Having a separate interface for the metadata clarifies that some
instances of snet.Path (e.g. the `partialPath` constructed from raw
forwarding information) do not have any of this metadata available.
Thus, moving the existing MTU and Expiry fields to this new interface
feels quite natural.

Additionally, remove `Fingerprint` from the `snet.Path` interface.
`Fingerprint` can be implemented in terms of snet.Path, so it does not
need to be part of the interface. Make this a simple helper function 
in `snet` instead.


<!-- 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/3839)
<!-- Reviewable:end -->


matzf: Related to #3779.
matzf: /rebase
matzf: /rebase
matzf: /rebase
matzf: /rebase

Patch:
diff --git a/go/cs/segutil/router.go b/go/cs/segutil/router.go
index c54c9a6b7..8c7532635 100644
--- a/go/cs/segutil/router.go
+++ b/go/cs/segutil/router.go
@@ -17,8 +17,6 @@ package segutil
 import (
 	"bytes"
 	"context"
-	"crypto/sha256"
-	"encoding/binary"
 	"fmt"
 	"net"
 	"strings"
@@ -95,8 +93,10 @@ func (r *Router) translate(comb *combinator.Path, dst addr.IA) (path, error) {
 		interfaces: make([]pathInterface, 0, len(comb.Interfaces)),
 		underlay:   nextHop,
 		spath:      sp,
-		mtu:        comb.Mtu,
-		expiry:     comb.ComputeExpTime(),
+		metadata: pathMetadata{
+			mtu:    comb.Mtu,
+			expiry: comb.ComputeExpTime(),
+		},
 	}
 	for _, intf := range comb.Interfaces {
 		p.interfaces = append(p.interfaces, pathInterface{ia: intf.IA(), ifid: intf.ID()})
@@ -108,21 +108,13 @@ type path struct {
 	interfaces []pathInterface
 	underlay   *net.UDPAddr
 	spath      *spath.Path
-	mtu        uint16
-	expiry     time.Time
 	dst        addr.IA
+	metadata   pathMetadata
 }
 
-func (p path) Fingerprint() snet.PathFingerprint {
-	if len(p.interfaces) == 0 {
-		return ""
-	}
-	h := sha256.New()
-	for _, intf := range p.interfaces {
-		binary.Write(h, binary.BigEndian, intf.IA().IAInt())
-		binary.Write(h, binary.BigEndian, intf.ID())
-	}
-	return snet.PathFingerprint(h.Sum(nil))
+type pathMetadata struct {
+	mtu    uint16
+	expiry time.Time
 }
 
 func (p path) UnderlayNextHop() *net.UDPAddr {
@@ -161,12 +153,8 @@ func (p path) Destination() addr.IA {
 	return p.interfaces[len(p.interfaces)-1].IA()
 }
 
-func (p path) MTU() uint16 {
-	return p.mtu
-}
-
-func (p path) Expiry() time.Time {
-	return p.expiry
+func (p path) Metadata() snet.PathMetadata {
+	return p.metadata
 }
 
 func (p path) Copy() snet.Path {
@@ -174,15 +162,14 @@ func (p path) Copy() snet.Path {
 		interfaces: append(p.interfaces[:0:0], p.interfaces...),
 		underlay:   p.UnderlayNextHop(), // creates copy
 		spath:      p.Path(),            // creates copy
-		mtu:        p.mtu,
-		expiry:     p.expiry,
+		metadata:   p.metadata,
 	}
 }
 
 func (p path) String() string {
 	hops := p.fmtInterfaces()
 	return fmt.Sprintf("Hops: [%s] MTU: %d, NextHop: %s",
-		strings.Join(hops, ">"), p.mtu, p.underlay)
+		strings.Join(hops, ">"), p.Metadata().MTU(), p.UnderlayNextHop())
 }
 
 func (p path) fmtInterfaces() []string {
@@ -201,3 +188,11 @@ func (p path) fmtInterfaces() []string {
 	hops = append(hops, fmt.Sprintf("%d %s", intf.ID(), intf.IA()))
 	return hops
 }
+
+func (m pathMetadata) MTU() uint16 {
+	return m.mtu
+}
+
+func (m pathMetadata) Expiry() time.Time {
+	return m.expiry
+}
diff --git a/go/lib/infra/messenger/addr.go b/go/lib/infra/messenger/addr.go
index 787ce2247..fdc0ffda5 100644
--- a/go/lib/infra/messenger/addr.go
+++ b/go/lib/infra/messenger/addr.go
@@ -150,7 +150,7 @@ func (r AddressRewriter) buildFullAddress(ctx context.Context,
 	}()
 
 	// SVC addresses in the local AS get resolved via topology lookup
-	if p.Fingerprint() == "" { //when local AS
+	if len(p.Interfaces()) == 0 { //when local AS
 		ov, err := r.SVCRouter.GetUnderlay(s.SVC)
 		if err != nil {
 			return nil, common.NewBasicError("Unable to resolve underlay", err)
diff --git a/go/lib/pathmgr/pathmgr.go b/go/lib/pathmgr/pathmgr.go
index b2c8c846b..8481306de 100644
--- a/go/lib/pathmgr/pathmgr.go
+++ b/go/lib/pathmgr/pathmgr.go
@@ -286,8 +286,8 @@ func apsToPs(aps spathmeta.AppPathSet) pathpol.PathSet {
 
 func psToAps(ps pathpol.PathSet) spathmeta.AppPathSet {
 	aps := make(spathmeta.AppPathSet)
-	for _, path := range ps {
-		aps[path.Fingerprint()] = path.(snet.Path)
+	for key, path := range ps {
+		aps[key] = path.(snet.Path)
 	}
 	return aps
 }
diff --git a/go/lib/pathmgr/syncpaths.go b/go/lib/pathmgr/syncpaths.go
index 49d8450c9..9377f6fcc 100644
--- a/go/lib/pathmgr/syncpaths.go
+++ b/go/lib/pathmgr/syncpaths.go
@@ -104,9 +104,9 @@ func (sp *SyncPaths) Destroy() {
 
 func setSubtract(x, y spathmeta.AppPathSet) spathmeta.AppPathSet {
 	result := make(spathmeta.AppPathSet)
-	for _, ap := range x {
-		if _, ok := y[ap.Fingerprint()]; !ok {
-			result.Add(ap)
+	for key, ap := range x {
+		if _, ok := y[key]; !ok {
+			result[key] = ap
 		}
 	}
 	return result
diff --git a/go/lib/pathpol/pathset.go b/go/lib/pathpol/pathset.go
index eb7eb2643..56b89af9f 100644
--- a/go/lib/pathpol/pathset.go
+++ b/go/lib/pathpol/pathset.go
@@ -25,6 +25,4 @@ type PathSet map[snet.PathFingerprint]Path
 type Path interface {
 	// Interfaces returns all the interfaces of this path.
 	Interfaces() []snet.PathInterface
-	// Returns a string that uniquely identifies this path.
-	Fingerprint() snet.PathFingerprint
 }
diff --git a/go/lib/sciond/apitypes.go b/go/lib/sciond/apitypes.go
index 77c934812..bb956c4aa 100644
--- a/go/lib/sciond/apitypes.go
+++ b/go/lib/sciond/apitypes.go
@@ -16,8 +16,6 @@ package sciond
 
 import (
 	"context"
-	"crypto/sha256"
-	"encoding/binary"
 	"fmt"
 	"net"
 	"strings"
@@ -101,13 +99,13 @@ func ifinfoReplyToMap(ifinfoReply *IFInfoReply) map[common.IFIDType]*net.UDPAddr
 	return m
 }
 
-type Path struct {
+// path implements snet.Path for paths obtained from sciond
+type path struct {
 	interfaces []pathInterface
 	underlay   *net.UDPAddr
 	spath      *spath.Path
-	mtu        uint16
-	expiry     time.Time
 	dst        addr.IA
+	metadata   pathMetadata
 }
 
 func pathReplyToPaths(pathReply *PathReply, dst addr.IA) ([]snet.Path, error) {
@@ -125,12 +123,11 @@ func pathReplyToPaths(pathReply *PathReply, dst addr.IA) ([]snet.Path, error) {
 	return paths, nil
 }
 
-func pathReplyEntryToPath(pe PathReplyEntry, dst addr.IA) (Path, error) {
+func pathReplyEntryToPath(pe PathReplyEntry, dst addr.IA) (path, error) {
 	if len(pe.Path.Interfaces) == 0 {
-		return Path{
-			dst:    dst,
-			mtu:    pe.Path.Mtu,
-			expiry: pe.Path.Expiry(),
+		return path{
+			dst:      dst,
+			metadata: pathReplyEntryToMetadata(pe),
 		}, nil
 	}
 
@@ -138,19 +135,18 @@ func pathReplyEntryToPath(pe PathReplyEntry, dst addr.IA) (Path, error) {
 	if !pe.Path.HeaderV2 {
 		sp = spath.New(pe.Path.FwdPath)
 		if err := sp.InitOffsets(); err != nil {
-			return Path{}, serrors.WrapStr("path error", err)
+			return path{}, serrors.WrapStr("path error", err)
 		}
 	} else {
 		sp = spath.NewV2(pe.Path.FwdPath, false)
 	}
 
 	underlayAddr := pe.HostInfo.Underlay()
-	p := Path{
+	p := path{
 		interfaces: make([]pathInterface, 0, len(pe.Path.Interfaces)),
 		underlay:   underlayAddr,
 		spath:      sp,
-		mtu:        pe.Path.Mtu,
-		expiry:     pe.Path.Expiry(),
+		metadata:   pathReplyEntryToMetadata(pe),
 	}
 	for _, intf := range pe.Path.Interfaces {
 		p.interfaces = append(p.interfaces, pathInterface{ia: intf.IA(), id: intf.ID()})
@@ -158,19 +154,14 @@ func pathReplyEntryToPath(pe PathReplyEntry, dst addr.IA) (Path, error) {
 	return p, nil
 }
 
-func (p Path) Fingerprint() snet.PathFingerprint {
-	if len(p.interfaces) == 0 {
-		return ""
-	}
-	h := sha256.New()
-	for _, intf := range p.interfaces {
-		binary.Write(h, binary.BigEndian, intf.IA().IAInt())
-		binary.Write(h, binary.BigEndian, intf.ID())
+func pathReplyEntryToMetadata(pe PathReplyEntry) pathMetadata {
+	return pathMetadata{
+		mtu:    pe.Path.Mtu,
+		expiry: pe.Path.Expiry(),
 	}
-	return snet.PathFingerprint(h.Sum(nil))
 }
 
-func (p Path) UnderlayNextHop() *net.UDPAddr {
+func (p path) UnderlayNextHop() *net.UDPAddr {
 	if p.underlay == nil {
 		return nil
 	}
@@ -181,14 +172,14 @@ func (p Path) UnderlayNextHop() *net.UDPAddr {
 	}
 }
 
-func (p Path) Path() *spath.Path {
+func (p path) Path() *spath.Path {
 	if p.spath == nil {
 		return nil
 	}
 	return p.spath.Copy()
 }
 
-func (p Path) Interfaces() []snet.PathInterface {
+func (p path) Interfaces() []snet.PathInterface {
 	if p.interfaces == nil {
 		return nil
 	}
@@ -199,38 +190,33 @@ func (p Path) Interfaces() []snet.PathInterface {
 	return intfs
 }
 
-func (p Path) Destination() addr.IA {
+func (p path) Destination() addr.IA {
 	if len(p.interfaces) == 0 {
 		return p.dst
 	}
 	return p.interfaces[len(p.interfaces)-1].IA()
 }
 
-func (p Path) MTU() uint16 {
-	return p.mtu
+func (p path) Metadata() snet.PathMetadata {
+	return p.metadata
 }
 
-func (p Path) Expiry() time.Time {
-	return p.expiry
-}
-
-func (p Path) Copy() snet.Path {
-	return Path{
+func (p path) Copy() snet.Path {
+	return path{
 		interfaces: append(p.interfaces[:0:0], p.interfaces...),
 		underlay:   p.UnderlayNextHop(), // creates copy
 		spath:      p.Path(),            // creates copy
-		mtu:        p.mtu,
-		expiry:     p.expiry,
+		metadata:   p.metadata,
 	}
 }
 
-func (p Path) String() string {
+func (p path) String() string {
 	hops := p.fmtInterfaces()
 	return fmt.Sprintf("Hops: [%s] MTU: %d, NextHop: %s",
-		strings.Join(hops, ">"), p.mtu, p.underlay)
+		strings.Join(hops, ">"), p.metadata.mtu, p.underlay)
 }
 
-func (p Path) fmtInterfaces() []string {
+func (p path) fmtInterfaces() []string {
 	var hops []string
 	if len(p.interfaces) == 0 {
 		return hops
@@ -254,3 +240,16 @@ type pathInterface struct {
 
 func (i pathInterface) ID() common.IFIDType { return i.id }
 func (i pathInterface) IA() addr.IA         { return i.ia }
+
+type pathMetadata struct {
+	mtu    uint16
+	expiry time.Time
+}
+
+func (m pathMetadata) MTU() uint16 {
+	return m.mtu
+}
+
+func (m pathMetadata) Expiry() time.Time {
+	return m.expiry
+}
diff --git a/go/lib/sciond/fake/fake.go b/go/lib/sciond/fake/fake.go
index 72892a780..fdb28ab87 100644
--- a/go/lib/sciond/fake/fake.go
+++ b/go/lib/sciond/fake/fake.go
@@ -30,9 +30,12 @@ func New(script *Script) sciond.Connector {
 	}
 	for _, entry := range script.Entries {
 		for _, path := range entry.Paths {
-			path.creationTime = c.creationTime
+			creationTime := c.creationTime
 			lifetime := time.Duration(path.JSONExpirationTimestamp) * time.Second
-			path.expirationTime = path.creationTime.Add(lifetime)
+			path.metadata = pathMetadata{
+				creationTime:   creationTime,
+				expirationTime: creationTime.Add(lifetime),
+			}
 		}
 	}
 	return c
@@ -68,22 +71,15 @@ type Entry struct {
 }
 
 type Path struct {
-	JSONFingerprint string   `json:"fingerprint"`
-	JSONNextHop     *UDPAddr `json:"next_hop,omitempty"`
-	JSONIA          addr.IA  `json:"ia"`
+	// JSONInterfaces encodes a list of interfaces
+	JSONInterfaces []PathInterface `json:"interfaces,omitempty"`
+	JSONNextHop    *UDPAddr        `json:"next_hop,omitempty"`
 	// JSONExpirationTimestamp contains the point in time when the path expires, in seconds,
 	// relative to the time of fake connector creation. Negative timestamps are also supported, and
 	// would mean SCIOND served a path that expired in the past.
 	JSONExpirationTimestamp int `json:"expiration_timestamp"`
 
-	// creationTime contains the time when this object was constructed.
-	creationTime time.Time
-	// expirationTime contains the time when this path expires.
-	expirationTime time.Time
-}
-
-func (p Path) Fingerprint() snet.PathFingerprint {
-	return snet.PathFingerprint(p.JSONFingerprint)
+	metadata pathMetadata
 }
 
 func (p Path) UnderlayNextHop() *net.UDPAddr {
@@ -103,39 +99,65 @@ func DummyPath() *spath.Path {
 }
 
 func (p Path) Interfaces() []snet.PathInterface {
-	return []snet.PathInterface{}
+	ifaces := make([]snet.PathInterface, len(p.JSONInterfaces))
+	for i := range p.JSONInterfaces {
+		ifaces[i] = p.JSONInterfaces[i]
+	}
+	return ifaces
 }
 
 func (p Path) Destination() addr.IA {
-	return p.JSONIA
+	return p.JSONInterfaces[len(p.JSONInterfaces)-1].JSONIA
 }
 
-func (p Path) MTU() uint16 {
-	return 1472
-}
-
-func (p Path) Expiry() time.Time {
-	return p.expirationTime
+func (p Path) Metadata() snet.PathMetadata {
+	return p.metadata
 }
 
 func (p Path) Copy() snet.Path {
 	return &Path{
-		JSONFingerprint: p.JSONFingerprint,
+		JSONInterfaces: p.JSONInterfaces,
 		JSONNextHop: &UDPAddr{
 			IP:   append(p.JSONNextHop.IP[:0:0], p.JSONNextHop.IP...),
 			Port: p.JSONNextHop.Port,
 			Zone: p.JSONNextHop.Zone,
 		},
-		JSONIA:                  p.JSONIA,
 		JSONExpirationTimestamp: p.JSONExpirationTimestamp,
-		creationTime:            p.creationTime,
-		expirationTime:          p.expirationTime,
+		metadata:                p.metadata,
 	}
 }
 
 func (p Path) String() string {
-	return fmt.Sprintf("FakePath(IA: %v, NextHop: %v, Fingerprint: %v)",
-		p.JSONIA, p.JSONNextHop, p.JSONFingerprint)
+	return fmt.Sprintf("FakePath(Dest: %v, NextHop: %v, Fingerprint: %v)",
+		p.Destination(), p.JSONNextHop, snet.Fingerprint(p))
+}
+
+type PathInterface struct {
+	JSONIA addr.IA         `json:"ia"`
+	JSONID common.IFIDType `json:"id"`
+}
+
+func (i PathInterface) ID() common.IFIDType {
+	return i.JSONID
+}
+
+func (i PathInterface) IA() addr.IA {
+	return i.JSONIA
+}
+
+type pathMetadata struct {
+	// creationTime contains the time when this object was constructed.
+	creationTime time.Time
+	// expirationTime contains the time when this path expires.
+	expirationTime time.Time
+}
+
+func (m pathMetadata) MTU() uint16 {
+	return 1472
+}
+
+func (m pathMetadata) Expiry() time.Time {
+	return m.expirationTime
 }
 
 // UDPAddr decorates net.UDPAddr with custom JSON marshaling logic.
diff --git a/go/lib/snet/mock_snet/snet.go b/go/lib/snet/mock_snet/snet.go
index 697720970..c5fcd8028 100644
--- a/go/lib/snet/mock_snet/snet.go
+++ b/go/lib/snet/mock_snet/snet.go
@@ -266,34 +266,6 @@ func (mr *MockPathMockRecorder) Destination() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destination", reflect.TypeOf((*MockPath)(nil).Destination))
 }
 
-// Expiry mocks base method
-func (m *MockPath) Expiry() time.Time {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Expiry")
-	ret0, _ := ret[0].(time.Time)
-	return ret0
-}
-
-// Expiry indicates an expected call of Expiry
-func (mr *MockPathMockRecorder) Expiry() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Expiry", reflect.TypeOf((*MockPath)(nil).Expiry))
-}
-
-// Fingerprint mocks base method
-func (m *MockPath) Fingerprint() snet.PathFingerprint {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Fingerprint")
-	ret0, _ := ret[0].(snet.PathFingerprint)
-	return ret0
-}
-
-// Fingerprint indicates an expected call of Fingerprint
-func (mr *MockPathMockRecorder) Fingerprint() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fingerprint", reflect.TypeOf((*MockPath)(nil).Fingerprint))
-}
-
 // Interfaces mocks base method
 func (m *MockPath) Interfaces() []snet.PathInterface {
 	m.ctrl.T.Helper()
@@ -308,18 +280,18 @@ func (mr *MockPathMockRecorder) Interfaces() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Interfaces", reflect.TypeOf((*MockPath)(nil).Interfaces))
 }
 
-// MTU mocks base method
-func (m *MockPath) MTU() uint16 {
+// Metadata mocks base method
+func (m *MockPath) Metadata() snet.PathMetadata {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "MTU")
-	ret0, _ := ret[0].(uint16)
+	ret := m.ctrl.Call(m, "Metadata")
+	ret0, _ := ret[0].(snet.PathMetadata)
 	return ret0
 }
 
-// MTU indicates an expected call of MTU
-func (mr *MockPathMockRecorder) MTU() *gomock.Call {
+// Metadata indicates an expected call of Metadata
+func (mr *MockPathMockRecorder) Metadata() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MTU", reflect.TypeOf((*MockPath)(nil).MTU))
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Metadata", reflect.TypeOf((*MockPath)(nil).Metadata))
 }
 
 // Path mocks base method
diff --git a/go/lib/snet/path.go b/go/lib/snet/path.go
index 9a687b073..4d5e32597 100644
--- a/go/lib/snet/path.go
+++ b/go/lib/snet/path.go
@@ -15,6 +15,8 @@
 package snet
 
 import (
+	"crypto/sha256"
+	"encoding/binary"
 	"fmt"
 	"net"
 	"time"
@@ -24,12 +26,6 @@ import (
 	"github.com/scionproto/scion/go/lib/spath"
 )
 
-type PathFingerprint string
-
-func (pf PathFingerprint) String() string {
-	return common.RawBytes(pf).String()
-}
-
 // Path is an abstract representation of a path. Most applications do not need
 // access to the raw internals.
 //
@@ -39,10 +35,6 @@ func (pf PathFingerprint) String() string {
 // without a source of paths). An empty path only contains a Destination value,
 // all other values are zero values.
 type Path interface {
-	// Fingerprint uniquely identifies the path based on the sequence of
-	// ASes and BRs. Other metadata, such as MTU or NextHop have no effect
-	// on the fingerprint. Empty string means unknown fingerprint.
-	Fingerprint() PathFingerprint
 	// UnderlayNextHop returns the address:port pair of a local-AS underlay
 	// speaker. Usually, this is a border router that will forward the traffic.
 	UnderlayNextHop() *net.UDPAddr
@@ -56,11 +48,9 @@ type Path interface {
 	// Destination is the AS the path points to. Empty paths return the local
 	// AS of the router that created them.
 	Destination() addr.IA
-	// MTU returns the MTU of the path. If the result is zero, MTU is unknown.
-	MTU() uint16
-	// Expiry returns the expiration time of the path. If the result is a zero
-	// value expiration time is unknown.
-	Expiry() time.Time
+	// Metadata returns supplementary information about this path.
+	// Returns nil if the metadata is not available.
+	Metadata() PathMetadata
 	// Copy create a copy of the path.
 	Copy() Path
 }
@@ -75,6 +65,37 @@ type PathInterface interface {
 	IA() addr.IA
 }
 
+// PathMetadata contains supplementary information about a path.
+type PathMetadata interface {
+	// MTU returns the MTU of the path.
+	MTU() uint16
+	// Expiry returns the expiration time of the path.
+	Expiry() time.Time
+}
+
+type PathFingerprint string
+
+func (pf PathFingerprint) String() string {
+	return common.RawBytes(pf).String()
+}
+
+// Fingerprint uniquely identifies the path based on the sequence of
+// ASes and BRs, i.e. by its PathInterfaces.
+// Other metadata, such as MTU or NextHop have no effect on the fingerprint.
+// Returns empty string for paths where the interfaces list is not available.
+func Fingerprint(path Path) PathFingerprint {
+	interfaces := path.Interfaces()
+	if len(interfaces) == 0 {
+		return ""
+	}
+	h := sha256.New()
+	for _, intf := range interfaces {
+		binary.Write(h, binary.BigEndian, intf.IA().IAInt())
+		binary.Write(h, binary.BigEndian, intf.ID())
+	}
+	return PathFingerprint(h.Sum(nil))
+}
+
 // partialPath is a path object with incomplete metadata. It is used as a
 // temporary solution where a full path cannot be reconstituted from other
 // objects, notably snet.UDPAddr and snet.SVCAddr.
@@ -84,10 +105,6 @@ type partialPath struct {
 	destination addr.IA
 }
 
-func (p *partialPath) Fingerprint() PathFingerprint {
-	return ""
-}
-
 func (p *partialPath) UnderlayNextHop() *net.UDPAddr {
 	return p.underlay
 }
@@ -107,12 +124,8 @@ func (p *partialPath) Destination() addr.IA {
 	return p.destination
 }
 
-func (p *partialPath) MTU() uint16 {
-	return 0
-}
-
-func (p *partialPath) Expiry() time.Time {
-	return time.Time{}
+func (p *partialPath) Metadata() PathMetadata {
+	return nil
 }
 
 func (p *partialPath) Copy() Path {
diff --git a/go/lib/spath/spathmeta/apppath.go b/go/lib/spath/spathmeta/apppath.go
index 434a8788a..2986218df 100644
--- a/go/lib/spath/spathmeta/apppath.go
+++ b/go/lib/spath/spathmeta/apppath.go
@@ -40,7 +40,7 @@ func NewAppPathSet(paths []snet.Path) AppPathSet {
 
 // Add adds the given path to the path set.
 func (aps AppPathSet) Add(path snet.Path) {
-	aps[path.Fingerprint()] = path
+	aps[snet.Fingerprint(path)] = path
 }
 
 func (aps AppPathSet) Copy() AppPathSet {
diff --git a/go/lib/svc/resolver.go b/go/lib/svc/resolver.go
index fdaa01663..ceb77b131 100644
--- a/go/lib/svc/resolver.go
+++ b/go/lib/svc/resolver.go
@@ -18,7 +18,6 @@ import (
 	"bytes"
 	"context"
 	"net"
-	"time"
 
 	"github.com/opentracing/opentracing-go"
 	"github.com/opentracing/opentracing-go/ext"
@@ -180,10 +179,6 @@ type path struct {
 	destination addr.IA
 }
 
-func (p *path) Fingerprint() snet.PathFingerprint {
-	return ""
-}
-
 func (p *path) UnderlayNextHop() *net.UDPAddr {
 	return p.underlay
 }
@@ -200,12 +195,8 @@ func (p *path) Destination() addr.IA {
 	return p.destination
 }
 
-func (p *path) MTU() uint16 {
-	return 0
-}
-
-func (p *path) Expiry() time.Time {
-	return time.Time{}
+func (p *path) Metadata() snet.PathMetadata {
+	return nil
 }
 
 func (p *path) Copy() snet.Path {
diff --git a/go/pkg/sciond/fetcher/filter.go b/go/pkg/sciond/fetcher/filter.go
index 4b8c21f18..ddac1f9ac 100644
--- a/go/pkg/sciond/fetcher/filter.go
+++ b/go/pkg/sciond/fetcher/filter.go
@@ -38,7 +38,7 @@ func pathsToPs(paths []*combinator.Path) pathpol.PathSet {
 	ps := make(pathpol.PathSet, len(paths))
 	for _, path := range paths {
 		wp := newPathWrap(path)
-		ps[wp.Fingerprint()] = wp
+		ps[wp.key] = wp
 	}
 	return ps
 }
@@ -71,5 +71,4 @@ func newPathWrap(p *combinator.Path) pathWrap {
 	}
 }
 
-func (p pathWrap) Interfaces() []snet.PathInterface  { return p.intfs }
-func (p pathWrap) Fingerprint() snet.PathFingerprint { return p.key }
+func (p pathWrap) Interfaces() []snet.PathInterface { return p.intfs }
diff --git a/go/pkg/showpaths/showpaths.go b/go/pkg/showpaths/showpaths.go
index e06200090..38c2217a1 100644
--- a/go/pkg/showpaths/showpaths.go
+++ b/go/pkg/showpaths/showpaths.go
@@ -127,7 +127,8 @@ func Run(ctx context.Context, dst addr.IA, cfg Config) (*Result, error) {
 	res := &Result{Destination: dst}
 	for _, path := range paths {
 		fingerprint := "local"
-		if fp := path.Fingerprint().String(); fp != "" {
+		if len(path.Interfaces()) > 0 {
+			fp := snet.Fingerprint(path).String()
 			fingerprint = fp[:16]
 		}
 		var nextHop string
@@ -138,8 +139,8 @@ func Run(ctx context.Context, dst addr.IA, cfg Config) (*Result, error) {
 			FullPath:    path,
 			Fingerprint: fingerprint,
 			NextHop:     nextHop,
-			Expiry:      path.Expiry(),
-			MTU:         path.MTU(),
+			Expiry:      path.Metadata().Expiry(),
+			MTU:         path.Metadata().MTU(),
 			Local:       localIP,
 			Hops:        []Hop{},
 		}
diff --git a/go/scion/ping.go b/go/scion/ping.go
index 4db20fa1d..05a1bd557 100644
--- a/go/scion/ping.go
+++ b/go/scion/ping.go
@@ -104,7 +104,8 @@ func newPing(pather CommandPather) *cobra.Command {
 			}
 			pldSize := int(flags.size)
 			if flags.maxMTU {
-				pldSize, err = calcMaxPldSize(local, remote, int(path.MTU()), features.HeaderV2)
+				mtu := int(path.Metadata().MTU())
+				pldSize, err = calcMaxPldSize(local, remote, mtu, features.HeaderV2)
 				if err != nil {
 					return err
 				}
diff --git a/go/sig/egress/iface/sesspath.go b/go/sig/egress/iface/sesspath.go
index cbdcd4522..75517bc98 100644
--- a/go/sig/egress/iface/sesspath.go
+++ b/go/sig/egress/iface/sesspath.go
@@ -41,7 +41,8 @@ func (sp *SessPath) Path() snet.Path {
 }
 
 func (sp *SessPath) IsCloseToExpiry() bool {
-	return sp.Path().Expiry().Before(time.Now().Add(SafetyInterval))
+	metadata := sp.Path().Metadata()
+	return metadata == nil || metadata.Expiry().Before(time.Now().Add(SafetyInterval))
 }
 
 func (sp *SessPath) Copy() *SessPath {
diff --git a/go/sig/egress/session/sessmon.go b/go/sig/egress/session/sessmon.go
index 2a534c4af..41c0eed10 100644
--- a/go/sig/egress/session/sessmon.go
+++ b/go/sig/egress/session/sessmon.go
@@ -123,19 +123,29 @@ func (sm *sessMonitor) updatePaths() {
 		return
 	}
 	currPath := sm.smRemote.SessPath
-	expTime := currPath.Path().Expiry()
-	mtu := currPath.Path().MTU()
+	var oldExpTime time.Time
+	var oldMtu uint16
+	if md := currPath.Path().Metadata(); md != nil {
+		oldExpTime = md.Expiry()
+		oldMtu = md.MTU()
+	}
 	sm.sessPathPool.Update(sm.pool.Paths())
 	metrics.SessionPaths.WithLabelValues(sm.sess.IA().String(),
 		sm.sess.SessId.String()).Set(float64(sm.sessPathPool.PathCount()))
 	// Expiration or MTU of the current path may have changed during the update.
 	// In such a case we want to push the updated path to the Session.
-	if currPath.Path().Expiry() != expTime || currPath.Path().MTU() != mtu {
+	var expTime time.Time
+	var mtu uint16
+	if md := currPath.Path().Metadata(); md != nil {
+		expTime = md.Expiry()
+		mtu = md.MTU()
+	}
+	if expTime != oldExpTime || mtu != oldMtu {
 		sm.logger.Debug("sessMonitor: Path metadata changed",
-			"oldExpiration", expTime,
-			"newExpiration", currPath.Path().Expiry(),
-			"oldMTU", mtu,
-			"newMTU", currPath.Path().MTU)
+			"oldExpiration", oldExpTime,
+			"newExpiration", expTime,
+			"oldMTU", oldMtu,
+			"newMTU", mtu)
 		sm.updateSessSnap()
 	}
 }
@@ -226,7 +236,10 @@ func (sm *sessMonitor) updateSessSnap() {
 	}
 	sm.sess.currRemote.Store(remote)
 	if remote.SessPath != nil {
-		mtu := remote.SessPath.Path().MTU()
+		var mtu uint16
+		if md := remote.SessPath.Path().Metadata(); md != nil {
+			mtu = md.MTU()
+		}
 		metrics.SessionMTU.WithLabelValues(sm.sess.IA().String(),
 			sm.sess.SessId.String()).Set(float64(mtu))
 	}
diff --git a/go/sig/egress/worker/worker.go b/go/sig/egress/worker/worker.go
index 202b63e0d..7a17ef0cf 100644
--- a/go/sig/egress/worker/worker.go
+++ b/go/sig/egress/worker/worker.go
@@ -230,7 +230,9 @@ func (w *worker) resetFrame(f *frame) {
 			w.currPathEntry = remote.SessPath.Path()
 		}
 		if w.currPathEntry != nil {
-			mtu = w.currPathEntry.MTU()
+			if md := w.currPathEntry.Metadata(); md != nil {
+				mtu = md.MTU()
+			}
 			pathLen = uint16(len(w.currPathEntry.Path().Raw))
 		}
 	}
diff --git a/go/sig/internal/snetmigrate/path.go b/go/sig/internal/snetmigrate/path.go
index dff01ccbb..df53012e4 100644
--- a/go/sig/internal/snetmigrate/path.go
+++ b/go/sig/internal/snetmigrate/path.go
@@ -16,7 +16,6 @@ package snetmigrate
 
 import (
 	"net"
-	"time"
 
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/snet"
@@ -28,10 +27,6 @@ type emptyPath struct {
 	source addr.IA
 }
 
-func (p *emptyPath) Fingerprint() snet.PathFingerprint {
-	return ""
-}
-
 func (p *emptyPath) UnderlayNextHop() *net.UDPAddr {
 	return nil
 }
@@ -48,12 +43,8 @@ func (p *emptyPath) Destination() addr.IA {
 	return p.source
 }
 
-func (p *emptyPath) MTU() uint16 {
-	return 0
-}
-
-func (p *emptyPath) Expiry() time.Time {
-	return time.Time{}
+func (p *emptyPath) Metadata() snet.PathMetadata {
+	return nil
 }
 
 func (p *emptyPath) Copy() snet.Path {
diff --git a/go/tools/scmp/main.go b/go/tools/scmp/main.go
index a368a00ff..c57f9b4df 100644
--- a/go/tools/scmp/main.go
+++ b/go/tools/scmp/main.go
@@ -229,7 +229,7 @@ func setPathAndMtu() {
 	cmn.PathEntry = path
 	cmn.Remote.Path = path.Path()
 	cmn.Remote.NextHop = path.UnderlayNextHop()
-	cmn.Mtu = path.MTU()
+	cmn.Mtu = path.Metadata().MTU()
 }
 
 // setLocalASInfo queries the local AS information from SCIOND; sets cmn.LocalIA and localMtu.
diff --git a/go/tools/showpaths/paths.go b/go/tools/showpaths/paths.go
index b0052a7e1..67ff0cdd4 100644
--- a/go/tools/showpaths/paths.go
+++ b/go/tools/showpaths/paths.go
@@ -104,8 +104,10 @@ func main() {
 	for i, path := range paths {
 		fmt.Printf("[%2d] %s", i, fmt.Sprintf("%s", path))
 		if *expiration {
-			fmt.Printf(" Expires: %s (%s)", path.Expiry(),
-				time.Until(path.Expiry()).Truncate(time.Second))
+			if md := path.Metadata(); md != nil {
+				fmt.Printf(" Expires: %s (%s)", md.Expiry(),
+					time.Until(md.Expiry()).Truncate(time.Second))
+			}
 		}
 		if *status {
 			fmt.Printf(" Status: %s", pathStatuses[pathprobe.PathKey(path)])


Test patch:
diff --git a/acceptance/sig_failover/testdata/1-ff00_0_110/sig/fake_sciond.json b/acceptance/sig_failover/testdata/1-ff00_0_110/sig/fake_sciond.json
index 9814abb3b..79906e2f2 100644
--- a/acceptance/sig_failover/testdata/1-ff00_0_110/sig/fake_sciond.json
+++ b/acceptance/sig_failover/testdata/1-ff00_0_110/sig/fake_sciond.json
@@ -4,15 +4,31 @@
             "reply_start_timestamp": 0,
             "paths": [
                 {
-                    "fingerprint": "PathAAAA",
+                    "interfaces": [
+                      {
+                        "ia": "1-ff00:0:110",
+                        "id": 1
+                      },
+                      {
+                        "ia": "1-ff00:0:111",
+                        "id": 1
+                      }
+                    ],
                     "next_hop": "242.254.100.3:50000",
-                    "ia": "1-ff00:0:111",
                     "expiration_timestamp": 7200
                 },
                 {
-                    "fingerprint": "PathBBBB",
+                    "interfaces": [
+                      {
+                        "ia": "1-ff00:0:110",
+                        "id": 2
+                      },
+                      {
+                        "ia": "1-ff00:0:111",
+                        "id": 2
+                      }
+                    ],
                     "next_hop": "242.254.100.4:50000",
-                    "ia": "1-ff00:0:111",
                     "expiration_timestamp": 7200
                 }
             ]
diff --git a/acceptance/sig_failover/testdata/1-ff00_0_111/sig/fake_sciond.json b/acceptance/sig_failover/testdata/1-ff00_0_111/sig/fake_sciond.json
index af8ee0bdf..15a16afd5 100644
--- a/acceptance/sig_failover/testdata/1-ff00_0_111/sig/fake_sciond.json
+++ b/acceptance/sig_failover/testdata/1-ff00_0_111/sig/fake_sciond.json
@@ -4,15 +4,31 @@
             "reply_start_timestamp": 0,
             "paths": [
                 {
-                    "fingerprint": "PathAAAA",
+                    "interfaces": [
+                      {
+                        "ia": "1-ff00:0:111",
+                        "id": 1
+                      },
+                      {
+                        "ia": "1-ff00:0:110",
+                        "id": 1
+                      }
+                    ],
                     "next_hop": "242.254.200.3:50000",
-                    "ia": "1-ff00:0:110",
                     "expiration_timestamp": 7200
                 },
                 {
-                    "fingerprint": "PathBBBB",
+                    "interfaces": [
+                      {
+                        "ia": "1-ff00:0:111",
+                        "id": 2
+                      },
+                      {
+                        "ia": "1-ff00:0:110",
+                        "id": 2
+                      }
+                    ],
                     "next_hop": "242.254.200.4:50000",
-                    "ia": "1-ff00:0:110",
                     "expiration_timestamp": 7200
                 }
             ]
diff --git a/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/sig/fake_sciond.json b/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/sig/fake_sciond.json
index 5a34814ad..b865c341f 100644
--- a/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/sig/fake_sciond.json
+++ b/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/sig/fake_sciond.json
@@ -4,9 +4,17 @@
             "reply_start_timestamp": 0,
             "paths": [
                 {
-                    "fingerprint": "PathAAAAExpiresSoon",
+                    "interfaces": [
+                      {
+                        "ia": "1-ff00:0:110",
+                        "id": 1
+                      },
+                      {
+                        "ia": "1-ff00:0:111",
+                        "id": 1
+                      }
+                    ],
                     "next_hop": "242.254.100.3:50000",
-                    "ia": "1-ff00:0:111",
                     "expiration_timestamp": 10
                 }
             ]
@@ -15,15 +23,31 @@
             "reply_start_timestamp": 5,
             "paths": [
                 {
-                    "fingerprint": "PathAAAAExpiresSoon",
+                    "interfaces": [
+                      {
+                        "ia": "1-ff00:0:110",
+                        "id": 1
+                      },
+                      {
+                        "ia": "1-ff00:0:111",
+                        "id": 1
+                      }
+                    ],
                     "next_hop": "242.254.100.3:50000",
-                    "ia": "1-ff00:0:111",
                     "expiration_timestamp": 10
                 },
                 {
-                    "fingerprint": "PathBBBBExpiresLate",
+                    "interfaces": [
+                      {
+                        "ia": "1-ff00:0:110",
+                        "id": 2
+                      },
+                      {
+                        "ia": "1-ff00:0:111",
+                        "id": 2
+                      }
+                    ],
                     "next_hop": "242.254.100.4:50000",
-                    "ia": "1-ff00:0:111",
                     "expiration_timestamp": 7200
                 }
             ]
diff --git a/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/sig/fake_sciond.json b/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/sig/fake_sciond.json
index 03e237784..a42ed5ca7 100644
--- a/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/sig/fake_sciond.json
+++ b/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/sig/fake_sciond.json
@@ -4,9 +4,17 @@
             "seconds": 0,
             "paths": [
                 {
-                    "fingerprint": "PathAAAAExpiresSoon",
+                    "interfaces": [
+                      {
+                        "ia": "1-ff00:0:111",
+                        "id": 1
+                      },
+                      {
+                        "ia": "1-ff00:0:110",
+                        "id": 1
+                      }
+                    ],
                     "next_hop": "242.254.200.3:50000",
-                    "ia": "1-ff00:0:110",
                     "expiry_seconds": 10
                 }
             ]
@@ -15,15 +23,31 @@
             "reply_start_timestamp": 5,
             "paths": [
                 {
-                    "fingerprint": "PathAAAAExpiresSoon",
+                    "interfaces": [
+                      {
+                        "ia": "1-ff00:0:111",
+                        "id": 1
+                      },
+                      {
+                        "ia": "1-ff00:0:110",
+                        "id": 1
+                      }
+                    ],
                     "next_hop": "242.254.200.3:50000",
-                    "ia": "1-ff00:0:110",
                     "expiration_timestamp": 10
                 },
                 {
-                    "fingerprint": "PathBBBBExpiresLate",
+                    "interfaces": [
+                      {
+                        "ia": "1-ff00:0:111",
+                        "id": 2
+                      },
+                      {
+                        "ia": "1-ff00:0:110",
+                        "id": 2
+                      }
+                    ],
                     "next_hop": "242.254.200.4:50000",
-                    "ia": "1-ff00:0:110",
                     "expiration_timestamp": 7200
                 }
             ]
diff --git a/go/lib/infra/messenger/addr_test.go b/go/lib/infra/messenger/addr_test.go
index de91a8b0e..a2d5b194b 100644
--- a/go/lib/infra/messenger/addr_test.go
+++ b/go/lib/infra/messenger/addr_test.go
@@ -20,7 +20,6 @@ import (
 	"fmt"
 	"net"
 	"testing"
-	"time"
 
 	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
@@ -98,7 +97,7 @@ func TestRedirectQUIC(t *testing.T) {
 		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
 		path.EXPECT().Path().Return(nil)
 		path.EXPECT().UnderlayNextHop().Return(&net.UDPAddr{IP: net.ParseIP("10.1.1.1")})
-		path.EXPECT().Fingerprint().Return(snet.PathFingerprint("foo"))
+		path.EXPECT().Interfaces().Return(make([]snet.PathInterface, 1)) // just non-empty
 
 		aw := messenger.AddressRewriter{
 			Router:                router,
@@ -132,7 +131,7 @@ func TestRedirectQUIC(t *testing.T) {
 		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
 		path.EXPECT().Path().Return(nil)
 		path.EXPECT().UnderlayNextHop().Return(&net.UDPAddr{IP: net.ParseIP("10.1.1.1")})
-		path.EXPECT().Fingerprint().Return(snet.PathFingerprint("foo"))
+		path.EXPECT().Interfaces().Return(make([]snet.PathInterface, 1)) // just non-empty
 
 		aw := messenger.AddressRewriter{
 			Router:                router,
@@ -162,7 +161,7 @@ func TestRedirectQUIC(t *testing.T) {
 		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
 		path.EXPECT().Path().Return(nil)
 		path.EXPECT().UnderlayNextHop().Return(&net.UDPAddr{IP: net.ParseIP("10.1.1.1")})
-		path.EXPECT().Fingerprint().Return(snet.PathFingerprint(""))
+		path.EXPECT().Interfaces().Return(nil)
 		svcRouter := mock_messenger.NewMockLocalSVCRouter(ctrl)
 		svcRouter.EXPECT().GetUnderlay(addr.SvcBS).Return(
 			&net.UDPAddr{IP: net.ParseIP("10.1.1.1")}, nil,
@@ -234,7 +233,7 @@ func TestBuildFullAddress(t *testing.T) {
 		path := mock_snet.NewMockPath(ctrl)
 		path.EXPECT().Path().Return(&spath.Path{})
 		path.EXPECT().UnderlayNextHop().Return(&net.UDPAddr{})
-		path.EXPECT().Fingerprint().Return(snet.PathFingerprint("foo"))
+		path.EXPECT().Interfaces().Return(make([]snet.PathInterface, 1)) // just non-empty
 		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
 		input := &snet.SVCAddr{IA: remoteIA, SVC: addr.SvcBS}
 		a, err := aw.BuildFullAddress(context.Background(), input)
@@ -266,7 +265,7 @@ func TestBuildFullAddress(t *testing.T) {
 		svcRouter.EXPECT().GetUnderlay(addr.SvcBS).Return(underlayAddr, nil)
 
 		path := mock_snet.NewMockPath(ctrl)
-		path.EXPECT().Fingerprint()
+		path.EXPECT().Interfaces()
 		path.EXPECT().Path()
 		path.EXPECT().UnderlayNextHop()
 		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
@@ -293,7 +292,7 @@ func TestBuildFullAddress(t *testing.T) {
 		svcRouter.EXPECT().GetUnderlay(addr.SvcBS).Return(nil, errors.New("err"))
 
 		path := mock_snet.NewMockPath(ctrl)
-		path.EXPECT().Fingerprint()
+		path.EXPECT().Interfaces()
 		path.EXPECT().Path()
 		path.EXPECT().UnderlayNextHop()
 		router.EXPECT().Route(gomock.Any(), gomock.Any()).Return(path, nil)
@@ -450,10 +449,6 @@ func initResolver(resolver *mock_messenger.MockResolver, f func(*mock_messenger.
 
 type testPath struct{}
 
-func (t *testPath) Fingerprint() snet.PathFingerprint {
-	panic("not implemented")
-}
-
 func (t *testPath) UnderlayNextHop() *net.UDPAddr {
 	panic("not implemented")
 }
@@ -470,11 +465,7 @@ func (t *testPath) Destination() addr.IA {
 	panic("not implemented")
 }
 
-func (t *testPath) MTU() uint16 {
-	panic("not implemented")
-}
-
-func (t *testPath) Expiry() time.Time {
+func (t *testPath) Metadata() snet.PathMetadata {
 	panic("not implemented")
 }
 
diff --git a/go/lib/pathmgr/util_test.go b/go/lib/pathmgr/util_test.go
index 7abfbc98a..f05518b4f 100644
--- a/go/lib/pathmgr/util_test.go
+++ b/go/lib/pathmgr/util_test.go
@@ -16,8 +16,6 @@
 package pathmgr_test
 
 import (
-	"crypto/sha256"
-	"encoding/binary"
 	"strconv"
 	"strings"
 	"testing"
@@ -54,7 +52,6 @@ func createPath(t testing.TB, ctrl *gomock.Controller, desc string) snet.Path {
 		})
 	}
 	path.EXPECT().Interfaces().Return(interfaces).AnyTimes()
-	path.EXPECT().Fingerprint().Return(fingerprint(interfaces)).AnyTimes()
 	return path
 }
 
@@ -66,18 +63,6 @@ func mustIfID(t testing.TB, s string) common.IFIDType {
 	return common.IFIDType(ifID)
 }
 
-func fingerprint(interfaces []snet.PathInterface) snet.PathFingerprint {
-	if len(interfaces) == 0 {
-		return ""
-	}
-	h := sha256.New()
-	for _, intf := range interfaces {
-		binary.Write(h, binary.BigEndian, intf.IA().IAInt())
-		binary.Write(h, binary.BigEndian, intf.ID())
-	}
-	return snet.PathFingerprint(h.Sum(nil))
-}
-
 type intf struct {
 	ia addr.IA
 	id common.IFIDType
diff --git a/go/lib/pathpol/policy_test.go b/go/lib/pathpol/policy_test.go
index ce4a8e86a..8d94c0c7e 100644
--- a/go/lib/pathpol/policy_test.go
+++ b/go/lib/pathpol/policy_test.go
@@ -639,7 +639,6 @@ func (p PathProvider) GetPaths(src, dst addr.IA) PathSet {
 		}
 		result[snet.PathFingerprint(key.String())] = &testPath{
 			interfaces: pathIntfs,
-			key:        snet.PathFingerprint(key.String()),
 		}
 	}
 	return result
@@ -647,15 +646,12 @@ func (p PathProvider) GetPaths(src, dst addr.IA) PathSet {
 
 type testPath struct {
 	interfaces []snet.PathInterface
-	key        snet.PathFingerprint
 }
 
 func (p *testPath) Interfaces() []snet.PathInterface {
 	return p.interfaces
 }
 
-func (p *testPath) Fingerprint() snet.PathFingerprint { return p.key }
-
 type testPathIntf struct {
 	ia   addr.IA
 	ifid common.IFIDType
diff --git a/go/lib/sciond/fake/fake_test.go b/go/lib/sciond/fake/fake_test.go
index f39586ceb..59a2e8910 100644
--- a/go/lib/sciond/fake/fake_test.go
+++ b/go/lib/sciond/fake/fake_test.go
@@ -30,12 +30,14 @@ func TestJSONConversion(t *testing.T) {
 				ReplyStartTimestamp: 0,
 				Paths: []*fake.Path{
 					{
-						JSONFingerprint: "Foo",
+						JSONInterfaces: []fake.PathInterface{
+							{JSONIA: xtest.MustParseIA("1-ff00:0:ffff"), JSONID: 1},
+							{JSONIA: xtest.MustParseIA("1-ff00:0:1"), JSONID: 1},
+						},
 						JSONNextHop: &fake.UDPAddr{
 							IP:   net.IP{192, 168, 0, 1},
 							Port: 80,
 						},
-						JSONIA:                  xtest.MustParseIA("1-ff00:0:1"),
 						JSONExpirationTimestamp: 7200,
 					},
 				},
@@ -69,12 +71,14 @@ func TestPaths(t *testing.T) {
 				ReplyStartTimestamp: 0,
 				Paths: []*fake.Path{
 					{
-						JSONFingerprint: "Foo",
+						JSONInterfaces: []fake.PathInterface{
+							{JSONIA: xtest.MustParseIA("1-ff00:0:ffff"), JSONID: 1},
+							{JSONIA: xtest.MustParseIA("1-ff00:0:1"), JSONID: 1},
+						},
 						JSONNextHop: &fake.UDPAddr{
 							IP:   net.IP{10, 0, 0, 1},
 							Port: 80,
 						},
-						JSONIA:                  xtest.MustParseIA("1-ff00:0:1"),
 						JSONExpirationTimestamp: 7200,
 					},
 				},
@@ -83,12 +87,14 @@ func TestPaths(t *testing.T) {
 				ReplyStartTimestamp: 1,
 				Paths: []*fake.Path{
 					{
-						JSONFingerprint: "Foo2",
+						JSONInterfaces: []fake.PathInterface{
+							{JSONIA: xtest.MustParseIA("1-ff00:0:ffff"), JSONID: 1},
+							{JSONIA: xtest.MustParseIA("2-ff00:0:2"), JSONID: 1},
+						},
 						JSONNextHop: &fake.UDPAddr{
 							IP:   net.IP{10, 0, 0, 2},
 							Port: 80,
 						},
-						JSONIA:                  xtest.MustParseIA("2-ff00:0:2"),
 						JSONExpirationTimestamp: 10800,
 					},
 				},
@@ -106,15 +112,17 @@ func TestPaths(t *testing.T) {
 	require.NoError(t, err)
 
 	require.Equal(t, 1, len(paths))
-	assert.Equal(t, "Foo", string(paths[0].Fingerprint()))
+	assert.NotEqual(t, "", string(snet.Fingerprint(paths[0])))
+	assert.Equal(t, snet.Fingerprint(script.Entries[0].Paths[0]), snet.Fingerprint(paths[0]))
 	assert.Equal(t, &net.UDPAddr{IP: net.IP{10, 0, 0, 1}, Port: 80}, paths[0].UnderlayNextHop())
 	assert.Equal(t, fake.DummyPath(), paths[0].Path())
-	assert.Equal(t, []snet.PathInterface{}, paths[0].Interfaces())
+	assert.Equal(t, 2, len(paths[0].Interfaces()))
+	assert.Equal(t, paths[0].Destination(), paths[0].Interfaces()[1].IA())
 	assert.Equal(t, xtest.MustParseIA("1-ff00:0:1"), paths[0].Destination())
-	assert.Equal(t, uint16(1472), paths[0].MTU())
+	assert.Equal(t, uint16(1472), paths[0].Metadata().MTU())
 	// path valid for more than an hour, but less than three
-	assert.True(t, paths[0].Expiry().After(time.Now().Add(time.Hour)))
-	assert.True(t, paths[0].Expiry().Before(time.Now().Add(3*time.Hour)))
+	assert.True(t, paths[0].Metadata().Expiry().After(time.Now().Add(time.Hour)))
+	assert.True(t, paths[0].Metadata().Expiry().Before(time.Now().Add(3*time.Hour)))
 
 	time.Sleep(time.Second)
 
@@ -127,26 +135,30 @@ func TestPaths(t *testing.T) {
 	require.NoError(t, err)
 
 	require.Equal(t, 1, len(paths))
-	assert.Equal(t, "Foo2", string(paths[0].Fingerprint()))
+	assert.NotEqual(t, "", string(snet.Fingerprint(paths[0])))
+	assert.Equal(t, snet.Fingerprint(script.Entries[1].Paths[0]), snet.Fingerprint(paths[0]))
 	assert.Equal(t, &net.UDPAddr{IP: net.IP{10, 0, 0, 2}, Port: 80}, paths[0].UnderlayNextHop())
 	assert.Equal(t, fake.DummyPath(), paths[0].Path())
-	assert.Equal(t, []snet.PathInterface{}, paths[0].Interfaces())
+	assert.Equal(t, 2, len(paths[0].Interfaces()))
+	assert.Equal(t, paths[0].Destination(), paths[0].Interfaces()[1].IA())
 	assert.Equal(t, xtest.MustParseIA("2-ff00:0:2"), paths[0].Destination())
-	assert.Equal(t, uint16(1472), paths[0].MTU())
+	assert.Equal(t, uint16(1472), paths[0].Metadata().MTU())
 	// path valid for more than two hours, but less than four
-	assert.True(t, paths[0].Expiry().After(time.Now().Add(2*time.Hour)))
-	assert.True(t, paths[0].Expiry().Before(time.Now().Add(4*time.Hour)))
+	assert.True(t, paths[0].Metadata().Expiry().After(time.Now().Add(2*time.Hour)))
+	assert.True(t, paths[0].Metadata().Expiry().Before(time.Now().Add(4*time.Hour)))
 }
 
 func TestPathCopy(t *testing.T) {
 	path := fake.Path{
-		JSONFingerprint: "foo",
+		JSONInterfaces: []fake.PathInterface{
+			{JSONIA: xtest.MustParseIA("1-ff00:0:ffff"), JSONID: 1},
+			{JSONIA: xtest.MustParseIA("1-ff00:0:1"), JSONID: 1},
+		},
 		JSONNextHop: &fake.UDPAddr{
 			IP:   net.IP{192, 168, 0, 1},
 			Port: 80,
 			Zone: "abc",
 		},
-		JSONIA: xtest.MustParseIA("1-ff00:0:1"),
 	}
 
 	copyPath := path.Copy()
diff --git a/go/lib/sciond/fake/testdata/sd.json b/go/lib/sciond/fake/testdata/sd.json
index 1886f8027..32ef1f3ba 100644
--- a/go/lib/sciond/fake/testdata/sd.json
+++ b/go/lib/sciond/fake/testdata/sd.json
@@ -4,9 +4,17 @@
         "reply_start_timestamp": 0,
         "paths": [
           {
-            "fingerprint": "Foo",
+            "interfaces": [
+              {
+                "ia": "1-ff00:0:ffff",
+                "id": 1
+              },
+              {
+                "ia": "1-ff00:0:1",
+                "id": 1
+              }
+            ],
             "next_hop": "192.168.0.1:80",
-            "ia": "1-ff00:0:1",
             "expiration_timestamp": 7200
           }
         ]