Repository: scionproto/scion
Pull request #3571 (2020-01-06 12:32:25)

Remove path resolution step from snet writes
Resolving paths in snet writes requires complex interactions between contexts and deadlines. However, most SCION applications never used this feature, instead opting to manage paths themselves (see CS, PS, SIG). Removing path resolution from writes makes snet simpler to reason about and maintain.


<!-- 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/3571)
<!-- Reviewable:end -->


karampok: @matzf  yes after this PR, user must always complete the path. Any objection use case for that?

Patch:
diff --git a/go/lib/snet/BUILD.bazel b/go/lib/snet/BUILD.bazel
index 51d36f8fc..59a89b29e 100644
--- a/go/lib/snet/BUILD.bazel
+++ b/go/lib/snet/BUILD.bazel
@@ -28,7 +28,6 @@ go_library(
         "//go/lib/log:go_default_library",
         "//go/lib/scmp:go_default_library",
         "//go/lib/serrors:go_default_library",
-        "//go/lib/snet/internal/ctxmonitor:go_default_library",
         "//go/lib/snet/internal/metrics:go_default_library",
         "//go/lib/sock/reliable:go_default_library",
         "//go/lib/spath:go_default_library",
@@ -52,14 +51,6 @@ go_test(
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/layers:go_default_library",
-        "//go/lib/mocks/net/mock_net:go_default_library",
-        "//go/lib/snet/internal/ctxmonitor:go_default_library",
-        "//go/lib/snet/internal/ctxmonitor/mock_ctxmonitor:go_default_library",
-        "//go/lib/snet/mock_snet:go_default_library",
-        "//go/lib/spath:go_default_library",
-        "//go/lib/topology/overlay:go_default_library",
-        "//go/lib/xtest:go_default_library",
-        "@com_github_golang_mock//gomock:go_default_library",
         "@com_github_stretchr_testify//assert:go_default_library",
     ],
 )
