Repository: scionproto/scion
Pull request #3806 (2020-06-30 12:13:10)

cs: prepare beaconing for header changes
Prepare the beaconing package to be able to deal with using the two
path header formats.

The beacon extender is abstracted into an interface. When support
for header v2 is added, go/pkg/cs just needs to inject the correct
beacon extender.

The onehop.Sender is abstracted into an interface. When support
for header v2 is added, go/pkg/cs just needs to inject a one hop
sender that conforms to the new format.

The SegmentRegistration RPC is abstracted into an interface that has
no resemblance with infra.Messenger. A compat adapter is introduced
in beaconing/compat. Thus, a messenger that uses the new header format
can be injected.

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



Patch:
diff --git a/go/cs/beaconing/BUILD.bazel b/go/cs/beaconing/BUILD.bazel
index 2c2e5934e..2900b3236 100644
--- a/go/cs/beaconing/BUILD.bazel
+++ b/go/cs/beaconing/BUILD.bazel
@@ -5,7 +5,6 @@ go_library(
     srcs = [
         "doc.go",
         "extender.go",
-        "extender_config.go",
         "handler.go",
         "originator.go",
         "propagator.go",
@@ -24,7 +23,6 @@ go_library(
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl:go_default_library",
-        "//go/lib/ctrl/path_mgmt:go_default_library",
         "//go/lib/ctrl/seg:go_default_library",
         "//go/lib/infra:go_default_library",
         "//go/lib/infra/messenger:go_default_library",
@@ -59,11 +57,9 @@ go_test(
         "//go/cs/beaconing/mock_beaconing:go_default_library",
         "//go/cs/ifstate:go_default_library",
         "//go/cs/metrics:go_default_library",
-        "//go/cs/onehop:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl:go_default_library",
-        "//go/lib/ctrl/path_mgmt:go_default_library",
         "//go/lib/ctrl/seg:go_default_library",
         "//go/lib/infra:go_default_library",
         "//go/lib/infra/mock_infra:go_default_library",
@@ -74,7 +70,6 @@ go_test(
         "//go/lib/scrypto/cppki:go_default_library",
         "//go/lib/serrors:go_default_library",
         "//go/lib/snet:go_default_library",
-        "//go/lib/snet/mock_snet:go_default_library",
         "//go/lib/spath:go_default_library",
         "//go/lib/topology:go_default_library",
         "//go/lib/util:go_default_library",
diff --git a/go/cs/beaconing/compat/BUILD.bazel b/go/cs/beaconing/compat/BUILD.bazel
new file mode 100644
index 000000000..57c4655d3
--- /dev/null
+++ b/go/cs/beaconing/compat/BUILD.bazel
@@ -0,0 +1,14 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["messenger.go"],
+    importpath = "github.com/scionproto/scion/go/cs/beaconing/compat",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/lib/ctrl/path_mgmt:go_default_library",
+        "//go/lib/ctrl/seg:go_default_library",
+        "//go/lib/infra:go_default_library",
+        "//go/lib/infra/messenger:go_default_library",
+    ],
+)
diff --git a/go/cs/beaconing/compat/messenger.go b/go/cs/beaconing/compat/messenger.go
new file mode 100644
index 000000000..654ef43a6
--- /dev/null
+++ b/go/cs/beaconing/compat/messenger.go
@@ -0,0 +1,40 @@
+// Copyright 2020 Anapaya Systems
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package compat
+
+import (
+	"context"
+	"net"
+
+	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
+	"github.com/scionproto/scion/go/lib/ctrl/seg"
+	"github.com/scionproto/scion/go/lib/infra"
+	"github.com/scionproto/scion/go/lib/infra/messenger"
+)
+
+// RPC is an adapter for infra.Messenger
+type RPC struct {
+	Messenger infra.Messenger
+}
+
+// RegisterSegment registers a segment with the remote.
+func (rpc RPC) RegisterSegment(ctx context.Context, meta seg.Meta, remote net.Addr) error {
+	req := &path_mgmt.SegReg{
+		SegRecs: &path_mgmt.SegRecs{
+			Recs: []*seg.Meta{&meta},
+		},
+	}
+	return rpc.Messenger.SendSegReg(ctx, req, remote, messenger.NextId())
+}
diff --git a/go/cs/beaconing/extender.go b/go/cs/beaconing/extender.go
index 92f80900a..4a607ab31 100644
--- a/go/cs/beaconing/extender.go
+++ b/go/cs/beaconing/extender.go
@@ -16,12 +16,13 @@ package beaconing
 
 import (
 	"context"
-	"sync"
+	"hash"
 	"time"
 
 	"github.com/scionproto/scion/go/cs/ifstate"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/ctrl"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/log"
 	"github.com/scionproto/scion/go/lib/serrors"
@@ -30,28 +31,47 @@ import (
 	"github.com/scionproto/scion/go/lib/util"
 )
 
-// segExtender appends AS entries to provided path segments.
-type segExtender struct {
-	cfg    ExtenderConf
-	macMtx sync.Mutex
+// legacyIfIDSize is the default bit-size for ifids in the hop-fields.
+const legacyIfIDSize = 12
+
+// Extender extends path segments.
+type Extender interface {
+	// Extend extends the path segment. The zero value for ingress indicates
+	// that the created AS entry is the initial entry in a path. The zero value
+	// for egress indicates that the created AS entry is the last entry in the
+	// path, and the beacon is terminated.
+	Extend(ctx context.Context, seg *seg.PathSegment, ingress, egress common.IFIDType,
+		peers []common.IFIDType) error
 }
 
-func (cfg ExtenderConf) new() (*segExtender, error) {
-	cfg.InitDefaults()
-	if err := cfg.Validate(); err != nil {
-		return nil, err
-	}
-	return &segExtender{cfg: cfg}, nil
+// LegacyExtender appends AS entries to provided path segments.
+type LegacyExtender struct {
+	// IA is the local IA
+	IA addr.IA
+	// Signer is used to sign path segments.
+	Signer ctrl.Signer
+	// MAC is used to calculate the hop field MAC.
+	MAC func() hash.Hash
+	// Intfs holds all interfaces in the AS.
+	Intfs *ifstate.Interfaces
+	// MTU is the MTU value set in the AS entries.
+	MTU uint16
+	// GetMaxExpTime returns the maximum relative expiration time.
+	MaxExpTime func() spath.ExpTimeType
+	// Task contains an identifier specific to the task that uses the extender.
+	Task string
+	// StaticInfo contains the configuration used for the StaticInfo Extension.
+	StaticInfo func() *StaticInfoCfg
 }
 
-// extend extends the path segment. Prev should include the full raw hop field,
-// if any, including the flags byte. A zero ingress interface indicates, that
-// the created AS entry is the initial entry. A zero egress interface indicates,
-// that the segment is terminated.
-func (s *segExtender) extend(ctx context.Context, pseg *seg.PathSegment,
-	inIfid, egIfid common.IFIDType, peers []common.IFIDType) error {
+// Extend extends the beacon with hop fields of the old format.
+func (s *LegacyExtender) Extend(ctx context.Context, pseg *seg.PathSegment,
+	ingress, egress common.IFIDType, peers []common.IFIDType) error {
 
-	if inIfid == 0 && egIfid == 0 {
+	if s.MTU == 0 {
+		return serrors.New("MTU not set")
+	}
+	if ingress == 0 && egress == 0 {
 		return serrors.New("Ingress and egress must not be both 0")
 	}
 	infoF, err := pseg.InfoF()
@@ -63,42 +83,42 @@ func (s *segExtender) extend(ctx context.Context, pseg *seg.PathSegment,
 		// Validated segments are guaranteed to have at least one hop entry.
 		prev = pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].RawHopField
 	}
-	hopEntries, err := s.createHopEntries(inIfid, egIfid, peers, prev, infoF.Timestamp())
+	hopEntries, err := s.createHopEntries(ingress, egress, peers, prev, infoF.Timestamp())
 	if err != nil {
 		return err
 	}
 	asEntry := &seg.ASEntry{
-		RawIA:      s.cfg.IA.IAInt(),
-		IfIDSize:   s.cfg.IfidSize,
-		MTU:        s.cfg.MTU,
+		RawIA:      s.IA.IAInt(),
+		IfIDSize:   legacyIfIDSize,
+		MTU:        s.MTU,
 		HopEntries: hopEntries,
 	}
-	if s.cfg.StaticInfoCfg != nil {
-		staticInfoPeers := createPeerMap(s.cfg)
-		staticInfo := s.cfg.StaticInfoCfg.generateStaticinfo(staticInfoPeers, egIfid, inIfid)
+	if static := s.StaticInfo(); static != nil {
+		staticInfoPeers := createPeerMap(s.Intfs)
+		staticInfo := static.generateStaticinfo(staticInfoPeers, egress, ingress)
 		asEntry.Exts.StaticInfo = &staticInfo
 	}
-	if err := pseg.AddASEntry(ctx, asEntry, s.cfg.Signer); err != nil {
+	if err := pseg.AddASEntry(ctx, asEntry, s.Signer); err != nil {
 		return err
 	}
-	if egIfid == 0 {
+	if egress == 0 {
 		return pseg.Validate(seg.ValidateSegment)
 	}
 	return pseg.Validate(seg.ValidateBeacon)
 }
 
-func (s *segExtender) createHopEntries(inIfid, egIfid common.IFIDType, peers []common.IFIDType,
+func (s *LegacyExtender) createHopEntries(ingress, egress common.IFIDType, peers []common.IFIDType,
 	prev common.RawBytes, ts time.Time) ([]*seg.HopEntry, error) {
 
-	hopEntry, err := s.createHopEntry(inIfid, egIfid, prev, ts)
+	hopEntry, err := s.createHopEntry(ingress, egress, prev, ts)
 	if err != nil {
 		return nil, common.NewBasicError("Unable to create first hop entry", err)
 	}
 	hopEntries := []*seg.HopEntry{hopEntry}
 	for _, ifid := range peers {
-		hopEntry, err := s.createHopEntry(ifid, egIfid, hopEntries[0].RawHopField, ts)
+		hopEntry, err := s.createHopEntry(ifid, egress, hopEntries[0].RawHopField, ts)
 		if err != nil {
-			log.Debug("Ignoring peer link upon error", "task", s.cfg.task, "ifid", ifid, "err", err)
+			log.Debug("Ignoring peer link upon error", "task", s.Task, "ifid", ifid, "err", err)
 			continue
 		}
 		hopEntries = append(hopEntries, hopEntry)
@@ -106,25 +126,25 @@ func (s *segExtender) createHopEntries(inIfid, egIfid common.IFIDType, peers []c
 	return hopEntries, nil
 }
 
-func (s *segExtender) createHopEntry(inIfid, egIfid common.IFIDType, prev common.RawBytes,
+func (s *LegacyExtender) createHopEntry(ingress, egress common.IFIDType, prev common.RawBytes,
 	ts time.Time) (*seg.HopEntry, error) {
 
-	remoteInIA, remoteInIfid, remoteInMtu, err := s.remoteInfo(inIfid)
+	remoteInIA, remoteInIfID, remoteInMtu, err := s.remoteInfo(ingress)
 	if err != nil {
-		return nil, common.NewBasicError("Invalid remote ingress interface", err, "ifid", inIfid)
+		return nil, common.NewBasicError("Invalid remote ingress interface", err, "ifid", ingress)
 	}
-	remoteOutIA, remoteOutIfid, _, err := s.remoteInfo(egIfid)
+	remoteOutIA, remoteOutIfid, _, err := s.remoteInfo(egress)
 	if err != nil {
-		return nil, common.NewBasicError("Invalid remote egress interface", err, "ifid", egIfid)
+		return nil, common.NewBasicError("Invalid remote egress interface", err, "ifid", egress)
 	}
-	hopF, err := s.createHopF(inIfid, egIfid, prev, ts)
+	hopF, err := s.createHopF(ingress, egress, prev, ts)
 	if err != nil {
 		return nil, err
 	}
 	hop := &seg.HopEntry{
 		RawHopField: hopF.Pack(),
 		RawInIA:     remoteInIA,
-		RemoteInIF:  remoteInIfid,
+		RemoteInIF:  remoteInIfID,
 		InMTU:       uint16(remoteInMtu),
 		RawOutIA:    remoteOutIA,
 		RemoteOutIF: remoteOutIfid,
@@ -132,13 +152,13 @@ func (s *segExtender) createHopEntry(inIfid, egIfid common.IFIDType, prev common
 	return hop, nil
 }
 
-func (s *segExtender) remoteInfo(ifid common.IFIDType) (
+func (s *LegacyExtender) remoteInfo(ifid common.IFIDType) (
 	addr.IAInt, common.IFIDType, uint16, error) {
 
 	if ifid == 0 {
 		return 0, 0, 0, nil
 	}
-	intf := s.cfg.Intfs.Get(ifid)
+	intf := s.Intfs.Get(ifid)
 	if intf == nil {
 		return 0, 0, 0, serrors.New("Interface not found")
 	}
@@ -158,27 +178,24 @@ func (s *segExtender) remoteInfo(ifid common.IFIDType) (
 
 // createHopF creates a hop field with the provided parameters. The previous hop
 // field, if any, must contain all raw bytes including the flags.
-func (s *segExtender) createHopF(inIfid, egIfid common.IFIDType, prev common.RawBytes,
+func (s *LegacyExtender) createHopF(ingress, egress common.IFIDType, prev common.RawBytes,
 	ts time.Time) (*spath.HopField, error) {
 
 	hop := &spath.HopField{
-		ConsIngress: inIfid,
-		ConsEgress:  egIfid,
-		ExpTime:     s.cfg.GetMaxExpTime(),
+		ConsIngress: ingress,
+		ConsEgress:  egress,
+		ExpTime:     s.MaxExpTime(),
 	}
 	if prev != nil {
 		// Do not include the flags of the hop field in the mac input.
 		prev = prev[1:]
 	}
-	s.macMtx.Lock()
-	defer s.macMtx.Unlock()
-	hop.Mac = hop.CalcMac(s.cfg.Mac, util.TimeToSecs(ts), prev)
+	hop.Mac = hop.CalcMac(s.MAC(), util.TimeToSecs(ts), prev)
 	return hop, nil
 }
 
-// IntfActive returns whether the interface is active.
-func (s *segExtender) IntfActive(ifid common.IFIDType) bool {
-	intf := s.cfg.Intfs.Get(ifid)
+func intfActive(intfs *ifstate.Interfaces, ifid common.IFIDType) bool {
+	intf := intfs.Get(ifid)
 	return intf != nil && intf.State() == ifstate.Active
 }
 
@@ -191,9 +208,9 @@ func min(a, b spath.ExpTimeType) spath.ExpTimeType {
 
 // createPeerMap creates a set of peers indicating whether the
 // interface identified by the key is used for peering or not.
-func createPeerMap(cfg ExtenderConf) map[common.IFIDType]struct{} {
+func createPeerMap(intfs *ifstate.Interfaces) map[common.IFIDType]struct{} {
 	peers := make(map[common.IFIDType]struct{})
-	for ifID, ifInfo := range cfg.Intfs.All() {
+	for ifID, ifInfo := range intfs.All() {
 		if ifInfo.TopoInfo().LinkType == topology.Peer {
 			peers[ifID] = struct{}{}
 		}
diff --git a/go/cs/beaconing/extender_config.go b/go/cs/beaconing/extender_config.go
deleted file mode 100644
index a04387610..000000000
--- a/go/cs/beaconing/extender_config.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2019 Anapaya Systems
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package beaconing
-
-import (
-	"hash"
-
-	"github.com/scionproto/scion/go/cs/ifstate"
-	"github.com/scionproto/scion/go/lib/addr"
-	"github.com/scionproto/scion/go/lib/ctrl"
-	"github.com/scionproto/scion/go/lib/serrors"
-	"github.com/scionproto/scion/go/lib/spath"
-)
-
-const (
-	// DefaultIfidSize is the default bit-size for ifids in the hop-fields.
-	DefaultIfidSize = 12
-)
-
-// ExtenderConf is the configuration used when extending beacons.
-type ExtenderConf struct {
-	// IA is the local IA
-	IA addr.IA
-	// Signer is used to sign path segments.
-	Signer ctrl.Signer
-	// Mac is used to calculate the hop field MAC.
-	Mac hash.Hash
-	// Intfs holds all interfaces in the AS.
-	Intfs *ifstate.Interfaces
-	// MTU is the MTU value set in the AS entries.
-	MTU uint16
-	// IfidSize is the bit-size of the ifid in the hop-fields.
-	IfidSize uint8
-	// GetMaxExpTime returns the maximum relative expiration time.
-	GetMaxExpTime func() spath.ExpTimeType
-	// task contains an identifier specific to the task that uses the extender.
-	task string
-	// StaticInfoCfg contains the Configdata used for the StaticInfo Extension.
-	StaticInfoCfg *StaticInfoCfg
-}
-
-// InitDefaults initializes the default values, if not set.
-func (cfg *ExtenderConf) InitDefaults() {
-	if cfg.IfidSize == 0 {
-		cfg.IfidSize = DefaultIfidSize
-	}
-}
-
-// Validate checks that the config contains a signer.
-func (cfg *ExtenderConf) Validate() error {
-	if cfg.Signer == nil {
-		return serrors.New("Signer must be set")
-	}
-	if cfg.IfidSize == 0 {
-		return serrors.New("IfidSize must be set")
-	}
-	if cfg.MTU == 0 {
-		return serrors.New("MTU must be set")
-	}
-	return nil
-}
diff --git a/go/cs/beaconing/mock_beaconing/BUILD.bazel b/go/cs/beaconing/mock_beaconing/BUILD.bazel
index d5a50f172..67f9b765c 100644
--- a/go/cs/beaconing/mock_beaconing/BUILD.bazel
+++ b/go/cs/beaconing/mock_beaconing/BUILD.bazel
@@ -7,6 +7,10 @@ go_library(
     visibility = ["//visibility:public"],
     deps = [
         "//go/cs/beacon:go_default_library",
+        "//go/lib/addr:go_default_library",
+        "//go/lib/common:go_default_library",
+        "//go/lib/ctrl:go_default_library",
+        "//go/lib/ctrl/seg:go_default_library",
         "//go/lib/infra/modules/seghandler:go_default_library",
         "//go/proto:go_default_library",
         "@com_github_golang_mock//gomock:go_default_library",
diff --git a/go/cs/beaconing/mock_beaconing/beaconing.go b/go/cs/beaconing/mock_beaconing/beaconing.go
index c982cf583..65cf2ab2b 100644
--- a/go/cs/beaconing/mock_beaconing/beaconing.go
+++ b/go/cs/beaconing/mock_beaconing/beaconing.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/scionproto/scion/go/cs/beaconing (interfaces: BeaconInserter,BeaconProvider,SegmentProvider,SegmentStore)
+// Source: github.com/scionproto/scion/go/cs/beaconing (interfaces: BeaconInserter,BeaconProvider,BeaconSender,RPC,SegmentProvider,SegmentStore)
 
 // Package mock_beaconing is a generated GoMock package.
 package mock_beaconing
@@ -8,8 +8,13 @@ import (
 	context "context"
 	gomock "github.com/golang/mock/gomock"
 	beacon "github.com/scionproto/scion/go/cs/beacon"
+	addr "github.com/scionproto/scion/go/lib/addr"
+	common "github.com/scionproto/scion/go/lib/common"
+	ctrl "github.com/scionproto/scion/go/lib/ctrl"
+	seg "github.com/scionproto/scion/go/lib/ctrl/seg"
 	seghandler "github.com/scionproto/scion/go/lib/infra/modules/seghandler"
 	proto "github.com/scionproto/scion/go/proto"
+	net "net"
 	reflect "reflect"
 )
 
@@ -103,6 +108,80 @@ func (mr *MockBeaconProviderMockRecorder) BeaconsToPropagate(arg0 interface{}) *
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeaconsToPropagate", reflect.TypeOf((*MockBeaconProvider)(nil).BeaconsToPropagate), arg0)
 }
 
+// MockBeaconSender is a mock of BeaconSender interface
+type MockBeaconSender struct {
+	ctrl     *gomock.Controller
+	recorder *MockBeaconSenderMockRecorder
+}
+
+// MockBeaconSenderMockRecorder is the mock recorder for MockBeaconSender
+type MockBeaconSenderMockRecorder struct {
+	mock *MockBeaconSender
+}
+
+// NewMockBeaconSender creates a new mock instance
+func NewMockBeaconSender(ctrl *gomock.Controller) *MockBeaconSender {
+	mock := &MockBeaconSender{ctrl: ctrl}
+	mock.recorder = &MockBeaconSenderMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockBeaconSender) EXPECT() *MockBeaconSenderMockRecorder {
+	return m.recorder
+}
+
+// Send mocks base method
+func (m *MockBeaconSender) Send(arg0 context.Context, arg1 *seg.Beacon, arg2 addr.IA, arg3 common.IFIDType, arg4 ctrl.Signer, arg5 *net.UDPAddr) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Send", arg0, arg1, arg2, arg3, arg4, arg5)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Send indicates an expected call of Send
+func (mr *MockBeaconSenderMockRecorder) Send(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockBeaconSender)(nil).Send), arg0, arg1, arg2, arg3, arg4, arg5)
+}
+
+// MockRPC is a mock of RPC interface
+type MockRPC struct {
+	ctrl     *gomock.Controller
+	recorder *MockRPCMockRecorder
+}
+
+// MockRPCMockRecorder is the mock recorder for MockRPC
+type MockRPCMockRecorder struct {
+	mock *MockRPC
+}
+
+// NewMockRPC creates a new mock instance
+func NewMockRPC(ctrl *gomock.Controller) *MockRPC {
+	mock := &MockRPC{ctrl: ctrl}
+	mock.recorder = &MockRPCMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockRPC) EXPECT() *MockRPCMockRecorder {
+	return m.recorder
+}
+
+// RegisterSegment mocks base method
+func (m *MockRPC) RegisterSegment(arg0 context.Context, arg1 seg.Meta, arg2 net.Addr) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "RegisterSegment", arg0, arg1, arg2)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// RegisterSegment indicates an expected call of RegisterSegment
+func (mr *MockRPCMockRecorder) RegisterSegment(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterSegment", reflect.TypeOf((*MockRPC)(nil).RegisterSegment), arg0, arg1, arg2)
+}
+
 // MockSegmentProvider is a mock of SegmentProvider interface
 type MockSegmentProvider struct {
 	ctrl     *gomock.Controller
diff --git a/go/cs/beaconing/originator.go b/go/cs/beaconing/originator.go
index 0b1a433aa..aa8acc03a 100644
--- a/go/cs/beaconing/originator.go
+++ b/go/cs/beaconing/originator.go
@@ -16,12 +16,14 @@ package beaconing
 
 import (
 	"context"
+	"net"
 	"time"
 
 	"github.com/scionproto/scion/go/cs/ifstate"
 	"github.com/scionproto/scion/go/cs/metrics"
-	"github.com/scionproto/scion/go/cs/onehop"
+	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/ctrl"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/log"
 	"github.com/scionproto/scion/go/lib/periodic"
@@ -33,35 +35,22 @@ import (
 
 var _ periodic.Task = (*Originator)(nil)
 
-// OriginatorConf is the configuration to create a new originator.
-type OriginatorConf struct {
-	Config       ExtenderConf
-	BeaconSender *onehop.BeaconSender
-	Period       time.Duration
+// BeaconSender sends the beacon on the provided interface.
+type BeaconSender interface {
+	Send(ctx context.Context, beacon *seg.Beacon, dst addr.IA,
+		egress common.IFIDType, signer ctrl.Signer, nextHop *net.UDPAddr) error
 }
 
 // Originator originates beacons. It should only be used by core ASes.
 type Originator struct {
-	*segExtender
-	beaconSender *onehop.BeaconSender
+	Extender     Extender
+	BeaconSender BeaconSender
+	IA           addr.IA
+	Signer       ctrl.Signer
+	Intfs        *ifstate.Interfaces
 
 	// tick is mutable.
-	tick tick
-}
-
-// New creates a new originator.
-func (cfg OriginatorConf) New() (*Originator, error) {
-	cfg.Config.task = "originator"
-	extender, err := cfg.Config.new()
-	if err != nil {
-		return nil, err
-	}
-	o := &Originator{
-		beaconSender: cfg.BeaconSender,
-		segExtender:  extender,
-		tick:         tick{period: cfg.Period},
-	}
-	return o, nil
+	Tick Tick
 }
 
 // Name returns the tasks name.
@@ -71,26 +60,26 @@ func (o *Originator) Name() string {
 
 // Run originates core and downstream beacons.
 func (o *Originator) Run(ctx context.Context) {
-	o.tick.now = time.Now()
+	o.Tick.now = time.Now()
 	o.originateBeacons(ctx, topology.Core)
 	o.originateBeacons(ctx, topology.Child)
-	metrics.Originator.Runtime().Add(time.Since(o.tick.now).Seconds())
-	o.tick.updateLast()
+	metrics.Originator.Runtime().Add(time.Since(o.Tick.now).Seconds())
+	o.Tick.updateLast()
 }
 
 // originateBeacons creates and sends a beacon for each active interface of
 // the specified link type.
 func (o *Originator) originateBeacons(ctx context.Context, linkType topology.LinkType) {
 	logger := log.FromCtx(ctx)
-	active, nonActive := sortedIntfs(o.cfg.Intfs, linkType)
-	if len(nonActive) > 0 && o.tick.passed() {
+	active, nonActive := sortedIntfs(o.Intfs, linkType)
+	if len(nonActive) > 0 && o.Tick.passed() {
 		logger.Debug("[beaconing.Originator] Ignore non-active interfaces", "ifids", nonActive)
 	}
 	intfs := o.needBeacon(active)
 	if len(intfs) == 0 {
 		return
 	}
-	infoF := o.createInfoF(o.tick.now)
+	infoF := o.createInfoF(o.Tick.now)
 	s := newSummary()
 	for _, ifid := range intfs {
 		b := beaconOriginator{
@@ -111,7 +100,7 @@ func (o *Originator) originateBeacons(ctx context.Context, linkType topology.Lin
 func (o *Originator) createInfoF(now time.Time) spath.InfoField {
 	infoF := spath.InfoField{
 		ConsDir: true,
-		ISD:     uint16(o.beaconSender.IA.I),
+		ISD:     uint16(o.IA.I),
 		TsInt:   util.TimeToSecs(now),
 	}
 	return infoF
@@ -119,16 +108,16 @@ func (o *Originator) createInfoF(now time.Time) spath.InfoField {
 
 // needBeacon returns a list of interfaces that need a beacon.
 func (o *Originator) needBeacon(active []common.IFIDType) []common.IFIDType {
-	if o.tick.passed() {
+	if o.Tick.passed() {
 		return active
 	}
 	stale := make([]common.IFIDType, 0, len(active))
 	for _, ifid := range active {
-		intf := o.cfg.Intfs.Get(ifid)
+		intf := o.Intfs.Get(ifid)
 		if intf == nil {
 			continue
 		}
-		if o.tick.now.Sub(intf.LastOriginate()) > o.tick.period {
+		if o.Tick.now.Sub(intf.LastOriginate()) > o.Tick.period {
 			stale = append(stale, ifid)
 		}
 	}
@@ -136,7 +125,7 @@ func (o *Originator) needBeacon(active []common.IFIDType) []common.IFIDType {
 }
 
 func (o *Originator) logSummary(logger log.Logger, s *summary, linkType topology.LinkType) {
-	if o.tick.passed() {
+	if o.Tick.passed() {
 		logger.Info("[beaconing.Originator] Originated beacons",
 			"type", linkType.String(), "egIfIds", s.IfIds())
 		return
@@ -158,7 +147,7 @@ type beaconOriginator struct {
 // originateBeacon originates a beacon on the given ifid.
 func (o *beaconOriginator) originateBeacon(ctx context.Context) error {
 	labels := metrics.OriginatorLabels{EgIfID: o.ifID, Result: metrics.Success}
-	intf := o.cfg.Intfs.Get(o.ifID)
+	intf := o.Intfs.Get(o.ifID)
 	if intf == nil {
 		metrics.Originator.Beacons(labels.WithResult(metrics.ErrVerify)).Inc()
 		return serrors.New("Interface does not exist")
@@ -170,12 +159,12 @@ func (o *beaconOriginator) originateBeacon(ctx context.Context) error {
 		return common.NewBasicError("Unable to create beacon", err, "ifid", o.ifID)
 	}
 
-	err = o.beaconSender.Send(
+	err = o.BeaconSender.Send(
 		ctx,
 		bseg,
 		topoInfo.IA,
 		o.ifID,
-		o.cfg.Signer,
+		o.Signer,
 		topoInfo.InternalAddr,
 	)
 	if err != nil {
@@ -192,14 +181,14 @@ func (o *beaconOriginator) createBeacon(ctx context.Context) (*seg.Beacon, error
 	if err != nil {
 		return nil, common.NewBasicError("Unable to create segment", err)
 	}
-	if err := o.extend(ctx, bseg, 0, o.ifID, nil); err != nil {
+	if err := o.Extender.Extend(ctx, bseg, 0, o.ifID, nil); err != nil {
 		return nil, common.NewBasicError("Unable to extend segment", err)
 	}
 	return &seg.Beacon{Segment: bseg}, nil
 }
 
 func (o *beaconOriginator) onSuccess(intf *ifstate.Interface) {
-	intf.Originate(o.tick.now)
+	intf.Originate(o.Tick.now)
 	o.summary.AddIfid(o.ifID)
 	o.summary.Inc()
 }
diff --git a/go/cs/beaconing/propagator.go b/go/cs/beaconing/propagator.go
index 17d2e0eb3..c9d8cac77 100644
--- a/go/cs/beaconing/propagator.go
+++ b/go/cs/beaconing/propagator.go
@@ -22,8 +22,9 @@ import (
 	"github.com/scionproto/scion/go/cs/beacon"
 	"github.com/scionproto/scion/go/cs/ifstate"
 	"github.com/scionproto/scion/go/cs/metrics"
-	"github.com/scionproto/scion/go/cs/onehop"
+	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/ctrl"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/log"
 	"github.com/scionproto/scion/go/lib/periodic"
@@ -37,47 +38,22 @@ type BeaconProvider interface {
 
 var _ periodic.Task = (*Propagator)(nil)
 
-// PropagatorConf is the configuration to create a new propagator.
-type PropagatorConf struct {
-	Config         ExtenderConf
-	BeaconProvider BeaconProvider
-	BeaconSender   *onehop.BeaconSender
-	Period         time.Duration
-	Core           bool
-	AllowIsdLoop   bool
-}
-
 // Propagator forwards beacons to neighboring ASes. In a core AS, the beacons
 // are propagated to neighbors on core links. In a non-core AS, the beacons are
 // forwarded on child links. Selection of the beacons is handled by the beacon
 // provider, the propagator only filters AS loops.
 type Propagator struct {
-	*segExtender
-	beaconSender *onehop.BeaconSender
-	provider     BeaconProvider
-	allowIsdLoop bool
-	core         bool
+	Extender     Extender
+	BeaconSender BeaconSender
+	Provider     BeaconProvider
+	IA           addr.IA
+	Signer       ctrl.Signer
+	Intfs        *ifstate.Interfaces
+	Core         bool
+	AllowIsdLoop bool
 
 	// tick is mutable.
-	tick tick
-}
-
-// New creates a new beacon propagation task.
-func (cfg PropagatorConf) New() (*Propagator, error) {
-	cfg.Config.task = "propagator"
-	extender, err := cfg.Config.new()
-	if err != nil {
-		return nil, err
-	}
-	p := &Propagator{
-		provider:     cfg.BeaconProvider,
-		beaconSender: cfg.BeaconSender,
-		core:         cfg.Core,
-		allowIsdLoop: cfg.AllowIsdLoop,
-		segExtender:  extender,
-		tick:         tick{period: cfg.Period},
-	}
-	return p, nil
+	Tick Tick
 }
 
 // Name returns the tasks name.
@@ -90,12 +66,12 @@ func (p *Propagator) Name() string {
 // interfaces. In a non-core beacon server, child interfaces are the target
 // interfaces.
 func (p *Propagator) Run(ctx context.Context) {
-	p.tick.now = time.Now()
+	p.Tick.now = time.Now()
 	if err := p.run(ctx); err != nil {
 		log.FromCtx(ctx).Error("[beaconing.Propagator] Unable to propagate beacons", "err", err)
 	}
-	p.tick.updateLast()
-	metrics.Propagator.Runtime().Add(time.Since(p.tick.now).Seconds())
+	p.Tick.updateLast()
+	metrics.Propagator.Runtime().Add(time.Since(p.Tick.now).Seconds())
 }
 
 func (p *Propagator) run(ctx context.Context) error {
@@ -104,12 +80,12 @@ func (p *Propagator) run(ctx context.Context) error {
 	if len(intfs) == 0 {
 		return nil
 	}
-	peers, nonActivePeers := sortedIntfs(p.cfg.Intfs, topology.Peer)
-	if len(nonActivePeers) > 0 && p.tick.passed() {
+	peers, nonActivePeers := sortedIntfs(p.Intfs, topology.Peer)
+	if len(nonActivePeers) > 0 && p.Tick.passed() {
 		logger.Debug("[beaconing.Propagator] Ignore non-active peering interfaces",
 			"ifids", nonActivePeers)
 	}
-	beacons, err := p.provider.BeaconsToPropagate(ctx)
+	beacons, err := p.Provider.BeaconsToPropagate(ctx)
 	if err != nil {
 		metrics.Propagator.InternalErrors().Inc()
 		return err
@@ -122,7 +98,7 @@ func (p *Propagator) run(ctx context.Context) error {
 			metrics.Propagator.InternalErrors().Inc()
 			continue
 		}
-		if !p.IntfActive(bOrErr.Beacon.InIfId) {
+		if !intfActive(p.Intfs, bOrErr.Beacon.InIfId) {
 			continue
 		}
 		b := beaconPropagator{
@@ -145,24 +121,24 @@ func (p *Propagator) run(ctx context.Context) error {
 // AS, these are all active child links.
 func (p *Propagator) needsBeacons(logger log.Logger) []common.IFIDType {
 	var activeIntfs, nonActiveIntfs []common.IFIDType
-	if p.core {
-		activeIntfs, nonActiveIntfs = sortedIntfs(p.cfg.Intfs, topology.Core)
+	if p.Core {
+		activeIntfs, nonActiveIntfs = sortedIntfs(p.Intfs, topology.Core)
 	} else {
-		activeIntfs, nonActiveIntfs = sortedIntfs(p.cfg.Intfs, topology.Child)
+		activeIntfs, nonActiveIntfs = sortedIntfs(p.Intfs, topology.Child)
 	}
-	if len(nonActiveIntfs) > 0 && p.tick.passed() {
+	if len(nonActiveIntfs) > 0 && p.Tick.passed() {
 		logger.Debug("[beaconing.Propagator] Ignore non-active interfaces", "ifids", nonActiveIntfs)
 	}
-	if p.tick.passed() {
+	if p.Tick.passed() {
 		return activeIntfs
 	}
 	stale := make([]common.IFIDType, 0, len(activeIntfs))
 	for _, ifid := range activeIntfs {
-		intf := p.cfg.Intfs.Get(ifid)
+		intf := p.Intfs.Get(ifid)
 		if intf == nil {
 			continue
 		}
-		if p.tick.now.Sub(intf.LastPropagate()) > p.tick.period {
+		if p.Tick.now.Sub(intf.LastPropagate()) > p.Tick.period {
 			stale = append(stale, ifid)
 		}
 	}
@@ -170,7 +146,7 @@ func (p *Propagator) needsBeacons(logger log.Logger) []common.IFIDType {
 }
 
 func (p *Propagator) logSummary(logger log.Logger, s *summary) {
-	if p.tick.passed() {
+	if p.Tick.passed() {
 		logger.Info("[beaconing.Propagator] Propagated beacons",
 			"count", s.count, "startIAs", len(s.srcs), "egIfIds", s.IfIds())
 		return
@@ -262,14 +238,14 @@ func (p *beaconPropagator) extendAndSend(ctx context.Context, bseg beacon.Beacon
 			metrics.Propagator.IntfTime(labels).Add(time.Since(now).Seconds())
 		}()
 
-		if err := p.extend(ctx, bseg.Segment, bseg.InIfId, egIfid, p.peers); err != nil {
+		if err := p.Extender.Extend(ctx, bseg.Segment, bseg.InIfId, egIfid, p.peers); err != nil {
 			p.logger.Error("[beaconing.Propagator] Unable to extend beacon",
 				"beacon", bseg, "err", err)
 			labels.Result = metrics.ErrCreate
 			metrics.Propagator.Beacons(labels).Inc()
 			return
 		}
-		intf := p.cfg.Intfs.Get(egIfid)
+		intf := p.Intfs.Get(egIfid)
 		if intf == nil {
 			p.logger.Error("[beaconing.Propagator] Interface removed", "egIfid", egIfid)
 			labels.Result = metrics.ErrCreate
@@ -277,12 +253,12 @@ func (p *beaconPropagator) extendAndSend(ctx context.Context, bseg beacon.Beacon
 			return
 		}
 		topoInfo := intf.TopoInfo()
-		err := p.beaconSender.Send(
+		err := p.BeaconSender.Send(
 			ctx,
 			&seg.Beacon{Segment: bseg.Segment},
 			topoInfo.IA,
 			egIfid,
-			p.cfg.Signer,
+			p.Signer,
 			topoInfo.InternalAddr,
 		)
 		if err != nil {
@@ -301,11 +277,11 @@ func (p *beaconPropagator) extendAndSend(ctx context.Context, bseg beacon.Beacon
 // shouldIgnore indicates whether a beacon should not be sent on the egress
 // interface because it creates a loop.
 func (p *beaconPropagator) shouldIgnore(bseg beacon.Beacon, egIfid common.IFIDType) bool {
-	intf := p.cfg.Intfs.Get(egIfid)
+	intf := p.Intfs.Get(egIfid)
 	if intf == nil {
 		return true
 	}
-	if err := beacon.FilterLoop(bseg, intf.TopoInfo().IA, p.allowIsdLoop); err != nil {
+	if err := beacon.FilterLoop(bseg, intf.TopoInfo().IA, p.AllowIsdLoop); err != nil {
 		p.logger.Debug("[beaconing.Propagator] Ignoring beacon on loop", "ifid", egIfid, "err", err)
 		return true
 	}
@@ -313,7 +289,7 @@ func (p *beaconPropagator) shouldIgnore(bseg beacon.Beacon, egIfid common.IFIDTy
 }
 
 func (p *beaconPropagator) onSuccess(intf *ifstate.Interface, egIfid common.IFIDType) {
-	intf.Propagate(p.tick.now)
+	intf.Propagate(p.Tick.now)
 	p.success.Inc()
 	p.summary.AddIfid(egIfid)
 }
diff --git a/go/cs/beaconing/registrar.go b/go/cs/beaconing/registrar.go
index 782590e8d..71da04799 100644
--- a/go/cs/beaconing/registrar.go
+++ b/go/cs/beaconing/registrar.go
@@ -21,13 +21,12 @@ import (
 	"time"
 
 	"github.com/scionproto/scion/go/cs/beacon"
+	"github.com/scionproto/scion/go/cs/ifstate"
 	"github.com/scionproto/scion/go/cs/metrics"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
-	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
+	"github.com/scionproto/scion/go/lib/ctrl"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
-	"github.com/scionproto/scion/go/lib/infra"
-	"github.com/scionproto/scion/go/lib/infra/messenger"
 	"github.com/scionproto/scion/go/lib/infra/modules/seghandler"
 	"github.com/scionproto/scion/go/lib/log"
 	"github.com/scionproto/scion/go/lib/periodic"
@@ -47,52 +46,31 @@ type SegmentStore interface {
 	StoreSegs(context.Context, []*seghandler.SegWithHP) (seghandler.SegStats, error)
 }
 
-var _ periodic.Task = (*Registrar)(nil)
-
-// RegistrarConf is the configuration to create a new registrar.
-type RegistrarConf struct {
-	Config       ExtenderConf
-	SegProvider  SegmentProvider
-	SegStore     SegmentStore
-	TopoProvider topology.Provider
-	Msgr         infra.Messenger
-	Period       time.Duration
-	SegType      proto.PathSegType
+// RPC registers the path segment with the remote.
+type RPC interface {
+	RegisterSegment(ctx context.Context, meta seg.Meta, remote net.Addr) error
 }
 
+var _ periodic.Task = (*Registrar)(nil)
+
 // Registrar is used to periodically register path segments with the appropriate
 // path servers. Core and Up segments are registered with the local path server.
 // Down segments are registered at the core.
 type Registrar struct {
-	*segExtender
-	msgr         infra.Messenger
-	segProvider  SegmentProvider
-	segStore     SegmentStore
-	topoProvider topology.Provider
-	segType      proto.PathSegType
+	Extender Extender
+	Provider SegmentProvider
+	Store    SegmentStore
+	RPC      RPC
+	IA       addr.IA
+	Signer   ctrl.Signer
+	Intfs    *ifstate.Interfaces
+	Type     proto.PathSegType
 
-	// mutable fields
-	lastSucc time.Time
-	tick     tick
-}
+	TopoProvider topology.Provider // FIXME(roosd): reduce this to the minimal required interface.
 
-// New creates a new segment regsitration task.
-func (cfg RegistrarConf) New() (*Registrar, error) {
-	cfg.Config.task = "registrar"
-	extender, err := cfg.Config.new()
-	if err != nil {
-		return nil, err
-	}
-	r := &Registrar{
-		segProvider:  cfg.SegProvider,
-		segStore:     cfg.SegStore,
-		topoProvider: cfg.TopoProvider,
-		segType:      cfg.SegType,
-		msgr:         cfg.Msgr,
-		tick:         tick{period: cfg.Period},
-		segExtender:  extender,
-	}
-	return r, nil
+	// tick is mutable.
+	Tick     Tick
+	lastSucc time.Time
 }
 
 // Name returns the tasks name.
@@ -102,30 +80,30 @@ func (r *Registrar) Name() string {
 
 // Run registers path segments for the specified type to path servers.
 func (r *Registrar) Run(ctx context.Context) {
-	r.tick.now = time.Now()
+	r.Tick.now = time.Now()
 	if err := r.run(ctx); err != nil {
 		log.FromCtx(ctx).Error("[beaconing.Registrar] Unable to register",
-			"type", r.segType, "err", err)
+			"type", r.Type, "err", err)
 	}
-	metrics.Registrar.RuntimeWithType(r.segType.String()).Add(time.Since(r.tick.now).Seconds())
-	r.tick.updateLast()
+	metrics.Registrar.RuntimeWithType(r.Type.String()).Add(time.Since(r.Tick.now).Seconds())
+	r.Tick.updateLast()
 }
 
 func (r *Registrar) run(ctx context.Context) error {
-	if r.tick.now.Sub(r.lastSucc) < r.tick.period && !r.tick.passed() {
+	if r.Tick.now.Sub(r.lastSucc) < r.Tick.period && !r.Tick.passed() {
 		return nil
 	}
 	logger := log.FromCtx(ctx)
-	segments, err := r.segProvider.SegmentsToRegister(ctx, r.segType)
+	segments, err := r.Provider.SegmentsToRegister(ctx, r.Type)
 	if err != nil {
 		return err
 	}
-	peers, nonActivePeers := sortedIntfs(r.cfg.Intfs, topology.Peer)
+	peers, nonActivePeers := sortedIntfs(r.Intfs, topology.Peer)
 	if len(nonActivePeers) > 0 {
-		logger.Debug("[beaconing.Registrar] Ignore non-active peer interfaces", "type", r.segType,
+		logger.Debug("[beaconing.Registrar] Ignore non-active peer interfaces", "type", r.Type,
 			"intfs", nonActivePeers)
 	}
-	if r.segType != proto.PathSegType_down {
+	if r.Type != proto.PathSegType_down {
 		return r.registerLocal(ctx, segments, peers)
 	}
 	return r.registerRemote(ctx, segments, peers)
@@ -141,23 +119,24 @@ func (r *Registrar) registerRemote(ctx context.Context, segments <-chan beacon.B
 	for bOrErr := range segments {
 		if bOrErr.Err != nil {
 			logger.Error("[beaconing.Registrar] Unable to get beacon", "err", bOrErr.Err)
-			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
+			metrics.Registrar.InternalErrorsWithType(r.Type.String()).Inc()
 			continue
 		}
-		if !r.IntfActive(bOrErr.Beacon.InIfId) {
+		if !intfActive(r.Intfs, bOrErr.Beacon.InIfId) {
 			continue
 		}
-		if err := r.extend(ctx, bOrErr.Beacon.Segment, bOrErr.Beacon.InIfId, 0, peers); err != nil {
-			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
+		err := r.Extender.Extend(ctx, bOrErr.Beacon.Segment, bOrErr.Beacon.InIfId, 0, peers)
+		if err != nil {
+			metrics.Registrar.InternalErrorsWithType(r.Type.String()).Inc()
 			logger.Error("[beaconing.Registrar] Unable to terminate beacon",
 				"beacon", bOrErr.Beacon, "err", err)
 			continue
 		}
 		expected++
 		s := remoteRegistrar{
-			segType:      r.segType,
-			msgr:         r.msgr,
-			topoProvider: r.topoProvider,
+			segType:      r.Type,
+			rpc:          r.RPC,
+			topoProvider: r.TopoProvider,
 			summary:      s,
 			wg:           &wg,
 		}
@@ -170,7 +149,7 @@ func (r *Registrar) registerRemote(ctx context.Context, segments <-chan beacon.B
 		return common.NewBasicError("No beacons registered", nil, "candidates", expected)
 	}
 	if s.count > 0 {
-		r.lastSucc = r.tick.now
+		r.lastSucc = r.Tick.now
 	}
 	r.logSummary(logger, s)
 	return nil
@@ -185,53 +164,54 @@ func (r *Registrar) registerLocal(ctx context.Context, segments <-chan beacon.Be
 	for bOrErr := range segments {
 		if bOrErr.Err != nil {
 			logger.Error("[beaconing.Registrar] Unable to get beacon", "err", bOrErr.Err)
-			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
+			metrics.Registrar.InternalErrorsWithType(r.Type.String()).Inc()
 			continue
 		}
-		if !r.IntfActive(bOrErr.Beacon.InIfId) {
+		if !intfActive(r.Intfs, bOrErr.Beacon.InIfId) {
 			continue
 		}
-		if err := r.extend(ctx, bOrErr.Beacon.Segment, bOrErr.Beacon.InIfId, 0, peers); err != nil {
-			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
+		err := r.Extender.Extend(ctx, bOrErr.Beacon.Segment, bOrErr.Beacon.InIfId, 0, peers)
+		if err != nil {
+			metrics.Registrar.InternalErrorsWithType(r.Type.String()).Inc()
 			logger.Error("[beaconing.Registrar] Unable to terminate beacon",
 				"beacon", bOrErr.Beacon, "err", err)
 			continue
 		}
 		toRegister = append(toRegister, &seghandler.SegWithHP{
-			Seg: &seg.Meta{Type: r.segType, Segment: bOrErr.Beacon.Segment},
+			Seg: &seg.Meta{Type: r.Type, Segment: bOrErr.Beacon.Segment},
 		})
 		beacons[bOrErr.Beacon.Segment.GetLoggingID()] = bOrErr.Beacon
 	}
 	if len(toRegister) == 0 {
 		return nil
 	}
-	stats, err := r.segStore.StoreSegs(ctx, toRegister)
+	stats, err := r.Store.StoreSegs(ctx, toRegister)
 	if err != nil {
-		metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
+		metrics.Registrar.InternalErrorsWithType(r.Type.String()).Inc()
 		return err
 	}
-	updateMetricsFromStat(stats, beacons, r.segType.String())
-	r.lastSucc = r.tick.now
+	updateMetricsFromStat(stats, beacons, r.Type.String())
+	r.lastSucc = r.Tick.now
 	r.logSummary(logger, summarizeStats(stats, beacons))
 	return nil
 }
 
 func (r *Registrar) logSummary(logger log.Logger, s *summary) {
-	if r.tick.passed() {
-		logger.Info("[beaconing.Registrar] Registered beacons", "type", r.segType, "count", s.count,
+	if r.Tick.passed() {
+		logger.Info("[beaconing.Registrar] Registered beacons", "type", r.Type, "count", s.count,
 			"startIAs", len(s.srcs))
 		return
 	}
 	if s.count > 0 {
 		logger.Info("[beaconing.Registrar] Registered beacons after stale period",
-			"type", r.segType, "count", s.count, "startIAs", len(s.srcs))
+			"type", r.Type, "count", s.count, "startIAs", len(s.srcs))
 	}
 }
 
 // remoteRegistrar registers one segment with the path server.
 type remoteRegistrar struct {
 	segType      proto.PathSegType
-	msgr         infra.Messenger
+	rpc          RPC
 	topoProvider topology.Provider
 	summary      *summary
 	wg           *sync.WaitGroup
@@ -241,36 +221,26 @@ type remoteRegistrar struct {
 // with the path server.
 func (r *remoteRegistrar) start(ctx context.Context, bseg beacon.Beacon) {
 	logger := log.FromCtx(ctx)
-	reg := &path_mgmt.SegReg{
-		SegRecs: &path_mgmt.SegRecs{
-			Recs: []*seg.Meta{
-				{
-					Type:    r.segType,
-					Segment: bseg.Segment,
-				},
-			},
-		},
-	}
 	addr, err := addrutil.GetPath(addr.SvcPS, bseg.Segment, r.topoProvider)
 	if err != nil {
 		metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
 		logger.Error("[beaconing.Registrar] Unable to choose server", "err", err)
 		return
 	}
-	r.startSendSegReg(ctx, bseg, reg, addr)
+	r.startSendSegReg(ctx, bseg, seg.Meta{Type: r.segType, Segment: bseg.Segment}, addr)
 }
 
 // startSendSegReg adds to the wait group and starts a goroutine that sends the
 // registration message to the peer.
 func (r *remoteRegistrar) startSendSegReg(ctx context.Context, bseg beacon.Beacon,
-	reg *path_mgmt.SegReg, addr net.Addr) {
+	reg seg.Meta, addr net.Addr) {
 
 	r.wg.Add(1)
 	go func() {
 		defer log.HandlePanic()
 		defer r.wg.Done()
 		logger := log.FromCtx(ctx)
-		if err := r.msgr.SendSegReg(ctx, reg, addr, messenger.NextId()); err != nil {
+		if err := r.rpc.RegisterSegment(ctx, reg, addr); err != nil {
 			logger.Error("[beaconing.Registrar] Unable to register segment", "type", r.segType,
 				"addr", addr, "err", err)
 			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
diff --git a/go/cs/beaconing/tick.go b/go/cs/beaconing/tick.go
index 5c6532046..b7dfbe789 100644
--- a/go/cs/beaconing/tick.go
+++ b/go/cs/beaconing/tick.go
@@ -18,22 +18,26 @@ import (
 	"time"
 )
 
-// tick keeps track whether the period has passed compared to the last time.
-type tick struct {
+// Tick keeps track whether the period has passed compared to the last time.
+type Tick struct {
 	now    time.Time
 	last   time.Time
 	period time.Duration
 }
 
+func NewTick(period time.Duration) Tick {
+	return Tick{period: period}
+}
+
 // updateLast updates the last time to the current time, if the period has
 // passed since last.
-func (t *tick) updateLast() {
+func (t *Tick) updateLast() {
 	if t.passed() {
 		t.last = t.now
 	}
 }
 
 // passed returns whether the last timestamp is further away from now than the period
-func (t *tick) passed() bool {
+func (t *Tick) passed() bool {
 	return t.now.Sub(t.last) >= t.period
 }
diff --git a/go/pkg/cs/BUILD.bazel b/go/pkg/cs/BUILD.bazel
index bcc53ffc5..97f1f824a 100644
--- a/go/pkg/cs/BUILD.bazel
+++ b/go/pkg/cs/BUILD.bazel
@@ -8,6 +8,7 @@ go_library(
     deps = [
         "//go/cs/beacon:go_default_library",
         "//go/cs/beaconing:go_default_library",
+        "//go/cs/beaconing/compat:go_default_library",
         "//go/cs/beaconstorage:go_default_library",
         "//go/cs/config:go_default_library",
         "//go/cs/handlers:go_default_library",
diff --git a/go/pkg/cs/main.go b/go/pkg/cs/main.go
index ca0e44787..09af0e345 100644
--- a/go/pkg/cs/main.go
+++ b/go/pkg/cs/main.go
@@ -36,6 +36,7 @@ import (
 
 	"github.com/scionproto/scion/go/cs/beacon"
 	"github.com/scionproto/scion/go/cs/beaconing"
+	beaconingcompat "github.com/scionproto/scion/go/cs/beaconing/compat"
 	"github.com/scionproto/scion/go/cs/beaconstorage"
 	"github.com/scionproto/scion/go/cs/config"
 	"github.com/scionproto/scion/go/cs/handlers"
@@ -684,7 +685,17 @@ func (t *PeriodicTasks) startOriginator(a *net.UDPAddr) (*periodic.Runner, error
 	if !topo.Core() {
 		return nil, nil
 	}
-	s, err := beaconing.OriginatorConf{
+	s := &beaconing.Originator{
+		Extender: &beaconing.LegacyExtender{
+			IA:         topo.IA(),
+			Signer:     t.signer,
+			MAC:        t.genMac,
+			Intfs:      t.intfs,
+			MTU:        topo.MTU(),
+			MaxExpTime: maxExpTimeFactory(t.store, beacon.PropPolicy),
+			StaticInfo: func() *beaconing.StaticInfoCfg { return staticInfoCfg },
+			Task:       "originator",
+		},
 		BeaconSender: &onehop.BeaconSender{
 			Sender: onehop.Sender{
 				Conn: t.conn,
@@ -695,29 +706,27 @@ func (t *PeriodicTasks) startOriginator(a *net.UDPAddr) (*periodic.Runner, error
 			AddressRewriter:  t.addressRewriter,
 			QUICBeaconSender: t.msgr,
 		},
-		Config: beaconing.ExtenderConf{
-			IA:            topo.IA(),
-			Intfs:         t.intfs,
-			Mac:           t.genMac(),
-			MTU:           topo.MTU(),
-			Signer:        t.signer,
-			GetMaxExpTime: maxExpTimeFactory(t.store, beacon.PropPolicy),
-		},
-		Period: Cfg.BS.OriginationInterval.Duration,
-	}.New()
-	if err != nil {
-		return nil, common.NewBasicError("Unable to start originator", err)
+		IA:     topo.IA(),
+		Intfs:  t.intfs,
+		Signer: t.signer,
+		Tick:   beaconing.NewTick(Cfg.BS.OriginationInterval.Duration),
 	}
-	return periodic.Start(s, 500*time.Millisecond,
-		Cfg.BS.OriginationInterval.Duration), nil
+	return periodic.Start(s, 500*time.Millisecond, Cfg.BS.OriginationInterval.Duration), nil
 }
 
 func (t *PeriodicTasks) startPropagator(a *net.UDPAddr) (*periodic.Runner, error) {
 	topo := t.TopoProvider.Get()
-	p, err := beaconing.PropagatorConf{
-		BeaconProvider: t.store,
-		AllowIsdLoop:   t.allowIsdLoop,
-		Core:           topo.Core(),
+	p := &beaconing.Propagator{
+		Extender: &beaconing.LegacyExtender{
+			IA:         topo.IA(),
+			Signer:     t.signer,
+			MAC:        t.genMac,
+			Intfs:      t.intfs,
+			MTU:        topo.MTU(),
+			MaxExpTime: maxExpTimeFactory(t.store, beacon.PropPolicy),
+			StaticInfo: func() *beaconing.StaticInfoCfg { return staticInfoCfg },
+			Task:       "propagator",
+		},
 		BeaconSender: &onehop.BeaconSender{
 			Sender: onehop.Sender{
 				Conn: t.conn,
@@ -728,22 +737,15 @@ func (t *PeriodicTasks) startPropagator(a *net.UDPAddr) (*periodic.Runner, error
 			AddressRewriter:  t.addressRewriter,
 			QUICBeaconSender: t.msgr,
 		},
-		Config: beaconing.ExtenderConf{
-			IA:            topo.IA(),
-			Intfs:         t.intfs,
-			Mac:           t.genMac(),
-			MTU:           topo.MTU(),
-			Signer:        t.signer,
-			GetMaxExpTime: maxExpTimeFactory(t.store, beacon.PropPolicy),
-			StaticInfoCfg: staticInfoCfg,
-		},
-		Period: Cfg.BS.PropagationInterval.Duration,
-	}.New()
-	if err != nil {
-		return nil, common.NewBasicError("Unable to start propagator", err)
+		Provider:     t.store,
+		IA:           topo.IA(),
+		Signer:       t.signer,
+		Intfs:        t.intfs,
+		AllowIsdLoop: t.allowIsdLoop,
+		Core:         topo.Core(),
+		Tick:         beaconing.NewTick(Cfg.BS.PropagationInterval.Duration),
 	}
-	return periodic.Start(p, 500*time.Millisecond,
-		Cfg.BS.PropagationInterval.Duration), nil
+	return periodic.Start(p, 500*time.Millisecond, Cfg.BS.PropagationInterval.Duration), nil
 }
 
 func (t *PeriodicTasks) startSegRegRunners() (segRegRunners, error) {
@@ -771,27 +773,28 @@ func (t *PeriodicTasks) startSegRegRunners() (segRegRunners, error) {
 func (t *PeriodicTasks) startRegistrar(topo topology.Topology, segType proto.PathSegType,
 	policyType beacon.PolicyType) (*periodic.Runner, error) {
 
-	r, err := beaconing.RegistrarConf{
-		Msgr:         t.msgr,
-		SegProvider:  t.store,
-		SegStore:     &seghandler.DefaultStorage{PathDB: t.pathDB},
-		SegType:      segType,
-		TopoProvider: t.TopoProvider,
-		Period:       Cfg.BS.RegistrationInterval.Duration,
-		Config: beaconing.ExtenderConf{
-			IA:            topo.IA(),
-			Intfs:         t.intfs,
-			Mac:           t.genMac(),
-			MTU:           topo.MTU(),
-			Signer:        t.signer,
-			GetMaxExpTime: maxExpTimeFactory(t.store, policyType),
+	r := &beaconing.Registrar{
+		Extender: &beaconing.LegacyExtender{
+			IA:         topo.IA(),
+			Signer:     t.signer,
+			MAC:        t.genMac,
+			Intfs:      t.intfs,
+			MTU:        topo.MTU(),
+			MaxExpTime: maxExpTimeFactory(t.store, policyType),
+			StaticInfo: func() *beaconing.StaticInfoCfg { return staticInfoCfg },
+			Task:       "registrar",
 		},
-	}.New()
-	if err != nil {
-		return nil, common.NewBasicError("unable to start registrar", err, "type", segType)
+		Provider:     t.store,
+		Store:        &seghandler.DefaultStorage{PathDB: t.pathDB},
+		RPC:          beaconingcompat.RPC{Messenger: t.msgr},
+		IA:           topo.IA(),
+		Signer:       t.signer,
+		Intfs:        t.intfs,
+		Type:         segType,
+		TopoProvider: t.TopoProvider,
+		Tick:         beaconing.NewTick(Cfg.BS.RegistrationInterval.Duration),
 	}
-	return periodic.Start(r, 500*time.Millisecond,
-		Cfg.BS.RegistrationInterval.Duration), nil
+	return periodic.Start(r, 500*time.Millisecond, Cfg.BS.RegistrationInterval.Duration), nil
 }
 
 func (t *PeriodicTasks) Kill() {
diff --git a/tools/gomocks b/tools/gomocks
index 77271406c..e74d6cb4e 100755
--- a/tools/gomocks
+++ b/tools/gomocks
@@ -40,7 +40,7 @@ MOCK_TARGETS = [
     (SCION_PACKAGE_PREFIX + "/go/pkg/trust/renewal", "DB"),
     (SCION_PACKAGE_PREFIX + "/go/cs/beacon", "DB,Transaction"),
     (SCION_PACKAGE_PREFIX + "/go/cs/beaconing",
-        "BeaconInserter,BeaconProvider,SegmentProvider,SegmentStore"),
+        "BeaconInserter,BeaconProvider,BeaconSender,RPC,SegmentProvider,SegmentStore"),
     (SCION_PACKAGE_PREFIX + "/go/cs/keepalive", "IfStatePusher,RevDropper"),
     (SCION_PACKAGE_PREFIX + "/go/cs/revocation", "Store"),
     (SCION_PACKAGE_PREFIX + "/go/cs/segutil", "Policy"),


Test patch:
diff --git a/go/cs/beaconing/extender_test.go b/go/cs/beaconing/extender_test.go
index 8cb0ca182..3e2c4b495 100644
--- a/go/cs/beaconing/extender_test.go
+++ b/go/cs/beaconing/extender_test.go
@@ -42,7 +42,7 @@ import (
 	"github.com/scionproto/scion/go/proto"
 )
 
-func TestExtenderExtend(t *testing.T) {
+func TestLegacyExtenderExtend(t *testing.T) {
 	topoProvider := itopotest.TopoProviderFromFile(t, topoNonCore)
 	mac, err := scrypto.InitMac(make([]byte, 16))
 	require.NoError(t, err)
@@ -123,15 +123,15 @@ func TestExtenderExtend(t *testing.T) {
 			intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
 			intfs.Get(graph.If_111_A_112_X).Activate(graph.If_112_X_111_A)
 			intfs.Get(peer).Activate(graph.If_121_X_111_C)
-			ext, err := ExtenderConf{
-				IA:            topoProvider.Get().IA(),
-				MTU:           1337,
-				Signer:        testSigner(t, priv, topoProvider.Get().IA()),
-				Mac:           mac,
-				Intfs:         intfs,
-				GetMaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
-			}.new()
-			require.NoError(t, err)
+			ext := &LegacyExtender{
+				IA:         topoProvider.Get().IA(),
+				Signer:     testSigner(t, priv, topoProvider.Get().IA()),
+				MAC:        func() hash.Hash { return mac },
+				Intfs:      intfs,
+				MTU:        1337,
+				MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+				StaticInfo: func() *StaticInfoCfg { return nil },
+			}
 			// Create path segment from description, if available.
 			pseg, err := seg.NewSeg(&spath.InfoField{ISD: 1, TsInt: util.TimeToSecs(time.Now())})
 			if len(test.seg) > 0 {
@@ -139,7 +139,7 @@ func TestExtenderExtend(t *testing.T) {
 			}
 			require.NoError(t, err)
 			// Extend the segment.
-			err = ext.extend(context.Background(), pseg,
+			err = ext.Extend(context.Background(), pseg,
 				test.inIfid, test.egIfid, append(test.inactivePeers, peer))
 			test.errAssertion(t, err)
 			if err != nil {
@@ -158,7 +158,7 @@ func TestExtenderExtend(t *testing.T) {
 
 			entry := pseg.ASEntries[pseg.MaxAEIdx()]
 			t.Run("AS entry", func(t *testing.T) {
-				assert.Equal(t, uint8(DefaultIfidSize), entry.IfIDSize)
+				assert.Equal(t, uint8(legacyIfIDSize), entry.IfIDSize)
 				assert.Equal(t, uint16(1337), entry.MTU)
 				assert.Equal(t, topoProvider.Get().IA(), entry.IA())
 				// Checks that inactive peers are ignored, even when provided.
@@ -174,13 +174,13 @@ func TestExtenderExtend(t *testing.T) {
 					prev = pseg.ASEntries[pseg.MaxAEIdx()-1].HopEntries[0].RawHopField
 				}
 				testHopEntry(t, entry.HopEntries[0], intfs, test.inIfid, test.egIfid)
-				testHopF(t, entry.HopEntries[0], mac, infoF.TsInt, ext.cfg.GetMaxExpTime(),
+				testHopF(t, entry.HopEntries[0], mac, infoF.TsInt, ext.MaxExpTime(),
 					test.inIfid, test.egIfid, prev)
 			})
 
 			t.Run("peer entry is correct", func(t *testing.T) {
 				testHopEntry(t, entry.HopEntries[1], intfs, peer, test.egIfid)
-				testHopF(t, entry.HopEntries[1], mac, infoF.TsInt, ext.cfg.GetMaxExpTime(),
+				testHopF(t, entry.HopEntries[1], mac, infoF.TsInt, ext.MaxExpTime(),
 					peer, test.egIfid, entry.HopEntries[0].RawHopField)
 			})
 		})
@@ -192,17 +192,18 @@ func TestExtenderExtend(t *testing.T) {
 		intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
 		require.NoError(t, err)
 		intfs.Get(graph.If_111_B_120_X).Activate(graph.If_120_X_111_B)
-		ext, err := ExtenderConf{
-			IA:            topoProvider.Get().IA(),
-			MTU:           1337,
-			Signer:        testSigner(t, priv, topoProvider.Get().IA()),
-			Mac:           mac,
-			GetMaxExpTime: maxExpTimeFactory(1),
-			Intfs:         intfs,
-		}.new()
+		ext := &LegacyExtender{
+			IA:         topoProvider.Get().IA(),
+			Signer:     testSigner(t, priv, topoProvider.Get().IA()),
+			MAC:        func() hash.Hash { return mac },
+			Intfs:      intfs,
+			MTU:        1337,
+			MaxExpTime: maxExpTimeFactory(1),
+			StaticInfo: func() *StaticInfoCfg { return nil },
+		}
 		require.NoError(t, err)
 		pseg := testBeacon(g, segDesc).Segment
-		err = ext.extend(context.Background(), pseg, graph.If_111_B_120_X, 0, []common.IFIDType{})
+		err = ext.Extend(context.Background(), pseg, graph.If_111_B_120_X, 0, []common.IFIDType{})
 		require.NoError(t, err)
 		hopF, err := pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].HopField()
 		require.NoError(t, err)
@@ -274,20 +275,19 @@ func TestExtenderExtend(t *testing.T) {
 				defer mctrl.Finish()
 				g := graph.NewDefaultGraph(mctrl)
 				intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
-				require.NoError(t, err)
 				test.Activate(intfs)
 
-				ext, err := ExtenderConf{
-					IA:            topoProvider.Get().IA(),
-					MTU:           1337,
-					Signer:        test.Signer(t),
-					Mac:           mac,
-					Intfs:         intfs,
-					GetMaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
-				}.new()
-				require.NoError(t, err)
+				ext := &LegacyExtender{
+					IA:         topoProvider.Get().IA(),
+					Signer:     test.Signer(t),
+					MAC:        func() hash.Hash { return mac },
+					Intfs:      intfs,
+					MTU:        1337,
+					MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+					StaticInfo: func() *StaticInfoCfg { return nil },
+				}
 				pseg := testBeacon(g, segDesc).Segment
-				err = ext.extend(context.Background(), pseg,
+				err = ext.Extend(context.Background(), pseg,
 					test.InIfID, test.EgIfID, []common.IFIDType{})
 				assert.Error(t, err)
 			})
diff --git a/go/cs/beaconing/originator_test.go b/go/cs/beaconing/originator_test.go
index 494f8ecfd..22689bcef 100644
--- a/go/cs/beaconing/originator_test.go
+++ b/go/cs/beaconing/originator_test.go
@@ -21,11 +21,9 @@ import (
 	"crypto/elliptic"
 	"crypto/rand"
 	"encoding/asn1"
-	"errors"
-	"fmt"
+	"hash"
 	"math/big"
 	"net"
-	"sync"
 	"testing"
 	"time"
 
@@ -34,18 +32,15 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"github.com/scionproto/scion/go/cs/beacon"
+	"github.com/scionproto/scion/go/cs/beaconing/mock_beaconing"
 	"github.com/scionproto/scion/go/cs/ifstate"
-	"github.com/scionproto/scion/go/cs/onehop"
+	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/infra/modules/itopo/itopotest"
 	"github.com/scionproto/scion/go/lib/scrypto"
 	"github.com/scionproto/scion/go/lib/serrors"
-	"github.com/scionproto/scion/go/lib/snet"
-	"github.com/scionproto/scion/go/lib/snet/mock_snet"
-	"github.com/scionproto/scion/go/lib/topology"
-	"github.com/scionproto/scion/go/lib/xtest"
 	"github.com/scionproto/scion/go/proto"
 )
 
@@ -66,54 +61,57 @@ func TestOriginatorRun(t *testing.T) {
 		mctrl := gomock.NewController(t)
 		defer mctrl.Finish()
 		intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
-		conn := mock_snet.NewMockPacketConn(mctrl)
-		o, err := OriginatorConf{
-			Config: ExtenderConf{
-				IA:            topoProvider.Get().IA(),
-				MTU:           topoProvider.Get().MTU(),
-				Signer:        signer,
-				Intfs:         intfs,
-				Mac:           mac,
-				GetMaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+		sender := mock_beaconing.NewMockBeaconSender(mctrl)
+		o := Originator{
+			Extender: &LegacyExtender{
+				IA:         topoProvider.Get().IA(),
+				MTU:        topoProvider.Get().MTU(),
+				Signer:     signer,
+				Intfs:      intfs,
+				MAC:        func() hash.Hash { return mac },
+				MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+				StaticInfo: func() *StaticInfoCfg { return nil },
 			},
-			Period: time.Hour,
-			BeaconSender: &onehop.BeaconSender{
-				Sender: onehop.Sender{
-					IA:   xtest.MustParseIA("1-ff00:0:110"),
-					Conn: conn,
-					Addr: &net.UDPAddr{
-						IP:   net.ParseIP("127.0.0.1"),
-						Port: 4242,
-					},
-					MAC: mac,
-				},
-			},
-		}.New()
+			BeaconSender: sender,
+			IA:           topoProvider.Get().IA(),
+			Signer:       signer,
+			Intfs:        intfs,
+			Tick:         NewTick(time.Hour),
+		}
+
 		require.NoError(t, err)
 		// Activate interfaces
 		intfs.Get(42).Activate(84)
 		intfs.Get(1129).Activate(82)
 
-		msgsMtx := sync.Mutex{}
-		var msgs []msg
-		conn.EXPECT().WriteTo(gomock.Any(), gomock.Any()).Times(2).DoAndReturn(
-			func(ipkt, iov interface{}) error {
-				msgsMtx.Lock()
-				defer msgsMtx.Unlock()
-				msgs = append(msgs, msg{
-					pkt: ipkt.(*snet.Packet),
-					ov:  iov.(*net.UDPAddr),
-				})
+		sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
+			gomock.Any()).Times(2).DoAndReturn(
+			func(_ context.Context, beacon *seg.Beacon, dst addr.IA, egress common.IFIDType,
+				_ ctrl.Signer, nextHop *net.UDPAddr) error {
+
+				err = beacon.Parse()
+				require.NoError(t, err)
+
+				// Check the beacon is valid and verifiable.
+				pseg := beacon.Segment
+				assert.NoError(t, pseg.Validate(seg.ValidateBeacon))
+				assert.NoError(t, pseg.VerifyASEntry(context.Background(),
+					segVerifier{pubKey: pub}, pseg.MaxAEIdx()))
+
+				// Extract the hop field from the current AS entry to compare.
+				hopF, err := pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].HopField()
+				require.NoError(t, err)
+				// Check the interface matches.
+				assert.Equal(t, hopF.ConsEgress, egress)
+				// Check that the beacon is sent to the correct border router.
+				brAddress := topoProvider.Get().IFInfoMap()[hopF.ConsEgress].InternalAddr
+				assert.Equal(t, brAddress, nextHop)
 				return nil
 			},
 		)
+
 		// Start beacon messages.
 		o.Run(nil)
-		for i, msg := range msgs {
-			t.Run(fmt.Sprintf("Packet %d is correct", i), func(t *testing.T) {
-				checkMsg(t, msg, pub, topoProvider.Get().IFInfoMap())
-			})
-		}
 		// The second run should not cause any beacons to originate.
 		o.Run(nil)
 	})
@@ -121,30 +119,25 @@ func TestOriginatorRun(t *testing.T) {
 		mctrl := gomock.NewController(t)
 		defer mctrl.Finish()
 		intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
-		conn := mock_snet.NewMockPacketConn(mctrl)
-		o, err := OriginatorConf{
-			Config: ExtenderConf{
-				IA:            topoProvider.Get().IA(),
-				MTU:           topoProvider.Get().MTU(),
-				Signer:        signer,
-				Intfs:         intfs,
-				Mac:           mac,
-				GetMaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
-			},
-			Period: 2 * time.Second,
-			BeaconSender: &onehop.BeaconSender{
-				Sender: onehop.Sender{
-					IA:   xtest.MustParseIA("1-ff00:0:110"),
-					Conn: conn,
-					Addr: &net.UDPAddr{
-						IP:   net.ParseIP("127.0.0.1"),
-						Port: 4242,
-					},
-					MAC: mac,
-				},
+		sender := mock_beaconing.NewMockBeaconSender(mctrl)
+
+		o := Originator{
+			Extender: &LegacyExtender{
+				IA:         topoProvider.Get().IA(),
+				MTU:        topoProvider.Get().MTU(),
+				Signer:     signer,
+				Intfs:      intfs,
+				MAC:        func() hash.Hash { return mac },
+				MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+				StaticInfo: func() *StaticInfoCfg { return nil },
 			},
-		}.New()
-		require.NoError(t, err)
+			BeaconSender: sender,
+			IA:           topoProvider.Get().IA(),
+			Signer:       signer,
+			Intfs:        intfs,
+			Tick:         NewTick(2 * time.Second),
+		}
+
 		// Activate interfaces
 		intfs.Get(42).Activate(84)
 		intfs.Get(1129).Activate(82)
@@ -153,9 +146,13 @@ func TestOriginatorRun(t *testing.T) {
 		// 2. Second run where the beacon is delivered. -> 1 call
 		// 3. Run where no beacon is sent. -> no call
 		// 4. Run where beacons are sent on all interfaces. -> 2 calls
-		first := conn.EXPECT().WriteTo(gomock.Any(), gomock.Any())
-		first.Return(errors.New("fail"))
-		conn.EXPECT().WriteTo(gomock.Any(), gomock.Any()).After(first).Times(4).Return(nil)
+
+		first := sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
+			gomock.Any(), gomock.Any())
+		first.Return(serrors.New("fail"))
+
+		sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
+			gomock.Any(), gomock.Any()).Times(4).Return(nil)
 		// Initial run. Two writes expected, one write will fail.
 		o.Run(nil)
 		time.Sleep(1 * time.Second)
@@ -169,40 +166,6 @@ func TestOriginatorRun(t *testing.T) {
 	})
 }
 
-type msg struct {
-	pkt *snet.Packet
-	ov  *net.UDPAddr
-}
-
-func checkMsg(t *testing.T, msg msg, pub crypto.PublicKey, infos topology.IfInfoMap) {
-	// Extract segment from the payload
-	spld, err := ctrl.NewSignedPldFromRaw(msg.pkt.Payload.(common.RawBytes))
-	require.NoError(t, err)
-	pld, err := spld.UnsafePld()
-	require.NoError(t, err)
-	err = pld.Beacon.Parse()
-	require.NoError(t, err)
-
-	// Check the beacon is valid and verifiable.
-	pseg := pld.Beacon.Segment
-	assert.NoError(t, pseg.Validate(seg.ValidateBeacon))
-	assert.NoError(t, pseg.VerifyASEntry(context.Background(),
-		segVerifier{pubKey: pub}, pseg.MaxAEIdx()))
-
-	// Extract the the first hop field from the constructed one hop path the
-	// beacon is sent on. We want to make sure that the beacon is sent on the
-	// correct egress interface.
-	hopF, err := msg.pkt.Path.GetHopField(msg.pkt.Path.HopOff)
-	require.NoError(t, err)
-	// Extract the hop field from the current AS entry to compare.
-	bHopF, err := pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].HopField()
-	require.NoError(t, err)
-	// Check the interface matches.
-	assert.Equal(t, bHopF.ConsEgress, hopF.ConsEgress)
-	// Check that the beacon is sent to the correct border router.
-	assert.Equal(t, infos[hopF.ConsEgress].InternalAddr, msg.ov)
-}
-
 type segVerifier struct {
 	pubKey crypto.PublicKey
 }
diff --git a/go/cs/beaconing/propagator_test.go b/go/cs/beaconing/propagator_test.go
index 9d797b3b3..7bad49080 100644
--- a/go/cs/beaconing/propagator_test.go
+++ b/go/cs/beaconing/propagator_test.go
@@ -15,35 +15,34 @@
 package beaconing
 
 import (
+	"context"
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
-	"errors"
-	"fmt"
+	"hash"
 	"net"
-	"sync"
 	"testing"
 	"time"
 
 	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
 	"github.com/scionproto/scion/go/cs/beacon"
 	"github.com/scionproto/scion/go/cs/beaconing/mock_beaconing"
 	"github.com/scionproto/scion/go/cs/ifstate"
-	"github.com/scionproto/scion/go/cs/onehop"
+	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/ctrl"
+	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/infra/modules/itopo/itopotest"
 	"github.com/scionproto/scion/go/lib/scrypto"
-	"github.com/scionproto/scion/go/lib/snet"
-	"github.com/scionproto/scion/go/lib/snet/mock_snet"
+	"github.com/scionproto/scion/go/lib/serrors"
 	"github.com/scionproto/scion/go/lib/xtest/graph"
 )
 
 func TestPropagatorRun(t *testing.T) {
-	macProp, err := scrypto.InitMac(make(common.RawBytes, 16))
-	require.NoError(t, err)
-	macSender, err := scrypto.InitMac(make(common.RawBytes, 16))
+	mac, err := scrypto.InitMac(make(common.RawBytes, 16))
 	require.NoError(t, err)
 	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	require.NoError(t, err)
@@ -145,40 +144,33 @@ func TestPropagatorRun(t *testing.T) {
 			mctrl := gomock.NewController(t)
 			defer mctrl.Finish()
 			topoProvider := itopotest.TopoProviderFromFile(t, topoFile[test.core])
+			intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
 			provider := mock_beaconing.NewMockBeaconProvider(mctrl)
-			conn := mock_snet.NewMockPacketConn(mctrl)
-			cfg := PropagatorConf{
-				Config: ExtenderConf{
-					IA:     topoProvider.Get().IA(),
-					Signer: testSigner(t, priv, topoProvider.Get().IA()),
-					Mac:    macProp,
-					Intfs: ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(),
-						ifstate.Config{}),
-					MTU:           topoProvider.Get().MTU(),
-					GetMaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
-				},
-				Period:         time.Hour,
-				BeaconProvider: provider,
-				Core:           test.core,
-				BeaconSender: &onehop.BeaconSender{
-					Sender: onehop.Sender{
-						IA:   topoProvider.Get().IA(),
-						Conn: conn,
-						Addr: &net.UDPAddr{
-							IP:   net.ParseIP("127.0.0.1"),
-							Port: 4242,
-						},
-						MAC: macSender,
-					},
+			sender := mock_beaconing.NewMockBeaconSender(mctrl)
+
+			p := Propagator{
+				Extender: &LegacyExtender{
+					IA:         topoProvider.Get().IA(),
+					MTU:        topoProvider.Get().MTU(),
+					Signer:     testSigner(t, priv, topoProvider.Get().IA()),
+					Intfs:      intfs,
+					MAC:        func() hash.Hash { return mac },
+					MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+					StaticInfo: func() *StaticInfoCfg { return nil },
 				},
+				BeaconSender: sender,
+				IA:           topoProvider.Get().IA(),
+				Signer:       testSigner(t, priv, topoProvider.Get().IA()),
+				Intfs:        intfs,
+				Tick:         NewTick(time.Hour),
+				Core:         test.core,
+				Provider:     provider,
 			}
-			p, err := cfg.New()
-			require.NoError(t, err)
 			for ifid, remote := range allIntfs[test.core] {
 				if test.inactive[ifid] {
 					continue
 				}
-				cfg.Config.Intfs.Get(ifid).Activate(remote)
+				intfs.Get(ifid).Activate(remote)
 			}
 			g := graph.NewDefaultGraph(mctrl)
 			provider.EXPECT().BeaconsToPropagate(gomock.Any()).MaxTimes(2).DoAndReturn(
@@ -191,25 +183,33 @@ func TestPropagatorRun(t *testing.T) {
 					return res, nil
 				},
 			)
-			msgsMtx := sync.Mutex{}
-			var msgs []msg
-			conn.EXPECT().WriteTo(gomock.Any(), gomock.Any()).Times(test.expected).DoAndReturn(
-				func(ipkt, iov interface{}) error {
-					msgsMtx.Lock()
-					defer msgsMtx.Unlock()
-					msgs = append(msgs, msg{
-						pkt: ipkt.(*snet.Packet),
-						ov:  iov.(*net.UDPAddr),
-					})
+
+			sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
+				gomock.Any(), gomock.Any()).Times(test.expected).DoAndReturn(
+				func(_ context.Context, beacon *seg.Beacon, dst addr.IA, egress common.IFIDType,
+					_ ctrl.Signer, nextHop *net.UDPAddr) error {
+
+					err = beacon.Parse()
+					require.NoError(t, err)
+
+					// Check the beacon is valid and verifiable.
+					pseg := beacon.Segment
+					assert.NoError(t, pseg.Validate(seg.ValidateBeacon))
+					assert.NoError(t, pseg.VerifyASEntry(context.Background(),
+						segVerifier{pubKey: pub}, pseg.MaxAEIdx()))
+
+					// Extract the hop field from the current AS entry to compare.
+					hopF, err := pseg.ASEntries[pseg.MaxAEIdx()].HopEntries[0].HopField()
+					require.NoError(t, err)
+					// Check the interface matches.
+					assert.Equal(t, hopF.ConsEgress, egress)
+					// Check that the beacon is sent to the correct border router.
+					brAddress := topoProvider.Get().IFInfoMap()[hopF.ConsEgress].InternalAddr
+					assert.Equal(t, brAddress, nextHop)
 					return nil
 				},
 			)
 			p.Run(nil)
-			for i, msg := range msgs {
-				t.Run(fmt.Sprintf("Packet %d is correct", i), func(t *testing.T) {
-					checkMsg(t, msg, pub, topoProvider.Get().IFInfoMap())
-				})
-			}
 			// Check that no beacons are sent, since the period has not passed yet.
 			p.Run(nil)
 		})
@@ -218,37 +218,31 @@ func TestPropagatorRun(t *testing.T) {
 		mctrl := gomock.NewController(t)
 		defer mctrl.Finish()
 		topoProvider := itopotest.TopoProviderFromFile(t, topoCore)
+		intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
 		provider := mock_beaconing.NewMockBeaconProvider(mctrl)
-		conn := mock_snet.NewMockPacketConn(mctrl)
-		cfg := PropagatorConf{
-			Config: ExtenderConf{
-				IA:     topoProvider.Get().IA(),
-				Signer: testSigner(t, priv, topoProvider.Get().IA()),
-				Mac:    macProp,
-				Intfs: ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(),
-					ifstate.Config{}),
-				MTU:           uint16(topoProvider.Get().MTU()),
-				GetMaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
-			},
-			Period:         2 * time.Second,
-			BeaconProvider: provider,
-			Core:           true,
-			BeaconSender: &onehop.BeaconSender{
-				Sender: onehop.Sender{
-					IA:   topoProvider.Get().IA(),
-					Conn: conn,
-					Addr: &net.UDPAddr{
-						IP:   net.ParseIP("127.0.0.1"),
-						Port: 4242,
-					},
-					MAC: macSender,
-				},
+		sender := mock_beaconing.NewMockBeaconSender(mctrl)
+
+		p := Propagator{
+			Extender: &LegacyExtender{
+				IA:         topoProvider.Get().IA(),
+				MTU:        topoProvider.Get().MTU(),
+				Signer:     testSigner(t, priv, topoProvider.Get().IA()),
+				Intfs:      intfs,
+				MAC:        func() hash.Hash { return mac },
+				MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+				StaticInfo: func() *StaticInfoCfg { return nil },
 			},
+			BeaconSender: sender,
+			IA:           topoProvider.Get().IA(),
+			Signer:       testSigner(t, priv, topoProvider.Get().IA()),
+			Intfs:        intfs,
+			Tick:         NewTick(2 * time.Second),
+			Core:         true,
+			Provider:     provider,
 		}
-		p, err := cfg.New()
-		require.NoError(t, err)
+
 		for ifid, remote := range allIntfs[true] {
-			cfg.Config.Intfs.Get(ifid).Activate(remote)
+			intfs.Get(ifid).Activate(remote)
 		}
 		g := graph.NewDefaultGraph(mctrl)
 		// We call run 4 times in this test, since the interface to 1-ff00:0:120
@@ -265,9 +259,12 @@ func TestPropagatorRun(t *testing.T) {
 		// 2. Second run where the beacon is delivered. -> 1 call
 		// 3. Run where no beacon is sent. -> no call
 		// 4. Run where beacons are sent on all interfaces. -> 2 calls
-		firstCall := conn.EXPECT().WriteTo(gomock.Any(), gomock.Any())
-		firstCall.Return(errors.New("fail"))
-		conn.EXPECT().WriteTo(gomock.Any(), gomock.Any()).After(firstCall).Times(4).Return(nil)
+		first := sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
+			gomock.Any(), gomock.Any())
+		first.Return(serrors.New("fail"))
+
+		sender.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
+			gomock.Any(), gomock.Any()).Times(4).Return(nil)
 		// Initial run. Two writes expected, one write will fail.
 		p.Run(nil)
 		time.Sleep(1 * time.Second)
diff --git a/go/cs/beaconing/registrar_test.go b/go/cs/beaconing/registrar_test.go
index d654dc887..afa29244c 100644
--- a/go/cs/beaconing/registrar_test.go
+++ b/go/cs/beaconing/registrar_test.go
@@ -22,6 +22,8 @@ import (
 	"crypto/rand"
 	"errors"
 	"fmt"
+	"hash"
+	"net"
 	"sync"
 	"testing"
 	"time"
@@ -36,9 +38,7 @@ import (
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl"
-	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
-	"github.com/scionproto/scion/go/lib/infra/mock_infra"
 	"github.com/scionproto/scion/go/lib/infra/modules/itopo/itopotest"
 	"github.com/scionproto/scion/go/lib/infra/modules/seghandler"
 	"github.com/scionproto/scion/go/lib/scrypto"
@@ -88,26 +88,29 @@ func TestRegistrarRun(t *testing.T) {
 			mctrl := gomock.NewController(t)
 			defer mctrl.Finish()
 			topoProvider := itopotest.TopoProviderFromFile(t, test.fn)
+			intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
 			segProvider := mock_beaconing.NewMockSegmentProvider(mctrl)
 			segStore := mock_beaconing.NewMockSegmentStore(mctrl)
-			cfg := RegistrarConf{
-				Config: ExtenderConf{
-					IA:     topoProvider.Get().IA(),
-					Signer: testSigner(t, priv, topoProvider.Get().IA()),
-					Mac:    mac,
-					Intfs: ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(),
-						ifstate.Config{}),
-					MTU:           topoProvider.Get().MTU(),
-					GetMaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+
+			r := Registrar{
+				Extender: &LegacyExtender{
+					IA:         topoProvider.Get().IA(),
+					MTU:        topoProvider.Get().MTU(),
+					Signer:     testSigner(t, priv, topoProvider.Get().IA()),
+					Intfs:      intfs,
+					MAC:        func() hash.Hash { return mac },
+					MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+					StaticInfo: func() *StaticInfoCfg { return nil },
 				},
-				Period:       time.Hour,
-				SegProvider:  segProvider,
-				SegStore:     segStore,
+				IA:           topoProvider.Get().IA(),
+				Signer:       testSigner(t, priv, topoProvider.Get().IA()),
+				Intfs:        intfs,
+				Tick:         NewTick(time.Hour),
+				Provider:     segProvider,
+				Store:        segStore,
 				TopoProvider: topoProvider,
-				SegType:      test.segType,
+				Type:         test.segType,
 			}
-			r, err := cfg.New()
-			require.NoError(t, err)
 			g := graph.NewDefaultGraph(mctrl)
 			segProvider.EXPECT().SegmentsToRegister(gomock.Any(), test.segType).DoAndReturn(
 				func(_, _ interface{}) (<-chan beacon.BeaconOrErr, error) {
@@ -128,7 +131,7 @@ func TestRegistrarRun(t *testing.T) {
 				},
 			)
 
-			for ifid, intf := range cfg.Config.Intfs.All() {
+			for ifid, intf := range intfs.All() {
 				if test.inactivePeers[ifid] {
 					continue
 				}
@@ -171,26 +174,29 @@ func TestRegistrarRun(t *testing.T) {
 			mctrl := gomock.NewController(t)
 			defer mctrl.Finish()
 			topoProvider := itopotest.TopoProviderFromFile(t, test.fn)
+			intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
 			segProvider := mock_beaconing.NewMockSegmentProvider(mctrl)
-			msgr := mock_infra.NewMockMessenger(mctrl)
-			cfg := RegistrarConf{
-				Config: ExtenderConf{
-					IA:     topoProvider.Get().IA(),
-					Signer: testSigner(t, priv, topoProvider.Get().IA()),
-					Mac:    mac,
-					Intfs: ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(),
-						ifstate.Config{}),
-					MTU:           topoProvider.Get().MTU(),
-					GetMaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+			rpc := mock_beaconing.NewMockRPC(mctrl)
+
+			r := Registrar{
+				Extender: &LegacyExtender{
+					IA:         topoProvider.Get().IA(),
+					MTU:        topoProvider.Get().MTU(),
+					Signer:     testSigner(t, priv, topoProvider.Get().IA()),
+					Intfs:      intfs,
+					MAC:        func() hash.Hash { return mac },
+					MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+					StaticInfo: func() *StaticInfoCfg { return nil },
 				},
-				Period:       time.Hour,
-				Msgr:         msgr,
-				SegProvider:  segProvider,
+				IA:           topoProvider.Get().IA(),
+				Signer:       testSigner(t, priv, topoProvider.Get().IA()),
+				Intfs:        intfs,
+				Tick:         NewTick(time.Hour),
+				Provider:     segProvider,
 				TopoProvider: topoProvider,
-				SegType:      test.segType,
+				Type:         test.segType,
+				RPC:          rpc,
 			}
-			r, err := cfg.New()
-			require.NoError(t, err)
 			g := graph.NewDefaultGraph(mctrl)
 			segProvider.EXPECT().SegmentsToRegister(gomock.Any(), test.segType).DoAndReturn(
 				func(_, _ interface{}) (<-chan beacon.BeaconOrErr, error) {
@@ -202,25 +208,26 @@ func TestRegistrarRun(t *testing.T) {
 					return res, nil
 				})
 			type regMsg struct {
-				Reg  *path_mgmt.SegReg
+				Meta seg.Meta
 				Addr *snet.SVCAddr
 			}
 			segMu := sync.Mutex{}
 			var sent []regMsg
 			// Collect the segments that are sent on the messenger.
-			msgr.EXPECT().SendSegReg(gomock.Any(), gomock.Any(), gomock.Any(),
+
+			rpc.EXPECT().RegisterSegment(gomock.Any(), gomock.Any(),
 				gomock.Any()).Times(len(test.beacons)).DoAndReturn(
-				func(_, isegreg, iaddr, _ interface{}) error {
+				func(_ context.Context, meta seg.Meta, remote net.Addr) error {
 					segMu.Lock()
 					defer segMu.Unlock()
 					sent = append(sent, regMsg{
-						Reg:  isegreg.(*path_mgmt.SegReg),
-						Addr: iaddr.(*snet.SVCAddr),
+						Meta: meta,
+						Addr: remote.(*snet.SVCAddr),
 					})
 					return nil
 				},
 			)
-			for ifid, intf := range cfg.Config.Intfs.All() {
+			for ifid, intf := range intfs.All() {
 				if test.inactivePeers[ifid] {
 					continue
 				}
@@ -230,8 +237,7 @@ func TestRegistrarRun(t *testing.T) {
 			require.Len(t, sent, len(test.beacons))
 			for segIdx, s := range sent {
 				t.Run(fmt.Sprintf("seg idx %d", segIdx), func(t *testing.T) {
-					require.Len(t, s.Reg.Recs, 1)
-					pseg := s.Reg.Recs[0].Segment
+					pseg := s.Meta.Segment
 
 					assert.NoError(t, pseg.Validate(seg.ValidateSegment))
 					assert.NoError(t, pseg.VerifyASEntry(context.Background(),
@@ -260,25 +266,27 @@ func TestRegistrarRun(t *testing.T) {
 		mctrl := gomock.NewController(t)
 		defer mctrl.Finish()
 		topoProvider := itopotest.TopoProviderFromFile(t, topoCore)
+		intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
 		segProvider := mock_beaconing.NewMockSegmentProvider(mctrl)
-		msgr := mock_infra.NewMockMessenger(mctrl)
-		cfg := RegistrarConf{
-			Config: ExtenderConf{
-				IA:     topoProvider.Get().IA(),
-				Signer: testSigner(t, priv, topoProvider.Get().IA()),
-				Mac:    mac,
-				Intfs: ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(),
-					ifstate.Config{}),
-				MTU:           topoProvider.Get().MTU(),
-				GetMaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+
+		r := Registrar{
+			Extender: &LegacyExtender{
+				IA:         topoProvider.Get().IA(),
+				MTU:        topoProvider.Get().MTU(),
+				Signer:     testSigner(t, priv, topoProvider.Get().IA()),
+				Intfs:      intfs,
+				MAC:        func() hash.Hash { return mac },
+				MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+				StaticInfo: func() *StaticInfoCfg { return nil },
 			},
-			Msgr:         msgr,
-			SegProvider:  segProvider,
+			IA:           topoProvider.Get().IA(),
+			Signer:       testSigner(t, priv, topoProvider.Get().IA()),
+			Intfs:        intfs,
+			Tick:         NewTick(time.Hour),
+			Provider:     segProvider,
 			TopoProvider: topoProvider,
-			SegType:      proto.PathSegType_core,
+			Type:         proto.PathSegType_core,
 		}
-		r, err := cfg.New()
-		require.NoError(t, err)
 		res := make(chan beacon.BeaconOrErr, 3)
 		segProvider.EXPECT().SegmentsToRegister(gomock.Any(), proto.PathSegType_core).DoAndReturn(
 			func(_, _ interface{}) (<-chan beacon.BeaconOrErr, error) {
@@ -300,24 +308,29 @@ func TestRegistrarRun(t *testing.T) {
 		mctrl := gomock.NewController(t)
 		defer mctrl.Finish()
 		topoProvider := itopotest.TopoProviderFromFile(t, topoNonCore)
+		intfs := ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(), ifstate.Config{})
 		segProvider := mock_beaconing.NewMockSegmentProvider(mctrl)
-		msgr := mock_infra.NewMockMessenger(mctrl)
-		cfg := RegistrarConf{
-			Config: ExtenderConf{
-				Signer: testSigner(t, priv, topoProvider.Get().IA()),
-				Mac:    mac,
-				Intfs: ifstate.NewInterfaces(topoProvider.Get().IFInfoMap(),
-					ifstate.Config{}),
-				MTU:           topoProvider.Get().MTU(),
-				GetMaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+		rpc := mock_beaconing.NewMockRPC(mctrl)
+
+		r := Registrar{
+			Extender: &LegacyExtender{
+				IA:         topoProvider.Get().IA(),
+				MTU:        topoProvider.Get().MTU(),
+				Signer:     testSigner(t, priv, topoProvider.Get().IA()),
+				Intfs:      intfs,
+				MAC:        func() hash.Hash { return mac },
+				MaxExpTime: maxExpTimeFactory(beacon.DefaultMaxExpTime),
+				StaticInfo: func() *StaticInfoCfg { return nil },
 			},
-			Msgr:         msgr,
-			SegProvider:  segProvider,
+			IA:           topoProvider.Get().IA(),
+			Signer:       testSigner(t, priv, topoProvider.Get().IA()),
+			Intfs:        intfs,
+			Tick:         NewTick(time.Hour),
+			Provider:     segProvider,
 			TopoProvider: topoProvider,
-			SegType:      proto.PathSegType_down,
+			Type:         proto.PathSegType_down,
+			RPC:          rpc,
 		}
-		r, err := cfg.New()
-		require.NoError(t, err)
 		g := graph.NewDefaultGraph(mctrl)
 		require.NoError(t, err)
 		segProvider.EXPECT().SegmentsToRegister(gomock.Any(),