Repository: scionproto/scion
Pull request #3639 (2020-01-29 07:11:52)

SIG: Option to configure number of paths retrieved


<!-- 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/3639)
<!-- Reviewable:end -->



Patch:
diff --git a/go/examples/pingpong/pingpong.go b/go/examples/pingpong/pingpong.go
index 55f667ef3..89bbef9ee 100644
--- a/go/examples/pingpong/pingpong.go
+++ b/go/examples/pingpong/pingpong.go
@@ -420,7 +420,7 @@ func choosePath(interactive bool) snet.Path {
 	if err != nil {
 		LogFatal("Unable to initialize SCION network", "err", err)
 	}
-	paths, err := sdConn.Paths(context.Background(), remote.IA, local.IA, 0, sd.PathReqFlags{})
+	paths, err := sdConn.Paths(context.Background(), remote.IA, local.IA, sd.PathReqFlags{})
 	if err != nil {
 		LogFatal("Failed to lookup paths", "err", err)
 	}
diff --git a/go/integration/end2end/main.go b/go/integration/end2end/main.go
index 532d2b83b..d4e9b3a27 100644
--- a/go/integration/end2end/main.go
+++ b/go/integration/end2end/main.go
@@ -244,8 +244,8 @@ func (c client) getRemote(ctx context.Context, n int) error {
 		return nil
 	}
 	// Get paths from sciond
-	paths, err := c.sdConn.Paths(ctx, remote.IA, integration.Local.IA, 1,
-		sciond.PathReqFlags{Refresh: n != 0})
+	paths, err := c.sdConn.Paths(ctx, remote.IA, integration.Local.IA,
+		sciond.PathReqFlags{Refresh: n != 0, PathCount: 1})
 	if err != nil {
 		return common.NewBasicError("Error requesting paths", err)
 	}
diff --git a/go/lib/env/env.go b/go/lib/env/env.go
index c176977ae..7be7ec5e3 100644
--- a/go/lib/env/env.go
+++ b/go/lib/env/env.go
@@ -135,6 +135,8 @@ type SciondClient struct {
 	// FakeData can be used to replace the local SCIOND with a fake data source.
 	// It must point to a fake SCIOND configuration file.
 	FakeData string
+	// PathCount is the maximum number of paths returned to the user.
+	PathCount uint16
 }
 
 func (cfg *SciondClient) InitDefaults() {
diff --git a/go/lib/env/sample.go b/go/lib/env/sample.go
index be8160396..12a74e116 100644
--- a/go/lib/env/sample.go
+++ b/go/lib/env/sample.go
@@ -39,6 +39,10 @@ Path = "/run/shm/sciond/default.sock"
 
 # Maximum time spent attempting to connect to sciond on start. (default 20s)
 InitialConnectPeriod = "20s"
+
+# Maximum numer of paths provided by SCIOND.
+# Zero means that all the paths should be provided. (default 0)
+PathCount = 0
 `
 
 const loggingFileSample = `
diff --git a/go/lib/sciond/apitypes.go b/go/lib/sciond/apitypes.go
index fb8e559d8..3fb3d068f 100644
--- a/go/lib/sciond/apitypes.go
+++ b/go/lib/sciond/apitypes.go
@@ -39,7 +39,7 @@ type Querier struct {
 }
 
 func (q Querier) Query(ctx context.Context, dst addr.IA) ([]snet.Path, error) {
-	return q.Connector.Paths(ctx, dst, q.IA, q.MaxPaths, PathReqFlags{})
+	return q.Connector.Paths(ctx, dst, q.IA, PathReqFlags{PathCount: q.MaxPaths})
 }
 
 // RevHandler is an adapter for sciond connector to implement snet.RevocationHandler.
diff --git a/go/lib/sciond/fake/fake.go b/go/lib/sciond/fake/fake.go
index 54e2570cf..88937980d 100644
--- a/go/lib/sciond/fake/fake.go
+++ b/go/lib/sciond/fake/fake.go
@@ -164,11 +164,11 @@ type connector struct {
 	script *Script
 }
 
-func (c connector) Paths(_ context.Context, _, _ addr.IA, max uint16,
-	_ sciond.PathReqFlags) ([]snet.Path, error) {
+func (c connector) Paths(_ context.Context, _, _ addr.IA,
+	flags sciond.PathReqFlags) ([]snet.Path, error) {
 
 	secondsElapsed := int(time.Since(c.creationTime).Seconds())
-	intMax := int(max)
+	intMax := int(flags.PathCount)
 
 	var entry *Entry
 	for i := 0; i < len(c.script.Entries); i++ {
diff --git a/go/lib/sciond/mock_sciond/sciond.go b/go/lib/sciond/mock_sciond/sciond.go
index c018cf475..4931aa645 100644
--- a/go/lib/sciond/mock_sciond/sciond.go
+++ b/go/lib/sciond/mock_sciond/sciond.go
@@ -123,18 +123,18 @@ func (mr *MockConnectorMockRecorder) IFInfo(arg0, arg1 interface{}) *gomock.Call
 }
 
 // Paths mocks base method
-func (m *MockConnector) Paths(arg0 context.Context, arg1, arg2 addr.IA, arg3 uint16, arg4 sciond.PathReqFlags) ([]snet.Path, error) {
+func (m *MockConnector) Paths(arg0 context.Context, arg1, arg2 addr.IA, arg3 sciond.PathReqFlags) ([]snet.Path, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Paths", arg0, arg1, arg2, arg3, arg4)
+	ret := m.ctrl.Call(m, "Paths", arg0, arg1, arg2, arg3)
 	ret0, _ := ret[0].([]snet.Path)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // Paths indicates an expected call of Paths
-func (mr *MockConnectorMockRecorder) Paths(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
+func (mr *MockConnectorMockRecorder) Paths(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Paths", reflect.TypeOf((*MockConnector)(nil).Paths), arg0, arg1, arg2, arg3, arg4)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Paths", reflect.TypeOf((*MockConnector)(nil).Paths), arg0, arg1, arg2, arg3)
 }
 
 // RevNotification mocks base method
diff --git a/go/lib/sciond/sciond.go b/go/lib/sciond/sciond.go
index 0f8c19cd1..18f82fd56 100644
--- a/go/lib/sciond/sciond.go
+++ b/go/lib/sciond/sciond.go
@@ -83,8 +83,7 @@ func (s *service) Connect(ctx context.Context) (Connector, error) {
 // an error occurs, or the method successfully returns.
 type Connector interface {
 	// Paths requests from SCIOND a set of end to end paths between src and
-	// dst. max specifies the maximum number of paths returned.
-	Paths(ctx context.Context, dst, src addr.IA, max uint16, f PathReqFlags) ([]snet.Path, error)
+	Paths(ctx context.Context, dst, src addr.IA, f PathReqFlags) ([]snet.Path, error)
 	// ASInfo requests from SCIOND information about AS ia.
 	ASInfo(ctx context.Context, ia addr.IA) (*ASInfoReply, error)
 	// IFInfo requests from SCIOND addresses and ports of interfaces. Slice
@@ -161,7 +160,7 @@ func (c *conn) ctxAwareConnect(ctx context.Context) (*disp.Dispatcher, error) {
 	}
 }
 
-func (c *conn) Paths(ctx context.Context, dst, src addr.IA, max uint16,
+func (c *conn) Paths(ctx context.Context, dst, src addr.IA,
 	f PathReqFlags) ([]snet.Path, error) {
 
 	roundTripper, err := c.ctxAwareConnect(ctx)
@@ -176,10 +175,9 @@ func (c *conn) Paths(ctx context.Context, dst, src addr.IA, max uint16,
 			TraceId: tracing.IDFromCtx(ctx),
 			Which:   proto.SCIONDMsg_Which_pathReq,
 			PathReq: &PathReq{
-				Dst:      dst.IAInt(),
-				Src:      src.IAInt(),
-				MaxPaths: max,
-				Flags:    f,
+				Dst:   dst.IAInt(),
+				Src:   src.IAInt(),
+				Flags: f,
 			},
 		},
 		nil,
diff --git a/go/lib/sciond/types.go b/go/lib/sciond/types.go
index e0f7eb05c..11acccef2 100644
--- a/go/lib/sciond/types.go
+++ b/go/lib/sciond/types.go
@@ -123,11 +123,10 @@ func (p *Pld) union() (interface{}, error) {
 }
 
 type PathReq struct {
-	Dst      addr.IAInt
-	Src      addr.IAInt
-	MaxPaths uint16
-	HPCfgs   []*path_mgmt.HPGroupId `capnp:"hpCfgs"`
-	Flags    PathReqFlags
+	Dst    addr.IAInt
+	Src    addr.IAInt
+	HPCfgs []*path_mgmt.HPGroupId `capnp:"hpCfgs"`
+	Flags  PathReqFlags
 }
 
 func (pathReq *PathReq) Copy() *PathReq {
@@ -135,21 +134,21 @@ func (pathReq *PathReq) Copy() *PathReq {
 		return nil
 	}
 	return &PathReq{
-		Dst:      pathReq.Dst,
-		Src:      pathReq.Src,
-		MaxPaths: pathReq.MaxPaths,
-		Flags:    pathReq.Flags,
+		Dst:   pathReq.Dst,
+		Src:   pathReq.Src,
+		Flags: pathReq.Flags,
 	}
 }
 
 func (pathReq *PathReq) String() string {
-	return fmt.Sprintf("%v -> %v, maxPaths=%d, flags=%v",
-		pathReq.Src, pathReq.Dst, pathReq.MaxPaths, pathReq.Flags)
+	return fmt.Sprintf("%v -> %v, flags=%v",
+		pathReq.Src, pathReq.Dst, pathReq.Flags)
 }
 
 type PathReqFlags struct {
-	Refresh bool
-	Hidden  bool
+	PathCount uint16 `capnp:"-"`
+	Refresh   bool
+	Hidden    bool
 }
 
 type PathReply struct {
diff --git a/go/sciond/internal/fetcher/fetcher.go b/go/sciond/internal/fetcher/fetcher.go
index 4ee990eb0..38a8edbbf 100644
--- a/go/sciond/internal/fetcher/fetcher.go
+++ b/go/sciond/internal/fetcher/fetcher.go
@@ -158,9 +158,9 @@ func (f *fetcherHandler) GetPaths(ctx context.Context, req *sciond.PathReq,
 		return f.buildSCIONDReply(nil, 0, sciond.ErrorInternal), err
 	}
 	if len(paths) == 0 {
-		return f.buildSCIONDReply(nil, req.MaxPaths, sciond.ErrorNoPaths), nil
+		return f.buildSCIONDReply(nil, req.Flags.PathCount, sciond.ErrorNoPaths), nil
 	}
-	return f.buildSCIONDReply(paths, req.MaxPaths, sciond.ErrorOk), nil
+	return f.buildSCIONDReply(paths, req.Flags.PathCount, sciond.ErrorOk), nil
 }
 
 // buildSCIONDReply constructs a fresh SCIOND PathReply from the information
diff --git a/go/sig/internal/pathmgr/BUILD.bazel b/go/sig/internal/pathmgr/BUILD.bazel
index 3655727e8..43e3ec678 100644
--- a/go/sig/internal/pathmgr/BUILD.bazel
+++ b/go/sig/internal/pathmgr/BUILD.bazel
@@ -3,7 +3,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 go_library(
     name = "go_default_library",
     srcs = [
-        "defines.go",
         "pathmgr.go",
         "polling_policy.go",
         "syncpaths.go",
diff --git a/go/sig/internal/pathmgr/defines.go b/go/sig/internal/pathmgr/defines.go
deleted file mode 100644
index 36bf6761e..000000000
--- a/go/sig/internal/pathmgr/defines.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2017 ETH Zurich
-//
-// 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 pathmgr
-
-const (
-	// the number of max paths requested in each SCIOND query
-	numReqPaths = 5
-)
diff --git a/go/sig/internal/pathmgr/pathmgr.go b/go/sig/internal/pathmgr/pathmgr.go
index cddd9c65e..f82a578f7 100644
--- a/go/sig/internal/pathmgr/pathmgr.go
+++ b/go/sig/internal/pathmgr/pathmgr.go
@@ -76,6 +76,8 @@ const (
 	DefaultErrorRefire = time.Second
 	// DefaultQueryTimeout is the time allocated for a query to SCIOND
 	DefaultQueryTimeout = 5 * time.Second
+	// DefaultPathCount is the maximum number of paths returned to the user.
+	DefaultPathCount = 5
 )
 
 // Policy is used to filter paths
@@ -123,6 +125,7 @@ type resolver struct {
 	sciondConn   sciond.Connector
 	timers       Timers
 	watchFactory *WatchFactory
+	pathCount    uint16
 }
 
 // New creates a new path management context.
@@ -132,12 +135,16 @@ type resolver struct {
 // (see package constants). When a query for a path older than maxAge reaches
 // the resolver, SCIOND is used to refresh the path. New returns with an error
 // if a connection to SCIOND could not be established.
-func New(conn sciond.Connector, timers Timers) Resolver {
+func New(conn sciond.Connector, timers Timers, pathCount uint16) Resolver {
 	timers.initDefaults()
+	if pathCount == 0 {
+		pathCount = DefaultPathCount
+	}
 	r := &resolver{
 		sciondConn:   conn,
 		timers:       timers,
 		watchFactory: NewWatchFactory(timers),
+		pathCount:    pathCount,
 	}
 	return r
 }
@@ -145,7 +152,10 @@ func New(conn sciond.Connector, timers Timers) Resolver {
 func (r *resolver) Query(ctx context.Context, src, dst addr.IA,
 	flags sciond.PathReqFlags) spathmeta.AppPathSet {
 
-	paths, err := r.sciondConn.Paths(ctx, dst, src, numReqPaths, flags)
+	if flags.PathCount == 0 {
+		flags.PathCount = r.pathCount
+	}
+	paths, err := r.sciondConn.Paths(ctx, dst, src, flags)
 	if err != nil {
 		r.logger(ctx).Error("SCIOND network error", "err", err)
 		return make(spathmeta.AppPathSet)
diff --git a/go/sig/internal/sigcmn/common.go b/go/sig/internal/sigcmn/common.go
index e6e46114b..5a4f45707 100644
--- a/go/sig/internal/sigcmn/common.go
+++ b/go/sig/internal/sigcmn/common.go
@@ -101,7 +101,7 @@ func initNetworkWithFakeSCIOND(cfg sigconfig.SigConf,
 	if err != nil {
 		return nil, nil, serrors.WrapStr("unable to initialize fake SCIOND service", err)
 	}
-	pathResolver := pathmgr.New(sciondConn, pathmgr.Timers{})
+	pathResolver := pathmgr.New(sciondConn, pathmgr.Timers{}, sdCfg.PathCount)
 	network := snet.NewNetworkWithPR(cfg.IA, Dispatcher, &snetmigrate.PathQuerier{
 		Resolver: pathResolver,
 		IA:       cfg.IA,
@@ -116,7 +116,7 @@ func initNetworkWithRealSCIOND(cfg sigconfig.SigConf,
 	deadline := time.Now().Add(sdCfg.InitialConnectPeriod.Duration)
 	var retErr error
 	for tries := 0; time.Now().Before(deadline); tries++ {
-		resolver, err := snetmigrate.ResolverFromSD(sdCfg.Path)
+		resolver, err := snetmigrate.ResolverFromSD(sdCfg.Path, sdCfg.PathCount)
 		if err == nil {
 			return snet.NewNetworkWithPR(cfg.IA, Dispatcher, &snetmigrate.PathQuerier{
 				Resolver: resolver,
diff --git a/go/sig/internal/snetmigrate/resolver.go b/go/sig/internal/snetmigrate/resolver.go
index 69f5cbee6..3eedf4666 100644
--- a/go/sig/internal/snetmigrate/resolver.go
+++ b/go/sig/internal/snetmigrate/resolver.go
@@ -22,14 +22,15 @@ import (
 	"github.com/scionproto/scion/go/sig/internal/pathmgr"
 )
 
-func ResolverFromSD(sciondPath string) (pathmgr.Resolver, error) {
+func ResolverFromSD(sciondPath string, pathCount uint16) (pathmgr.Resolver, error) {
 	var pathResolver pathmgr.Resolver
 	if sciondPath != "" {
-		sciondConn, err := sciond.NewService(sciondPath).Connect(context.Background())
+		sciondConn, err := sciond.NewService(sciondPath).Connect(
+			context.Background())
 		if err != nil {
 			return nil, common.NewBasicError("Unable to initialize SCIOND service", err)
 		}
-		pathResolver = pathmgr.New(sciondConn, pathmgr.Timers{})
+		pathResolver = pathmgr.New(sciondConn, pathmgr.Timers{}, pathCount)
 	}
 	return pathResolver, nil
 }
diff --git a/go/tools/scmp/main.go b/go/tools/scmp/main.go
index 19220fc77..e9b413216 100644
--- a/go/tools/scmp/main.go
+++ b/go/tools/scmp/main.go
@@ -110,7 +110,7 @@ func doCommand(cmd string) int {
 }
 
 func choosePath() snet.Path {
-	paths, err := sdConn.Paths(context.Background(), cmn.Remote.IA, cmn.Local.IA, 0,
+	paths, err := sdConn.Paths(context.Background(), cmn.Remote.IA, cmn.Local.IA,
 		sciond.PathReqFlags{Refresh: *refresh})
 	if err != nil {
 		cmn.Fatal("Failed to retrieve paths from SCIOND: %v\n", err)
diff --git a/go/tools/showpaths/paths.go b/go/tools/showpaths/paths.go
index 92b9dfec8..cf085f538 100644
--- a/go/tools/showpaths/paths.go
+++ b/go/tools/showpaths/paths.go
@@ -142,8 +142,8 @@ func getPaths(ctx context.Context) ([]snet.Path, error) {
 	if err != nil {
 		return nil, serrors.WrapStr("failed to connect to SCIOND", err)
 	}
-	paths, err := sdConn.Paths(ctx, dstIA, srcIA, uint16(*maxPaths),
-		sciond.PathReqFlags{Refresh: *refresh})
+	paths, err := sdConn.Paths(ctx, dstIA, srcIA,
+		sciond.PathReqFlags{Refresh: *refresh, PathCount: uint16(*maxPaths)})
 	if err != nil {
 		return nil, serrors.WrapStr("failed to retrieve paths from SCIOND", err)
 	}


Test patch:
diff --git a/go/lib/sciond/fake/fake_test.go b/go/lib/sciond/fake/fake_test.go
index 46d05629b..9cc0c9eb1 100644
--- a/go/lib/sciond/fake/fake_test.go
+++ b/go/lib/sciond/fake/fake_test.go
@@ -101,8 +101,7 @@ func TestPaths(t *testing.T) {
 		context.Background(),
 		xtest.MustParseIA("1-ff00:0:1"),
 		xtest.MustParseIA("1-ff00:0:2"),
-		5,
-		sciond.PathReqFlags{},
+		sciond.PathReqFlags{PathCount: 5},
 	)
 	require.NoError(t, err)
 
@@ -123,8 +122,7 @@ func TestPaths(t *testing.T) {
 		context.Background(),
 		xtest.MustParseIA("1-ff00:0:1"),
 		xtest.MustParseIA("1-ff00:0:2"),
-		5,
-		sciond.PathReqFlags{},
+		sciond.PathReqFlags{PathCount: 5},
 	)
 	require.NoError(t, err)
 
diff --git a/go/sig/internal/pathmgr/pathmgr_test.go b/go/sig/internal/pathmgr/pathmgr_test.go
index f080f799b..620ffda44 100644
--- a/go/sig/internal/pathmgr/pathmgr_test.go
+++ b/go/sig/internal/pathmgr/pathmgr_test.go
@@ -50,7 +50,7 @@ func TestQuery(t *testing.T) {
 	defer ctrl.Finish()
 
 	sd := mock_sciond.NewMockConnector(ctrl)
-	pm := pathmgr.New(sd, pathmgr.Timers{})
+	pm := pathmgr.New(sd, pathmgr.Timers{}, 5)
 
 	srcIA, dstIA := xtest.MustParseIA("1-ff00:0:133"), xtest.MustParseIA("1-ff00:0:131")
 
@@ -63,7 +63,7 @@ func TestQuery(t *testing.T) {
 		t.Log("get")
 		paths = append(paths, p)
 		sdAnswer := buildSDAnswer(t, ctrl, paths...)
-		sd.EXPECT().Paths(gomock.Any(), dstIA, srcIA, gomock.Any(), gomock.Any()).Return(
+		sd.EXPECT().Paths(gomock.Any(), dstIA, srcIA, gomock.Any()).Return(
 			sdAnswer, nil,
 		)
 		aps := pm.Query(context.Background(), srcIA, dstIA, sciond.PathReqFlags{})
@@ -87,7 +87,7 @@ func TestQueryFilter(t *testing.T) {
 	defer ctrl.Finish()
 
 	sd := mock_sciond.NewMockConnector(ctrl)
-	pm := pathmgr.New(sd, pathmgr.Timers{})
+	pm := pathmgr.New(sd, pathmgr.Timers{}, 5)
 
 	srcIA := xtest.MustParseIA("1-ff00:0:133")
 	dstIA := xtest.MustParseIA("1-ff00:0:131")
@@ -95,7 +95,7 @@ func TestQueryFilter(t *testing.T) {
 	pathOne := fmt.Sprintf("%s#1019 1-ff00:0:132#1910 1-ff00:0:132#1916 %s#1619", srcIA, dstIA)
 	paths := []string{pathOne}
 
-	sd.EXPECT().Paths(gomock.Any(), dstIA, srcIA, gomock.Any(), gomock.Any()).Return(
+	sd.EXPECT().Paths(gomock.Any(), dstIA, srcIA, gomock.Any()).Return(
 		buildSDAnswer(t, ctrl, paths...), nil,
 	).AnyTimes()
 
@@ -153,12 +153,12 @@ func TestWatchCount(t *testing.T) {
 	defer ctrl.Finish()
 
 	sd := mock_sciond.NewMockConnector(ctrl)
-	pr := pathmgr.New(sd, pathmgr.Timers{})
+	pr := pathmgr.New(sd, pathmgr.Timers{}, 5)
 
 	src := xtest.MustParseIA("1-ff00:0:111")
 	dst := xtest.MustParseIA("1-ff00:0:110")
 
-	sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any(), gomock.Any()).AnyTimes()
+	sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any()).AnyTimes()
 
 	assert.Equal(t, pr.WatchCount(), 0, " the count is initially 0")
 	sp, err := pr.Watch(context.Background(), src, dst)
@@ -175,13 +175,13 @@ func TestWatchPolling(t *testing.T) {
 	defer ctrl.Finish()
 
 	sd := mock_sciond.NewMockConnector(ctrl)
-	pr := pathmgr.New(sd, pathmgr.Timers{ErrorRefire: getDuration(1)})
+	pr := pathmgr.New(sd, pathmgr.Timers{ErrorRefire: getDuration(1)}, 5)
 
 	src := xtest.MustParseIA("1-ff00:0:111")
 	dst := xtest.MustParseIA("1-ff00:0:110")
 	gomock.InOrder(
-		sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any(), gomock.Any()),
-		sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any(), gomock.Any()).Return(
+		sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any()),
+		sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any()).Return(
 			buildSDAnswer(t, ctrl,
 				"1-ff00:0:111#105 1-ff00:0:130#1002 1-ff00:0:130#1004 1-ff00:0:110#2",
 			), nil,
@@ -201,17 +201,17 @@ func TestWatchFilter(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 	sd := mock_sciond.NewMockConnector(ctrl)
-	pr := pathmgr.New(sd, pathmgr.Timers{ErrorRefire: getDuration(1)})
+	pr := pathmgr.New(sd, pathmgr.Timers{ErrorRefire: getDuration(1)}, 5)
 
 	src := xtest.MustParseIA("1-ff00:0:111")
 	dst := xtest.MustParseIA("1-ff00:0:110")
 	gomock.InOrder(
-		sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any(), gomock.Any()).Return(
+		sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any()).Return(
 			buildSDAnswer(t, ctrl,
 				"1-ff00:0:111#104 1-ff00:0:120#5 1-ff00:0:120#6 1-ff00:0:110#1",
 			), nil,
 		),
-		sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any(), gomock.Any()).Return(
+		sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any()).Return(
 			buildSDAnswer(t, ctrl,
 				"1-ff00:0:111#105 1-ff00:0:130#1002 1-ff00:0:130#1004 1-ff00:0:110#2",
 				"1-ff00:0:111#104 1-ff00:0:120#5 1-ff00:0:120#6 1-ff00:0:110#1",
@@ -256,9 +256,9 @@ func TestRevokeFastRecovery(t *testing.T) {
 	pr := pathmgr.New(sd, pathmgr.Timers{
 		NormalRefire: getDuration(100),
 		ErrorRefire:  getDuration(1),
-	})
+	}, 5)
 
-	sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any(), gomock.Any()).Return(
+	sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any()).Return(
 		buildSDAnswer(t, ctrl,
 			"1-ff00:0:111#105 1-ff00:0:130#1002 1-ff00:0:130#1004 1-ff00:0:110#2",
 		), nil,
@@ -277,7 +277,7 @@ func TestRevokeFastRecovery(t *testing.T) {
 		sd.EXPECT().RevNotification(gomock.Any(), gomock.Any()).Return(
 			&sciond.RevReply{Result: sciond.RevValid}, nil,
 		),
-		sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any(), gomock.Any()).MinTimes(2),
+		sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any()).MinTimes(2),
 	)
 	pr.Revoke(context.Background(), NewTestRev(t, xtest.MustParseIA("1-ff00:0:130"), 1002))
 	time.Sleep(getDuration(5))
@@ -351,12 +351,12 @@ func TestRevoke(t *testing.T) {
 	for name, test := range tests {
 		t.Run(name, func(t *testing.T) {
 			sd := mock_sciond.NewMockConnector(ctrl)
-			pr := pathmgr.New(sd, pathmgr.Timers{})
+			pr := pathmgr.New(sd, pathmgr.Timers{}, 5)
 
-			sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any(), gomock.Any()).Return(
+			sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any()).Return(
 				buildSDAnswer(t, ctrl, test.Paths...), nil,
 			)
-			sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any(), gomock.Any()).AnyTimes()
+			sd.EXPECT().Paths(gomock.Any(), dst, src, gomock.Any()).AnyTimes()
 			sp, err := pr.Watch(context.Background(), src, dst)
 			require.NoError(t, err)
 			sd.EXPECT().RevNotification(gomock.Any(), gomock.Any()).Return(