diff --git a/go/lib/snet/internal/ctxmonitor/BUILD.bazel b/go/lib/snet/internal/ctxmonitor/BUILD.bazel
deleted file mode 100644
index 96b1fa4ee..000000000
--- a/go/lib/snet/internal/ctxmonitor/BUILD.bazel
+++ /dev/null
@@ -1,19 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-go_library(
-    name = "go_default_library",
-    srcs = ["ctxmonitor.go"],
-    importpath = "github.com/scionproto/scion/go/lib/snet/internal/ctxmonitor",
-    visibility = ["//go/lib/snet:__subpackages__"],
-    deps = ["//go/lib/log:go_default_library"],
-)
-
-go_test(
-    name = "go_default_test",
-    srcs = ["ctxmonitor_test.go"],
-    embed = [":go_default_library"],
-    deps = [
-        "//go/lib/xtest:go_default_library",
-        "@com_github_stretchr_testify//assert:go_default_library",
-    ],
-)
diff --git a/go/lib/snet/internal/ctxmonitor/ctxmonitor.go b/go/lib/snet/internal/ctxmonitor/ctxmonitor.go
deleted file mode 100644
index 4ffb6b251..000000000
--- a/go/lib/snet/internal/ctxmonitor/ctxmonitor.go
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2018 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 ctxmonitor
-
-import (
-	"context"
-	"sync"
-	"time"
-
-	"github.com/scionproto/scion/go/lib/log"
-)
-
-// Monitor implements deadline-aware context creation. Implementations
-// guarantee that any contexts returned by the methods are cancelled when the
-// deadline passed in through SetDeadline is reached. Any change to deadline is
-// respected, including setting it into the past.
-//
-// Monitor is targeted for use in libraries that implement both deadline-aware
-// logic (e.g., net.Conn and net.PacketConn implementations), but also have
-// context-aware internals.
-//
-// IMPORTANT: If the deadline set through SetDeadline is reached and a
-// context is canceled, the error will be context.Canceled instead
-// of context.DeadlineExceeded.
-type Monitor interface {
-	// WithDeadline creates a context similarly to context.WithDeadline, except
-	// the newly created context is also subject to any deadlines set by
-	// SetDeadline.
-	WithDeadline(ctx context.Context, deadline time.Time) (context.Context, context.CancelFunc)
-	// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
-	WithTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc)
-	// SetDeadline sets an absolute time after which all tracked contexts are
-	// canceled. SetDeadline can be set in the past to mark everything as
-	// done, or can be extended in the future through repeated calls.
-	//
-	// A 0 value for the deadline means contexts will never be canceled by the monitor.
-	SetDeadline(deadline time.Time)
-	// Count returns the number of contexts in the monitor that haven't expired or
-	// been canceled yet.
-	Count() int
-}
-
-type ctxMap map[context.Context]context.CancelFunc
-
-var _ Monitor = (*monitor)(nil)
-
-type monitor struct {
-	deadlineRunner *DeadlineRunner
-
-	mtx      sync.Mutex
-	ctxs     ctxMap
-	deadline time.Time
-}
-
-func NewMonitor() Monitor {
-	m := &monitor{
-		ctxs: make(ctxMap),
-	}
-	m.deadlineRunner = NewDeadlineRunner(m.clean)
-	return m
-}
-
-func (m *monitor) WithTimeout(ctx context.Context,
-	timeout time.Duration) (context.Context, context.CancelFunc) {
-
-	return m.WithDeadline(ctx, time.Now().Add(timeout))
-}
-
-func (m *monitor) WithDeadline(ctx context.Context,
-	deadline time.Time) (context.Context, context.CancelFunc) {
-
-	m.mtx.Lock()
-	defer m.mtx.Unlock()
-
-	subCtx, cancelF := context.WithDeadline(ctx, deadline)
-	if m.deadlinePassed(time.Now()) {
-		// Deadline has already passed, all new contexts are already Done
-		cancelF()
-		return subCtx, cancelF
-	}
-	m.ctxs[subCtx] = cancelF
-	return subCtx, m.createDeletionLocked(subCtx, cancelF)
-}
-
-func (m *monitor) deadlinePassed(reference time.Time) bool {
-	return !m.deadline.Equal(time.Time{}) && m.deadline.Before(reference)
-}
-
-// createDeletionLocked returns a cancellation function that also deletes the
-// context information from the internal monitor map. This ensures proper
-// cleanup when contexts are canceled even if the monitor deadline is never
-// reached.
-func (m *monitor) createDeletionLocked(ctx context.Context,
-	cancelF context.CancelFunc) context.CancelFunc {
-
-	return func() {
-		m.mtx.Lock()
-		// If the map reference was changed, this is a no-op (and the entry in
-		// the old map will be GC'd together with the map)
-		delete(m.ctxs, ctx)
-		m.mtx.Unlock()
-		cancelF()
-	}
-}
-
-func (m *monitor) SetDeadline(deadline time.Time) {
-	m.mtx.Lock()
-	defer m.mtx.Unlock()
-	m.deadline = deadline
-	m.deadlineRunner.SetDeadline(deadline)
-}
-
-func (m *monitor) clean() {
-	m.mtx.Lock()
-	for _, cancelF := range m.ctxs {
-		cancelF()
-	}
-	m.ctxs = make(ctxMap)
-	m.mtx.Unlock()
-}
-
-func (m *monitor) Count() int {
-	m.mtx.Lock()
-	defer m.mtx.Unlock()
-	var count int
-	for ctx := range m.ctxs {
-		if ctx.Err() == nil {
-			count += 1
-		}
-	}
-	return count
-}
-
-// DeadlineRunner runs a callback function in a goroutine whenever the deadline
-// is reached. The function is called at most once per call to SetDeadline.
-//
-// Multiple executions of the callback can happen at once, so the callback must
-// ensure proper synchronization.
-//
-// SetDeadline is safe to call from multiple goroutines.
-type DeadlineRunner struct {
-	f     func()
-	mtx   sync.Mutex
-	timer *time.Timer
-}
-
-func NewDeadlineRunner(f func()) *DeadlineRunner {
-	return &DeadlineRunner{f: f}
-}
-
-func (r *DeadlineRunner) SetDeadline(deadline time.Time) {
-	r.mtx.Lock()
-	defer r.mtx.Unlock()
-	if r.timer != nil {
-		// We don't care about the return value of stop. If the function called
-		// by AfterFunc executed already, it means the old deadline was
-		// respected and we may execute it again according to the new deadline.
-		// Otherwise, we cancel the old timer and just wait for the new one.
-		r.timer.Stop()
-	}
-	if deadline.Equal(time.Time{}) {
-		return
-	}
-	r.timer = time.AfterFunc(deadline.Sub(time.Now()), func() {
-		defer log.LogPanicAndExit()
-		r.f()
-	})
-}
diff --git a/go/lib/snet/internal/ctxmonitor/mock_ctxmonitor/BUILD.bazel b/go/lib/snet/internal/ctxmonitor/mock_ctxmonitor/BUILD.bazel
deleted file mode 100644
index 90dca3f04..000000000
--- a/go/lib/snet/internal/ctxmonitor/mock_ctxmonitor/BUILD.bazel
+++ /dev/null
@@ -1,9 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-go_library(
-    name = "go_default_library",
-    srcs = ["ctxmonitor.go"],
-    importpath = "github.com/scionproto/scion/go/lib/snet/internal/ctxmonitor/mock_ctxmonitor",
-    visibility = ["//go/lib/snet:__subpackages__"],
-    deps = ["@com_github_golang_mock//gomock:go_default_library"],
-)
diff --git a/go/lib/snet/internal/ctxmonitor/mock_ctxmonitor/ctxmonitor.go b/go/lib/snet/internal/ctxmonitor/mock_ctxmonitor/ctxmonitor.go
deleted file mode 100644
index 6f981c2b8..000000000
--- a/go/lib/snet/internal/ctxmonitor/mock_ctxmonitor/ctxmonitor.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/scionproto/scion/go/lib/snet/internal/ctxmonitor (interfaces: Monitor)
-
-// Package mock_ctxmonitor is a generated GoMock package.
-package mock_ctxmonitor
-
-import (
-	context "context"
-	gomock "github.com/golang/mock/gomock"
-	reflect "reflect"
-	time "time"
-)
-
-// MockMonitor is a mock of Monitor interface
-type MockMonitor struct {
-	ctrl     *gomock.Controller
-	recorder *MockMonitorMockRecorder
-}
-
-// MockMonitorMockRecorder is the mock recorder for MockMonitor
-type MockMonitorMockRecorder struct {
-	mock *MockMonitor
-}
-
-// NewMockMonitor creates a new mock instance
-func NewMockMonitor(ctrl *gomock.Controller) *MockMonitor {
-	mock := &MockMonitor{ctrl: ctrl}
-	mock.recorder = &MockMonitorMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use
-func (m *MockMonitor) EXPECT() *MockMonitorMockRecorder {
-	return m.recorder
-}
-
-// Count mocks base method
-func (m *MockMonitor) Count() int {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Count")
-	ret0, _ := ret[0].(int)
-	return ret0
-}
-
-// Count indicates an expected call of Count
-func (mr *MockMonitorMockRecorder) Count() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockMonitor)(nil).Count))
-}
-
-// SetDeadline mocks base method
-func (m *MockMonitor) SetDeadline(arg0 time.Time) {
-	m.ctrl.T.Helper()
-	m.ctrl.Call(m, "SetDeadline", arg0)
-}
-
-// SetDeadline indicates an expected call of SetDeadline
-func (mr *MockMonitorMockRecorder) SetDeadline(arg0 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockMonitor)(nil).SetDeadline), arg0)
-}
-
-// WithDeadline mocks base method
-func (m *MockMonitor) WithDeadline(arg0 context.Context, arg1 time.Time) (context.Context, context.CancelFunc) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "WithDeadline", arg0, arg1)
-	ret0, _ := ret[0].(context.Context)
-	ret1, _ := ret[1].(context.CancelFunc)
-	return ret0, ret1
-}
-
-// WithDeadline indicates an expected call of WithDeadline
-func (mr *MockMonitorMockRecorder) WithDeadline(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithDeadline", reflect.TypeOf((*MockMonitor)(nil).WithDeadline), arg0, arg1)
-}
-
-// WithTimeout mocks base method
-func (m *MockMonitor) WithTimeout(arg0 context.Context, arg1 time.Duration) (context.Context, context.CancelFunc) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "WithTimeout", arg0, arg1)
-	ret0, _ := ret[0].(context.Context)
-	ret1, _ := ret[1].(context.CancelFunc)
-	return ret0, ret1
-}
-
-// WithTimeout indicates an expected call of WithTimeout
-func (mr *MockMonitorMockRecorder) WithTimeout(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithTimeout", reflect.TypeOf((*MockMonitor)(nil).WithTimeout), arg0, arg1)
-}
diff --git a/go/lib/snet/mock_snet/snet.go b/go/lib/snet/mock_snet/snet.go
index f324e2f83..9d89fef80 100644
--- a/go/lib/snet/mock_snet/snet.go
+++ b/go/lib/snet/mock_snet/snet.go
@@ -291,7 +291,7 @@ func (m *MockNetwork) EXPECT() *MockNetworkMockRecorder {
 }
 
 // Dial mocks base method
-func (m *MockNetwork) Dial(arg0 string, arg1 *net.UDPAddr, arg2 *snet.UDPAddr, arg3 addr.HostSVC, arg4 time.Duration) (snet.Conn, error) {
+func (m *MockNetwork) Dial(arg0 context.Context, arg1 string, arg2 *net.UDPAddr, arg3 *snet.UDPAddr, arg4 addr.HostSVC) (snet.Conn, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Dial", arg0, arg1, arg2, arg3, arg4)
 	ret0, _ := ret[0].(snet.Conn)
@@ -306,7 +306,7 @@ func (mr *MockNetworkMockRecorder) Dial(arg0, arg1, arg2, arg3, arg4 interface{}
 }
 
 // Listen mocks base method
-func (m *MockNetwork) Listen(arg0 string, arg1 *net.UDPAddr, arg2 addr.HostSVC, arg3 time.Duration) (snet.Conn, error) {
+func (m *MockNetwork) Listen(arg0 context.Context, arg1 string, arg2 *net.UDPAddr, arg3 addr.HostSVC) (snet.Conn, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Listen", arg0, arg1, arg2, arg3)
 	ret0, _ := ret[0].(snet.Conn)
diff --git a/go/lib/snet/snet.go b/go/lib/snet/snet.go
index 88999c69e..a1691e08a 100644
--- a/go/lib/snet/snet.go
+++ b/go/lib/snet/snet.go
@@ -96,6 +96,8 @@ func NewCustomNetworkWithPR(ia addr.IA, pktDispatcher PacketDispatcherService) *
 // Dial returns a SCION connection to remote. Nil values for listen are not
 // supported yet. Parameter network must be "udp". The returned connection's
 // Read and Write methods can be used to receive and send SCION packets.
+// Remote address requires a path and the underlay net hop to be set if the
+// destination is in a remote AS.
 //
 // The context is used for connection setup, it doesn't affect the returned
 // connection.
diff --git a/go/lib/snet/writer.go b/go/lib/snet/writer.go
index 0418f42ba..08f4d1de6 100644
--- a/go/lib/snet/writer.go
+++ b/go/lib/snet/writer.go
@@ -16,7 +16,6 @@
 package snet
 
 import (
-	"context"
 	"fmt"
 	"net"
 	"sync"
@@ -25,8 +24,6 @@ import (
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/l4"
-	"github.com/scionproto/scion/go/lib/serrors"
-	"github.com/scionproto/scion/go/lib/snet/internal/ctxmonitor"
 	"github.com/scionproto/scion/go/lib/topology/overlay"
 )
 
@@ -49,9 +46,8 @@ const (
 )
 
 type scionConnWriter struct {
-	base     *scionConnBase
-	conn     PacketConn
-	resolver *remoteAddressResolver
+	base *scionConnBase
+	conn PacketConn
 
 	mtx    sync.Mutex
 	buffer common.RawBytes
@@ -61,13 +57,8 @@ func newScionConnWriter(base *scionConnBase, querier PathQuerier,
 	conn PacketConn) *scionConnWriter {
 
 	return &scionConnWriter{
-		base: base,
-		conn: conn,
-		resolver: &remoteAddressResolver{
-			localIA:     base.scionNet.localIA,
-			pathQuerier: querier,
-			monitor:     ctxmonitor.NewMonitor(),
-		},
+		base:   base,
+		conn:   conn,
 		buffer: make(common.RawBytes, common.MaxMTU),
 	}
 }
@@ -98,18 +89,6 @@ func (c *scionConnWriter) Write(b []byte) (int, error) {
 }
 
 func (c *scionConnWriter) write(b []byte, raddr *Addr) (int, error) {
-	var connRemote *Addr
-	if c.base.remote != nil {
-		connRemote = c.base.remote.ToAddr()
-	}
-	raddr, err := c.resolver.ResolveAddrPair(connRemote, raddr)
-	if err != nil {
-		return 0, err
-	}
-	return c.writeWithLock(b, raddr)
-}
-
-func (c *scionConnWriter) writeWithLock(b []byte, raddr *Addr) (int, error) {
 	c.mtx.Lock()
 	defer c.mtx.Unlock()
 	pkt := &SCIONPacket{
@@ -127,6 +106,11 @@ func (c *scionConnWriter) writeWithLock(b []byte, raddr *Addr) (int, error) {
 			Payload: common.RawBytes(b),
 		},
 	}
+
+	if raddr.NextHop == nil && c.base.scionNet.localIA.Equal(raddr.IA) {
+		raddr.NextHop = &net.UDPAddr{IP: raddr.Host.L3.IP(), Port: overlay.EndhostPort}
+	}
+
 	if err := c.conn.WriteTo(pkt, raddr.NextHop); err != nil {
 		return 0, err
 	}
@@ -134,97 +118,5 @@ func (c *scionConnWriter) writeWithLock(b []byte, raddr *Addr) (int, error) {
 }
 
 func (c *scionConnWriter) SetWriteDeadline(t time.Time) error {
-	if err := c.conn.SetWriteDeadline(t); err != nil {
-		return err
-	}
-	c.resolver.monitor.SetDeadline(t)
-	return nil
-}
-
-// remoteAddressResolver validates the contents of a remote snet address,
-// taking into account both the remote address that might be present on a conn
-// object, and the remote address passed in as argument to WriteTo or
-// WriteToSCION.
-type remoteAddressResolver struct {
-	// localIA is the local AS. Path and overlay resolution differs between
-	// destinations residing in the local AS, and destinations residing in
-	// other ASes.
-	localIA addr.IA
-	// pathResolver is a source of paths and overlay addresses for snet.
-	pathQuerier PathQuerier
-	// monitor tracks contexts created for sciond
-	monitor ctxmonitor.Monitor
-}
-
-func (r *remoteAddressResolver) ResolveAddrPair(connAddr, argAddr *Addr) (*Addr, error) {
-	switch {
-	case connAddr == nil && argAddr == nil:
-		return nil, common.NewBasicError(ErrNoAddr, nil)
-	case connAddr != nil && argAddr != nil:
-		return nil, common.NewBasicError(ErrDuplicateAddr, nil)
-	case connAddr != nil:
-		return r.ResolveAddr(connAddr)
-	default:
-		// argAddr != nil
-		return r.ResolveAddr(argAddr)
-	}
-}
-
-func (r *remoteAddressResolver) ResolveAddr(address *Addr) (*Addr, error) {
-	if address == nil {
-		return nil, common.NewBasicError(ErrAddressIsNil, nil)
-	}
-	if address.Host == nil {
-		return nil, common.NewBasicError(ErrNoApplicationAddress, nil)
-	}
-	if r.localIA.Equal(address.IA) {
-		return r.resolveLocalDestination(address)
-	}
-	return r.resolveRemoteDestination(address)
-}
-
-func (r *remoteAddressResolver) resolveLocalDestination(address *Addr) (*Addr, error) {
-	if address.Path != nil {
-		return nil, common.NewBasicError(ErrExtraPath, nil)
-	}
-	if address.NextHop == nil {
-		return addOverlayFromScionAddress(address), nil
-	}
-	return address, nil
-}
-
-func (r *remoteAddressResolver) resolveRemoteDestination(address *Addr) (*Addr, error) {
-	switch {
-	case address.Path != nil && address.NextHop == nil:
-		return nil, common.NewBasicError(ErrBadOverlay, nil)
-	case address.Path == nil && address.NextHop != nil:
-		return nil, common.NewBasicError(ErrMustHavePath, nil)
-	case address.Path != nil:
-		return address, nil
-	default:
-		return r.addPath(address)
-	}
-}
-
-func (r *remoteAddressResolver) addPath(address *Addr) (*Addr, error) {
-	var err error
-	address = address.Copy()
-	ctx, cancelF := r.monitor.WithTimeout(context.Background(), DefaultPathQueryTimeout)
-	defer cancelF()
-	paths, err := r.pathQuerier.Query(ctx, address.IA)
-	if err != nil {
-		return nil, serrors.Wrap(ErrPath, err)
-	}
-	if len(paths) == 0 {
-		return nil, serrors.WithCtx(ErrPath, "reason", "no path found")
-	}
-	address.NextHop = paths[0].OverlayNextHop()
-	address.Path = paths[0].Path()
-	return address, nil
-}
-
-func addOverlayFromScionAddress(address *Addr) *Addr {
-	address = address.Copy()
-	address.NextHop = &net.UDPAddr{IP: address.Host.L3.IP(), Port: overlay.EndhostPort}
-	return address
+	return c.conn.SetWriteDeadline(t)
 }
diff --git a/tools/gomocks b/tools/gomocks
index 9cfd14ac3..aea52b4be 100755
--- a/tools/gomocks
+++ b/tools/gomocks
@@ -58,7 +58,6 @@ MOCK_TARGETS = [
         (SCION_PACKAGE_PREFIX + "/go/lib/sciond", "Service,Connector"),
         (SCION_PACKAGE_PREFIX + "/go/lib/snet", "Conn,PacketDispatcherService,Network,PacketConn," +
             "Path,PathQuerier,Router"),
-        (SCION_PACKAGE_PREFIX + "/go/lib/snet/internal/ctxmonitor", "Monitor"),
         (SCION_PACKAGE_PREFIX + "/go/lib/sock/reliable", "Dispatcher"),
         (SCION_PACKAGE_PREFIX + "/go/lib/sock/reliable/reconnect", "IOOperation,Reconnecter"),
         (SCION_PACKAGE_PREFIX + "/go/lib/svc", "RequestHandler,RoundTripper"),


Test patch:
diff --git a/go/lib/snet/export_test.go b/go/lib/snet/export_test.go
index 5de16c477..04c9deeeb 100644
--- a/go/lib/snet/export_test.go
+++ b/go/lib/snet/export_test.go
@@ -18,7 +18,6 @@ import (
 	"net"
 
 	"github.com/scionproto/scion/go/lib/addr"
-	"github.com/scionproto/scion/go/lib/snet/internal/ctxmonitor"
 )
 
 var NewScionConnWriter = newScionConnWriter
@@ -29,13 +28,3 @@ func NewScionConnBase(localIA addr.IA, listen *net.UDPAddr) *scionConnBase {
 		scionNet: &SCIONNetwork{localIA: localIA},
 	}
 }
-
-func NewRemoteAddressResolver(localIA addr.IA, pq PathQuerier,
-	monitor ctxmonitor.Monitor) *remoteAddressResolver {
-
-	return &remoteAddressResolver{
-		localIA:     localIA,
-		pathQuerier: pq,
-		monitor:     monitor,
-	}
-}
diff --git a/go/lib/snet/internal/ctxmonitor/ctxmonitor_test.go b/go/lib/snet/internal/ctxmonitor/ctxmonitor_test.go
deleted file mode 100644
index 17be8d7f9..000000000
--- a/go/lib/snet/internal/ctxmonitor/ctxmonitor_test.go
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright 2018 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 ctxmonitor
-
-import (
-	"context"
-	"sync/atomic"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-
-	"github.com/scionproto/scion/go/lib/xtest"
-)
-
-const (
-	sleepForCallback = 10 * time.Millisecond
-	maxWaitTime      = time.Second
-)
-
-func TestMonitor(t *testing.T) {
-	t.Log("Given a monitor")
-	//m := NewMonitor()
-	t.Run("Add one context via Deadline, count is 1", func(t *testing.T) {
-		m := NewMonitor()
-		m.WithDeadline(context.Background(), time.Now().Add(time.Millisecond))
-		assert.Equal(t, m.Count(), 1)
-	})
-	t.Run("Add one context via Timeout, count is 1", func(t *testing.T) {
-		m := NewMonitor()
-		m.WithTimeout(context.Background(), time.Second)
-		assert.Equal(t, m.Count(), 1)
-	})
-	t.Run("Add two contexts, count is 2", func(t *testing.T) {
-		m := NewMonitor()
-		m.WithTimeout(context.Background(), time.Second)
-		m.WithTimeout(context.Background(), time.Second)
-		assert.Equal(t, m.Count(), 2)
-	})
-	t.Run("Add one context, wait for it to expire, count is 0", func(t *testing.T) {
-		m := NewMonitor()
-		ctx, cancelF := m.WithTimeout(context.Background(), time.Millisecond)
-		xtest.AssertReadReturnsBefore(t, ctx.Done(), maxWaitTime)
-		assert.Equal(t, m.Count(), 0)
-		cancelF()
-	})
-	t.Run("Add one context, cancel it, count is 0", func(t *testing.T) {
-		m := NewMonitor()
-		_, cancelF := m.WithTimeout(context.Background(), time.Millisecond)
-		cancelF()
-		assert.Equal(t, m.Count(), 0)
-	})
-	t.Run("Add one context, wait for parent to expire, count is 0", func(t *testing.T) {
-		m := NewMonitor()
-		parent, cancelF := context.WithTimeout(context.Background(), time.Microsecond)
-		m.WithTimeout(parent, time.Second)
-		xtest.AssertReadReturnsBefore(t, parent.Done(), maxWaitTime)
-		assert.Equal(t, m.Count(), 0)
-		cancelF()
-	})
-	t.Run("Add one context, cancel parent, count is 0", func(t *testing.T) {
-		m := NewMonitor()
-		parent, parentCancelF := context.WithTimeout(context.Background(), time.Second)
-		m.WithTimeout(parent, time.Second)
-		parentCancelF()
-		assert.Equal(t, m.Count(), 0)
-	})
-	t.Run("Add one context, set deadline in the past, count is 0, ctx is Done", func(t *testing.T) {
-		m := NewMonitor()
-		ctx, cancelF := m.WithTimeout(context.Background(), time.Second)
-		m.SetDeadline(time.Now().Add(-time.Second))
-		time.Sleep(sleepForCallback)
-		assert.Equal(t, m.Count(), 0, "count")
-		assert.Equal(t, ctx.Err(), context.Canceled, "err")
-		cancelF()
-	})
-	t.Run("Add one context, set deadline in the future, count is 1, ctx is not Done",
-		func(t *testing.T) {
-			m := NewMonitor()
-			ctx, cancelF := m.WithTimeout(context.Background(), time.Second)
-			m.SetDeadline(time.Now().Add(time.Second))
-			time.Sleep(sleepForCallback)
-			assert.Equal(t, m.Count(), 1, "count")
-			assert.NoError(t, ctx.Err())
-			cancelF()
-		})
-	t.Run("Set deadline to now, add context, ctx is immediately Done", func(t *testing.T) {
-		m := NewMonitor()
-		m.SetDeadline(time.Now())
-		ctx, cancelF := m.WithTimeout(context.Background(), time.Second)
-		assert.Equal(t, m.Count(), 0, "count")
-		assert.Error(t, ctx.Err())
-		cancelF()
-	})
-	t.Run("Set deadline in future, add context with deadline after, ctx is not Done",
-		func(t *testing.T) {
-			m := NewMonitor()
-			deadline := time.Now().Add(5 * time.Second)
-			m.SetDeadline(deadline)
-			ctx, cancelF := m.WithDeadline(context.Background(), deadline.Add(time.Second))
-			assert.NoError(t, ctx.Err())
-			cancelF()
-		})
-}
-
-func TestDeadlineRunner(t *testing.T) {
-	t.Log("Given a deadline runner")
-	t.Run("If no deadline, function is not executed", func(t *testing.T) {
-		var runCount int64
-		assert.Equal(t, atomic.LoadInt64(&runCount), int64(0))
-	})
-	t.Run("If deadline in the past, function is executed", func(t *testing.T) {
-		var runCount int64
-		done := make(chan struct{})
-		r := NewDeadlineRunner(
-			func() {
-				atomic.AddInt64(&runCount, 1)
-				close(done)
-			})
-
-		r.SetDeadline(time.Now().Add(-time.Second))
-		xtest.AssertReadReturnsBefore(t, done, time.Second)
-		assert.Equal(t, atomic.LoadInt64(&runCount), int64(1))
-	})
-	t.Run("If deadline in the future, function is not executed", func(t *testing.T) {
-		var runCount int64
-		done := make(chan struct{})
-		r := NewDeadlineRunner(
-			func() {
-				atomic.AddInt64(&runCount, 1)
-				close(done)
-			})
-
-		r.SetDeadline(time.Now().Add(time.Second))
-		assert.Equal(t, atomic.LoadInt64(&runCount), int64(0))
-	})
-	t.Run("If deadline in the future and we wait, function is executed", func(t *testing.T) {
-		var runCount int64
-		done := make(chan struct{})
-		r := NewDeadlineRunner(
-			func() {
-				atomic.AddInt64(&runCount, 1)
-				close(done)
-			})
-
-		r.SetDeadline(time.Now().Add(10 * time.Millisecond))
-		xtest.AssertReadReturnsBefore(t, done, time.Second)
-		assert.Equal(t, atomic.LoadInt64(&runCount), int64(1))
-	})
-	t.Run("If deadline in the future, and then extended, the first doesn't trigger",
-		func(t *testing.T) {
-			var runCount int64
-			done := make(chan struct{})
-			r := NewDeadlineRunner(
-				func() {
-					atomic.AddInt64(&runCount, 1)
-					close(done)
-				})
-
-			r.SetDeadline(time.Now().Add(10 * time.Millisecond))
-			r.SetDeadline(time.Now().Add(time.Second))
-			time.Sleep(20 * time.Millisecond)
-			assert.Equal(t, atomic.LoadInt64(&runCount), int64(0))
-		})
-	t.Run("If deadline in the future, and is then set in past, function is executed",
-		func(t *testing.T) {
-			var runCount int64
-			done := make(chan struct{})
-			r := NewDeadlineRunner(
-				func() {
-					atomic.AddInt64(&runCount, 1)
-					close(done)
-				})
-
-			r.SetDeadline(time.Now().Add(time.Second))
-			r.SetDeadline(time.Now().Add(-time.Second))
-			xtest.AssertReadReturnsBefore(t, done, time.Second)
-			assert.Equal(t, atomic.LoadInt64(&runCount), int64(1))
-		})
-	t.Run("Setting a deadline to 0 resets it, function is not executed", func(t *testing.T) {
-		var runCount int64
-		done := make(chan struct{})
-		r := NewDeadlineRunner(
-			func() {
-				atomic.AddInt64(&runCount, 1)
-				close(done)
-			})
-		r.SetDeadline(time.Now().Add(10 * time.Millisecond))
-		r.SetDeadline(time.Time{})
-		time.Sleep(sleepForCallback)
-		assert.Equal(t, atomic.LoadInt64(&runCount), int64(0))
-	})
-}
diff --git a/go/lib/snet/writer_test.go b/go/lib/snet/writer_test.go
index 4a8db1396..185fcf943 100644
--- a/go/lib/snet/writer_test.go
+++ b/go/lib/snet/writer_test.go
@@ -14,209 +14,3 @@
 // limitations under the License.
 
 package snet_test
-
-import (
-	"context"
-	"fmt"
-	"net"
-	"testing"
-	"time"
-
-	"github.com/golang/mock/gomock"
-	"github.com/stretchr/testify/assert"
-
-	"github.com/scionproto/scion/go/lib/addr"
-	"github.com/scionproto/scion/go/lib/mocks/net/mock_net"
-	"github.com/scionproto/scion/go/lib/snet"
-	"github.com/scionproto/scion/go/lib/snet/internal/ctxmonitor"
-	"github.com/scionproto/scion/go/lib/snet/internal/ctxmonitor/mock_ctxmonitor"
-	"github.com/scionproto/scion/go/lib/snet/mock_snet"
-	"github.com/scionproto/scion/go/lib/spath"
-	"github.com/scionproto/scion/go/lib/topology/overlay"
-	"github.com/scionproto/scion/go/lib/xtest"
-)
-
-func buildNullMonitorMock(ctrl *gomock.Controller) ctxmonitor.Monitor {
-	monitor := mock_ctxmonitor.NewMockMonitor(ctrl)
-	monitor.EXPECT().WithTimeout(gomock.Any(), gomock.Any()).AnyTimes().
-		Return(context.Background(), func() {})
-	return monitor
-}
-
-func TestConnRemoteAddressResolver(t *testing.T) {
-	t.Log("Given a remote address resolver")
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-	resolver := snet.NewRemoteAddressResolver(addr.IA{}, nil, buildNullMonitorMock(ctrl))
-	t.Run("If both addresses are unknown, error out", func(t *testing.T) {
-		address, err := resolver.ResolveAddrPair(nil, nil)
-		assert.Error(t, err, "err")
-		assert.Nil(t, address, "address")
-	})
-	t.Run("If both address are known, error out", func(t *testing.T) {
-		connRemoteAddress := MustParseAddr("1-ff00:0:113,[127.0.0.1]:80")
-		argRemoteAddress := MustParseAddr("1-ff00:0:110,[127.0.0.1]:80")
-		address, err := resolver.ResolveAddrPair(connRemoteAddress, argRemoteAddress)
-		assert.Error(t, err, "err")
-		assert.Nil(t, address, "address")
-	})
-}
-
-func TestRemoteAddressResolver(t *testing.T) {
-	t.Log("Given a single remote address resolver")
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-	pathSource := mock_snet.NewMockPathQuerier(ctrl)
-	resolver := snet.NewRemoteAddressResolver(
-		xtest.MustParseIA("1-ff00:0:110"),
-		pathSource,
-		buildNullMonitorMock(ctrl),
-	)
-
-	t.Run("error if address is nil", func(t *testing.T) {
-		address, err := resolver.ResolveAddr(nil)
-		assert.EqualError(t, err, string(snet.ErrAddressIsNil))
-		assert.Nil(t, address, "address")
-	})
-
-	t.Run("error if app address is unset", func(t *testing.T) {
-		address := &snet.Addr{}
-		address, err := resolver.ResolveAddr(address)
-		assert.EqualError(t, err, string(snet.ErrNoApplicationAddress))
-		assert.Nil(t, address, "address")
-	})
-
-	t.Run("if destination is in local AS", func(t *testing.T) {
-		t.Run("error if path set.", func(t *testing.T) {
-			inAddress := MustParseAddr("1-ff00:0:110,[127.0.0.1]:80")
-			inAddress.Path = &spath.Path{}
-			outAddress, err := resolver.ResolveAddr(inAddress)
-			assert.EqualError(t, err, string(snet.ErrExtraPath))
-			assert.Nil(t, outAddress, "address")
-		})
-		t.Run("return same address if path unset, and overlay address set.", func(t *testing.T) {
-			inAddress := MustParseAddr("1-ff00:0:110,[127.0.0.1]:80")
-			inAddress.NextHop = &net.UDPAddr{}
-			outAddress, err := resolver.ResolveAddr(inAddress)
-			assert.NoError(t, err, "err")
-			assert.Equal(t, outAddress, inAddress)
-		})
-		t.Run("inherit overlay data if overlay address unset.", func(t *testing.T) {
-			inAddress := MustParseAddr("1-ff00:0:110,[127.0.0.1]:80")
-			outAddress, err := resolver.ResolveAddr(inAddress)
-			assert.NoError(t, err)
-			assert.NotNil(t, outAddress)
-			assert.Equal(t, outAddress.NextHop.IP, outAddress.Host.L3.IP(), "overlay addr")
-			assert.Equal(t, outAddress.NextHop.Port, overlay.EndhostPort, "overlay port")
-		})
-	})
-	t.Run("if destination is not in local AS", func(t *testing.T) {
-		t.Run("error if path set but overlay address unset.", func(t *testing.T) {
-			inAddress := MustParseAddr("1-ff00:0:113,[127.0.0.1]:80")
-			inAddress.Path = &spath.Path{}
-			outAddress, err := resolver.ResolveAddr(inAddress)
-			assert.EqualError(t, err, string(snet.ErrBadOverlay))
-			assert.Nil(t, outAddress, "address")
-		})
-		t.Run("error if overlay set but path unset.", func(t *testing.T) {
-			inAddress := MustParseAddr("1-ff00:0:113,[127.0.0.1]:80")
-			inAddress.NextHop = &net.UDPAddr{}
-			outAddress, err := resolver.ResolveAddr(inAddress)
-			assert.EqualError(t, err, string(snet.ErrMustHavePath))
-			assert.Nil(t, outAddress, "address")
-		})
-		t.Run("return same address if path and overlay set.", func(t *testing.T) {
-			inAddress := MustParseAddr("1-ff00:0:113,[127.0.0.1]:80")
-			inAddress.Path = &spath.Path{}
-			inAddress.NextHop = &net.UDPAddr{}
-			outAddress, err := resolver.ResolveAddr(inAddress)
-			assert.NoError(t, err, "err")
-			assert.Equal(t, outAddress, inAddress)
-		})
-		t.Run("request path if path and overlay unset", func(t *testing.T) {
-			t.Run("if request not successful, error.", func(t *testing.T) {
-				inAddress := MustParseAddr("1-ff00:0:113,[127.0.0.1]:80")
-				pathSource.EXPECT().Query(gomock.Any(), gomock.Any()).
-					Return(nil, fmt.Errorf("some error"))
-				outAddress, err := resolver.ResolveAddr(inAddress)
-				xtest.AssertErrorsIs(t, err, snet.ErrPath)
-				assert.Nil(t, outAddress, "address")
-			})
-			t.Run("if request successful, return address.", func(t *testing.T) {
-				inAddress := MustParseAddr("1-ff00:0:113,[127.0.0.1]:80")
-				ctrlPath := mock_snet.NewMockPath(ctrl)
-				path := &spath.Path{}
-				overlayAddr := &net.UDPAddr{}
-				ctrlPath.EXPECT().Path().Return(path)
-				ctrlPath.EXPECT().OverlayNextHop().Return(overlayAddr)
-				pathSource.EXPECT().Query(gomock.Any(), gomock.Any()).
-					Return([]snet.Path{ctrlPath}, nil)
-				outAddress, err := resolver.ResolveAddr(inAddress)
-				assert.NoError(t, err)
-				assert.NotNil(t, outAddress)
-				assert.Equal(t, outAddress.Path, path)
-				assert.Equal(t, outAddress.NextHop, overlayAddr)
-			})
-		})
-	})
-}
-
-func TestSetDeadline(t *testing.T) {
-	t.Log("Given an snet write connection")
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	done := make(chan struct{}, 3)
-
-	querierMock := mock_snet.NewMockPathQuerier(ctrl)
-	querierMock.EXPECT().Query(gomock.Any(), gomock.Any()).DoAndReturn(
-
-		func(ctx context.Context, _ interface{}) ([]snet.Path, error) {
-			select {
-			case <-ctx.Done():
-				done <- struct{}{}
-			}
-			return nil, nil
-		},
-	).AnyTimes()
-	connMock := mock_net.NewMockPacketConn(ctrl)
-	connMock.EXPECT().SetWriteDeadline(gomock.Any()).AnyTimes().Return(nil)
-	packetConn := snet.NewSCIONPacketConn(connMock, nil)
-
-	conn := snet.NewScionConnWriter(snet.NewScionConnBase(
-		xtest.MustParseIA("2-ff00:0:1"), MustParseUDPAddr("[127.0.0.1]:80"),
-	), querierMock, packetConn)
-	t.Run("And writes to multiple destinations for which path resolution is slow",
-		func(t *testing.T) {
-			addresses := []*snet.Addr{
-				MustParseAddr("1-ff00:0:1,[127.0.0.1]:80"),
-				MustParseAddr("1-ff00:0:2,[127.0.0.1]:80"),
-				MustParseAddr("1-ff00:0:3,[127.0.0.1]:80"),
-			}
-			for _, address := range addresses {
-				go conn.WriteTo([]byte{1, 2, 3}, address)
-			}
-			t.Run("Setting the deadline in the past unlocks all writers", func(t *testing.T) {
-				conn.SetWriteDeadline(time.Now().Add(-time.Second))
-				for range addresses {
-					xtest.AssertReadReturnsBefore(t, done, time.Second)
-				}
-			})
-		})
-}
-
-func MustParseAddr(str string) *snet.Addr {
-	address, err := snet.AddrFromString(str)
-	if err != nil {
-		panic(fmt.Sprintf("cannot parse address %s", str))
-	}
-	return address
-}
-
-func MustParseUDPAddr(ipPort string) *net.UDPAddr {
-	a, err := net.ResolveUDPAddr("udp", ipPort)
-	if err != nil {
-		panic(fmt.Sprintf("cannot resolve udp addr %s, %v", ipPort, err))
-	}
-	return a
-}