Repository: scionproto/scion
Pull request #3590 (2020-01-08 15:14:06)

Monolithic Control Service POC
This commit introduces a monolithic control service server which
aggregates BS, CS, and PS services functionality.

The monolith removes the need for several intra-AS RPC calls (e.g., a BS
no longer needs to push crypto and segments to the CS and PS) and
removes the need for multiple database instances.

The current implementation is just a POC, and is inefficient. Some calls
from the monolithic process go via the SCION RPC mechanism to itself,
and the Certificate Service modules within the monolith still use the
local SCIOND for paths even though that SCIOND goes back to the Path
Service modules in the same monolith. These should be fixed in future
work.

The Go Dispatcher is patched to support applications binding to a
wildcard SVC. Traffic for BS, CS, and PS is delivered to the wildcard
SVC. This is not a valid wire SVC address, and is only locally relevant
on a host supporting the monolithic control service.

Also includes generator support for the monolithic control service and a
CI end2end test.

Fixes https://github.com/scionproto/scion/issues/3584.

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



Patch:
diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml
index ae17f1268..bd3325e12 100644
--- a/.buildkite/pipeline.yml
+++ b/.buildkite/pipeline.yml
@@ -64,7 +64,7 @@ steps:
       - tar -kxf bazel-bin/scion.tar -C bin --overwrite
       - tar -kxf bazel-bin/scion-ci.tar -C bin --overwrite
       - ./scion.sh topology
-      - ./scion.sh run nobuild && sleep 10
+      - ./scion.sh run && sleep 10
       - ./bin/end2end_integration -log.console warn
       - ./integration/revocation_test.sh
     key: revocation_tests
@@ -80,16 +80,32 @@ steps:
     - tar -kxf bazel-bin/scion.tar -C bin --overwrite
     - tar -kxf bazel-bin/scion-ci.tar -C bin --overwrite
     - ./scion.sh topology
-    - ./scion.sh run nobuild && sleep 10
+    - ./scion.sh run && sleep 10
     - ./bin/cert_req_integration -log.console warn
     - ./bin/pp_integration  -log.console warn
     - ./bin/scmp_integration -log.console warn
     - ./bin/end2end_integration -log.console warn
     artifact_paths:
       - "artifacts.out/**/*"
-    timeout_in_minutes: 5
+    timeout_in_minutes: 10
     key: integration_tests
     retry:
       automatic:
         - exit_status: -1  # Agent was lost
         - exit_status: 255 # Forced agent shutdown
+  - label: "Integration: end2end_integration"
+    command:
+    - bazel --bazelrc=.bazelrc_ci build //:scion //:scion-ci >/dev/null 2>&1
+    - tar -kxf bazel-bin/scion.tar -C bin --overwrite
+    - tar -kxf bazel-bin/scion-ci.tar -C bin --overwrite
+    - ./scion.sh topology --monolith
+    - ./scion.sh run && sleep 10
+    - ./bin/end2end_integration -log.console warn
+    artifact_paths:
+      - "artifacts.out/**/*"
+    timeout_in_minutes: 10
+    key: integration_tests_monolith
+    retry:
+      automatic:
+        - exit_status: -1  # Agent was lost
+        - exit_status: 255 # Forced agent shutdown
diff --git a/BUILD.bazel b/BUILD.bazel
index b3018a17e..b6987a02d 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -13,6 +13,7 @@ pkg_tar(
     srcs = [
         "//go/beacon_srv:beacon_srv",
         "//go/border:border",
+        "//go/cs:cs",
         "//go/cert_srv:cert_srv",
         "//go/godispatcher:godispatcher",
         "//go/tools/logdog:logdog",
diff --git a/go/beacon_srv/BUILD.bazel b/go/beacon_srv/BUILD.bazel
index 12ec3f033..26c77cca4 100644
--- a/go/beacon_srv/BUILD.bazel
+++ b/go/beacon_srv/BUILD.bazel
@@ -7,14 +7,15 @@ go_library(
     importpath = "github.com/scionproto/scion/go/beacon_srv",
     visibility = ["//visibility:private"],
     deps = [
-        "//go/beacon_srv/internal/beacon:go_default_library",
-        "//go/beacon_srv/internal/beaconing:go_default_library",
-        "//go/beacon_srv/internal/beaconstorage:go_default_library",
         "//go/beacon_srv/internal/config:go_default_library",
-        "//go/beacon_srv/internal/ifstate:go_default_library",
-        "//go/beacon_srv/internal/keepalive:go_default_library",
-        "//go/beacon_srv/internal/onehop:go_default_library",
-        "//go/beacon_srv/internal/revocation:go_default_library",
+        "//go/cs/beacon:go_default_library",
+        "//go/cs/beaconing:go_default_library",
+        "//go/cs/beaconstorage:go_default_library",
+        "//go/cs/config:go_default_library",
+        "//go/cs/ifstate:go_default_library",
+        "//go/cs/keepalive:go_default_library",
+        "//go/cs/onehop:go_default_library",
+        "//go/cs/revocation:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/discovery:go_default_library",
diff --git a/go/beacon_srv/internal/config/BUILD.bazel b/go/beacon_srv/internal/config/BUILD.bazel
index f6c58211f..3fc1e4782 100644
--- a/go/beacon_srv/internal/config/BUILD.bazel
+++ b/go/beacon_srv/internal/config/BUILD.bazel
@@ -2,14 +2,12 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
-    srcs = [
-        "config.go",
-        "sample.go",
-    ],
+    srcs = ["config.go"],
     importpath = "github.com/scionproto/scion/go/beacon_srv/internal/config",
     visibility = ["//go/beacon_srv:__subpackages__"],
     deps = [
-        "//go/beacon_srv/internal/beaconstorage:go_default_library",
+        "//go/cs/beaconstorage:go_default_library",
+        "//go/cs/config:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/config:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
@@ -26,7 +24,8 @@ go_test(
     srcs = ["config_test.go"],
     embed = [":go_default_library"],
     deps = [
-        "//go/beacon_srv/internal/beaconstorage/beaconstoragetest:go_default_library",
+        "//go/cs/beaconstorage/beaconstoragetest:go_default_library",
+        "//go/cs/config:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
         "//go/lib/env/envtest:go_default_library",
         "//go/lib/infra/modules/idiscovery/idiscoverytest:go_default_library",
diff --git a/go/beacon_srv/internal/config/config.go b/go/beacon_srv/internal/config/config.go
index 8da668478..5e6f62a3c 100644
--- a/go/beacon_srv/internal/config/config.go
+++ b/go/beacon_srv/internal/config/config.go
@@ -19,7 +19,8 @@ import (
 	"io"
 	"time"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beaconstorage"
+	"github.com/scionproto/scion/go/cs/beaconstorage"
+	controlconfig "github.com/scionproto/scion/go/cs/config"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/config"
 	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
@@ -30,6 +31,10 @@ import (
 	"github.com/scionproto/scion/go/lib/util"
 )
 
+const (
+	idSample = "bs-1"
+)
+
 const (
 	// DefaultKeepaliveInterval is the default interval between sending
 	// interface keepalives.
@@ -67,7 +72,7 @@ type Config struct {
 	TrustDB        truststorage.TrustDBConf
 	BeaconDB       beaconstorage.BeaconDBConf
 	Discovery      idiscovery.Config
-	BS             BSConfig
+	BS             controlconfig.BSConfig
 	EnableQUICTest bool
 }
 
@@ -198,7 +203,7 @@ func (cfg *BSConfig) Validate() error {
 
 // Sample generates a sample for the beacon server specific configuration.
 func (cfg *BSConfig) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) {
-	config.WriteString(dst, bsconfigSample)
+	config.WriteString(dst, controlconfig.BSSample)
 	config.WriteSample(dst, path, ctx, &cfg.Policies)
 }
 
@@ -242,7 +247,7 @@ type Policies struct {
 
 // Sample generates a sample for the beacon server specific configuration.
 func (cfg *Policies) Sample(dst io.Writer, _ config.Path, _ config.CtxMap) {
-	config.WriteString(dst, policiesSample)
+	config.WriteString(dst, controlconfig.PoliciesSample)
 }
 
 // ConfigName is the toml key for the beacon server specific configuration.
diff --git a/go/beacon_srv/internal/metrics/BUILD.bazel b/go/beacon_srv/internal/metrics/BUILD.bazel
deleted file mode 100644
index 802aed731..000000000
--- a/go/beacon_srv/internal/metrics/BUILD.bazel
+++ /dev/null
@@ -1,30 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-
-go_library(
-    name = "go_default_library",
-    srcs = [
-        "beaconing.go",
-        "ifstate.go",
-        "keepalive.go",
-        "metrics.go",
-        "originator.go",
-        "propagator.go",
-        "registrar.go",
-        "revocation.go",
-    ],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/metrics",
-    visibility = ["//go/beacon_srv:__subpackages__"],
-    deps = [
-        "//go/lib/addr:go_default_library",
-        "//go/lib/common:go_default_library",
-        "//go/lib/prom:go_default_library",
-        "@com_github_prometheus_client_golang//prometheus:go_default_library",
-    ],
-)
-
-go_test(
-    name = "go_default_test",
-    srcs = ["metrics_test.go"],
-    embed = [":go_default_library"],
-    deps = ["//go/lib/prom/promtest:go_default_library"],
-)
diff --git a/go/beacon_srv/main.go b/go/beacon_srv/main.go
index d06b55511..2fb5bcf2a 100644
--- a/go/beacon_srv/main.go
+++ b/go/beacon_srv/main.go
@@ -31,14 +31,15 @@ import (
 	"github.com/prometheus/client_golang/prometheus"
 	"gopkg.in/yaml.v2"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/beaconing"
-	"github.com/scionproto/scion/go/beacon_srv/internal/beaconstorage"
 	"github.com/scionproto/scion/go/beacon_srv/internal/config"
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
-	"github.com/scionproto/scion/go/beacon_srv/internal/keepalive"
-	"github.com/scionproto/scion/go/beacon_srv/internal/onehop"
-	"github.com/scionproto/scion/go/beacon_srv/internal/revocation"
+	"github.com/scionproto/scion/go/cs/beacon"
+	"github.com/scionproto/scion/go/cs/beaconing"
+	"github.com/scionproto/scion/go/cs/beaconstorage"
+	controlconfig "github.com/scionproto/scion/go/cs/config"
+	"github.com/scionproto/scion/go/cs/ifstate"
+	"github.com/scionproto/scion/go/cs/keepalive"
+	"github.com/scionproto/scion/go/cs/onehop"
+	"github.com/scionproto/scion/go/cs/revocation"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/discovery"
@@ -598,7 +599,7 @@ func loadStore(core bool, ia addr.IA, cfg config.Config) (beaconstorage.Store, e
 	return cfg.BeaconDB.NewStore(ia, policies)
 }
 
-func loadCorePolicies(cfg config.Policies) (beacon.CorePolicies, error) {
+func loadCorePolicies(cfg controlconfig.Policies) (beacon.CorePolicies, error) {
 	var err error
 	var policies beacon.CorePolicies
 	if policies.Prop, err = loadPolicy(cfg.Propagation, beacon.PropPolicy); err != nil {
@@ -610,7 +611,7 @@ func loadCorePolicies(cfg config.Policies) (beacon.CorePolicies, error) {
 	return policies, nil
 }
 
-func loadPolicies(cfg config.Policies) (beacon.Policies, error) {
+func loadPolicies(cfg controlconfig.Policies) (beacon.Policies, error) {
 	var err error
 	var policies beacon.Policies
 	if policies.Prop, err = loadPolicy(cfg.Propagation, beacon.PropPolicy); err != nil {
diff --git a/go/cert_srv/internal/config/BUILD.bazel b/go/cert_srv/internal/config/BUILD.bazel
index 3a587722e..3e4936bef 100644
--- a/go/cert_srv/internal/config/BUILD.bazel
+++ b/go/cert_srv/internal/config/BUILD.bazel
@@ -2,20 +2,16 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
-    srcs = [
-        "config.go",
-        "sample.go",
-    ],
+    srcs = ["config.go"],
     importpath = "github.com/scionproto/scion/go/cert_srv/internal/config",
     visibility = ["//go/cert_srv:__subpackages__"],
     deps = [
+        "//go/cs/config:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/config:go_default_library",
         "//go/lib/env:go_default_library",
         "//go/lib/infra/modules/idiscovery:go_default_library",
-        "//go/lib/serrors:go_default_library",
         "//go/lib/truststorage:go_default_library",
-        "//go/lib/util:go_default_library",
     ],
 )
 
@@ -25,6 +21,7 @@ go_test(
     data = glob(["testdata/**"]),
     embed = [":go_default_library"],
     deps = [
+        "//go/cs/config:go_default_library",
         "//go/lib/env/envtest:go_default_library",
         "//go/lib/infra/modules/idiscovery/idiscoverytest:go_default_library",
         "//go/lib/truststorage/truststoragetest:go_default_library",
diff --git a/go/cert_srv/internal/config/config.go b/go/cert_srv/internal/config/config.go
index f2d51701e..059f5cf7e 100644
--- a/go/cert_srv/internal/config/config.go
+++ b/go/cert_srv/internal/config/config.go
@@ -18,13 +18,16 @@ import (
 	"io"
 	"time"
 
+	controlconfig "github.com/scionproto/scion/go/cs/config"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/config"
 	"github.com/scionproto/scion/go/lib/env"
 	"github.com/scionproto/scion/go/lib/infra/modules/idiscovery"
-	"github.com/scionproto/scion/go/lib/serrors"
 	"github.com/scionproto/scion/go/lib/truststorage"
-	"github.com/scionproto/scion/go/lib/util"
+)
+
+const (
+	idSample = "cs-1"
 )
 
 const (
@@ -58,7 +61,7 @@ type Config struct {
 	Sciond    env.SciondClient `toml:"sd_client"`
 	TrustDB   truststorage.TrustDBConf
 	Discovery idiscovery.Config
-	CS        CSConfig
+	CS        controlconfig.CSConfig
 }
 
 func (cfg *Config) InitDefaults() {
@@ -106,61 +109,3 @@ func (cfg *Config) Sample(dst io.Writer, path config.Path, _ config.CtxMap) {
 func (cfg *Config) ConfigName() string {
 	return "cs_config"
 }
-
-var _ config.Config = (*CSConfig)(nil)
-
-type CSConfig struct {
-	// LeafReissueLeadTime indicates how long in advance of leaf cert expiration
-	// the reissuance process starts.
-	LeafReissueLeadTime util.DurWrap
-	// IssuerReissueLeadTime indicates how long in advance core cert expiration
-	// the self reissuance process starts.
-	IssuerReissueLeadTime util.DurWrap
-	// ReissueRate is the interval between two consecutive reissue requests.
-	ReissueRate util.DurWrap
-	// ReissueTimeout is the timeout for resissue request.
-	ReissueTimeout util.DurWrap
-	// AutomaticRenewal whether automatic reissuing is enabled.
-	AutomaticRenewal bool
-	// DisableCorePush disables the core pusher task.
-	DisableCorePush bool
-}
-
-func (cfg *CSConfig) InitDefaults() {
-	if cfg.LeafReissueLeadTime.Duration == 0 {
-		cfg.LeafReissueLeadTime.Duration = LeafReissTime
-	}
-	if cfg.IssuerReissueLeadTime.Duration == 0 {
-		cfg.IssuerReissueLeadTime.Duration = IssuerReissTime
-	}
-	if cfg.ReissueRate.Duration == 0 {
-		cfg.ReissueRate.Duration = ReissReqRate
-	}
-	if cfg.ReissueTimeout.Duration == 0 {
-		cfg.ReissueTimeout.Duration = ReissueReqTimeout
-	}
-}
-
-func (cfg *CSConfig) Validate() error {
-	if cfg.LeafReissueLeadTime.Duration == 0 {
-		return serrors.New("LeafReissueLeadTime must not be zero")
-	}
-	if cfg.IssuerReissueLeadTime.Duration == 0 {
-		return serrors.New("IssuerReissueLeadTime must not be zero")
-	}
-	if cfg.ReissueRate.Duration == 0 {
-		return serrors.New("ReissueRate must not be zero")
-	}
-	if cfg.ReissueTimeout.Duration == 0 {
-		return serrors.New("ReissueTimeout must not be zero")
-	}
-	return nil
-}
-
-func (cfg *CSConfig) Sample(dst io.Writer, path config.Path, _ config.CtxMap) {
-	config.WriteString(dst, csconfigSample)
-}
-
-func (cfg *CSConfig) ConfigName() string {
-	return "cs"
-}
diff --git a/go/cert_srv/internal/metrics/BUILD.bazel b/go/cert_srv/internal/metrics/BUILD.bazel
deleted file mode 100644
index 871823570..000000000
--- a/go/cert_srv/internal/metrics/BUILD.bazel
+++ /dev/null
@@ -1,8 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-go_library(
-    name = "go_default_library",
-    srcs = ["metrics.go"],
-    importpath = "github.com/scionproto/scion/go/cert_srv/internal/metrics",
-    visibility = ["//go/cert_srv:__subpackages__"],
-)
diff --git a/go/cert_srv/internal/metrics/metrics.go b/go/cert_srv/internal/metrics/metrics.go
deleted file mode 100644
index 0f5437662..000000000
--- a/go/cert_srv/internal/metrics/metrics.go
+++ /dev/null
@@ -1,18 +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 metrics
-
-// Namespace is the metrics namespace for the certificate server.
-const Namespace = "cs"
diff --git a/go/cert_srv/main.go b/go/cert_srv/main.go
index 5e4b4e184..0ba49d485 100644
--- a/go/cert_srv/main.go
+++ b/go/cert_srv/main.go
@@ -77,7 +77,6 @@ func realMain() int {
 	defer log.Flush()
 	defer env.LogAppStopped(common.CS, cfg.General.ID)
 	defer log.LogPanicAndExit()
-	// Setup the state and the messenger
 	if err := setup(); err != nil {
 		log.Crit("Setup failed", "err", err)
 		return 1
diff --git a/go/cs/BUILD.bazel b/go/cs/BUILD.bazel
new file mode 100644
index 000000000..29b0b518d
--- /dev/null
+++ b/go/cs/BUILD.bazel
@@ -0,0 +1,57 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["main.go"],
+    importpath = "github.com/scionproto/scion/go/cs",
+    visibility = ["//visibility:private"],
+    deps = [
+        "//go/cs/beacon:go_default_library",
+        "//go/cs/beaconing:go_default_library",
+        "//go/cs/beaconstorage:go_default_library",
+        "//go/cs/config:go_default_library",
+        "//go/cs/handlers:go_default_library",
+        "//go/cs/ifstate:go_default_library",
+        "//go/cs/keepalive:go_default_library",
+        "//go/cs/onehop:go_default_library",
+        "//go/cs/revocation:go_default_library",
+        "//go/cs/segreq:go_default_library",
+        "//go/cs/segsyncer:go_default_library",
+        "//go/lib/addr:go_default_library",
+        "//go/lib/common:go_default_library",
+        "//go/lib/discovery:go_default_library",
+        "//go/lib/env:go_default_library",
+        "//go/lib/fatal:go_default_library",
+        "//go/lib/infra:go_default_library",
+        "//go/lib/infra/infraenv:go_default_library",
+        "//go/lib/infra/messenger:go_default_library",
+        "//go/lib/infra/modules/idiscovery:go_default_library",
+        "//go/lib/infra/modules/itopo:go_default_library",
+        "//go/lib/infra/modules/trust:go_default_library",
+        "//go/lib/keyconf:go_default_library",
+        "//go/lib/log:go_default_library",
+        "//go/lib/pathdb:go_default_library",
+        "//go/lib/pathstorage:go_default_library",
+        "//go/lib/periodic:go_default_library",
+        "//go/lib/prom:go_default_library",
+        "//go/lib/revcache:go_default_library",
+        "//go/lib/scrypto:go_default_library",
+        "//go/lib/serrors:go_default_library",
+        "//go/lib/snet:go_default_library",
+        "//go/lib/sock/reliable:go_default_library",
+        "//go/lib/sock/reliable/reconnect:go_default_library",
+        "//go/lib/spath:go_default_library",
+        "//go/lib/topology:go_default_library",
+        "//go/proto:go_default_library",
+        "@com_github_burntsushi_toml//:go_default_library",
+        "@com_github_opentracing_opentracing_go//:go_default_library",
+        "@com_github_prometheus_client_golang//prometheus:go_default_library",
+        "@in_gopkg_yaml_v2//:go_default_library",
+    ],
+)
+
+go_binary(
+    name = "cs",
+    embed = [":go_default_library"],
+    visibility = ["//visibility:public"],
+)
diff --git a/go/beacon_srv/internal/beacon/BUILD.bazel b/go/cs/beacon/BUILD.bazel
similarity index 85%
rename from go/beacon_srv/internal/beacon/BUILD.bazel
rename to go/cs/beacon/BUILD.bazel
index ec05bf197..1e6fad9e6 100644
--- a/go/beacon_srv/internal/beacon/BUILD.bazel
+++ b/go/cs/beacon/BUILD.bazel
@@ -11,8 +11,8 @@ go_library(
         "selection_algo.go",
         "store.go",
     ],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/beacon",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/beacon",
+    visibility = ["//visibility:public"],
     deps = [
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
@@ -44,9 +44,9 @@ go_test(
     data = glob(["testdata/**"]),
     embed = [":go_default_library"],
     deps = [
-        "//go/beacon_srv/internal/beacon/beacondbsqlite:go_default_library",
-        "//go/beacon_srv/internal/beacon/beacondbtest:go_default_library",
-        "//go/beacon_srv/internal/beacon/mock_beacon:go_default_library",
+        "//go/cs/beacon/beacondbsqlite:go_default_library",
+        "//go/cs/beacon/beacondbtest:go_default_library",
+        "//go/cs/beacon/mock_beacon:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/seg:go_default_library",
diff --git a/go/beacon_srv/internal/beacon/beacon.go b/go/cs/beacon/beacon.go
similarity index 100%
rename from go/beacon_srv/internal/beacon/beacon.go
rename to go/cs/beacon/beacon.go
diff --git a/go/beacon_srv/internal/beacon/beacon_test.go b/go/cs/beacon/beacon_test.go
similarity index 100%
rename from go/beacon_srv/internal/beacon/beacon_test.go
rename to go/cs/beacon/beacon_test.go
diff --git a/go/beacon_srv/internal/beacon/beacondbsqlite/BUILD.bazel b/go/cs/beacon/beacondbsqlite/BUILD.bazel
similarity index 74%
rename from go/beacon_srv/internal/beacon/beacondbsqlite/BUILD.bazel
rename to go/cs/beacon/beacondbsqlite/BUILD.bazel
index c48d40755..814f9e988 100644
--- a/go/beacon_srv/internal/beacon/beacondbsqlite/BUILD.bazel
+++ b/go/cs/beacon/beacondbsqlite/BUILD.bazel
@@ -6,10 +6,10 @@ go_library(
         "db.go",
         "schema.go",
     ],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/beacon/beacondbsqlite",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/beacon/beacondbsqlite",
+    visibility = ["//visibility:public"],
     deps = [
-        "//go/beacon_srv/internal/beacon:go_default_library",
+        "//go/cs/beacon:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
@@ -26,8 +26,8 @@ go_test(
     srcs = ["db_test.go"],
     embed = [":go_default_library"],
     deps = [
-        "//go/beacon_srv/internal/beacon:go_default_library",
-        "//go/beacon_srv/internal/beacon/beacondbtest:go_default_library",
+        "//go/cs/beacon:go_default_library",
+        "//go/cs/beacon/beacondbtest:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/xtest:go_default_library",
         "@com_github_golang_mock//gomock:go_default_library",
diff --git a/go/beacon_srv/internal/beacon/beacondbsqlite/db.go b/go/cs/beacon/beacondbsqlite/db.go
similarity index 99%
rename from go/beacon_srv/internal/beacon/beacondbsqlite/db.go
rename to go/cs/beacon/beacondbsqlite/db.go
index 1ba9542da..36bd2972f 100644
--- a/go/beacon_srv/internal/beacon/beacondbsqlite/db.go
+++ b/go/cs/beacon/beacondbsqlite/db.go
@@ -23,7 +23,7 @@ import (
 
 	_ "github.com/mattn/go-sqlite3"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
+	"github.com/scionproto/scion/go/cs/beacon"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
diff --git a/go/beacon_srv/internal/beacon/mock_beacon/BUILD.bazel b/go/cs/beacon/mock_beacon/BUILD.bazel
similarity index 63%
rename from go/beacon_srv/internal/beacon/mock_beacon/BUILD.bazel
rename to go/cs/beacon/mock_beacon/BUILD.bazel
index 111754fd3..4d08e2e13 100644
--- a/go/beacon_srv/internal/beacon/mock_beacon/BUILD.bazel
+++ b/go/cs/beacon/mock_beacon/BUILD.bazel
@@ -3,10 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
 go_library(
     name = "go_default_library",
     srcs = ["beacon.go"],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/beacon/mock_beacon",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/beacon/mock_beacon",
+    visibility = ["//visibility:public"],
     deps = [
-        "//go/beacon_srv/internal/beacon:go_default_library",
+        "//go/cs/beacon:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
diff --git a/go/beacon_srv/internal/beacon/mock_beacon/beacon.go b/go/cs/beacon/mock_beacon/beacon.go
similarity index 98%
rename from go/beacon_srv/internal/beacon/mock_beacon/beacon.go
rename to go/cs/beacon/mock_beacon/beacon.go
index aad1a56a5..2387ee906 100644
--- a/go/beacon_srv/internal/beacon/mock_beacon/beacon.go
+++ b/go/cs/beacon/mock_beacon/beacon.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/scionproto/scion/go/beacon_srv/internal/beacon (interfaces: DB,Transaction)
+// Source: github.com/scionproto/scion/go/cs/beacon (interfaces: DB,Transaction)
 
 // Package mock_beacon is a generated GoMock package.
 package mock_beacon
@@ -7,13 +7,14 @@ package mock_beacon
 import (
 	context "context"
 	sql "database/sql"
+	reflect "reflect"
+	time "time"
+
 	gomock "github.com/golang/mock/gomock"
-	beacon "github.com/scionproto/scion/go/beacon_srv/internal/beacon"
+	beacon "github.com/scionproto/scion/go/cs/beacon"
 	addr "github.com/scionproto/scion/go/lib/addr"
 	common "github.com/scionproto/scion/go/lib/common"
 	path_mgmt "github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
-	reflect "reflect"
-	time "time"
 )
 
 // MockDB is a mock of DB interface
diff --git a/go/beacon_srv/internal/beacon/testdata/HPGCfg_ff00_0_110-69b5.json b/go/cs/beacon/testdata/HPGCfg_ff00_0_110-69b5.json
similarity index 100%
rename from go/beacon_srv/internal/beacon/testdata/HPGCfg_ff00_0_110-69b5.json
rename to go/cs/beacon/testdata/HPGCfg_ff00_0_110-69b5.json
diff --git a/go/beacon_srv/internal/beacon/testdata/HPGCfg_ffaa_0_222-abcd.json b/go/cs/beacon/testdata/HPGCfg_ffaa_0_222-abcd.json
similarity index 100%
rename from go/beacon_srv/internal/beacon/testdata/HPGCfg_ffaa_0_222-abcd.json
rename to go/cs/beacon/testdata/HPGCfg_ffaa_0_222-abcd.json
diff --git a/go/beacon_srv/internal/beacon/testdata/hp_policy.yml b/go/cs/beacon/testdata/hp_policy.yml
similarity index 100%
rename from go/beacon_srv/internal/beacon/testdata/hp_policy.yml
rename to go/cs/beacon/testdata/hp_policy.yml
diff --git a/go/beacon_srv/internal/beacon/testdata/policy.yml b/go/cs/beacon/testdata/policy.yml
similarity index 100%
rename from go/beacon_srv/internal/beacon/testdata/policy.yml
rename to go/cs/beacon/testdata/policy.yml
diff --git a/go/beacon_srv/internal/beacon/testdata/typedPolicy.yml b/go/cs/beacon/testdata/typedPolicy.yml
similarity index 100%
rename from go/beacon_srv/internal/beacon/testdata/typedPolicy.yml
rename to go/cs/beacon/testdata/typedPolicy.yml
diff --git a/go/beacon_srv/internal/beaconing/BUILD.bazel b/go/cs/beaconing/BUILD.bazel
similarity index 80%
rename from go/beacon_srv/internal/beaconing/BUILD.bazel
rename to go/cs/beaconing/BUILD.bazel
index ff3e066c7..3e2e9022e 100644
--- a/go/beacon_srv/internal/beaconing/BUILD.bazel
+++ b/go/cs/beaconing/BUILD.bazel
@@ -13,13 +13,13 @@ go_library(
         "tick.go",
         "util.go",
     ],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/beaconing",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/beaconing",
+    visibility = ["//visibility:public"],
     deps = [
-        "//go/beacon_srv/internal/beacon:go_default_library",
-        "//go/beacon_srv/internal/ifstate:go_default_library",
-        "//go/beacon_srv/internal/metrics:go_default_library",
-        "//go/beacon_srv/internal/onehop:go_default_library",
+        "//go/cs/beacon: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",
@@ -52,10 +52,10 @@ go_test(
     data = glob(["testdata/**"]),
     embed = [":go_default_library"],
     deps = [
-        "//go/beacon_srv/internal/beacon:go_default_library",
-        "//go/beacon_srv/internal/beaconing/mock_beaconing:go_default_library",
-        "//go/beacon_srv/internal/ifstate:go_default_library",
-        "//go/beacon_srv/internal/onehop:go_default_library",
+        "//go/cs/beacon:go_default_library",
+        "//go/cs/beaconing/mock_beaconing:go_default_library",
+        "//go/cs/ifstate: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",
diff --git a/go/beacon_srv/internal/beaconing/doc.go b/go/cs/beaconing/doc.go
similarity index 100%
rename from go/beacon_srv/internal/beaconing/doc.go
rename to go/cs/beaconing/doc.go
diff --git a/go/beacon_srv/internal/beaconing/extender.go b/go/cs/beaconing/extender.go
similarity index 99%
rename from go/beacon_srv/internal/beaconing/extender.go
rename to go/cs/beaconing/extender.go
index c42dc54e9..2f7bb89f3 100644
--- a/go/beacon_srv/internal/beaconing/extender.go
+++ b/go/cs/beaconing/extender.go
@@ -18,7 +18,7 @@ import (
 	"sync"
 	"time"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
+	"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/seg"
diff --git a/go/beacon_srv/internal/beaconing/extender_config.go b/go/cs/beaconing/extender_config.go
similarity index 96%
rename from go/beacon_srv/internal/beaconing/extender_config.go
rename to go/cs/beaconing/extender_config.go
index 797d2091c..d7c5d9168 100644
--- a/go/beacon_srv/internal/beaconing/extender_config.go
+++ b/go/cs/beaconing/extender_config.go
@@ -17,7 +17,7 @@ package beaconing
 import (
 	"hash"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
+	"github.com/scionproto/scion/go/cs/ifstate"
 	"github.com/scionproto/scion/go/lib/infra"
 	"github.com/scionproto/scion/go/lib/serrors"
 	"github.com/scionproto/scion/go/lib/spath"
diff --git a/go/beacon_srv/internal/beaconing/handler.go b/go/cs/beaconing/handler.go
similarity index 97%
rename from go/beacon_srv/internal/beaconing/handler.go
rename to go/cs/beaconing/handler.go
index 682293d75..22539f255 100644
--- a/go/beacon_srv/internal/beaconing/handler.go
+++ b/go/cs/beaconing/handler.go
@@ -17,9 +17,9 @@ package beaconing
 import (
 	"context"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
-	"github.com/scionproto/scion/go/beacon_srv/internal/metrics"
+	"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/seg"
diff --git a/go/beacon_srv/internal/beaconing/mock_beaconing/BUILD.bazel b/go/cs/beaconing/mock_beaconing/BUILD.bazel
similarity index 54%
rename from go/beacon_srv/internal/beaconing/mock_beaconing/BUILD.bazel
rename to go/cs/beaconing/mock_beaconing/BUILD.bazel
index 55108a91a..3d0fd5f1a 100644
--- a/go/beacon_srv/internal/beaconing/mock_beaconing/BUILD.bazel
+++ b/go/cs/beaconing/mock_beaconing/BUILD.bazel
@@ -3,10 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
 go_library(
     name = "go_default_library",
     srcs = ["beaconing.go"],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/beaconing/mock_beaconing",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/beaconing/mock_beaconing",
+    visibility = ["//visibility:public"],
     deps = [
-        "//go/beacon_srv/internal/beacon:go_default_library",
+        "//go/cs/beacon:go_default_library",
         "//go/proto:go_default_library",
         "@com_github_golang_mock//gomock:go_default_library",
     ],
diff --git a/go/beacon_srv/internal/beaconing/mock_beaconing/beaconing.go b/go/cs/beaconing/mock_beaconing/beaconing.go
similarity index 96%
rename from go/beacon_srv/internal/beaconing/mock_beaconing/beaconing.go
rename to go/cs/beaconing/mock_beaconing/beaconing.go
index da0d71d51..413bf8ce4 100644
--- a/go/beacon_srv/internal/beaconing/mock_beaconing/beaconing.go
+++ b/go/cs/beaconing/mock_beaconing/beaconing.go
@@ -1,15 +1,16 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/scionproto/scion/go/beacon_srv/internal/beaconing (interfaces: BeaconInserter,BeaconProvider,SegmentProvider)
+// Source: github.com/scionproto/scion/go/cs/beaconing (interfaces: BeaconInserter,BeaconProvider,SegmentProvider)
 
 // Package mock_beaconing is a generated GoMock package.
 package mock_beaconing
 
 import (
 	context "context"
+	reflect "reflect"
+
 	gomock "github.com/golang/mock/gomock"
-	beacon "github.com/scionproto/scion/go/beacon_srv/internal/beacon"
+	beacon "github.com/scionproto/scion/go/cs/beacon"
 	proto "github.com/scionproto/scion/go/proto"
-	reflect "reflect"
 )
 
 // MockBeaconInserter is a mock of BeaconInserter interface
diff --git a/go/beacon_srv/internal/beaconing/originator.go b/go/cs/beaconing/originator.go
similarity index 96%
rename from go/beacon_srv/internal/beaconing/originator.go
rename to go/cs/beaconing/originator.go
index 74f8dda6e..cc1010f63 100644
--- a/go/beacon_srv/internal/beaconing/originator.go
+++ b/go/cs/beaconing/originator.go
@@ -18,9 +18,9 @@ import (
 	"context"
 	"time"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
-	"github.com/scionproto/scion/go/beacon_srv/internal/metrics"
-	"github.com/scionproto/scion/go/beacon_srv/internal/onehop"
+	"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/common"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/log"
diff --git a/go/beacon_srv/internal/beaconing/propagator.go b/go/cs/beaconing/propagator.go
similarity index 97%
rename from go/beacon_srv/internal/beaconing/propagator.go
rename to go/cs/beaconing/propagator.go
index f935d247b..2236176e8 100644
--- a/go/beacon_srv/internal/beaconing/propagator.go
+++ b/go/cs/beaconing/propagator.go
@@ -19,10 +19,10 @@ import (
 	"sync"
 	"time"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
-	"github.com/scionproto/scion/go/beacon_srv/internal/metrics"
-	"github.com/scionproto/scion/go/beacon_srv/internal/onehop"
+	"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/common"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/log"
diff --git a/go/beacon_srv/internal/beaconing/registrar.go b/go/cs/beaconing/registrar.go
similarity index 98%
rename from go/beacon_srv/internal/beaconing/registrar.go
rename to go/cs/beaconing/registrar.go
index 4c85895b5..cc489d7e0 100644
--- a/go/beacon_srv/internal/beaconing/registrar.go
+++ b/go/cs/beaconing/registrar.go
@@ -20,8 +20,8 @@ import (
 	"sync"
 	"time"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/metrics"
+	"github.com/scionproto/scion/go/cs/beacon"
+	"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"
diff --git a/go/beacon_srv/internal/beaconing/testdata/topology-core.json b/go/cs/beaconing/testdata/topology-core.json
similarity index 100%
rename from go/beacon_srv/internal/beaconing/testdata/topology-core.json
rename to go/cs/beaconing/testdata/topology-core.json
diff --git a/go/beacon_srv/internal/beaconing/testdata/topology.json b/go/cs/beaconing/testdata/topology.json
similarity index 100%
rename from go/beacon_srv/internal/beaconing/testdata/topology.json
rename to go/cs/beaconing/testdata/topology.json
diff --git a/go/beacon_srv/internal/beaconing/tick.go b/go/cs/beaconing/tick.go
similarity index 100%
rename from go/beacon_srv/internal/beaconing/tick.go
rename to go/cs/beaconing/tick.go
diff --git a/go/beacon_srv/internal/beaconing/util.go b/go/cs/beaconing/util.go
similarity index 96%
rename from go/beacon_srv/internal/beaconing/util.go
rename to go/cs/beaconing/util.go
index 1e983e729..78aa1e609 100644
--- a/go/beacon_srv/internal/beaconing/util.go
+++ b/go/cs/beaconing/util.go
@@ -19,8 +19,8 @@ import (
 	"sync"
 	"time"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
-	"github.com/scionproto/scion/go/beacon_srv/internal/onehop"
+	"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"
diff --git a/go/beacon_srv/internal/beaconstorage/BUILD.bazel b/go/cs/beaconstorage/BUILD.bazel
similarity index 77%
rename from go/beacon_srv/internal/beaconstorage/BUILD.bazel
rename to go/cs/beaconstorage/BUILD.bazel
index b4c05ed7f..218c572c3 100644
--- a/go/beacon_srv/internal/beaconstorage/BUILD.bazel
+++ b/go/cs/beaconstorage/BUILD.bazel
@@ -7,11 +7,11 @@ go_library(
         "sample.go",
         "store.go",
     ],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/beaconstorage",
+    importpath = "github.com/scionproto/scion/go/cs/beaconstorage",
     visibility = ["//visibility:public"],
     deps = [
-        "//go/beacon_srv/internal/beacon:go_default_library",
-        "//go/beacon_srv/internal/beacon/beacondbsqlite:go_default_library",
+        "//go/cs/beacon:go_default_library",
+        "//go/cs/beacon/beacondbsqlite:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/config:go_default_library",
diff --git a/go/beacon_srv/internal/beaconstorage/config.go b/go/cs/beaconstorage/config.go
similarity index 97%
rename from go/beacon_srv/internal/beaconstorage/config.go
rename to go/cs/beaconstorage/config.go
index 898e3f03e..0f93e7764 100644
--- a/go/beacon_srv/internal/beaconstorage/config.go
+++ b/go/cs/beaconstorage/config.go
@@ -21,8 +21,8 @@ import (
 	"fmt"
 	"io"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon/beacondbsqlite"
+	"github.com/scionproto/scion/go/cs/beacon"
+	"github.com/scionproto/scion/go/cs/beacon/beacondbsqlite"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/config"
diff --git a/go/beacon_srv/internal/beaconstorage/sample.go b/go/cs/beaconstorage/sample.go
similarity index 100%
rename from go/beacon_srv/internal/beaconstorage/sample.go
rename to go/cs/beaconstorage/sample.go
diff --git a/go/beacon_srv/internal/beaconstorage/store.go b/go/cs/beaconstorage/store.go
similarity index 98%
rename from go/beacon_srv/internal/beaconstorage/store.go
rename to go/cs/beaconstorage/store.go
index 13eab2077..727517fad 100644
--- a/go/beacon_srv/internal/beaconstorage/store.go
+++ b/go/cs/beaconstorage/store.go
@@ -17,7 +17,7 @@ package beaconstorage
 import (
 	"context"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
+	"github.com/scionproto/scion/go/cs/beacon"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
diff --git a/go/cs/config/BUILD.bazel b/go/cs/config/BUILD.bazel
new file mode 100644
index 000000000..5af365106
--- /dev/null
+++ b/go/cs/config/BUILD.bazel
@@ -0,0 +1,42 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = [
+        "bs_sample.go",
+        "config.go",
+        "cs_sample.go",
+        "ps_sample.go",
+    ],
+    importpath = "github.com/scionproto/scion/go/cs/config",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/cs/beaconstorage:go_default_library",
+        "//go/lib/common:go_default_library",
+        "//go/lib/config:go_default_library",
+        "//go/lib/ctrl/path_mgmt:go_default_library",
+        "//go/lib/env:go_default_library",
+        "//go/lib/infra/modules/idiscovery:go_default_library",
+        "//go/lib/pathstorage:go_default_library",
+        "//go/lib/serrors:go_default_library",
+        "//go/lib/truststorage:go_default_library",
+        "//go/lib/util:go_default_library",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["config_test.go"],
+    embed = [":go_default_library"],
+    deps = [
+        "//go/cs/beaconstorage/beaconstoragetest:go_default_library",
+        "//go/lib/ctrl/path_mgmt:go_default_library",
+        "//go/lib/env/envtest:go_default_library",
+        "//go/lib/infra/modules/idiscovery/idiscoverytest:go_default_library",
+        "//go/lib/pathstorage/pathstoragetest:go_default_library",
+        "//go/lib/truststorage/truststoragetest:go_default_library",
+        "//go/lib/util:go_default_library",
+        "@com_github_burntsushi_toml//:go_default_library",
+        "@com_github_stretchr_testify//assert:go_default_library",
+    ],
+)
diff --git a/go/beacon_srv/internal/config/sample.go b/go/cs/config/bs_sample.go
similarity index 96%
rename from go/beacon_srv/internal/config/sample.go
rename to go/cs/config/bs_sample.go
index cd9fd26df..cc987b793 100644
--- a/go/beacon_srv/internal/config/sample.go
+++ b/go/cs/config/bs_sample.go
@@ -14,9 +14,7 @@
 
 package config
 
-const idSample = "bs-1"
-
-const bsconfigSample = `
+const BSSample = `
 # The interval between sending interface keepalives. (default 1s)
 KeepaliveInterval = "1s"
 
@@ -44,7 +42,7 @@ RevTTL = "10s"
 RevOverlap = "5s"
 `
 
-const policiesSample = `
+const PoliciesSample = `
 # Output a sample policy file by providing the -help-policy flag.
 
 # The file path for the propagation policy. In case of the empty string,
diff --git a/go/cs/config/config.go b/go/cs/config/config.go
new file mode 100644
index 000000000..2cf18b7a0
--- /dev/null
+++ b/go/cs/config/config.go
@@ -0,0 +1,386 @@
+// 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 config describes the configuration of the beacon server.
+package config
+
+import (
+	"io"
+	"time"
+
+	"github.com/scionproto/scion/go/cs/beaconstorage"
+	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/config"
+	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
+	"github.com/scionproto/scion/go/lib/env"
+	"github.com/scionproto/scion/go/lib/infra/modules/idiscovery"
+	"github.com/scionproto/scion/go/lib/pathstorage"
+	"github.com/scionproto/scion/go/lib/serrors"
+	"github.com/scionproto/scion/go/lib/truststorage"
+	"github.com/scionproto/scion/go/lib/util"
+)
+
+const (
+	// DefaultKeepaliveInterval is the default interval between sending
+	// interface keepalives.
+	DefaultKeepaliveInterval = time.Second
+	// DefaultKeepaliveTimeout is the timeout indicating how long an interface
+	// can receive no keepalive default until it is considered expired.
+	DefaultKeepaliveTimeout = 3 * time.Second
+	// DefaultOriginationInterval is the default interval between originating
+	// beacons in a core BS.
+	DefaultOriginationInterval = 5 * time.Second
+	// DefaultPropagationInterval is the default interval between propagating beacons.
+	DefaultPropagationInterval = 5 * time.Second
+	// DefaultRegistrationInterval is the default interval between registering segments.
+	DefaultRegistrationInterval = 5 * time.Second
+	// DefaultExpiredCheckInterval is the default interval between checking for
+	// expired interfaces.
+	DefaultExpiredCheckInterval = 200 * time.Millisecond
+	// DefaultRevTTL is the default revocation TTL.
+	DefaultRevTTL = path_mgmt.MinRevTTL
+	// DefaultRevOverlap specifies the default for how long before the expiry of an existing
+	// revocation the revoker can reissue a new revocation.
+	DefaultRevOverlap = DefaultRevTTL / 2
+
+	// LeafReissTime is the default value for CSConf.LeafReissTime. It is set to
+	// the default path segment TTL to provide optimal coverage.
+	LeafReissTime = 6 * time.Hour
+	// IssuerReissTime is the default value for CSConf.IssuerReissTime. It is larger
+	// than the leaf certificate validity period in order to provide optimal coverage.
+	IssuerReissTime = (3*24 + 1) * time.Hour
+	// ReissReqRate is the default interval between two consecutive reissue requests.
+	ReissReqRate = 10 * time.Second
+	// ReissueReqTimeout is the default timeout of a reissue request.
+	ReissueReqTimeout = 5 * time.Second
+)
+
+var (
+	DefaultQueryInterval      = 5 * time.Minute
+	DefaultCryptoSyncInterval = 30 * time.Second
+)
+
+// Error values
+const (
+	ErrKeyConf   common.ErrMsg = "Unable to load KeyConf"
+	ErrCustomers common.ErrMsg = "Unable to load Customers"
+)
+
+var _ config.Config = (*Config)(nil)
+
+// Config is the beacon server configuration.
+type Config struct {
+	General        env.General
+	Features       env.Features
+	Logging        env.Logging
+	Metrics        env.Metrics
+	Tracing        env.Tracing
+	QUIC           env.QUIC         `toml:"quic"`
+	Sciond         env.SciondClient `toml:"sd_client"`
+	TrustDB        truststorage.TrustDBConf
+	BeaconDB       beaconstorage.BeaconDBConf
+	Discovery      idiscovery.Config
+	BS             BSConfig
+	CS             CSConfig
+	PS             PSConfig
+	EnableQUICTest bool
+}
+
+// InitDefaults initializes the default values for all parts of the config.
+func (cfg *Config) InitDefaults() {
+	config.InitAll(
+		&cfg.General,
+		&cfg.Features,
+		&cfg.Logging,
+		&cfg.Metrics,
+		&cfg.Tracing,
+		&cfg.Sciond,
+		&cfg.TrustDB,
+		&cfg.BeaconDB,
+		&cfg.Discovery,
+		&cfg.BS,
+		&cfg.CS,
+		&cfg.PS,
+	)
+}
+
+// Validate validates all parts of the config.
+func (cfg *Config) Validate() error {
+	return config.ValidateAll(
+		&cfg.General,
+		&cfg.Features,
+		&cfg.Logging,
+		&cfg.Metrics,
+		&cfg.Sciond,
+		&cfg.TrustDB,
+		&cfg.BeaconDB,
+		&cfg.Discovery,
+		&cfg.BS,
+		&cfg.CS,
+		&cfg.PS,
+	)
+}
+
+// Sample generates a sample config file for the beacon server.
+func (cfg *Config) Sample(dst io.Writer, path config.Path, _ config.CtxMap) {
+	config.WriteSample(dst, path, config.CtxMap{config.ID: idSample},
+		&cfg.General,
+		&cfg.Features,
+		&cfg.Logging,
+		&cfg.Metrics,
+		&cfg.Tracing,
+		&cfg.QUIC,
+		&cfg.Sciond,
+		&cfg.TrustDB,
+		&cfg.BeaconDB,
+		&cfg.Discovery,
+		&cfg.BS,
+		&cfg.CS,
+		&cfg.PS,
+	)
+}
+
+// ConfigName is the toml key.
+func (cfg *Config) ConfigName() string {
+	return "cs_config"
+}
+
+var _ config.Config = (*BSConfig)(nil)
+
+// BSConfig holds the configuration specific to the beacon server.
+type BSConfig struct {
+	// KeepaliveInterval is the interval between sending interface keepalives.
+	KeepaliveInterval util.DurWrap
+	// KeepaliveTimeout is the timeout indicating how long an interface can
+	// receive no keepalive until it is considered expired.
+	KeepaliveTimeout util.DurWrap
+	// OriginationInterval is the interval between originating beacons in a core BS.
+	OriginationInterval util.DurWrap
+	// PropagationInterval is the interval between propagating beacons.
+	PropagationInterval util.DurWrap
+	// RegistrationInterval is the interval between registering segments.
+	RegistrationInterval util.DurWrap
+	// ExpiredCheckInterval is the interval between checking whether interfaces
+	// have expired and should be revoked.
+	ExpiredCheckInterval util.DurWrap
+	// RevTTL is the revocation TTL. (default 10s)
+	RevTTL util.DurWrap
+	// RevOverlap specifies for how long before the expiry of an existing revocation the revoker
+	// can reissue a new revocation. (default 5s)
+	RevOverlap util.DurWrap
+	// Policies contains the policy files.
+	Policies Policies
+}
+
+// InitDefaults the default values for the durations that are equal to zero.
+func (cfg *BSConfig) InitDefaults() {
+	initDurWrap(&cfg.KeepaliveInterval, DefaultKeepaliveInterval)
+	initDurWrap(&cfg.KeepaliveTimeout, DefaultKeepaliveTimeout)
+	initDurWrap(&cfg.OriginationInterval, DefaultOriginationInterval)
+	initDurWrap(&cfg.PropagationInterval, DefaultPropagationInterval)
+	initDurWrap(&cfg.RegistrationInterval, DefaultRegistrationInterval)
+	initDurWrap(&cfg.ExpiredCheckInterval, DefaultExpiredCheckInterval)
+	initDurWrap(&cfg.RevTTL, DefaultRevTTL)
+	initDurWrap(&cfg.RevOverlap, DefaultRevOverlap)
+}
+
+// Validate validates that all durations are set.
+func (cfg *BSConfig) Validate() error {
+	if cfg.KeepaliveInterval.Duration == 0 {
+		return serrors.New("KeepaliveInterval not set")
+	}
+	if cfg.KeepaliveTimeout.Duration == 0 {
+		return serrors.New("KeepaliveTimeout not set")
+	}
+	if cfg.OriginationInterval.Duration == 0 {
+		return serrors.New("OriginationInterval not set")
+	}
+	if cfg.PropagationInterval.Duration == 0 {
+		return serrors.New("PropagationInterval not set")
+	}
+	if cfg.RegistrationInterval.Duration == 0 {
+		return serrors.New("RegistrationInterval not set")
+	}
+	if cfg.ExpiredCheckInterval.Duration == 0 {
+		return serrors.New("ExpiredCheckInterval not set")
+	}
+	if cfg.RevTTL.Duration == 0 {
+		return serrors.New("RevTTL is not set")
+	}
+	if cfg.RevTTL.Duration < path_mgmt.MinRevTTL {
+		return common.NewBasicError("RevTTL must be equal or greater than MinRevTTL", nil,
+			"MinRevTTL", path_mgmt.MinRevTTL)
+	}
+	if cfg.RevOverlap.Duration == 0 {
+		return serrors.New("RevOverlap not set")
+	}
+	if cfg.RevOverlap.Duration > cfg.RevTTL.Duration {
+		return serrors.New("RevOverlap cannot be greater than RevTTL")
+	}
+	return nil
+}
+
+// Sample generates a sample for the beacon server specific configuration.
+func (cfg *BSConfig) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) {
+	config.WriteString(dst, BSSample)
+	config.WriteSample(dst, path, ctx, &cfg.Policies)
+}
+
+// ConfigName is the toml key for the beacon server specific configuration.
+func (cfg *BSConfig) ConfigName() string {
+	return "bs"
+}
+
+func initDurWrap(w *util.DurWrap, def time.Duration) {
+	if w.Duration == 0 {
+		w.Duration = def
+	}
+}
+
+var _ config.Config = (*CSConfig)(nil)
+
+type CSConfig struct {
+	// LeafReissueLeadTime indicates how long in advance of leaf cert expiration
+	// the reissuance process starts.
+	LeafReissueLeadTime util.DurWrap
+	// IssuerReissueLeadTime indicates how long in advance core cert expiration
+	// the self reissuance process starts.
+	IssuerReissueLeadTime util.DurWrap
+	// ReissueRate is the interval between two consecutive reissue requests.
+	ReissueRate util.DurWrap
+	// ReissueTimeout is the timeout for resissue request.
+	ReissueTimeout util.DurWrap
+	// AutomaticRenewal whether automatic reissuing is enabled.
+	AutomaticRenewal bool
+	// DisableCorePush disables the core pusher task.
+	DisableCorePush bool
+}
+
+func (cfg *CSConfig) InitDefaults() {
+	if cfg.LeafReissueLeadTime.Duration == 0 {
+		cfg.LeafReissueLeadTime.Duration = LeafReissTime
+	}
+	if cfg.IssuerReissueLeadTime.Duration == 0 {
+		cfg.IssuerReissueLeadTime.Duration = IssuerReissTime
+	}
+	if cfg.ReissueRate.Duration == 0 {
+		cfg.ReissueRate.Duration = ReissReqRate
+	}
+	if cfg.ReissueTimeout.Duration == 0 {
+		cfg.ReissueTimeout.Duration = ReissueReqTimeout
+	}
+}
+
+func (cfg *CSConfig) Validate() error {
+	if cfg.LeafReissueLeadTime.Duration == 0 {
+		return serrors.New("LeafReissueLeadTime must not be zero")
+	}
+	if cfg.IssuerReissueLeadTime.Duration == 0 {
+		return serrors.New("IssuerReissueLeadTime must not be zero")
+	}
+	if cfg.ReissueRate.Duration == 0 {
+		return serrors.New("ReissueRate must not be zero")
+	}
+	if cfg.ReissueTimeout.Duration == 0 {
+		return serrors.New("ReissueTimeout must not be zero")
+	}
+	return nil
+}
+
+func (cfg *CSConfig) Sample(dst io.Writer, path config.Path, _ config.CtxMap) {
+	config.WriteString(dst, CSSample)
+}
+
+func (cfg *CSConfig) ConfigName() string {
+	return "cs"
+}
+
+var _ config.Config = (*PSConfig)(nil)
+
+type PSConfig struct {
+	// SegSync enables the "old" replication of down segments between cores,
+	// using SegSync messages.
+	SegSync  bool
+	PathDB   pathstorage.PathDBConf
+	RevCache pathstorage.RevCacheConf
+	// QueryInterval specifies after how much time segments
+	// for a destination should be refetched.
+	QueryInterval util.DurWrap
+	// CryptoSyncInterval specifies the interval of crypto pushes towards
+	// the local CS.
+	CryptoSyncInterval util.DurWrap
+}
+
+func (cfg *PSConfig) InitDefaults() {
+	if cfg.QueryInterval.Duration == 0 {
+		cfg.QueryInterval.Duration = DefaultQueryInterval
+	}
+	if cfg.CryptoSyncInterval.Duration == 0 {
+		cfg.CryptoSyncInterval.Duration = DefaultCryptoSyncInterval
+	}
+	config.InitAll(&cfg.PathDB, &cfg.RevCache)
+}
+
+func (cfg *PSConfig) Validate() error {
+	if cfg.QueryInterval.Duration == 0 {
+		return serrors.New("QueryInterval must not be zero")
+	}
+	return config.ValidateAll(&cfg.PathDB, &cfg.RevCache)
+}
+
+func (cfg *PSConfig) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) {
+	config.WriteString(dst, PSSample)
+	config.WriteSample(dst, path, ctx, &cfg.PathDB, &cfg.RevCache)
+}
+
+func (cfg *PSConfig) ConfigName() string {
+	return "ps"
+}
+
+var _ config.Config = (*Policies)(nil)
+
+// Policies contains the file paths of the policies.
+type Policies struct {
+	config.NoDefaulter
+	config.NoValidator
+	// Propagation contains the file path for the propagation policy. If this
+	// is the empty string, the default policy is used.
+	Propagation string
+	// CoreRegistration contains the file path for the core registration
+	// policy. If this is the empty string, the default policy is used. In a
+	// non-core beacon server, this field is ignored.
+	CoreRegistration string
+	// UpRegistration contains the file path for the up registration policy. If
+	// this is the empty string, the default policy is used. In a core beacon
+	// server, this field is ignored.
+	UpRegistration string
+	// DownRegistration contains the file path for the down registration policy.
+	// If this is the empty string, the default policy is used. In a core beacon
+	// server, this field is ignored.
+	DownRegistration string
+	// HiddenPathRegistration contains the file path for the hidden path registration policy
+	// and the corresponding hidden path groups.
+	// If this is the empty string, no hidden path functionality is used.
+	HiddenPathRegistration string
+}
+
+// Sample generates a sample for the beacon server specific configuration.
+func (cfg *Policies) Sample(dst io.Writer, _ config.Path, _ config.CtxMap) {
+	config.WriteString(dst, PoliciesSample)
+}
+
+// ConfigName is the toml key for the beacon server specific configuration.
+func (cfg *Policies) ConfigName() string {
+	return "policies"
+}
diff --git a/go/cert_srv/internal/config/sample.go b/go/cs/config/cs_sample.go
similarity index 98%
rename from go/cert_srv/internal/config/sample.go
rename to go/cs/config/cs_sample.go
index e60738d96..b36d6dcc3 100644
--- a/go/cert_srv/internal/config/sample.go
+++ b/go/cs/config/cs_sample.go
@@ -16,7 +16,7 @@ package config
 
 const idSample = "cs-1"
 
-const csconfigSample = `
+const CSSample = `
 # Time between starting reissue requests and leaf cert expiration. If not
 # specified, this is set to default PathSegmentTTL. (default 6h)
 LeafReissueLeadTime = "6h"
diff --git a/go/path_srv/internal/config/sample.go b/go/cs/config/ps_sample.go
similarity index 95%
rename from go/path_srv/internal/config/sample.go
rename to go/cs/config/ps_sample.go
index e00253f40..72ea14783 100644
--- a/go/path_srv/internal/config/sample.go
+++ b/go/cs/config/ps_sample.go
@@ -14,9 +14,7 @@
 
 package config
 
-const idSample = "ps-1"
-
-const psSample = `
+const PSSample = `
 # Enable the "old" replication of down segments between cores using SegSync
 # messages. (default false)
 SegSync = false
diff --git a/go/path_srv/internal/handlers/BUILD.bazel b/go/cs/handlers/BUILD.bazel
similarity index 88%
rename from go/path_srv/internal/handlers/BUILD.bazel
rename to go/cs/handlers/BUILD.bazel
index d9bb0fb10..56d90ebfc 100644
--- a/go/path_srv/internal/handlers/BUILD.bazel
+++ b/go/cs/handlers/BUILD.bazel
@@ -1,5 +1,4 @@
 load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-load("@com_github_jmhodges_bazel_gomock//:gomock.bzl", "gomock")
 
 go_library(
     name = "go_default_library",
@@ -10,9 +9,10 @@ go_library(
         "segrevoc.go",
         "segsync.go",
     ],
-    importpath = "github.com/scionproto/scion/go/path_srv/internal/handlers",
-    visibility = ["//go/path_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/handlers",
+    visibility = ["//visibility:public"],
     deps = [
+        "//go/cs/metrics:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
@@ -28,7 +28,6 @@ go_library(
         "//go/lib/revcache:go_default_library",
         "//go/lib/snet:go_default_library",
         "//go/lib/topology:go_default_library",
-        "//go/path_srv/internal/metrics:go_default_library",
         "//go/proto:go_default_library",
         "@com_github_opentracing_opentracing_go//:go_default_library",
     ],
diff --git a/go/path_srv/internal/handlers/common.go b/go/cs/handlers/common.go
similarity index 100%
rename from go/path_srv/internal/handlers/common.go
rename to go/cs/handlers/common.go
diff --git a/go/path_srv/internal/handlers/common_test.go b/go/cs/handlers/common_test.go
similarity index 100%
rename from go/path_srv/internal/handlers/common_test.go
rename to go/cs/handlers/common_test.go
diff --git a/go/path_srv/internal/handlers/log.go b/go/cs/handlers/log.go
similarity index 100%
rename from go/path_srv/internal/handlers/log.go
rename to go/cs/handlers/log.go
diff --git a/go/path_srv/internal/handlers/segreg.go b/go/cs/handlers/segreg.go
similarity index 98%
rename from go/path_srv/internal/handlers/segreg.go
rename to go/cs/handlers/segreg.go
index 2ea52203c..e6c3046a8 100644
--- a/go/path_srv/internal/handlers/segreg.go
+++ b/go/cs/handlers/segreg.go
@@ -15,6 +15,7 @@
 package handlers
 
 import (
+	"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"
@@ -23,7 +24,6 @@ import (
 	"github.com/scionproto/scion/go/lib/infra/modules/seghandler"
 	"github.com/scionproto/scion/go/lib/log"
 	"github.com/scionproto/scion/go/lib/snet"
-	"github.com/scionproto/scion/go/path_srv/internal/metrics"
 	"github.com/scionproto/scion/go/proto"
 )
 
diff --git a/go/path_srv/internal/handlers/segrevoc.go b/go/cs/handlers/segrevoc.go
similarity index 87%
rename from go/path_srv/internal/handlers/segrevoc.go
rename to go/cs/handlers/segrevoc.go
index 47339727e..481e912b5 100644
--- a/go/path_srv/internal/handlers/segrevoc.go
+++ b/go/cs/handlers/segrevoc.go
@@ -15,6 +15,7 @@
 package handlers
 
 import (
+	"github.com/scionproto/scion/go/cs/metrics"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
 	"github.com/scionproto/scion/go/lib/infra"
@@ -22,7 +23,6 @@ import (
 	"github.com/scionproto/scion/go/lib/infra/modules/segfetcher"
 	"github.com/scionproto/scion/go/lib/infra/modules/segverifier"
 	"github.com/scionproto/scion/go/lib/log"
-	"github.com/scionproto/scion/go/path_srv/internal/metrics"
 	"github.com/scionproto/scion/go/proto"
 )
 
@@ -47,7 +47,7 @@ func NewRevocHandler(args HandlerArgs) infra.Handler {
 func (h *revocHandler) Handle() *infra.HandlerResult {
 	ctx := h.request.Context()
 	logger := log.FromCtx(ctx).New("from", h.request.Peer)
-	labels := metrics.RevocationLabels{
+	labels := metrics.PSRevocationLabels{
 		Result: metrics.ErrInternal,
 		Src:    metrics.RevSrcNotification,
 	}
@@ -55,13 +55,13 @@ func (h *revocHandler) Handle() *infra.HandlerResult {
 	if !ok {
 		logger.Error("[revocHandler] wrong message type, expected path_mgmt.SignedRevInfo",
 			"msg", h.request.Message, "type", common.TypeOf(h.request.Message))
-		metrics.Revocation.Count(labels).Inc()
+		metrics.PSRevocation.Count(labels).Inc()
 		return infra.MetricsErrInternal
 	}
 	rw, ok := infra.ResponseWriterFromContext(ctx)
 	if !ok {
 		logger.Error("[revocHandler] Unable to service request, no Messenger found")
-		metrics.Revocation.Count(labels).Inc()
+		metrics.PSRevocation.Count(labels).Inc()
 		return infra.MetricsErrInternal
 	}
 	logger = logger.New("signer", revocation.Sign.Src)
@@ -71,7 +71,7 @@ func (h *revocHandler) Handle() *infra.HandlerResult {
 	if err != nil {
 		logger.Warn("[revocHandler] Couldn't parse revocation", "err", err)
 		sendAck(proto.Ack_ErrCode_reject, messenger.AckRejectFailedToParse)
-		metrics.Revocation.Count(labels.WithResult(metrics.ErrParse)).Inc()
+		metrics.PSRevocation.Count(labels.WithResult(metrics.ErrParse)).Inc()
 		return infra.MetricsErrInvalid
 	}
 	logger = logger.New("revInfo", revInfo)
@@ -81,17 +81,17 @@ func (h *revocHandler) Handle() *infra.HandlerResult {
 	if err != nil {
 		logger.Warn("Couldn't verify revocation", "err", err)
 		sendAck(proto.Ack_ErrCode_reject, messenger.AckRejectFailedToVerify)
-		metrics.Revocation.Count(labels.WithResult(metrics.ErrCrypto)).Inc()
+		metrics.PSRevocation.Count(labels.WithResult(metrics.ErrCrypto)).Inc()
 		return infra.MetricsErrInvalid
 	}
 	_, err = h.revCache.Insert(ctx, revocation)
 	if err != nil {
 		logger.Error("Failed to insert revInfo", "err", err)
 		sendAck(proto.Ack_ErrCode_retry, messenger.AckRetryDBError)
-		metrics.Revocation.Count(labels.WithResult(metrics.ErrDB)).Inc()
+		metrics.PSRevocation.Count(labels.WithResult(metrics.ErrDB)).Inc()
 		return infra.MetricsErrRevCache(err)
 	}
 	sendAck(proto.Ack_ErrCode_ok, "")
-	metrics.Revocation.Count(labels.WithResult(metrics.OkSuccess)).Inc()
+	metrics.PSRevocation.Count(labels.WithResult(metrics.OkSuccess)).Inc()
 	return infra.MetricsResultOk
 }
diff --git a/go/path_srv/internal/handlers/segsync.go b/go/cs/handlers/segsync.go
similarity index 98%
rename from go/path_srv/internal/handlers/segsync.go
rename to go/cs/handlers/segsync.go
index 7d454453d..d61618fe7 100644
--- a/go/path_srv/internal/handlers/segsync.go
+++ b/go/cs/handlers/segsync.go
@@ -15,6 +15,7 @@
 package handlers
 
 import (
+	"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"
@@ -23,7 +24,6 @@ import (
 	"github.com/scionproto/scion/go/lib/infra/modules/seghandler"
 	"github.com/scionproto/scion/go/lib/log"
 	"github.com/scionproto/scion/go/lib/snet"
-	"github.com/scionproto/scion/go/path_srv/internal/metrics"
 	"github.com/scionproto/scion/go/proto"
 )
 
diff --git a/go/path_srv/internal/handlers/testdata/topology_as1_132.json b/go/cs/handlers/testdata/topology_as1_132.json
similarity index 100%
rename from go/path_srv/internal/handlers/testdata/topology_as1_132.json
rename to go/cs/handlers/testdata/topology_as1_132.json
diff --git a/go/path_srv/internal/handlers/testdata/topology_as2_211.json b/go/cs/handlers/testdata/topology_as2_211.json
similarity index 100%
rename from go/path_srv/internal/handlers/testdata/topology_as2_211.json
rename to go/cs/handlers/testdata/topology_as2_211.json
diff --git a/go/path_srv/internal/handlers/testdata/topology_as2_222.json b/go/cs/handlers/testdata/topology_as2_222.json
similarity index 100%
rename from go/path_srv/internal/handlers/testdata/topology_as2_222.json
rename to go/cs/handlers/testdata/topology_as2_222.json
diff --git a/go/beacon_srv/internal/ifstate/BUILD.bazel b/go/cs/ifstate/BUILD.bazel
similarity index 88%
rename from go/beacon_srv/internal/ifstate/BUILD.bazel
rename to go/cs/ifstate/BUILD.bazel
index c8849f131..3a0a334b2 100644
--- a/go/beacon_srv/internal/ifstate/BUILD.bazel
+++ b/go/cs/ifstate/BUILD.bazel
@@ -4,16 +4,17 @@ go_library(
     name = "go_default_library",
     srcs = [
         "doc.go",
+        "export_state.go",
         "handler.go",
         "ifstate.go",
         "metrics.go",
         "pusher.go",
         "revoker.go",
     ],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/ifstate",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/ifstate",
+    visibility = ["//visibility:public"],
     deps = [
-        "//go/beacon_srv/internal/metrics:go_default_library",
+        "//go/cs/metrics:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
@@ -33,7 +34,6 @@ go_library(
 go_test(
     name = "go_default_test",
     srcs = [
-        "export_test.go",
         "handler_test.go",
         "ifstate_test.go",
         "pusher_test.go",
@@ -42,7 +42,7 @@ go_test(
     data = glob(["testdata/**"]),
     embed = [":go_default_library"],
     deps = [
-        "//go/beacon_srv/internal/ifstate/mock_ifstate:go_default_library",
+        "//go/cs/ifstate/mock_ifstate:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl:go_default_library",
diff --git a/go/beacon_srv/internal/ifstate/doc.go b/go/cs/ifstate/doc.go
similarity index 100%
rename from go/beacon_srv/internal/ifstate/doc.go
rename to go/cs/ifstate/doc.go
diff --git a/go/beacon_srv/internal/ifstate/export_test.go b/go/cs/ifstate/export_state.go
similarity index 100%
rename from go/beacon_srv/internal/ifstate/export_test.go
rename to go/cs/ifstate/export_state.go
diff --git a/go/beacon_srv/internal/ifstate/handler.go b/go/cs/ifstate/handler.go
similarity index 100%
rename from go/beacon_srv/internal/ifstate/handler.go
rename to go/cs/ifstate/handler.go
diff --git a/go/beacon_srv/internal/ifstate/handler_test.go b/go/cs/ifstate/handler_test.go
similarity index 100%
rename from go/beacon_srv/internal/ifstate/handler_test.go
rename to go/cs/ifstate/handler_test.go
diff --git a/go/beacon_srv/internal/ifstate/ifstate.go b/go/cs/ifstate/ifstate.go
similarity index 100%
rename from go/beacon_srv/internal/ifstate/ifstate.go
rename to go/cs/ifstate/ifstate.go
diff --git a/go/beacon_srv/internal/ifstate/ifstate_test.go b/go/cs/ifstate/ifstate_test.go
similarity index 100%
rename from go/beacon_srv/internal/ifstate/ifstate_test.go
rename to go/cs/ifstate/ifstate_test.go
diff --git a/go/beacon_srv/internal/ifstate/metrics.go b/go/cs/ifstate/metrics.go
similarity index 96%
rename from go/beacon_srv/internal/ifstate/metrics.go
rename to go/cs/ifstate/metrics.go
index af5b88ea1..699954d67 100644
--- a/go/beacon_srv/internal/ifstate/metrics.go
+++ b/go/cs/ifstate/metrics.go
@@ -17,7 +17,7 @@ package ifstate
 import (
 	"github.com/prometheus/client_golang/prometheus"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/metrics"
+	"github.com/scionproto/scion/go/cs/metrics"
 )
 
 var _ prometheus.Collector = (*Collector)(nil)
diff --git a/go/beacon_srv/internal/ifstate/mock_ifstate/BUILD.bazel b/go/cs/ifstate/mock_ifstate/BUILD.bazel
similarity index 64%
rename from go/beacon_srv/internal/ifstate/mock_ifstate/BUILD.bazel
rename to go/cs/ifstate/mock_ifstate/BUILD.bazel
index 8b72555a9..eee855a7d 100644
--- a/go/beacon_srv/internal/ifstate/mock_ifstate/BUILD.bazel
+++ b/go/cs/ifstate/mock_ifstate/BUILD.bazel
@@ -3,8 +3,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
 go_library(
     name = "go_default_library",
     srcs = ["ifstate.go"],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/ifstate/mock_ifstate",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/ifstate/mock_ifstate",
+    visibility = ["//visibility:public"],
     deps = [
         "//go/lib/ctrl/path_mgmt:go_default_library",
         "@com_github_golang_mock//gomock:go_default_library",
diff --git a/go/beacon_srv/internal/ifstate/mock_ifstate/ifstate.go b/go/cs/ifstate/mock_ifstate/ifstate.go
similarity index 94%
rename from go/beacon_srv/internal/ifstate/mock_ifstate/ifstate.go
rename to go/cs/ifstate/mock_ifstate/ifstate.go
index 5debd320d..258dda991 100644
--- a/go/beacon_srv/internal/ifstate/mock_ifstate/ifstate.go
+++ b/go/cs/ifstate/mock_ifstate/ifstate.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/scionproto/scion/go/beacon_srv/internal/ifstate (interfaces: RevInserter)
+// Source: github.com/scionproto/scion/go/cs/ifstate (interfaces: RevInserter)
 
 // Package mock_ifstate is a generated GoMock package.
 package mock_ifstate
diff --git a/go/beacon_srv/internal/ifstate/pusher.go b/go/cs/ifstate/pusher.go
similarity index 100%
rename from go/beacon_srv/internal/ifstate/pusher.go
rename to go/cs/ifstate/pusher.go
diff --git a/go/beacon_srv/internal/ifstate/pusher_test.go b/go/cs/ifstate/pusher_test.go
similarity index 100%
rename from go/beacon_srv/internal/ifstate/pusher_test.go
rename to go/cs/ifstate/pusher_test.go
diff --git a/go/beacon_srv/internal/ifstate/revoker.go b/go/cs/ifstate/revoker.go
similarity index 99%
rename from go/beacon_srv/internal/ifstate/revoker.go
rename to go/cs/ifstate/revoker.go
index 63cf3a26e..399497146 100644
--- a/go/beacon_srv/internal/ifstate/revoker.go
+++ b/go/cs/ifstate/revoker.go
@@ -20,7 +20,7 @@ import (
 	"sync"
 	"time"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/metrics"
+	"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"
diff --git a/go/beacon_srv/internal/ifstate/testdata/topology.json b/go/cs/ifstate/testdata/topology.json
similarity index 100%
rename from go/beacon_srv/internal/ifstate/testdata/topology.json
rename to go/cs/ifstate/testdata/topology.json
diff --git a/go/beacon_srv/internal/keepalive/BUILD.bazel b/go/cs/keepalive/BUILD.bazel
similarity index 75%
rename from go/beacon_srv/internal/keepalive/BUILD.bazel
rename to go/cs/keepalive/BUILD.bazel
index 7ee76d1a7..50d8c8ac6 100644
--- a/go/beacon_srv/internal/keepalive/BUILD.bazel
+++ b/go/cs/keepalive/BUILD.bazel
@@ -7,12 +7,12 @@ go_library(
         "handler.go",
         "sender.go",
     ],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/keepalive",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/keepalive",
+    visibility = ["//visibility:public"],
     deps = [
-        "//go/beacon_srv/internal/ifstate:go_default_library",
-        "//go/beacon_srv/internal/metrics:go_default_library",
-        "//go/beacon_srv/internal/onehop: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",
@@ -34,9 +34,9 @@ go_test(
     data = glob(["testdata/**"]),
     embed = [":go_default_library"],
     deps = [
-        "//go/beacon_srv/internal/ifstate:go_default_library",
-        "//go/beacon_srv/internal/keepalive/mock_keepalive:go_default_library",
-        "//go/beacon_srv/internal/onehop:go_default_library",
+        "//go/cs/ifstate:go_default_library",
+        "//go/cs/keepalive/mock_keepalive:go_default_library",
+        "//go/cs/onehop:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl:go_default_library",
         "//go/lib/ctrl/ifid:go_default_library",
diff --git a/go/beacon_srv/internal/keepalive/doc.go b/go/cs/keepalive/doc.go
similarity index 100%
rename from go/beacon_srv/internal/keepalive/doc.go
rename to go/cs/keepalive/doc.go
diff --git a/go/beacon_srv/internal/keepalive/handler.go b/go/cs/keepalive/handler.go
similarity index 97%
rename from go/beacon_srv/internal/keepalive/handler.go
rename to go/cs/keepalive/handler.go
index 7c47ecc6a..f9b735cb2 100644
--- a/go/beacon_srv/internal/keepalive/handler.go
+++ b/go/cs/keepalive/handler.go
@@ -18,8 +18,8 @@ import (
 	"context"
 	"time"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
-	"github.com/scionproto/scion/go/beacon_srv/internal/metrics"
+	"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/ifid"
diff --git a/go/beacon_srv/internal/keepalive/mock_keepalive/BUILD.bazel b/go/cs/keepalive/mock_keepalive/BUILD.bazel
similarity index 66%
rename from go/beacon_srv/internal/keepalive/mock_keepalive/BUILD.bazel
rename to go/cs/keepalive/mock_keepalive/BUILD.bazel
index f41cd5ab5..1110c5853 100644
--- a/go/beacon_srv/internal/keepalive/mock_keepalive/BUILD.bazel
+++ b/go/cs/keepalive/mock_keepalive/BUILD.bazel
@@ -3,8 +3,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
 go_library(
     name = "go_default_library",
     srcs = ["keepalive.go"],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/keepalive/mock_keepalive",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/keepalive/mock_keepalive",
+    visibility = ["//visibility:public"],
     deps = [
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
diff --git a/go/beacon_srv/internal/keepalive/mock_keepalive/keepalive.go b/go/cs/keepalive/mock_keepalive/keepalive.go
similarity index 96%
rename from go/beacon_srv/internal/keepalive/mock_keepalive/keepalive.go
rename to go/cs/keepalive/mock_keepalive/keepalive.go
index 1deb1602a..03ff3dff3 100644
--- a/go/beacon_srv/internal/keepalive/mock_keepalive/keepalive.go
+++ b/go/cs/keepalive/mock_keepalive/keepalive.go
@@ -1,15 +1,16 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/scionproto/scion/go/beacon_srv/internal/keepalive (interfaces: IfStatePusher,RevDropper)
+// Source: github.com/scionproto/scion/go/cs/keepalive (interfaces: IfStatePusher,RevDropper)
 
 // Package mock_keepalive is a generated GoMock package.
 package mock_keepalive
 
 import (
 	context "context"
+	reflect "reflect"
+
 	gomock "github.com/golang/mock/gomock"
 	addr "github.com/scionproto/scion/go/lib/addr"
 	common "github.com/scionproto/scion/go/lib/common"
-	reflect "reflect"
 )
 
 // MockIfStatePusher is a mock of IfStatePusher interface
diff --git a/go/beacon_srv/internal/keepalive/sender.go b/go/cs/keepalive/sender.go
similarity index 95%
rename from go/beacon_srv/internal/keepalive/sender.go
rename to go/cs/keepalive/sender.go
index ba4d6dc50..5c39a6f1d 100644
--- a/go/beacon_srv/internal/keepalive/sender.go
+++ b/go/cs/keepalive/sender.go
@@ -18,8 +18,8 @@ import (
 	"context"
 	"time"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/metrics"
-	"github.com/scionproto/scion/go/beacon_srv/internal/onehop"
+	"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"
diff --git a/go/beacon_srv/internal/keepalive/testdata/topology.json b/go/cs/keepalive/testdata/topology.json
similarity index 100%
rename from go/beacon_srv/internal/keepalive/testdata/topology.json
rename to go/cs/keepalive/testdata/topology.json
diff --git a/go/cs/main.go b/go/cs/main.go
new file mode 100644
index 000000000..79df082fd
--- /dev/null
+++ b/go/cs/main.go
@@ -0,0 +1,796 @@
+// 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.
+
+// The beacon server implementation.
+package main
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"hash"
+	"net"
+	"os"
+	"path/filepath"
+	"sync"
+	"time"
+
+	"github.com/BurntSushi/toml"
+	"github.com/opentracing/opentracing-go"
+	"github.com/prometheus/client_golang/prometheus"
+	"gopkg.in/yaml.v2"
+
+	"github.com/scionproto/scion/go/cs/beacon"
+	"github.com/scionproto/scion/go/cs/beaconing"
+	"github.com/scionproto/scion/go/cs/beaconstorage"
+	"github.com/scionproto/scion/go/cs/config"
+	"github.com/scionproto/scion/go/cs/handlers"
+	"github.com/scionproto/scion/go/cs/ifstate"
+	"github.com/scionproto/scion/go/cs/keepalive"
+	"github.com/scionproto/scion/go/cs/onehop"
+	"github.com/scionproto/scion/go/cs/revocation"
+	"github.com/scionproto/scion/go/cs/segreq"
+	"github.com/scionproto/scion/go/cs/segsyncer"
+	"github.com/scionproto/scion/go/lib/addr"
+	"github.com/scionproto/scion/go/lib/common"
+	"github.com/scionproto/scion/go/lib/discovery"
+	"github.com/scionproto/scion/go/lib/env"
+	"github.com/scionproto/scion/go/lib/fatal"
+	"github.com/scionproto/scion/go/lib/infra"
+	"github.com/scionproto/scion/go/lib/infra/infraenv"
+	"github.com/scionproto/scion/go/lib/infra/messenger"
+	"github.com/scionproto/scion/go/lib/infra/modules/idiscovery"
+	"github.com/scionproto/scion/go/lib/infra/modules/itopo"
+	"github.com/scionproto/scion/go/lib/infra/modules/trust"
+	"github.com/scionproto/scion/go/lib/keyconf"
+	"github.com/scionproto/scion/go/lib/log"
+	"github.com/scionproto/scion/go/lib/pathdb"
+	"github.com/scionproto/scion/go/lib/pathstorage"
+	"github.com/scionproto/scion/go/lib/periodic"
+	"github.com/scionproto/scion/go/lib/prom"
+	"github.com/scionproto/scion/go/lib/revcache"
+	"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/sock/reliable"
+	"github.com/scionproto/scion/go/lib/sock/reliable/reconnect"
+	"github.com/scionproto/scion/go/lib/spath"
+	"github.com/scionproto/scion/go/lib/topology"
+	"github.com/scionproto/scion/go/proto"
+)
+
+var (
+	cfg config.Config
+
+	intfs *ifstate.Interfaces
+	tasks *periodicTasks
+
+	helpPolicy bool
+)
+
+func init() {
+	flag.Usage = env.Usage
+}
+
+func main() {
+	os.Exit(realMain())
+}
+
+func realMain() int {
+	fatal.Init()
+	env.AddFlags()
+	flag.BoolVar(&helpPolicy, "help-policy", false, "Output sample policy file.")
+	flag.Parse()
+	if v, ok := checkFlags(&cfg); !ok {
+		return v
+	}
+	if err := setupBasic(); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		return 1
+	}
+	defer log.Flush()
+	defer env.LogAppStopped(common.CPService, cfg.General.ID)
+	defer log.LogPanicAndExit()
+	if err := setup(); err != nil {
+		log.Crit("Setup failed", "err", err)
+		return 1
+	}
+	pathDB, revCache, err := pathstorage.NewPathStorage(cfg.PS.PathDB, cfg.PS.RevCache)
+	if err != nil {
+		log.Crit("Unable to initialize path storage", "err", err)
+		return 1
+	}
+	defer revCache.Close()
+	pathDB = pathdb.WithMetrics("std", pathDB)
+	defer pathDB.Close()
+
+	topo := itopo.Get()
+	// Check that this process ID is present for all services it implements
+	if !topo.Exists(addr.SvcBS, cfg.General.ID) {
+		log.Crit("Unable to find topo address for BS module")
+		return 1
+	}
+	if !topo.Exists(addr.SvcCS, cfg.General.ID) {
+		log.Crit("Unable to find topo address for CS module")
+		return 1
+	}
+	if !topo.Exists(addr.SvcCS, cfg.General.ID) {
+		log.Crit("Unable to find topo address for PS module")
+		return 1
+	}
+
+	tracer, trCloser, err := cfg.Tracing.NewTracer(cfg.General.ID)
+	if err != nil {
+		log.Crit("Unable to create tracer", "err", err)
+		return 1
+	}
+	defer trCloser.Close()
+	opentracing.SetGlobalTracer(tracer)
+
+	// TODO(scrye): This needs to be replaced with what the PS is using to eliminate
+	// the need for SCIOND.
+	router, err := infraenv.NewRouter(topo.IA(), cfg.Sciond)
+	if err != nil {
+		log.Crit("Unable to initialize path router", "err", err)
+		return 1
+	}
+
+	nc := infraenv.NetworkConfig{
+		IA: topo.IA(),
+		// Inherit addresses from BS; they should all match though
+		// TODO(scrye): add check for addresses
+		Public:                topo.PublicAddress(addr.SvcBS, cfg.General.ID),
+		SVC:                   addr.SvcWildcard,
+		ReconnectToDispatcher: cfg.General.ReconnectToDispatcher,
+		QUIC: infraenv.QUIC{
+			Address:  cfg.QUIC.Address,
+			CertFile: cfg.QUIC.CertFile,
+			KeyFile:  cfg.QUIC.KeyFile,
+		},
+		SVCResolutionFraction: cfg.QUIC.ResolutionFraction,
+		Router:                router,
+		SVCRouter:             messenger.NewSVCRouter(itopo.Provider()),
+	}
+	msgr, err := nc.Messenger()
+	if err != nil {
+		log.Crit(infraenv.ErrAppUnableToInitMessenger.Error(), "err", err)
+		return 1
+	}
+	defer msgr.CloseServer()
+
+	trustDB, err := cfg.TrustDB.New()
+	if err != nil {
+		log.Crit("Error initializing trust database", "err", err)
+		return 1
+	}
+	defer trustDB.Close()
+	inserter := trust.DefaultInserter{
+		BaseInserter: trust.BaseInserter{DB: trustDB},
+	}
+	provider := trust.Provider{
+		DB:       trustDB,
+		Recurser: trust.ASLocalRecurser{IA: topo.IA()},
+		Resolver: trust.DefaultResolver{
+			DB:       trustDB,
+			Inserter: inserter,
+			RPC:      trust.DefaultRPC{Msgr: msgr},
+		},
+		Router: trust.AuthRouter{
+			ISD:    topo.IA().I,
+			Router: router,
+			DB:     trustDB,
+		},
+	}
+	trustStore := trust.Store{
+		Inspector:      trust.DefaultInspector{Provider: provider},
+		CryptoProvider: provider,
+		Inserter:       inserter,
+		DB:             trustDB,
+	}
+	certsDir := filepath.Join(cfg.General.ConfigDir, "certs")
+	if err = trustStore.LoadCryptoMaterial(context.Background(), certsDir); err != nil {
+		log.Crit("Error loading crypto material", "err", err)
+		return 1
+	}
+	gen := trust.SignerGen{
+		IA: topo.IA(),
+		KeyRing: keyconf.LoadingRing{
+			Dir: filepath.Join(cfg.General.ConfigDir, "keys"),
+			IA:  topo.IA(),
+		},
+		Provider: trustStore,
+	}
+	signer, err := gen.Signer(context.Background())
+	if err != nil {
+		log.Crit("Error initializing signer", "err", err)
+		return 1
+	}
+
+	beaconStore, err := loadStore(topo.Core(), topo.IA(), cfg)
+	if err != nil {
+		log.Crit("Unable to open beacon store", "err", err)
+		return 1
+	}
+	defer beaconStore.Close()
+	intfs = ifstate.NewInterfaces(topo.IFInfoMap(), ifstate.Config{})
+	prometheus.MustRegister(ifstate.NewCollector(intfs))
+	msgr.AddHandler(infra.ChainRequest, trustStore.NewChainReqHandler())
+	msgr.AddHandler(infra.TRCRequest, trustStore.NewTRCReqHandler())
+	msgr.AddHandler(infra.Chain, trustStore.NewChainPushHandler())
+	msgr.AddHandler(infra.TRC, trustStore.NewTRCPushHandler())
+	msgr.AddHandler(infra.IfStateReq, ifstate.NewHandler(intfs))
+	msgr.AddHandler(infra.Seg, beaconing.NewHandler(topo.IA(), intfs, beaconStore,
+		trust.NewVerifier(trustStore)))
+	msgr.AddHandler(infra.IfId, keepalive.NewHandler(topo.IA(), intfs,
+		keepalive.StateChangeTasks{
+			RevDropper: beaconStore,
+			IfStatePusher: ifstate.PusherConf{
+				Intfs:        intfs,
+				Msgr:         msgr,
+				TopoProvider: itopo.Provider(),
+			}.New(),
+		}),
+	)
+	args := handlers.HandlerArgs{
+		PathDB:          pathDB,
+		RevCache:        revCache,
+		ASInspector:     trustStore,
+		VerifierFactory: verificationFactory{Provider: trustStore},
+		QueryInterval:   cfg.PS.QueryInterval.Duration,
+		IA:              topo.IA(),
+		TopoProvider:    itopo.Provider(),
+		SegRequestAPI:   msgr,
+	}
+	msgr.AddHandler(infra.SegRequest, segreq.NewHandler(args))
+	msgr.AddHandler(infra.SegReg, handlers.NewSegRegHandler(args))
+	if cfg.PS.SegSync && topo.Core() {
+		// Old down segment sync mechanism
+		msgr.AddHandler(infra.SegSync, handlers.NewSyncHandler(args))
+	}
+	msgr.AddHandler(infra.SignedRev, &chainedHandler{
+		handlers: []infra.Handler{
+			handlers.NewRevocHandler(args),
+			revocation.NewHandler(beaconStore, trust.NewVerifier(trustStore), 5*time.Second),
+		},
+	})
+
+	cfg.Metrics.StartPrometheus()
+	go func() {
+		defer log.LogPanicAndExit()
+		msgr.ListenAndServe()
+	}()
+
+	dispatcherService := reliable.NewDispatcher("")
+	if cfg.General.ReconnectToDispatcher {
+		dispatcherService = reconnect.NewDispatcherService(dispatcherService)
+	}
+	pktDisp := &snet.DefaultPacketDispatcherService{
+		Dispatcher: dispatcherService,
+	}
+	// We do not need to drain the connection, since the src address is spoofed
+	// to contain the topo address.
+	a := topo.PublicAddress(addr.SvcBS, cfg.General.ID)
+	ohpAddress := &net.UDPAddr{
+		IP: append(a.IP[:0:0], a.IP...), Port: 0,
+	}
+	conn, _, err := pktDisp.Register(context.Background(), topo.IA(), ohpAddress, addr.SvcNone)
+	if err != nil {
+		log.Crit("Unable to create SCION packet conn", "err", err)
+		return 1
+	}
+	tasks = &periodicTasks{
+		args:         args,
+		intfs:        intfs,
+		conn:         conn.(*snet.SCIONPacketConn),
+		trustStore:   trustStore,
+		trustDB:      trustDB,
+		store:        beaconStore,
+		msgr:         msgr,
+		topoProvider: itopo.Provider(),
+		addressRewriter: nc.AddressRewriter(
+			&onehop.OHPPacketDispatcherService{
+				PacketDispatcherService: &snet.DefaultPacketDispatcherService{
+					Dispatcher: reliable.NewDispatcher(""),
+				},
+			},
+		),
+	}
+	msgr.UpdateSigner(signer, []infra.MessageType{infra.Seg, infra.ChainIssueRequest})
+	// TODO(scrye): this breaks Interface Keepalives if it is enabled
+	// msgr.UpdateVerifier(trust.NewVerifier(trustStore))
+
+	if tasks.genMac, err = macGenFactory(); err != nil {
+		log.Crit("Unable to initialize MAC generator", "err", err)
+		return 1
+	}
+	discoRunners, err := idiscovery.StartRunners(cfg.Discovery, discovery.Full,
+		idiscovery.TopoHandlers{}, nil, "cs")
+	if err != nil {
+		log.Crit("Unable to start topology fetcher", "err", err)
+		return 1
+	}
+	defer discoRunners.Kill()
+
+	if err := tasks.Start(); err != nil {
+		log.Crit("Unable to start leader tasks", "err", err)
+		return 1
+	}
+	defer tasks.Kill()
+	select {
+	case <-fatal.ShutdownChan():
+		// Whenever we receive a SIGINT or SIGTERM we exit without an error.
+		return 0
+	case <-fatal.FatalChan():
+		return 1
+	}
+}
+
+type segRegRunners struct {
+	coreRegistrar *periodic.Runner
+	upRegistrar   *periodic.Runner
+	downRegistrar *periodic.Runner
+	core          bool
+}
+
+func (s segRegRunners) Kill() {
+	s.coreRegistrar.Kill()
+	s.upRegistrar.Kill()
+	s.downRegistrar.Kill()
+}
+
+type periodicTasks struct {
+	args            handlers.HandlerArgs
+	intfs           *ifstate.Interfaces
+	conn            *snet.SCIONPacketConn
+	genMac          func() hash.Hash
+	trustStore      trust.Store
+	trustDB         trust.DB
+	store           beaconstorage.Store
+	msgr            infra.Messenger
+	topoProvider    topology.Provider
+	allowIsdLoop    bool
+	addressRewriter *messenger.AddressRewriter
+
+	keepalive  *periodic.Runner
+	originator *periodic.Runner
+	propagator *periodic.Runner
+	revoker    *periodic.Runner
+	registrars segRegRunners
+
+	corePusher *periodic.Runner
+	reissuance *periodic.Runner
+
+	beaconCleaner *periodic.Runner
+	revCleaner    *periodic.Runner
+
+	segSyncers    []*periodic.Runner
+	pathDBCleaner *periodic.Runner
+	cryptosyncer  *periodic.Runner
+	rcCleaner     *periodic.Runner
+
+	mtx     sync.Mutex
+	running bool
+}
+
+func (t *periodicTasks) Start() error {
+	t.mtx.Lock()
+	defer t.mtx.Unlock()
+	if t.running {
+		log.Warn("Trying to start tasks, but they are running! Ignored.")
+		return nil
+	}
+	t.running = true
+	topo := t.topoProvider.Get()
+	bs := topo.PublicAddress(addr.SvcBS, cfg.General.ID)
+	if bs == nil {
+		return serrors.New("Unable to find topo address")
+	}
+
+	var err error
+	if t.registrars, err = t.startSegRegRunners(); err != nil {
+		return err
+	}
+	if t.revoker, err = t.startRevoker(); err != nil {
+		return err
+	}
+	if t.keepalive, err = t.startKeepaliveSender(bs); err != nil {
+		return err
+	}
+	if t.originator, err = t.startOriginator(bs); err != nil {
+		return err
+	}
+	if t.propagator, err = t.startPropagator(bs); err != nil {
+		return err
+	}
+
+	t.beaconCleaner = periodic.Start(
+		beaconstorage.NewBeaconCleaner(t.store),
+		30*time.Second, 30*time.Second)
+	t.revCleaner = periodic.Start(
+		beaconstorage.NewRevocationCleaner(t.store), 5*time.Second, 5*time.Second)
+
+	// t.corePusher = t.startCorePusher()
+	// t.reissuance = t.startReissuance(t.corePusher)
+
+	if cfg.PS.SegSync && itopo.Get().Core() {
+		t.segSyncers, err = segsyncer.StartAll(t.args, t.msgr)
+		if err != nil {
+			return common.NewBasicError("Unable to start seg syncer", err)
+		}
+	}
+	t.pathDBCleaner = periodic.Start(pathdb.NewCleaner(t.args.PathDB, "ps_segments"),
+		300*time.Second, 295*time.Second)
+	// TODO(roosd): Re-enable
+	// t.cryptosyncer = periodic.Start(&cryptosyncer.Syncer{
+	// 	DB:    t.trustDB,
+	// 	Msger: t.msger,
+	// 	IA:    t.args.IA,
+	// }, cfg.PS.CryptoSyncInterval.Duration, cfg.PS.CryptoSyncInterval.Duration)
+	t.rcCleaner = periodic.Start(revcache.NewCleaner(t.args.RevCache, "ps_revocation"),
+		10*time.Second, 10*time.Second)
+
+	log.Info("Started periodic tasks")
+	return nil
+}
+
+func (t *periodicTasks) startRevoker() (*periodic.Runner, error) {
+	topo := t.topoProvider.Get()
+	signer, err := t.createSigner(topo.IA())
+	if err != nil {
+		return nil, err
+	}
+	r := ifstate.RevokerConf{
+		Intfs:        t.intfs,
+		Msgr:         t.msgr,
+		RevInserter:  t.store,
+		Signer:       signer,
+		TopoProvider: t.topoProvider,
+		RevConfig: ifstate.RevConfig{
+			RevTTL:     cfg.BS.RevTTL.Duration,
+			RevOverlap: cfg.BS.RevOverlap.Duration,
+		},
+	}.New()
+	return periodic.Start(r, cfg.BS.ExpiredCheckInterval.Duration,
+		cfg.BS.ExpiredCheckInterval.Duration), nil
+}
+
+func (t *periodicTasks) startKeepaliveSender(a *net.UDPAddr) (*periodic.Runner, error) {
+	s := &keepalive.Sender{
+		Sender: &onehop.Sender{
+			Conn: t.conn,
+			IA:   t.topoProvider.Get().IA(),
+			MAC:  t.genMac(),
+			Addr: a,
+		},
+		Signer:       infra.NullSigner,
+		TopoProvider: t.topoProvider,
+	}
+	return periodic.Start(s, cfg.BS.KeepaliveInterval.Duration,
+		cfg.BS.KeepaliveInterval.Duration), nil
+}
+
+func (t *periodicTasks) startOriginator(a *net.UDPAddr) (*periodic.Runner, error) {
+	topo := t.topoProvider.Get()
+	if !topo.Core() {
+		return nil, nil
+	}
+	signer, err := t.createSigner(topo.IA())
+	if err != nil {
+		return nil, err
+	}
+	s, err := beaconing.OriginatorConf{
+		BeaconSender: &onehop.BeaconSender{
+			Sender: onehop.Sender{
+				Conn: t.conn,
+				IA:   topo.IA(),
+				MAC:  t.genMac(),
+				Addr: a,
+			},
+			AddressRewriter:  t.addressRewriter,
+			QUICBeaconSender: t.msgr,
+		},
+		Config: beaconing.ExtenderConf{
+			Intfs:         t.intfs,
+			Mac:           t.genMac(),
+			MTU:           topo.MTU(),
+			Signer:        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)
+	}
+	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()
+	signer, err := t.createSigner(topo.IA())
+	if err != nil {
+		return nil, err
+	}
+	p, err := beaconing.PropagatorConf{
+		BeaconProvider: t.store,
+		AllowIsdLoop:   t.allowIsdLoop,
+		Core:           topo.Core(),
+		BeaconSender: &onehop.BeaconSender{
+			Sender: onehop.Sender{
+				Conn: t.conn,
+				IA:   topo.IA(),
+				MAC:  t.genMac(),
+				Addr: a,
+			},
+			AddressRewriter:  t.addressRewriter,
+			QUICBeaconSender: t.msgr,
+		},
+		Config: beaconing.ExtenderConf{
+			Intfs:         t.intfs,
+			Mac:           t.genMac(),
+			MTU:           topo.MTU(),
+			Signer:        signer,
+			GetMaxExpTime: maxExpTimeFactory(t.store, beacon.PropPolicy),
+		},
+		Period: cfg.BS.PropagationInterval.Duration,
+	}.New()
+	if err != nil {
+		return nil, common.NewBasicError("Unable to start propagator", err)
+	}
+	return periodic.Start(p, 500*time.Millisecond,
+		cfg.BS.PropagationInterval.Duration), nil
+}
+
+func (t *periodicTasks) startSegRegRunners() (segRegRunners, error) {
+	topo := t.topoProvider.Get()
+	s := segRegRunners{core: topo.Core()}
+	var err error
+	if s.core {
+		s.coreRegistrar, err = t.startRegistrar(topo, proto.PathSegType_core, beacon.CoreRegPolicy)
+		if err != nil {
+			return s, common.NewBasicError("Unable to create core segment registrar", err)
+		}
+	} else {
+		s.downRegistrar, err = t.startRegistrar(topo, proto.PathSegType_down, beacon.DownRegPolicy)
+		if err != nil {
+			return s, common.NewBasicError("Unable to create down segment registrar", err)
+		}
+		s.upRegistrar, err = t.startRegistrar(topo, proto.PathSegType_up, beacon.UpRegPolicy)
+		if err != nil {
+			return s, common.NewBasicError("Unable to create up segment registrar", err)
+		}
+	}
+	return s, nil
+}
+
+func (t *periodicTasks) startRegistrar(topo topology.Topology, segType proto.PathSegType,
+	policyType beacon.PolicyType) (*periodic.Runner, error) {
+
+	signer, err := t.createSigner(topo.IA())
+	if err != nil {
+		return nil, err
+	}
+	r, err := beaconing.RegistrarConf{
+		Msgr:         t.msgr,
+		SegProvider:  t.store,
+		SegType:      segType,
+		TopoProvider: t.topoProvider,
+		Period:       cfg.BS.RegistrationInterval.Duration,
+		Config: beaconing.ExtenderConf{
+			Intfs:         t.intfs,
+			Mac:           t.genMac(),
+			MTU:           topo.MTU(),
+			Signer:        signer,
+			GetMaxExpTime: maxExpTimeFactory(t.store, policyType),
+		},
+	}.New()
+	if err != nil {
+		return nil, common.NewBasicError("unable to start registrar", err, "type", segType)
+	}
+	return periodic.Start(r, 500*time.Millisecond,
+		cfg.BS.RegistrationInterval.Duration), nil
+}
+
+func (t *periodicTasks) createSigner(ia addr.IA) (infra.Signer, error) {
+	ctx, cancelF := context.WithTimeout(context.Background(), time.Second)
+	defer cancelF()
+	gen := trust.SignerGen{
+		IA:       itopo.Get().IA(),
+		Provider: t.trustStore,
+		KeyRing: keyconf.LoadingRing{
+			Dir: filepath.Join(cfg.General.ConfigDir, "keys"),
+			IA:  ia,
+		},
+	}
+	return gen.Signer(ctx)
+}
+
+func (t *periodicTasks) Kill() {
+	t.mtx.Lock()
+	defer t.mtx.Unlock()
+	if !t.running {
+		log.Warn("Trying to stop tasks, but they are not running! Ignored.")
+		return
+	}
+	t.registrars.Kill()
+	t.revoker.Kill()
+	t.keepalive.Kill()
+	t.originator.Kill()
+	t.propagator.Kill()
+	t.beaconCleaner.Kill()
+	t.revCleaner.Kill()
+	t.reissuance.Kill()
+	t.corePusher.Kill()
+	for i := range t.segSyncers {
+		syncer := t.segSyncers[i]
+		syncer.Kill()
+	}
+	t.pathDBCleaner.Kill()
+	t.cryptosyncer.Kill()
+	t.rcCleaner.Kill()
+	t.running = false
+	log.Info("Stopped periodic tasks.")
+}
+
+func macGenFactory() (func() hash.Hash, error) {
+	mk, err := keyconf.LoadMaster(filepath.Join(cfg.General.ConfigDir, "keys"))
+	if err != nil {
+		return nil, err
+	}
+	hfMacFactory, err := scrypto.HFMacFactory(mk.Key0)
+	if err != nil {
+		return nil, err
+	}
+	return hfMacFactory, nil
+}
+
+func maxExpTimeFactory(store beaconstorage.Store, p beacon.PolicyType) func() spath.ExpTimeType {
+	return func() spath.ExpTimeType {
+		return store.MaxExpTime(p)
+	}
+}
+
+func setupBasic() error {
+	if _, err := toml.DecodeFile(env.ConfigFile(), &cfg); err != nil {
+		return err
+	}
+	cfg.InitDefaults()
+	if err := env.InitLogging(&cfg.Logging); err != nil {
+		return err
+	}
+	prom.ExportElementID(cfg.General.ID)
+	return env.LogAppStarted(common.CS, cfg.General.ID)
+}
+
+func setup() error {
+	if err := cfg.Validate(); err != nil {
+		return common.NewBasicError("Unable to validate config", err)
+	}
+	clbks := itopo.Callbacks{UpdateStatic: handleTopoUpdate}
+	// Use CS for monolith for now
+	itopo.Init(cfg.General.ID, proto.ServiceType_cs, clbks)
+	topo, err := topology.FromJSONFile(cfg.General.Topology)
+	if err != nil {
+		return common.NewBasicError("Unable to load topology", err)
+	}
+	if _, _, err := itopo.SetStatic(topo, false); err != nil {
+		return common.NewBasicError("Unable to set initial static topology", err)
+	}
+	infraenv.InitInfraEnvironment(cfg.General.Topology)
+	return nil
+}
+
+func handleTopoUpdate() {
+	if intfs == nil {
+		return
+	}
+	intfs.Update(itopo.Get().IFInfoMap())
+}
+
+func loadStore(core bool, ia addr.IA, cfg config.Config) (beaconstorage.Store, error) {
+	if core {
+		policies, err := loadCorePolicies(cfg.BS.Policies)
+		if err != nil {
+			return nil, err
+		}
+		return cfg.BeaconDB.NewCoreStore(ia, policies)
+	}
+	policies, err := loadPolicies(cfg.BS.Policies)
+	if err != nil {
+		return nil, err
+	}
+	return cfg.BeaconDB.NewStore(ia, policies)
+}
+
+func loadCorePolicies(cfg config.Policies) (beacon.CorePolicies, error) {
+	var err error
+	var policies beacon.CorePolicies
+	if policies.Prop, err = loadPolicy(cfg.Propagation, beacon.PropPolicy); err != nil {
+		return policies, err
+	}
+	if policies.CoreReg, err = loadPolicy(cfg.CoreRegistration, beacon.CoreRegPolicy); err != nil {
+		return policies, err
+	}
+	return policies, nil
+}
+
+func loadPolicies(cfg config.Policies) (beacon.Policies, error) {
+	var err error
+	var policies beacon.Policies
+	if policies.Prop, err = loadPolicy(cfg.Propagation, beacon.PropPolicy); err != nil {
+		return policies, err
+	}
+	if policies.UpReg, err = loadPolicy(cfg.UpRegistration, beacon.UpRegPolicy); err != nil {
+		return policies, err
+	}
+	if policies.DownReg, err = loadPolicy(cfg.DownRegistration, beacon.DownRegPolicy); err != nil {
+		return policies, err
+	}
+	return policies, nil
+}
+
+func loadPolicy(fn string, t beacon.PolicyType) (beacon.Policy, error) {
+	var policy beacon.Policy
+	if fn != "" {
+		p, err := beacon.LoadPolicyFromYaml(fn, t)
+		if err != nil {
+			return policy, common.NewBasicError("Unable to load policy", err, "fn", fn, "type", t)
+		}
+		policy = *p
+	}
+	policy.InitDefaults()
+	return policy, nil
+}
+
+func checkFlags(cfg *config.Config) (int, bool) {
+	if helpPolicy {
+		var sample beacon.Policy
+		sample.InitDefaults()
+		raw, err := yaml.Marshal(sample)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "Err: Unable to produce sample policy err=%s\n", err)
+			return 1, false
+		}
+		os.Stdout.Write(raw)
+		return 0, false
+	}
+	return env.CheckFlags(cfg)
+}
+
+type verificationFactory struct {
+	Provider trust.CryptoProvider
+}
+
+func (v verificationFactory) NewSigner(common.RawBytes, infra.SignerMeta) (infra.Signer, error) {
+	return nil, serrors.New("signer generation not supported")
+}
+
+func (v verificationFactory) NewVerifier() infra.Verifier {
+	return trust.NewVerifier(v.Provider)
+}
+
+type chainedHandler struct {
+	handlers []infra.Handler
+}
+
+func (h *chainedHandler) Handle(r *infra.Request) *infra.HandlerResult {
+	for _, handler := range h.handlers {
+		handler.Handle(r)
+	}
+	// Always return success, since the metrics libraries ignore this result anyway
+	return &infra.HandlerResult{
+		Result: prom.Success,
+		Status: prom.StatusOk,
+	}
+}
diff --git a/go/path_srv/internal/metrics/BUILD.bazel b/go/cs/metrics/BUILD.bazel
similarity index 71%
rename from go/path_srv/internal/metrics/BUILD.bazel
rename to go/cs/metrics/BUILD.bazel
index 773080e41..2fe1b48e7 100644
--- a/go/path_srv/internal/metrics/BUILD.bazel
+++ b/go/cs/metrics/BUILD.bazel
@@ -3,16 +3,23 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 go_library(
     name = "go_default_library",
     srcs = [
+        "beaconing.go",
+        "ifstate.go",
+        "keepalive.go",
         "metrics.go",
+        "originator.go",
+        "propagator.go",
+        "registrar.go",
         "registration.go",
         "requests.go",
         "revocation.go",
         "sync.go",
     ],
-    importpath = "github.com/scionproto/scion/go/path_srv/internal/metrics",
-    visibility = ["//go/path_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/metrics",
+    visibility = ["//visibility:public"],
     deps = [
         "//go/lib/addr:go_default_library",
+        "//go/lib/common:go_default_library",
         "//go/lib/infra/modules/segfetcher:go_default_library",
         "//go/lib/prom:go_default_library",
         "//go/proto:go_default_library",
@@ -23,6 +30,7 @@ go_library(
 go_test(
     name = "go_default_test",
     srcs = [
+        "metrics_test.go",
         "registration_test.go",
         "requests_test.go",
         "revocation_test.go",
diff --git a/go/beacon_srv/internal/metrics/beaconing.go b/go/cs/metrics/beaconing.go
similarity index 100%
rename from go/beacon_srv/internal/metrics/beaconing.go
rename to go/cs/metrics/beaconing.go
diff --git a/go/beacon_srv/internal/metrics/ifstate.go b/go/cs/metrics/ifstate.go
similarity index 100%
rename from go/beacon_srv/internal/metrics/ifstate.go
rename to go/cs/metrics/ifstate.go
diff --git a/go/beacon_srv/internal/metrics/keepalive.go b/go/cs/metrics/keepalive.go
similarity index 100%
rename from go/beacon_srv/internal/metrics/keepalive.go
rename to go/cs/metrics/keepalive.go
diff --git a/go/beacon_srv/internal/metrics/metrics.go b/go/cs/metrics/metrics.go
similarity index 62%
rename from go/beacon_srv/internal/metrics/metrics.go
rename to go/cs/metrics/metrics.go
index c1d74cf63..d25902d18 100644
--- a/go/beacon_srv/internal/metrics/metrics.go
+++ b/go/cs/metrics/metrics.go
@@ -18,8 +18,8 @@ import (
 	"github.com/scionproto/scion/go/lib/prom"
 )
 
-// Namespace is the metrics namespace for the beacon server.
-const Namespace = "bs"
+// Namespace is the metrics namespace for the control service.
+const Namespace = "cs"
 
 // Labels values.
 const (
@@ -42,6 +42,18 @@ const (
 	ErrVerify = prom.ErrVerify
 	// ErrSend indicates an error during verification.
 	ErrSend = "err_send"
+	// ErrInternal indicates an internal problem (likely a code bug).
+	ErrInternal = prom.ErrInternal
+	// ErrCrypto indicates a problem with crypto.
+	ErrCrypto = prom.ErrCrypto
+	// ErrTimeout indicates a timeout error.
+	ErrTimeout = prom.ErrTimeout
+	// ErrNetwork indicates a problem with the network.
+	ErrNetwork = prom.ErrNetwork
+	// ErrNotClassified indicates an error that is not further classified.
+	ErrNotClassified = prom.ErrNotClassified
+	// ErrNoPath indicates no path is available to send a message.
+	ErrNoPath = "err_nopath"
 
 	// OkFiltered indicates beacon was filtered by policy.
 	OkFiltered = "ok_filtered"
@@ -52,6 +64,18 @@ const (
 	OkOld = "ok_old"
 	// OkUpdated indicates existing beacon in db was updated.
 	OkUpdated = "ok_updated"
+	// OkSuccess is no error.
+	OkSuccess = prom.Success
+	// OkRegistrationNew indicates something new was registered.
+	OkRegistrationNew = "ok_new"
+	// OkRegiststrationUpdated indicates a registration was updated.
+	OkRegiststrationUpdated = "ok_updated"
+	// OkRequestCached indicates the request could be processed and the result
+	// came from cache.
+	OkRequestCached = "ok_cached"
+	// OkRequestFetched indicates the request could be processed but the result
+	// needed to be fetched from a remote server.
+	OkRequestFetched = "ok_fetched"
 
 	// RevNew indicates a new issued revocation.
 	RevNew = "new"
@@ -79,4 +103,26 @@ var (
 	Revocation = newRevocation()
 	// Registrar is the single-instance struct to get prometheus metrics or counters.
 	Registrar = newRegistrar()
+	// Registrations contains metrics for segments registrations.
+	Registrations = newRegistration()
+	// Requests contains metrics for segments requests.
+	Requests = newRequests()
+	// Sync contains metrics for segment synchronization.
+	Sync = newSync()
+	// PSRevocation contains metrics for revocations.
+	PSRevocation = psNewRevocation()
+)
+
+// Revocation sources
+const (
+	RevSrcNotification = "notification"
+	RevSrcSCMP         = "scmp"
+	RevSrcPathReply    = "path_reply"
+)
+
+// Label values
+const (
+	LabelSrc    = prom.LabelSrc
+	LabelResult = prom.LabelResult
+	LabelType   = "type"
 )
diff --git a/go/beacon_srv/internal/metrics/revocation.go b/go/cs/metrics/revocation.go
similarity index 61%
rename from go/beacon_srv/internal/metrics/revocation.go
rename to go/cs/metrics/revocation.go
index 81bbc16c0..34d84a758 100644
--- a/go/beacon_srv/internal/metrics/revocation.go
+++ b/go/cs/metrics/revocation.go
@@ -51,3 +51,41 @@ func newRevocation() exporterR {
 func (e *exporterR) Received(l RevocationLabels) prometheus.Counter {
 	return e.received.WithLabelValues(l.Values()...)
 }
+
+// PSRevocationLabels are the labels for PS revocation metrics.
+type PSRevocationLabels struct {
+	Result string
+	Src    string
+}
+
+// Labels returns the labels.
+func (l PSRevocationLabels) Labels() []string {
+	return []string{prom.LabelResult, prom.LabelSrc}
+}
+
+// Values returns the values for the labels.
+func (l PSRevocationLabels) Values() []string {
+	return []string{l.Result, l.Src}
+}
+
+// WithResult returns the labels with the result set.
+func (l PSRevocationLabels) WithResult(result string) PSRevocationLabels {
+	l.Result = result
+	return l
+}
+
+type revocation struct {
+	count *prometheus.CounterVec
+}
+
+func psNewRevocation() revocation {
+	return revocation{
+		count: prom.NewCounterVecWithLabels(Namespace, "", "received_revocations_total",
+			"The amount of revocations received.",
+			PSRevocationLabels{Result: OkSuccess, Src: RevSrcNotification}),
+	}
+}
+
+func (r revocation) Count(l PSRevocationLabels) prometheus.Counter {
+	return r.count.WithLabelValues(l.Values()...)
+}
diff --git a/go/beacon_srv/internal/onehop/BUILD.bazel b/go/cs/onehop/BUILD.bazel
similarity index 91%
rename from go/beacon_srv/internal/onehop/BUILD.bazel
rename to go/cs/onehop/BUILD.bazel
index 8a633b674..fba526640 100644
--- a/go/beacon_srv/internal/onehop/BUILD.bazel
+++ b/go/cs/onehop/BUILD.bazel
@@ -6,8 +6,8 @@ go_library(
         "network.go",
         "sender.go",
     ],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/onehop",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/onehop",
+    visibility = ["//visibility:public"],
     deps = [
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
diff --git a/go/beacon_srv/internal/onehop/network.go b/go/cs/onehop/network.go
similarity index 100%
rename from go/beacon_srv/internal/onehop/network.go
rename to go/cs/onehop/network.go
diff --git a/go/beacon_srv/internal/onehop/sender.go b/go/cs/onehop/sender.go
similarity index 100%
rename from go/beacon_srv/internal/onehop/sender.go
rename to go/cs/onehop/sender.go
diff --git a/go/beacon_srv/internal/onehop/sender_test.go b/go/cs/onehop/sender_test.go
similarity index 100%
rename from go/beacon_srv/internal/onehop/sender_test.go
rename to go/cs/onehop/sender_test.go
diff --git a/go/beacon_srv/internal/revocation/BUILD.bazel b/go/cs/revocation/BUILD.bazel
similarity index 83%
rename from go/beacon_srv/internal/revocation/BUILD.bazel
rename to go/cs/revocation/BUILD.bazel
index a39f46d7c..0ad0e0c90 100644
--- a/go/beacon_srv/internal/revocation/BUILD.bazel
+++ b/go/cs/revocation/BUILD.bazel
@@ -6,10 +6,10 @@ go_library(
         "handler.go",
         "metrics.go",
     ],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/revocation",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/revocation",
+    visibility = ["//visibility:public"],
     deps = [
-        "//go/beacon_srv/internal/metrics:go_default_library",
+        "//go/cs/metrics:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
         "//go/lib/infra:go_default_library",
@@ -25,7 +25,7 @@ go_test(
     srcs = ["handler_test.go"],
     embed = [":go_default_library"],
     deps = [
-        "//go/beacon_srv/internal/revocation/mock_revocation:go_default_library",
+        "//go/cs/revocation/mock_revocation:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl:go_default_library",
         "//go/lib/ctrl/ack:go_default_library",
diff --git a/go/beacon_srv/internal/revocation/handler.go b/go/cs/revocation/handler.go
similarity index 98%
rename from go/beacon_srv/internal/revocation/handler.go
rename to go/cs/revocation/handler.go
index b25ab1f1b..c85fa6ed3 100644
--- a/go/beacon_srv/internal/revocation/handler.go
+++ b/go/cs/revocation/handler.go
@@ -19,7 +19,7 @@ import (
 	"context"
 	"time"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/metrics"
+	"github.com/scionproto/scion/go/cs/metrics"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
 	"github.com/scionproto/scion/go/lib/infra"
diff --git a/go/beacon_srv/internal/revocation/metrics.go b/go/cs/revocation/metrics.go
similarity index 100%
rename from go/beacon_srv/internal/revocation/metrics.go
rename to go/cs/revocation/metrics.go
diff --git a/go/beacon_srv/internal/revocation/mock_revocation/BUILD.bazel b/go/cs/revocation/mock_revocation/BUILD.bazel
similarity index 63%
rename from go/beacon_srv/internal/revocation/mock_revocation/BUILD.bazel
rename to go/cs/revocation/mock_revocation/BUILD.bazel
index d824fac94..3fcf0d56d 100644
--- a/go/beacon_srv/internal/revocation/mock_revocation/BUILD.bazel
+++ b/go/cs/revocation/mock_revocation/BUILD.bazel
@@ -3,8 +3,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
 go_library(
     name = "go_default_library",
     srcs = ["revocation.go"],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/revocation/mock_revocation",
-    visibility = ["//go/beacon_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/revocation/mock_revocation",
+    visibility = ["//visibility:public"],
     deps = [
         "//go/lib/ctrl/path_mgmt:go_default_library",
         "@com_github_golang_mock//gomock:go_default_library",
diff --git a/go/beacon_srv/internal/revocation/mock_revocation/revocation.go b/go/cs/revocation/mock_revocation/revocation.go
similarity index 94%
rename from go/beacon_srv/internal/revocation/mock_revocation/revocation.go
rename to go/cs/revocation/mock_revocation/revocation.go
index c01749125..ff332ebcf 100644
--- a/go/beacon_srv/internal/revocation/mock_revocation/revocation.go
+++ b/go/cs/revocation/mock_revocation/revocation.go
@@ -1,14 +1,15 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/scionproto/scion/go/beacon_srv/internal/revocation (interfaces: Store)
+// Source: github.com/scionproto/scion/go/cs/revocation (interfaces: Store)
 
 // Package mock_revocation is a generated GoMock package.
 package mock_revocation
 
 import (
 	context "context"
+	reflect "reflect"
+
 	gomock "github.com/golang/mock/gomock"
 	path_mgmt "github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
-	reflect "reflect"
 )
 
 // MockStore is a mock of Store interface
diff --git a/go/path_srv/internal/segreq/BUILD.bazel b/go/cs/segreq/BUILD.bazel
similarity index 86%
rename from go/path_srv/internal/segreq/BUILD.bazel
rename to go/cs/segreq/BUILD.bazel
index 313160ef5..0cbd6553c 100644
--- a/go/path_srv/internal/segreq/BUILD.bazel
+++ b/go/cs/segreq/BUILD.bazel
@@ -11,9 +11,11 @@ go_library(
         "splitter.go",
         "validator.go",
     ],
-    importpath = "github.com/scionproto/scion/go/path_srv/internal/segreq",
-    visibility = ["//go/path_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/segreq",
+    visibility = ["//visibility:public"],
     deps = [
+        "//go/cs/handlers:go_default_library",
+        "//go/cs/metrics:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
@@ -28,8 +30,6 @@ go_library(
         "//go/lib/serrors:go_default_library",
         "//go/lib/snet/addrutil:go_default_library",
         "//go/lib/topology:go_default_library",
-        "//go/path_srv/internal/handlers:go_default_library",
-        "//go/path_srv/internal/metrics:go_default_library",
         "//go/proto:go_default_library",
     ],
 )
@@ -45,6 +45,7 @@ go_test(
     ],
     embed = [":go_default_library"],
     deps = [
+        "//go/cs/segreq/mock_segreq:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
         "//go/lib/ctrl/seg:go_default_library",
@@ -55,7 +56,6 @@ go_test(
         "//go/lib/revcache:go_default_library",
         "//go/lib/revcache/mock_revcache:go_default_library",
         "//go/lib/xtest:go_default_library",
-        "//go/path_srv/internal/segreq/mock_segreq:go_default_library",
         "@com_github_golang_mock//gomock:go_default_library",
         "@com_github_stretchr_testify//assert:go_default_library",
         "@com_github_stretchr_testify//require:go_default_library",
diff --git a/go/path_srv/internal/segreq/doc.go b/go/cs/segreq/doc.go
similarity index 100%
rename from go/path_srv/internal/segreq/doc.go
rename to go/cs/segreq/doc.go
diff --git a/go/path_srv/internal/segreq/handler.go b/go/cs/segreq/handler.go
similarity index 97%
rename from go/path_srv/internal/segreq/handler.go
rename to go/cs/segreq/handler.go
index 8dfaba5aa..573ca5e81 100644
--- a/go/path_srv/internal/segreq/handler.go
+++ b/go/cs/segreq/handler.go
@@ -17,6 +17,8 @@ package segreq
 import (
 	"time"
 
+	"github.com/scionproto/scion/go/cs/handlers"
+	"github.com/scionproto/scion/go/cs/metrics"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
 	"github.com/scionproto/scion/go/lib/infra"
@@ -25,8 +27,6 @@ import (
 	"github.com/scionproto/scion/go/lib/log"
 	"github.com/scionproto/scion/go/lib/pathdb"
 	"github.com/scionproto/scion/go/lib/revcache"
-	"github.com/scionproto/scion/go/path_srv/internal/handlers"
-	"github.com/scionproto/scion/go/path_srv/internal/metrics"
 	"github.com/scionproto/scion/go/proto"
 )
 
diff --git a/go/path_srv/internal/segreq/mock_segreq/BUILD.bazel b/go/cs/segreq/mock_segreq/BUILD.bazel
similarity index 68%
rename from go/path_srv/internal/segreq/mock_segreq/BUILD.bazel
rename to go/cs/segreq/mock_segreq/BUILD.bazel
index c52c4ee77..c4e35f882 100644
--- a/go/path_srv/internal/segreq/mock_segreq/BUILD.bazel
+++ b/go/cs/segreq/mock_segreq/BUILD.bazel
@@ -3,8 +3,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
 go_library(
     name = "go_default_library",
     srcs = ["segreq.go"],
-    importpath = "github.com/scionproto/scion/go/path_srv/internal/segreq/mock_segreq",
-    visibility = ["//go/path_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/segreq/mock_segreq",
+    visibility = ["//visibility:public"],
     deps = [
         "//go/lib/addr:go_default_library",
         "//go/lib/pathdb/query:go_default_library",
diff --git a/go/path_srv/internal/segreq/mock_segreq/segreq.go b/go/cs/segreq/mock_segreq/segreq.go
similarity index 95%
rename from go/path_srv/internal/segreq/mock_segreq/segreq.go
rename to go/cs/segreq/mock_segreq/segreq.go
index 3d3ea2e46..f56a6205a 100644
--- a/go/path_srv/internal/segreq/mock_segreq/segreq.go
+++ b/go/cs/segreq/mock_segreq/segreq.go
@@ -1,15 +1,16 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/scionproto/scion/go/path_srv/internal/segreq (interfaces: LocalInfo)
+// Source: github.com/scionproto/scion/go/cs/segreq (interfaces: LocalInfo)
 
 // Package mock_segreq is a generated GoMock package.
 package mock_segreq
 
 import (
 	context "context"
+	reflect "reflect"
+
 	gomock "github.com/golang/mock/gomock"
 	addr "github.com/scionproto/scion/go/lib/addr"
 	query "github.com/scionproto/scion/go/lib/pathdb/query"
-	reflect "reflect"
 )
 
 // MockLocalInfo is a mock of LocalInfo interface
diff --git a/go/path_srv/internal/segsyncer/BUILD.bazel b/go/cs/segsyncer/BUILD.bazel
similarity index 77%
rename from go/path_srv/internal/segsyncer/BUILD.bazel
rename to go/cs/segsyncer/BUILD.bazel
index 805038228..b05f5eb49 100644
--- a/go/path_srv/internal/segsyncer/BUILD.bazel
+++ b/go/cs/segsyncer/BUILD.bazel
@@ -3,9 +3,11 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
 go_library(
     name = "go_default_library",
     srcs = ["segsyncer.go"],
-    importpath = "github.com/scionproto/scion/go/path_srv/internal/segsyncer",
-    visibility = ["//go/path_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/segsyncer",
+    visibility = ["//visibility:public"],
     deps = [
+        "//go/cs/handlers:go_default_library",
+        "//go/cs/metrics:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
@@ -20,8 +22,6 @@ go_library(
         "//go/lib/serrors:go_default_library",
         "//go/lib/snet/addrutil:go_default_library",
         "//go/lib/topology:go_default_library",
-        "//go/path_srv/internal/handlers:go_default_library",
-        "//go/path_srv/internal/metrics:go_default_library",
         "//go/proto:go_default_library",
     ],
 )
diff --git a/go/path_srv/internal/segsyncer/segsyncer.go b/go/cs/segsyncer/segsyncer.go
similarity index 98%
rename from go/path_srv/internal/segsyncer/segsyncer.go
rename to go/cs/segsyncer/segsyncer.go
index 42409206c..4cefb2176 100644
--- a/go/path_srv/internal/segsyncer/segsyncer.go
+++ b/go/cs/segsyncer/segsyncer.go
@@ -22,6 +22,8 @@ import (
 	"sort"
 	"time"
 
+	"github.com/scionproto/scion/go/cs/handlers"
+	"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"
@@ -36,8 +38,6 @@ import (
 	"github.com/scionproto/scion/go/lib/serrors"
 	"github.com/scionproto/scion/go/lib/snet/addrutil"
 	"github.com/scionproto/scion/go/lib/topology"
-	"github.com/scionproto/scion/go/path_srv/internal/handlers"
-	"github.com/scionproto/scion/go/path_srv/internal/metrics"
 	"github.com/scionproto/scion/go/proto"
 )
 
diff --git a/go/path_srv/internal/segutil/BUILD.bazel b/go/cs/segutil/BUILD.bazel
similarity index 80%
rename from go/path_srv/internal/segutil/BUILD.bazel
rename to go/cs/segutil/BUILD.bazel
index bb1749e46..2e890c2ce 100644
--- a/go/path_srv/internal/segutil/BUILD.bazel
+++ b/go/cs/segutil/BUILD.bazel
@@ -3,8 +3,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 go_library(
     name = "go_default_library",
     srcs = ["filter.go"],
-    importpath = "github.com/scionproto/scion/go/path_srv/internal/segutil",
-    visibility = ["//go/path_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/segutil",
+    visibility = ["//visibility:public"],
     deps = [
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
@@ -19,11 +19,11 @@ go_test(
     srcs = ["filter_test.go"],
     embed = [":go_default_library"],
     deps = [
+        "//go/cs/segutil/mock_segutil:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/seg:go_default_library",
         "//go/lib/pathpol:go_default_library",
         "//go/lib/xtest/graph:go_default_library",
-        "//go/path_srv/internal/segutil/mock_segutil:go_default_library",
         "@com_github_golang_mock//gomock:go_default_library",
         "@com_github_stretchr_testify//assert:go_default_library",
     ],
diff --git a/go/path_srv/internal/segutil/mock_segutil/BUILD.bazel b/go/cs/segutil/mock_segutil/BUILD.bazel
similarity index 64%
rename from go/path_srv/internal/segutil/mock_segutil/BUILD.bazel
rename to go/cs/segutil/mock_segutil/BUILD.bazel
index 12d3bd2ab..d094321b4 100644
--- a/go/path_srv/internal/segutil/mock_segutil/BUILD.bazel
+++ b/go/cs/segutil/mock_segutil/BUILD.bazel
@@ -3,8 +3,8 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
 go_library(
     name = "go_default_library",
     srcs = ["segutil.go"],
-    importpath = "github.com/scionproto/scion/go/path_srv/internal/segutil/mock_segutil",
-    visibility = ["//go/path_srv:__subpackages__"],
+    importpath = "github.com/scionproto/scion/go/cs/segutil/mock_segutil",
+    visibility = ["//visibility:public"],
     deps = [
         "//go/lib/pathpol:go_default_library",
         "@com_github_golang_mock//gomock:go_default_library",
diff --git a/go/path_srv/internal/segutil/mock_segutil/segutil.go b/go/cs/segutil/mock_segutil/segutil.go
similarity index 93%
rename from go/path_srv/internal/segutil/mock_segutil/segutil.go
rename to go/cs/segutil/mock_segutil/segutil.go
index c11cd8991..278d4c9ec 100644
--- a/go/path_srv/internal/segutil/mock_segutil/segutil.go
+++ b/go/cs/segutil/mock_segutil/segutil.go
@@ -1,13 +1,14 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/scionproto/scion/go/path_srv/internal/segutil (interfaces: Policy)
+// Source: github.com/scionproto/scion/go/cs/segutil (interfaces: Policy)
 
 // Package mock_segutil is a generated GoMock package.
 package mock_segutil
 
 import (
+	reflect "reflect"
+
 	gomock "github.com/golang/mock/gomock"
 	pathpol "github.com/scionproto/scion/go/lib/pathpol"
-	reflect "reflect"
 )
 
 // MockPolicy is a mock of Policy interface
diff --git a/go/godispatcher/internal/registration/svctable.go b/go/godispatcher/internal/registration/svctable.go
index a280ec142..8c9863181 100644
--- a/go/godispatcher/internal/registration/svctable.go
+++ b/go/godispatcher/internal/registration/svctable.go
@@ -107,6 +107,35 @@ func (t *svcTable) Register(svc addr.HostSVC, address *net.UDPAddr,
 	// state
 	address = copyUDPAddr(address)
 
+	if svc == addr.SvcWildcard {
+		refBS, err := t.registerOne(addr.SvcBS, address, value)
+		if err != nil {
+			return nil, err
+		}
+		refCS, err := t.registerOne(addr.SvcCS, address, value)
+		if err != nil {
+			refBS.Free()
+			return nil, err
+		}
+		refPS, err := t.registerOne(addr.SvcPS, address, value)
+		if err != nil {
+			refBS.Free()
+			refCS.Free()
+			return nil, err
+		}
+		return &svcTableReference{
+			cleanF: func() {
+				refBS.Free()
+				refCS.Free()
+				refPS.Free()
+			},
+		}, nil
+	}
+	return t.registerOne(svc, address, value)
+}
+
+func (t *svcTable) registerOne(svc addr.HostSVC, address *net.UDPAddr,
+	value interface{}) (Reference, error) {
 	if _, ok := t.m[svc]; !ok {
 		t.m[svc] = make(unicastIpTable)
 	}
diff --git a/go/lib/addr/host.go b/go/lib/addr/host.go
index 3de66bead..b6510330b 100644
--- a/go/lib/addr/host.go
+++ b/go/lib/addr/host.go
@@ -63,13 +63,14 @@ var (
 )
 
 const (
-	SvcBS   HostSVC = 0x0000
-	SvcPS   HostSVC = 0x0001
-	SvcCS   HostSVC = 0x0002
-	SvcSB   HostSVC = 0x0003
-	SvcSIG  HostSVC = 0x0004
-	SvcHPS  HostSVC = 0x0005
-	SvcNone HostSVC = 0xffff
+	SvcBS       HostSVC = 0x0000
+	SvcPS       HostSVC = 0x0001
+	SvcCS       HostSVC = 0x0002
+	SvcSB       HostSVC = 0x0003
+	SvcSIG      HostSVC = 0x0004
+	SvcHPS      HostSVC = 0x0005
+	SvcWildcard HostSVC = 0x0010
+	SvcNone     HostSVC = 0xffff
 
 	SVCMcast HostSVC = 0x8000
 )
@@ -212,6 +213,8 @@ func HostSVCFromString(str string) HostSVC {
 		return SvcSB | m
 	case "SIG":
 		return SvcSIG | m
+	case "Wildcard":
+		return SvcWildcard | m
 	default:
 		return SvcNone
 	}
@@ -279,6 +282,8 @@ func (h HostSVC) BaseString() string {
 		return "SB"
 	case SvcSIG:
 		return "SIG"
+	case SvcWildcard:
+		return "Wildcard"
 	default:
 		return "UNKNOWN"
 	}
diff --git a/go/lib/common/defs.go b/go/lib/common/defs.go
index 07225eae8..aba5e682a 100644
--- a/go/lib/common/defs.go
+++ b/go/lib/common/defs.go
@@ -38,6 +38,8 @@ const (
 	RS  = "RS"
 	SIG = "SIG"
 	DS  = "DS"
+
+	CPService = "Control Plane Service"
 )
 
 // Interface ID
diff --git a/go/lib/integration/integration.go b/go/lib/integration/integration.go
index 294318d2c..f40e43575 100644
--- a/go/lib/integration/integration.go
+++ b/go/lib/integration/integration.go
@@ -180,7 +180,7 @@ func generateAllSrcDst(hostAddr HostAddr, unique bool) []IAPair {
 
 type HostAddr func(ia addr.IA) *snet.UDPAddr
 
-// DispAddr reads the BS host Addr from the topology for the specified IA. In general this
+// DispAddr reads the CS host Addr from the topology for the specified IA. In general this
 // could be the IP of any service (PS/BS/CS) in that IA because they share the same dispatcher in
 // the dockerized topology.
 // The host IP is used as client or server address in the tests because the testing container is
@@ -192,8 +192,8 @@ var DispAddr HostAddr = func(ia addr.IA) *snet.UDPAddr {
 		log.Error("Error loading topology", "err", err)
 		os.Exit(1)
 	}
-	bs := topo.BS["bs"+ia.FileFmt(false)+"-1"]
-	return snet.NewUDPAddr(ia, nil, nil, bs.SCIONAddress)
+	cs := topo.CS["cs"+ia.FileFmt(false)+"-1"]
+	return snet.NewUDPAddr(ia, nil, nil, cs.SCIONAddress)
 }
 
 // interface kept similar to go 1.10
diff --git a/go/path_srv/BUILD.bazel b/go/path_srv/BUILD.bazel
index 5aad7c3ff..0ea0b472f 100644
--- a/go/path_srv/BUILD.bazel
+++ b/go/path_srv/BUILD.bazel
@@ -7,6 +7,9 @@ go_library(
     importpath = "github.com/scionproto/scion/go/path_srv",
     visibility = ["//visibility:private"],
     deps = [
+        "//go/cs/handlers:go_default_library",
+        "//go/cs/segreq:go_default_library",
+        "//go/cs/segsyncer:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/discovery:go_default_library",
@@ -27,9 +30,6 @@ go_library(
         "//go/lib/serrors:go_default_library",
         "//go/lib/topology:go_default_library",
         "//go/path_srv/internal/config:go_default_library",
-        "//go/path_srv/internal/handlers:go_default_library",
-        "//go/path_srv/internal/segreq:go_default_library",
-        "//go/path_srv/internal/segsyncer:go_default_library",
         "//go/proto:go_default_library",
         "@com_github_burntsushi_toml//:go_default_library",
         "@com_github_opentracing_opentracing_go//:go_default_library",
diff --git a/go/path_srv/internal/config/BUILD.bazel b/go/path_srv/internal/config/BUILD.bazel
index 2413fd750..56386c932 100644
--- a/go/path_srv/internal/config/BUILD.bazel
+++ b/go/path_srv/internal/config/BUILD.bazel
@@ -2,20 +2,15 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
-    srcs = [
-        "config.go",
-        "sample.go",
-    ],
+    srcs = ["config.go"],
     importpath = "github.com/scionproto/scion/go/path_srv/internal/config",
     visibility = ["//go/path_srv:__subpackages__"],
     deps = [
+        "//go/cs/config:go_default_library",
         "//go/lib/config:go_default_library",
         "//go/lib/env:go_default_library",
         "//go/lib/infra/modules/idiscovery:go_default_library",
-        "//go/lib/pathstorage:go_default_library",
-        "//go/lib/serrors:go_default_library",
         "//go/lib/truststorage:go_default_library",
-        "//go/lib/util:go_default_library",
     ],
 )
 
@@ -24,6 +19,7 @@ go_test(
     srcs = ["config_test.go"],
     embed = [":go_default_library"],
     deps = [
+        "//go/cs/config:go_default_library",
         "//go/lib/env/envtest:go_default_library",
         "//go/lib/infra/modules/idiscovery/idiscoverytest:go_default_library",
         "//go/lib/pathstorage/pathstoragetest:go_default_library",
diff --git a/go/path_srv/internal/config/config.go b/go/path_srv/internal/config/config.go
index 3bd7a299b..a627cf041 100644
--- a/go/path_srv/internal/config/config.go
+++ b/go/path_srv/internal/config/config.go
@@ -19,13 +19,15 @@ import (
 	"io"
 	"time"
 
+	controlconfig "github.com/scionproto/scion/go/cs/config"
 	"github.com/scionproto/scion/go/lib/config"
 	"github.com/scionproto/scion/go/lib/env"
 	"github.com/scionproto/scion/go/lib/infra/modules/idiscovery"
-	"github.com/scionproto/scion/go/lib/pathstorage"
-	"github.com/scionproto/scion/go/lib/serrors"
 	"github.com/scionproto/scion/go/lib/truststorage"
-	"github.com/scionproto/scion/go/lib/util"
+)
+
+const (
+	idSample = "ps-1"
 )
 
 var (
@@ -44,7 +46,7 @@ type Config struct {
 	QUIC      env.QUIC `toml:"quic"`
 	TrustDB   truststorage.TrustDBConf
 	Discovery idiscovery.Config
-	PS        PSConfig
+	PS        controlconfig.PSConfig
 }
 
 func (cfg *Config) InitDefaults() {
@@ -89,45 +91,3 @@ func (cfg *Config) Sample(dst io.Writer, path config.Path, _ config.CtxMap) {
 func (cfg *Config) ConfigName() string {
 	return "ps_config"
 }
-
-var _ config.Config = (*PSConfig)(nil)
-
-type PSConfig struct {
-	// SegSync enables the "old" replication of down segments between cores,
-	// using SegSync messages.
-	SegSync  bool
-	PathDB   pathstorage.PathDBConf
-	RevCache pathstorage.RevCacheConf
-	// QueryInterval specifies after how much time segments
-	// for a destination should be refetched.
-	QueryInterval util.DurWrap
-	// CryptoSyncInterval specifies the interval of crypto pushes towards
-	// the local CS.
-	CryptoSyncInterval util.DurWrap
-}
-
-func (cfg *PSConfig) InitDefaults() {
-	if cfg.QueryInterval.Duration == 0 {
-		cfg.QueryInterval.Duration = DefaultQueryInterval
-	}
-	if cfg.CryptoSyncInterval.Duration == 0 {
-		cfg.CryptoSyncInterval.Duration = DefaultCryptoSyncInterval
-	}
-	config.InitAll(&cfg.PathDB, &cfg.RevCache)
-}
-
-func (cfg *PSConfig) Validate() error {
-	if cfg.QueryInterval.Duration == 0 {
-		return serrors.New("QueryInterval must not be zero")
-	}
-	return config.ValidateAll(&cfg.PathDB, &cfg.RevCache)
-}
-
-func (cfg *PSConfig) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) {
-	config.WriteString(dst, psSample)
-	config.WriteSample(dst, path, ctx, &cfg.PathDB, &cfg.RevCache)
-}
-
-func (cfg *PSConfig) ConfigName() string {
-	return "ps"
-}
diff --git a/go/path_srv/internal/metrics/metrics.go b/go/path_srv/internal/metrics/metrics.go
deleted file mode 100644
index a49eef1e6..000000000
--- a/go/path_srv/internal/metrics/metrics.go
+++ /dev/null
@@ -1,80 +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 metrics
-
-import (
-	"github.com/scionproto/scion/go/lib/prom"
-)
-
-// Namespace is the metrics namespace for the path server.
-const Namespace = "ps"
-
-// Group of metrics.
-var (
-	// Registrations contains metrics for segments registrations.
-	Registrations = newRegistration()
-	// Requests contains metrics for segments requests.
-	Requests = newRequests()
-	// Sync contains metrics for segment synchronization.
-	Sync = newSync()
-	// Revocation contains metrics for revocations.
-	Revocation = newRevocation()
-)
-
-// Result values
-const (
-	// OkSuccess is no error.
-	OkSuccess = prom.Success
-	// OkRegistrationNew indicates something new was registered.
-	OkRegistrationNew = "ok_new"
-	// OkRegiststrationUpdated indicates a registration was updated.
-	OkRegiststrationUpdated = "ok_updated"
-	// OkRequestCached indicates the request could be processed and the result
-	// came from cache.
-	OkRequestCached = "ok_cached"
-	// OkRequestFetched indicates the request could be processed but the result
-	// needed to be fetched from a remote server.
-	OkRequestFetched = "ok_fetched"
-	// ErrParse indicates a parse error.
-	ErrParse = prom.ErrParse
-	// ErrInternal indicates an internal problem (likely a code bug).
-	ErrInternal = prom.ErrInternal
-	// ErrCrypto indicates a problem with crypto.
-	ErrCrypto = prom.ErrCrypto
-	// ErrDB indicates a problem with the DB.
-	ErrDB = prom.ErrDB
-	// ErrTimeout indicates a timeout error.
-	ErrTimeout = prom.ErrTimeout
-	// ErrNetwork indicates a problem with the network.
-	ErrNetwork = prom.ErrNetwork
-	// ErrNotClassified indicates an error that is not further classified.
-	ErrNotClassified = prom.ErrNotClassified
-	// ErrNoPath indicates no path is available to send a message.
-	ErrNoPath = "err_nopath"
-)
-
-// Revocation sources
-const (
-	RevSrcNotification = "notification"
-	RevSrcSCMP         = "scmp"
-	RevSrcPathReply    = "path_reply"
-)
-
-// Label values
-const (
-	LabelSrc    = prom.LabelSrc
-	LabelResult = prom.LabelResult
-	LabelType   = "type"
-)
diff --git a/go/path_srv/internal/metrics/revocation.go b/go/path_srv/internal/metrics/revocation.go
deleted file mode 100644
index 1cf029363..000000000
--- a/go/path_srv/internal/metrics/revocation.go
+++ /dev/null
@@ -1,59 +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 metrics
-
-import (
-	"github.com/prometheus/client_golang/prometheus"
-
-	"github.com/scionproto/scion/go/lib/prom"
-)
-
-// RevocationLabels are the labels for revocation metrics.
-type RevocationLabels struct {
-	Result string
-	Src    string
-}
-
-// Labels returns the labels.
-func (l RevocationLabels) Labels() []string {
-	return []string{prom.LabelResult, prom.LabelSrc}
-}
-
-// Values returns the values for the labels.
-func (l RevocationLabels) Values() []string {
-	return []string{l.Result, l.Src}
-}
-
-// WithResult returns the labels with the result set.
-func (l RevocationLabels) WithResult(result string) RevocationLabels {
-	l.Result = result
-	return l
-}
-
-type revocation struct {
-	count *prometheus.CounterVec
-}
-
-func newRevocation() revocation {
-	return revocation{
-		count: prom.NewCounterVecWithLabels(Namespace, "", "received_revocations_total",
-			"The amount of revocations received.",
-			RevocationLabels{Result: OkSuccess, Src: RevSrcNotification}),
-	}
-}
-
-func (r revocation) Count(l RevocationLabels) prometheus.Counter {
-	return r.count.WithLabelValues(l.Values()...)
-}
diff --git a/go/path_srv/main.go b/go/path_srv/main.go
index b82c0f41d..e79b6779c 100644
--- a/go/path_srv/main.go
+++ b/go/path_srv/main.go
@@ -27,6 +27,9 @@ import (
 	"github.com/BurntSushi/toml"
 	"github.com/opentracing/opentracing-go"
 
+	"github.com/scionproto/scion/go/cs/handlers"
+	"github.com/scionproto/scion/go/cs/segreq"
+	"github.com/scionproto/scion/go/cs/segsyncer"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/discovery"
@@ -47,9 +50,6 @@ import (
 	"github.com/scionproto/scion/go/lib/serrors"
 	"github.com/scionproto/scion/go/lib/topology"
 	"github.com/scionproto/scion/go/path_srv/internal/config"
-	"github.com/scionproto/scion/go/path_srv/internal/handlers"
-	"github.com/scionproto/scion/go/path_srv/internal/segreq"
-	"github.com/scionproto/scion/go/path_srv/internal/segsyncer"
 	"github.com/scionproto/scion/go/proto"
 )
 
diff --git a/python/topology/config.py b/python/topology/config.py
index bf29a2ce2..399050689 100755
--- a/python/topology/config.py
+++ b/python/topology/config.py
@@ -67,6 +67,7 @@ class ConfigGenerator(object):
     """
     Configuration and/or topology generator.
     """
+
     def __init__(self, args):
         """
         Initialize an instance of the class ConfigGenerator.
@@ -131,10 +132,13 @@ class ConfigGenerator(object):
         args = self._go_args(topo_dicts)
         go_gen = GoGenerator(args)
         go_gen.generate_br()
-        go_gen.generate_bs()
-        go_gen.generate_cs()
         go_gen.generate_sciond()
-        go_gen.generate_ps()
+        if self.args.monolith:
+            go_gen.generate_control_service()
+        else:
+            go_gen.generate_bs()
+            go_gen.generate_cs()
+            go_gen.generate_ps()
         go_gen.generate_co()
         go_gen.generate_disp()
 
diff --git a/python/topology/generator.py b/python/topology/generator.py
index 6c1d36bfe..a11a151e6 100755
--- a/python/topology/generator.py
+++ b/python/topology/generator.py
@@ -50,6 +50,9 @@ def add_arguments(parser):
     parser.add_argument('-f', '--svcfrac', type=float, default=0.4,
                         help='Attempt SVC resolution in RPC calls for a fraction of\
                         available timeout')
+    parser.add_argument('--monolith', action='store_true',
+                        help='Generate the same address for BS/CS/PS and run the monolithic\
+                        Control Service on it')
     parser.add_argument('--random-ifids', action='store_true',
                         help='Generate random IFIDs')
     parser.add_argument('--in-docker', action='store_true',
diff --git a/python/topology/go.py b/python/topology/go.py
index db2fba21b..695555f75 100755
--- a/python/topology/go.py
+++ b/python/topology/go.py
@@ -105,6 +105,52 @@ class GoGenerator(object):
         }
         return raw_entry
 
+    def generate_control_service(self):
+        for topo_id, topo in self.args.topo_dicts.items():
+            for elem_id, elem in topo.get("CertificateService", {}).items():
+                # only a single Go-BS per AS is currently supported
+                if elem_id.endswith("-1"):
+                    base = topo_id.base_dir(self.args.output_dir)
+                    bs_conf = self._build_control_service_conf(
+                        topo_id, topo["ISD_AS"], base, elem_id, elem)
+                    write_file(os.path.join(base, elem_id,
+                                            CS_CONFIG_NAME), toml.dumps(bs_conf))
+
+    def _build_control_service_conf(self, topo_id, ia, base, name, infra_elem):
+        config_dir = '/share/conf' if self.args.docker else os.path.join(
+            base, name)
+        raw_entry = {
+            'general': {
+                'ID': name,
+                'ConfigDir': config_dir,
+                'ReconnectToDispatcher': True,
+            },
+            'logging': self._log_entry(name),
+            'trustDB': trust_db_conf_entry(self.args, name),
+            'beaconDB': beacon_db_conf_entry(self.args, name),
+            'discovery': self._discovery_entry(),
+            'tracing': self._tracing_entry(),
+            'metrics': self._metrics_entry(name, infra_elem, BS_PROM_PORT),
+            'quic': self._quic_conf_entry(BS_QUIC_PORT, self.args.svcfrac, infra_elem),
+            'sd_client': {
+                'Path': get_default_sciond_path(topo_id),
+            },
+            'cs': {
+                'LeafReissueLeadTime': "6h",
+                'IssuerReissueLeadTime': "3d",
+                'ReissueRate': "10s",
+                'ReissueTimeout': "5s",
+            },
+            'ps': {
+                'pathDB': {
+                    'Backend': 'sqlite',
+                    'Connection': os.path.join(self.db_dir, '%s.path.db' % name),
+                },
+                'SegSync': True,
+            },
+        }
+        return raw_entry
+
     def generate_bs(self):
         for topo_id, topo in self.args.topo_dicts.items():
             for elem_id, elem in topo.get("BeaconService", {}).items():
diff --git a/python/topology/supervisor.py b/python/topology/supervisor.py
index a962add39..970586588 100755
--- a/python/topology/supervisor.py
+++ b/python/topology/supervisor.py
@@ -63,9 +63,12 @@ class SupervisorGenerator(object):
     def _as_conf(self, topo, base):
         entries = []
         entries.extend(self._br_entries(topo, "bin/border", base))
-        entries.extend(self._bs_entries(topo, base))
-        entries.extend(self._cs_entries(topo, base))
-        entries.extend(self._ps_entries(topo, base))
+        if self.args.monolith:
+            entries.extend(self._control_service_entries(topo, base))
+        else:
+            entries.extend(self._bs_entries(topo, base))
+            entries.extend(self._cs_entries(topo, base))
+            entries.extend(self._ps_entries(topo, base))
         return entries
 
     def _std_entries(self, topo, topo_key, cmd, base, port):
@@ -74,7 +77,8 @@ class SupervisorGenerator(object):
             conf_dir = os.path.join(base, elem_id)
             prom_addr = prom_addr_infra(self.args.docker, elem_id, elem, port)
             entries.append((elem_id, [cmd, "--prom", prom_addr, "--sciond_path",
-                                      get_default_sciond_path(ISD_AS(topo["ISD_AS"])),
+                                      get_default_sciond_path(
+                                          ISD_AS(topo["ISD_AS"])),
                                       elem_id, conf_dir]))
         return entries
 
@@ -85,6 +89,15 @@ class SupervisorGenerator(object):
             entries.append((k, [cmd, "-config", conf]))
         return entries
 
+    def _control_service_entries(self, topo, base):
+        entries = []
+        for k, v in topo.get("CertificateService", {}).items():
+            # only a single control service instance per AS is currently supported
+            if k.endswith("-1"):
+                conf = os.path.join(base, k, CS_CONFIG_NAME)
+                entries.append((k, ["bin/cs", "-config", conf]))
+        return entries
+
     def _bs_entries(self, topo, base):
         entries = []
         for k, v in topo.get("BeaconService", {}).items():
@@ -114,7 +127,7 @@ class SupervisorGenerator(object):
 
     def _sciond_entry(self, name, conf_dir):
         return self._common_entry(
-                name, ["bin/sciond", "-config", os.path.join(conf_dir, SD_CONFIG_NAME)])
+            name, ["bin/sciond", "-config", os.path.join(conf_dir, SD_CONFIG_NAME)])
 
     def _sciond_path(self, name):
         return os.path.join(SCIOND_API_SOCKDIR, "%s.sock" % name)
@@ -131,10 +144,12 @@ class SupervisorGenerator(object):
         names.append(sd_name)
         conf_dir = os.path.join(base, COMMON_DIR)
         config["program:%s" % sd_name] = self._sciond_entry(sd_name, conf_dir)
-        config["group:as%s" % topo_id.file_fmt()] = {"programs": ",".join(names)}
+        config["group:as%s" % topo_id.file_fmt()] = {
+            "programs": ",".join(names)}
         text = StringIO()
         config.write(text)
-        conf_path = os.path.join(topo_id.base_dir(self.args.output_dir), SUPERVISOR_CONF)
+        conf_path = os.path.join(topo_id.base_dir(
+            self.args.output_dir), SUPERVISOR_CONF)
         write_file(conf_path, text.getvalue())
 
     def _write_elem_conf(self, elem, entry, elem_dir, topo_id=None):
@@ -151,7 +166,8 @@ class SupervisorGenerator(object):
         elem = "dispatcher"
         elem_dir = os.path.join(self.args.output_dir, elem)
         config_file_path = os.path.join(elem_dir, DISP_CONFIG_NAME)
-        self._write_elem_conf(elem, ["bin/godispatcher", "-config", config_file_path], elem_dir)
+        self._write_elem_conf(
+            elem, ["bin/godispatcher", "-config", config_file_path], elem_dir)
 
     def _common_entry(self, name, cmd_args, elem_dir=None):
         entry = {
diff --git a/python/topology/topo.py b/python/topology/topo.py
index 3bb82e0fd..84c533255 100755
--- a/python/topology/topo.py
+++ b/python/topology/topo.py
@@ -222,25 +222,32 @@ class TopoGenerator(object):
         count = self._srv_count(as_conf, conf_key, def_num)
         for i in range(1, count + 1):
             elem_id = "%s%s-%s" % (nick, topo_id.file_fmt(), i)
-            reg_id = reg_id_func(elem_id) if reg_id_func else self._reg_id_disp(topo_id, elem_id)
+            reg_id = reg_id_func(
+                elem_id) if reg_id_func else self._reg_id_disp(topo_id, elem_id)
+
+            address_lookup_reg_id = reg_id
+            address_lookup_elem_id = elem_id
+            if self.args.monolith and (nick == "bs" or nick == "cs" or nick == "ps"):
+                address_lookup_reg_id = "cs"
+                address_lookup_elem_id = "%s%s-%s" % (
+                    address_lookup_reg_id, topo_id.file_fmt(), i)
             d = {
                 'Addrs': {
                     addr_type: {
                         'Public': {
-                            'Addr': self._reg_addr(topo_id, reg_id, addr_type),
-                            'L4Port': self.args.port_gen.register(elem_id),
+                            'Addr': self._reg_addr(topo_id, address_lookup_reg_id, addr_type),
+                            'L4Port': self.args.port_gen.register(address_lookup_elem_id),
                         }
                     }
                 }
             }
-            self.topo_dicts[topo_id][topo_key][elem_id] = d
+            self.topo_dicts[topo_id][topo_key][address_lookup_elem_id] = d
 
     def _reg_id_disp(self, topo_id, elem_id):
         return "disp" + topo_id.file_fmt() if self.args.docker else elem_id
 
     def _srv_count(self, as_conf, conf_key, def_num):
         count = as_conf.get(conf_key, def_num)
-        # only a single Go-PS/Go-CS per AS is currently supported
         if conf_key in ["path_servers", "certificate_servers", "beacon_servers"]:
             count = 1
         if conf_key == "discovery_servers" and not self.args.discovery:
@@ -297,16 +304,16 @@ class TopoGenerator(object):
             'PublicOverlay': {
                 'Addr': public_addr,
                 'OverlayPort': SCION_ROUTER_PORT
-                },
+            },
             'RemoteOverlay': {
                 'Addr': remote_addr,
                 'OverlayPort': SCION_ROUTER_PORT
-                },
+            },
             'Bandwidth': attrs.get('bw', DEFAULT_LINK_BW),
             'ISD_AS': str(remote),
             'LinkTo': LinkType.to_str(remote_type.lower()),
             'MTU': attrs.get('mtu', DEFAULT_MTU)
-            }
+        }
 
     def _gen_sig_entries(self, topo_id, as_conf):
         addr_type = addr_type_from_underlay(as_conf.get('underlay', DEFAULT_UNDERLAY))
@@ -374,6 +381,7 @@ class LinkEP(TopoID):
 
 class IFIDGenerator(object):
     """Generates unique interface IDs"""
+
     def __init__(self):
         self._ifids = set()
 


Test patch:
diff --git a/go/beacon_srv/internal/config/config_test.go b/go/beacon_srv/internal/config/config_test.go
index 2f985fcfa..67a452812 100644
--- a/go/beacon_srv/internal/config/config_test.go
+++ b/go/beacon_srv/internal/config/config_test.go
@@ -22,7 +22,8 @@ import (
 	"github.com/BurntSushi/toml"
 	"github.com/stretchr/testify/assert"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beaconstorage/beaconstoragetest"
+	"github.com/scionproto/scion/go/cs/beaconstorage/beaconstoragetest"
+	controlconfig "github.com/scionproto/scion/go/cs/config"
 	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
 	"github.com/scionproto/scion/go/lib/env/envtest"
 	"github.com/scionproto/scion/go/lib/infra/modules/idiscovery/idiscoverytest"
@@ -64,11 +65,11 @@ func InitTestConfig(cfg *Config) {
 	InitTestBSConfig(&cfg.BS)
 }
 
-func InitTestBSConfig(cfg *BSConfig) {
+func InitTestBSConfig(cfg *controlconfig.BSConfig) {
 	InitTestPolicies(&cfg.Policies)
 }
 
-func InitTestPolicies(cfg *Policies) {
+func InitTestPolicies(cfg *controlconfig.Policies) {
 	cfg.Propagation = "test"
 	cfg.CoreRegistration = "test"
 	cfg.UpRegistration = "test"
@@ -83,7 +84,7 @@ func CheckTestConfig(t *testing.T, cfg *Config, id string) {
 	CheckTestBSConfig(t, &cfg.BS)
 }
 
-func CheckTestBSConfig(t *testing.T, cfg *BSConfig) {
+func CheckTestBSConfig(t *testing.T, cfg *controlconfig.BSConfig) {
 	assert.Equal(t, DefaultKeepaliveTimeout, cfg.KeepaliveTimeout.Duration)
 	assert.Equal(t, DefaultKeepaliveInterval, cfg.KeepaliveInterval.Duration)
 	assert.Equal(t, DefaultOriginationInterval, cfg.OriginationInterval.Duration)
@@ -95,7 +96,7 @@ func CheckTestBSConfig(t *testing.T, cfg *BSConfig) {
 	CheckTestPolicies(t, &cfg.Policies)
 }
 
-func CheckTestPolicies(t *testing.T, cfg *Policies) {
+func CheckTestPolicies(t *testing.T, cfg *controlconfig.Policies) {
 	assert.Empty(t, cfg.Propagation)
 	assert.Empty(t, cfg.CoreRegistration)
 	assert.Empty(t, cfg.UpRegistration)
diff --git a/go/cert_srv/internal/config/config_test.go b/go/cert_srv/internal/config/config_test.go
index a7111f49f..330eeed5e 100644
--- a/go/cert_srv/internal/config/config_test.go
+++ b/go/cert_srv/internal/config/config_test.go
@@ -23,6 +23,7 @@ import (
 	"github.com/BurntSushi/toml"
 	"github.com/stretchr/testify/assert"
 
+	controlconfig "github.com/scionproto/scion/go/cs/config"
 	"github.com/scionproto/scion/go/lib/env/envtest"
 	"github.com/scionproto/scion/go/lib/infra/modules/idiscovery/idiscoverytest"
 	"github.com/scionproto/scion/go/lib/truststorage/truststoragetest"
@@ -77,7 +78,7 @@ func InitTestConfig(cfg *Config) {
 	InitTestCSConfig(&cfg.CS)
 }
 
-func InitTestCSConfig(cfg *CSConfig) {
+func InitTestCSConfig(cfg *controlconfig.CSConfig) {
 	cfg.AutomaticRenewal = true
 	cfg.DisableCorePush = true
 }
@@ -89,7 +90,7 @@ func CheckTestConfig(t *testing.T, cfg *Config, id string) {
 	CheckTestCSConfig(t, &cfg.CS)
 }
 
-func CheckTestCSConfig(t *testing.T, cfg *CSConfig) {
+func CheckTestCSConfig(t *testing.T, cfg *controlconfig.CSConfig) {
 	assert.Equal(t, ReissReqRate, cfg.ReissueRate.Duration)
 	assert.Equal(t, ReissueReqTimeout, cfg.ReissueTimeout.Duration)
 	assert.False(t, cfg.AutomaticRenewal)
diff --git a/go/beacon_srv/internal/beacon/beacondbsqlite/db_test.go b/go/cs/beacon/beacondbsqlite/db_test.go
similarity index 95%
rename from go/beacon_srv/internal/beacon/beacondbsqlite/db_test.go
rename to go/cs/beacon/beacondbsqlite/db_test.go
index 1f4b0fde1..4d714fa30 100644
--- a/go/beacon_srv/internal/beacon/beacondbsqlite/db_test.go
+++ b/go/cs/beacon/beacondbsqlite/db_test.go
@@ -28,8 +28,8 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon/beacondbtest"
+	"github.com/scionproto/scion/go/cs/beacon"
+	"github.com/scionproto/scion/go/cs/beacon/beacondbtest"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/xtest"
 )
diff --git a/go/beacon_srv/internal/beacon/beacondbsqlite/schema.go b/go/cs/beacon/beacondbsqlite/schema.go
similarity index 100%
rename from go/beacon_srv/internal/beacon/beacondbsqlite/schema.go
rename to go/cs/beacon/beacondbsqlite/schema.go
diff --git a/go/beacon_srv/internal/beacon/beacondbtest/BUILD.bazel b/go/cs/beacon/beacondbtest/BUILD.bazel
similarity index 84%
rename from go/beacon_srv/internal/beacon/beacondbtest/BUILD.bazel
rename to go/cs/beacon/beacondbtest/BUILD.bazel
index 3a1cdb427..061bda0c8 100644
--- a/go/beacon_srv/internal/beacon/beacondbtest/BUILD.bazel
+++ b/go/cs/beacon/beacondbtest/BUILD.bazel
@@ -3,10 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
 go_library(
     name = "go_default_library",
     srcs = ["beacondbtest.go"],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/beacon/beacondbtest",
+    importpath = "github.com/scionproto/scion/go/cs/beacon/beacondbtest",
     visibility = ["//visibility:public"],
     deps = [
-        "//go/beacon_srv/internal/beacon:go_default_library",
+        "//go/cs/beacon:go_default_library",
         "//go/lib/addr:go_default_library",
         "//go/lib/common:go_default_library",
         "//go/lib/ctrl/path_mgmt:go_default_library",
diff --git a/go/beacon_srv/internal/beacon/beacondbtest/beacondbtest.go b/go/cs/beacon/beacondbtest/beacondbtest.go
similarity index 99%
rename from go/beacon_srv/internal/beacon/beacondbtest/beacondbtest.go
rename to go/cs/beacon/beacondbtest/beacondbtest.go
index 476ff656d..48cba68af 100644
--- a/go/beacon_srv/internal/beacon/beacondbtest/beacondbtest.go
+++ b/go/cs/beacon/beacondbtest/beacondbtest.go
@@ -24,7 +24,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
+	"github.com/scionproto/scion/go/cs/beacon"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
diff --git a/go/beacon_srv/internal/beacon/db.go b/go/cs/beacon/db.go
similarity index 100%
rename from go/beacon_srv/internal/beacon/db.go
rename to go/cs/beacon/db.go
diff --git a/go/beacon_srv/internal/beacon/hp_policy.go b/go/cs/beacon/hp_policy.go
similarity index 100%
rename from go/beacon_srv/internal/beacon/hp_policy.go
rename to go/cs/beacon/hp_policy.go
diff --git a/go/beacon_srv/internal/beacon/hp_policy_test.go b/go/cs/beacon/hp_policy_test.go
similarity index 98%
rename from go/beacon_srv/internal/beacon/hp_policy_test.go
rename to go/cs/beacon/hp_policy_test.go
index be6c2db7e..64b075756 100644
--- a/go/beacon_srv/internal/beacon/hp_policy_test.go
+++ b/go/cs/beacon/hp_policy_test.go
@@ -23,7 +23,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
+	"github.com/scionproto/scion/go/cs/beacon"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/hiddenpath"
 	"github.com/scionproto/scion/go/lib/hiddenpath/hiddenpathtest"
diff --git a/go/beacon_srv/internal/beacon/metrics.go b/go/cs/beacon/metrics.go
similarity index 100%
rename from go/beacon_srv/internal/beacon/metrics.go
rename to go/cs/beacon/metrics.go
diff --git a/go/beacon_srv/internal/beacon/metrics_test.go b/go/cs/beacon/metrics_test.go
similarity index 84%
rename from go/beacon_srv/internal/beacon/metrics_test.go
rename to go/cs/beacon/metrics_test.go
index 13fe568e0..5b80d89a2 100644
--- a/go/beacon_srv/internal/beacon/metrics_test.go
+++ b/go/cs/beacon/metrics_test.go
@@ -18,9 +18,9 @@ import (
 	"context"
 	"testing"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon/beacondbsqlite"
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon/beacondbtest"
+	"github.com/scionproto/scion/go/cs/beacon"
+	"github.com/scionproto/scion/go/cs/beacon/beacondbsqlite"
+	"github.com/scionproto/scion/go/cs/beacon/beacondbtest"
 	"github.com/scionproto/scion/go/lib/xtest"
 )
 
diff --git a/go/beacon_srv/internal/beacon/policy.go b/go/cs/beacon/policy.go
similarity index 100%
rename from go/beacon_srv/internal/beacon/policy.go
rename to go/cs/beacon/policy.go
diff --git a/go/beacon_srv/internal/beacon/policy_test.go b/go/cs/beacon/policy_test.go
similarity index 99%
rename from go/beacon_srv/internal/beacon/policy_test.go
rename to go/cs/beacon/policy_test.go
index cd5e54935..a2acc9fdf 100644
--- a/go/beacon_srv/internal/beacon/policy_test.go
+++ b/go/cs/beacon/policy_test.go
@@ -19,7 +19,7 @@ import (
 
 	. "github.com/smartystreets/goconvey/convey"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
+	"github.com/scionproto/scion/go/cs/beacon"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/xtest"
diff --git a/go/beacon_srv/internal/beacon/selection_algo.go b/go/cs/beacon/selection_algo.go
similarity index 100%
rename from go/beacon_srv/internal/beacon/selection_algo.go
rename to go/cs/beacon/selection_algo.go
diff --git a/go/beacon_srv/internal/beacon/store.go b/go/cs/beacon/store.go
similarity index 100%
rename from go/beacon_srv/internal/beacon/store.go
rename to go/cs/beacon/store.go
diff --git a/go/beacon_srv/internal/beacon/store_test.go b/go/cs/beacon/store_test.go
similarity index 98%
rename from go/beacon_srv/internal/beacon/store_test.go
rename to go/cs/beacon/store_test.go
index 537e812c1..84dd6a77d 100644
--- a/go/beacon_srv/internal/beacon/store_test.go
+++ b/go/cs/beacon/store_test.go
@@ -21,8 +21,8 @@ import (
 
 	"github.com/golang/mock/gomock"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon/mock_beacon"
+	"github.com/scionproto/scion/go/cs/beacon"
+	"github.com/scionproto/scion/go/cs/beacon/mock_beacon"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
diff --git a/go/beacon_srv/internal/beaconing/extender_test.go b/go/cs/beaconing/extender_test.go
similarity index 98%
rename from go/beacon_srv/internal/beaconing/extender_test.go
rename to go/cs/beaconing/extender_test.go
index 54e2cc29d..6bdb487cb 100644
--- a/go/beacon_srv/internal/beaconing/extender_test.go
+++ b/go/cs/beaconing/extender_test.go
@@ -24,8 +24,8 @@ import (
 	"github.com/golang/mock/gomock"
 	. "github.com/smartystreets/goconvey/convey"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
+	"github.com/scionproto/scion/go/cs/beacon"
+	"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"
diff --git a/go/beacon_srv/internal/beaconing/handler_test.go b/go/cs/beaconing/handler_test.go
similarity index 97%
rename from go/beacon_srv/internal/beaconing/handler_test.go
rename to go/cs/beaconing/handler_test.go
index 8d05c0b81..c046c9682 100644
--- a/go/beacon_srv/internal/beaconing/handler_test.go
+++ b/go/cs/beaconing/handler_test.go
@@ -23,9 +23,9 @@ import (
 	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/beaconing/mock_beaconing"
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
+	"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/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
diff --git a/go/beacon_srv/internal/beaconing/originator_test.go b/go/cs/beaconing/originator_test.go
similarity index 97%
rename from go/beacon_srv/internal/beaconing/originator_test.go
rename to go/cs/beaconing/originator_test.go
index 7daf0eeff..19b449a32 100644
--- a/go/beacon_srv/internal/beaconing/originator_test.go
+++ b/go/cs/beaconing/originator_test.go
@@ -26,9 +26,9 @@ import (
 	"github.com/golang/mock/gomock"
 	. "github.com/smartystreets/goconvey/convey"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
-	"github.com/scionproto/scion/go/beacon_srv/internal/onehop"
+	"github.com/scionproto/scion/go/cs/beacon"
+	"github.com/scionproto/scion/go/cs/ifstate"
+	"github.com/scionproto/scion/go/cs/onehop"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
diff --git a/go/beacon_srv/internal/beaconing/propagator_test.go b/go/cs/beaconing/propagator_test.go
similarity index 96%
rename from go/beacon_srv/internal/beaconing/propagator_test.go
rename to go/cs/beaconing/propagator_test.go
index c222eb471..b97989fa9 100644
--- a/go/beacon_srv/internal/beaconing/propagator_test.go
+++ b/go/cs/beaconing/propagator_test.go
@@ -25,10 +25,10 @@ import (
 	"github.com/golang/mock/gomock"
 	. "github.com/smartystreets/goconvey/convey"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/beaconing/mock_beaconing"
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
-	"github.com/scionproto/scion/go/beacon_srv/internal/onehop"
+	"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/common"
 	"github.com/scionproto/scion/go/lib/infra/modules/itopo/itopotest"
 	"github.com/scionproto/scion/go/lib/scrypto"
diff --git a/go/beacon_srv/internal/beaconing/registrar_test.go b/go/cs/beaconing/registrar_test.go
similarity index 97%
rename from go/beacon_srv/internal/beaconing/registrar_test.go
rename to go/cs/beaconing/registrar_test.go
index 3e021ae33..eac89d818 100644
--- a/go/beacon_srv/internal/beaconing/registrar_test.go
+++ b/go/cs/beaconing/registrar_test.go
@@ -25,9 +25,9 @@ import (
 	"github.com/golang/mock/gomock"
 	. "github.com/smartystreets/goconvey/convey"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beacon"
-	"github.com/scionproto/scion/go/beacon_srv/internal/beaconing/mock_beaconing"
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
+	"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/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl"
diff --git a/go/beacon_srv/internal/beaconstorage/beaconstoragetest/BUILD.bazel b/go/cs/beaconstorage/beaconstoragetest/BUILD.bazel
similarity index 73%
rename from go/beacon_srv/internal/beaconstorage/beaconstoragetest/BUILD.bazel
rename to go/cs/beaconstorage/beaconstoragetest/BUILD.bazel
index 171293c3d..e754982ea 100644
--- a/go/beacon_srv/internal/beaconstorage/beaconstoragetest/BUILD.bazel
+++ b/go/cs/beaconstorage/beaconstoragetest/BUILD.bazel
@@ -3,10 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 go_library(
     name = "go_default_library",
     srcs = ["config.go"],
-    importpath = "github.com/scionproto/scion/go/beacon_srv/internal/beaconstorage/beaconstoragetest",
+    importpath = "github.com/scionproto/scion/go/cs/beaconstorage/beaconstoragetest",
     visibility = ["//visibility:public"],
     deps = [
-        "//go/beacon_srv/internal/beaconstorage:go_default_library",
+        "//go/cs/beaconstorage:go_default_library",
         "//go/lib/infra/modules/db:go_default_library",
         "//go/lib/util:go_default_library",
         "@com_github_stretchr_testify//assert:go_default_library",
@@ -18,7 +18,7 @@ go_test(
     srcs = ["config_test.go"],
     embed = [":go_default_library"],
     deps = [
-        "//go/beacon_srv/internal/beaconstorage:go_default_library",
+        "//go/cs/beaconstorage:go_default_library",
         "//go/lib/config:go_default_library",
         "@com_github_burntsushi_toml//:go_default_library",
         "@com_github_smartystreets_goconvey//convey:go_default_library",
diff --git a/go/beacon_srv/internal/beaconstorage/beaconstoragetest/config.go b/go/cs/beaconstorage/beaconstoragetest/config.go
similarity index 95%
rename from go/beacon_srv/internal/beaconstorage/beaconstoragetest/config.go
rename to go/cs/beaconstorage/beaconstoragetest/config.go
index 72b9a8a39..d0aab3e70 100644
--- a/go/beacon_srv/internal/beaconstorage/beaconstoragetest/config.go
+++ b/go/cs/beaconstorage/beaconstoragetest/config.go
@@ -20,7 +20,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beaconstorage"
+	"github.com/scionproto/scion/go/cs/beaconstorage"
 	"github.com/scionproto/scion/go/lib/infra/modules/db"
 	"github.com/scionproto/scion/go/lib/util"
 )
diff --git a/go/beacon_srv/internal/beaconstorage/beaconstoragetest/config_test.go b/go/cs/beaconstorage/beaconstoragetest/config_test.go
similarity index 94%
rename from go/beacon_srv/internal/beaconstorage/beaconstoragetest/config_test.go
rename to go/cs/beaconstorage/beaconstoragetest/config_test.go
index b979ad1b4..26c217208 100644
--- a/go/beacon_srv/internal/beaconstorage/beaconstoragetest/config_test.go
+++ b/go/cs/beaconstorage/beaconstoragetest/config_test.go
@@ -21,7 +21,7 @@ import (
 	"github.com/BurntSushi/toml"
 	. "github.com/smartystreets/goconvey/convey"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/beaconstorage"
+	"github.com/scionproto/scion/go/cs/beaconstorage"
 	"github.com/scionproto/scion/go/lib/config"
 )
 
diff --git a/go/cs/config/config_test.go b/go/cs/config/config_test.go
new file mode 100644
index 000000000..5394dfb2f
--- /dev/null
+++ b/go/cs/config/config_test.go
@@ -0,0 +1,134 @@
+// 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 config
+
+import (
+	"bytes"
+	"testing"
+	"time"
+
+	"github.com/BurntSushi/toml"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/scionproto/scion/go/cs/beaconstorage/beaconstoragetest"
+	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
+	"github.com/scionproto/scion/go/lib/env/envtest"
+	"github.com/scionproto/scion/go/lib/infra/modules/idiscovery/idiscoverytest"
+	"github.com/scionproto/scion/go/lib/pathstorage/pathstoragetest"
+	"github.com/scionproto/scion/go/lib/truststorage/truststoragetest"
+	"github.com/scionproto/scion/go/lib/util"
+)
+
+func TestConfigSample(t *testing.T) {
+	var sample bytes.Buffer
+	var cfg Config
+	cfg.Sample(&sample, nil, nil)
+
+	InitTestConfig(&cfg)
+	meta, err := toml.Decode(sample.String(), &cfg)
+	assert.NoError(t, err)
+	assert.Empty(t, meta.Undecoded())
+	CheckTestConfig(t, &cfg, idSample)
+}
+
+func TestInvalidTTL(t *testing.T) {
+	cfg := BSConfig{}
+	cfg.InitDefaults()
+	err := cfg.Validate()
+	assert.NoError(t, err)
+	cfg.RevOverlap = util.DurWrap{Duration: cfg.RevTTL.Duration + time.Second}
+	err = cfg.Validate()
+	assert.Error(t, err)
+	cfg.InitDefaults()
+	cfg.RevTTL = util.DurWrap{Duration: path_mgmt.MinRevTTL - time.Second}
+	err = cfg.Validate()
+	assert.Error(t, err)
+}
+
+func InitTestConfig(cfg *Config) {
+	envtest.InitTest(&cfg.General, &cfg.Logging, &cfg.Metrics, &cfg.Tracing, nil)
+	truststoragetest.InitTestConfig(&cfg.TrustDB)
+	beaconstoragetest.InitTestBeaconDBConf(&cfg.BeaconDB)
+	idiscoverytest.InitTestConfig(&cfg.Discovery)
+	InitTestBSConfig(&cfg.BS)
+}
+
+func InitTestBSConfig(cfg *BSConfig) {
+	InitTestPolicies(&cfg.Policies)
+}
+
+func InitTestPolicies(cfg *Policies) {
+	cfg.Propagation = "test"
+	cfg.CoreRegistration = "test"
+	cfg.UpRegistration = "test"
+	cfg.DownRegistration = "test"
+}
+
+func CheckTestConfig(t *testing.T, cfg *Config, id string) {
+	envtest.CheckTest(t, &cfg.General, &cfg.Logging, &cfg.Metrics, &cfg.Tracing, nil, id)
+	truststoragetest.CheckTestConfig(t, &cfg.TrustDB, id)
+	beaconstoragetest.CheckTestBeaconDBConf(t, &cfg.BeaconDB, id)
+	idiscoverytest.CheckTestConfig(t, &cfg.Discovery)
+	CheckTestBSConfig(t, &cfg.BS)
+	CheckTestCSConfig(t, &cfg.CS)
+	CheckTestPSConfig(t, &cfg.PS, id)
+}
+
+func CheckTestBSConfig(t *testing.T, cfg *BSConfig) {
+	assert.Equal(t, DefaultKeepaliveTimeout, cfg.KeepaliveTimeout.Duration)
+	assert.Equal(t, DefaultKeepaliveInterval, cfg.KeepaliveInterval.Duration)
+	assert.Equal(t, DefaultOriginationInterval, cfg.OriginationInterval.Duration)
+	assert.Equal(t, DefaultPropagationInterval, cfg.PropagationInterval.Duration)
+	assert.Equal(t, DefaultRegistrationInterval, cfg.RegistrationInterval.Duration)
+	assert.Equal(t, DefaultExpiredCheckInterval, cfg.ExpiredCheckInterval.Duration)
+	assert.Equal(t, DefaultRevTTL, cfg.RevTTL.Duration)
+	assert.Equal(t, DefaultRevOverlap, cfg.RevOverlap.Duration)
+	CheckTestPolicies(t, &cfg.Policies)
+}
+
+func CheckTestPolicies(t *testing.T, cfg *Policies) {
+	assert.Empty(t, cfg.Propagation)
+	assert.Empty(t, cfg.CoreRegistration)
+	assert.Empty(t, cfg.UpRegistration)
+	assert.Empty(t, cfg.DownRegistration)
+}
+
+func InitTestCSConfig(cfg *CSConfig) {
+	cfg.AutomaticRenewal = true
+	cfg.DisableCorePush = true
+}
+
+func CheckTestCSConfig(t *testing.T, cfg *CSConfig) {
+	assert.Equal(t, ReissReqRate, cfg.ReissueRate.Duration)
+	assert.Equal(t, ReissueReqTimeout, cfg.ReissueTimeout.Duration)
+	assert.False(t, cfg.AutomaticRenewal)
+	assert.Equal(t, LeafReissTime, cfg.LeafReissueLeadTime.Duration)
+	assert.Equal(t, IssuerReissTime, cfg.IssuerReissueLeadTime.Duration)
+	assert.False(t, cfg.DisableCorePush)
+}
+
+func InitTestPSConfig(cfg *PSConfig) {
+	cfg.SegSync = true
+	pathstoragetest.InitTestPathDBConf(&cfg.PathDB)
+	pathstoragetest.InitTestRevCacheConf(&cfg.RevCache)
+}
+
+func CheckTestPSConfig(t *testing.T, cfg *PSConfig, id string) {
+	pathstoragetest.CheckTestPathDBConf(t, &cfg.PathDB, id)
+	pathstoragetest.CheckTestRevCacheConf(t, &cfg.RevCache)
+	assert.False(t, cfg.SegSync)
+	assert.Equal(t, DefaultQueryInterval, cfg.QueryInterval.Duration)
+	assert.Equal(t, DefaultCryptoSyncInterval, cfg.CryptoSyncInterval.Duration)
+}
diff --git a/go/beacon_srv/internal/ifstate/revoker_test.go b/go/cs/ifstate/revoker_test.go
similarity index 99%
rename from go/beacon_srv/internal/ifstate/revoker_test.go
rename to go/cs/ifstate/revoker_test.go
index a1991d240..3b9029e68 100644
--- a/go/beacon_srv/internal/ifstate/revoker_test.go
+++ b/go/cs/ifstate/revoker_test.go
@@ -26,7 +26,7 @@ import (
 	"github.com/golang/mock/gomock"
 	. "github.com/smartystreets/goconvey/convey"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate/mock_ifstate"
+	"github.com/scionproto/scion/go/cs/ifstate/mock_ifstate"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl"
diff --git a/go/beacon_srv/internal/keepalive/handler_test.go b/go/cs/keepalive/handler_test.go
similarity index 97%
rename from go/beacon_srv/internal/keepalive/handler_test.go
rename to go/cs/keepalive/handler_test.go
index c7f37abe0..3810fe17b 100644
--- a/go/beacon_srv/internal/keepalive/handler_test.go
+++ b/go/cs/keepalive/handler_test.go
@@ -26,8 +26,8 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/ifstate"
-	"github.com/scionproto/scion/go/beacon_srv/internal/keepalive/mock_keepalive"
+	"github.com/scionproto/scion/go/cs/ifstate"
+	"github.com/scionproto/scion/go/cs/keepalive/mock_keepalive"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl"
 	"github.com/scionproto/scion/go/lib/ctrl/ifid"
diff --git a/go/beacon_srv/internal/keepalive/sender_test.go b/go/cs/keepalive/sender_test.go
similarity index 98%
rename from go/beacon_srv/internal/keepalive/sender_test.go
rename to go/cs/keepalive/sender_test.go
index 3d2aee102..224a46b4b 100644
--- a/go/beacon_srv/internal/keepalive/sender_test.go
+++ b/go/cs/keepalive/sender_test.go
@@ -23,7 +23,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/onehop"
+	"github.com/scionproto/scion/go/cs/onehop"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl"
 	"github.com/scionproto/scion/go/lib/infra"
diff --git a/go/beacon_srv/internal/metrics/metrics_test.go b/go/cs/metrics/metrics_test.go
similarity index 94%
rename from go/beacon_srv/internal/metrics/metrics_test.go
rename to go/cs/metrics/metrics_test.go
index a0165a308..93c5fd498 100644
--- a/go/beacon_srv/internal/metrics/metrics_test.go
+++ b/go/cs/metrics/metrics_test.go
@@ -17,7 +17,7 @@ package metrics_test
 import (
 	"testing"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/metrics"
+	"github.com/scionproto/scion/go/cs/metrics"
 	"github.com/scionproto/scion/go/lib/prom/promtest"
 )
 
diff --git a/go/beacon_srv/internal/metrics/originator.go b/go/cs/metrics/originator.go
similarity index 100%
rename from go/beacon_srv/internal/metrics/originator.go
rename to go/cs/metrics/originator.go
diff --git a/go/beacon_srv/internal/metrics/propagator.go b/go/cs/metrics/propagator.go
similarity index 100%
rename from go/beacon_srv/internal/metrics/propagator.go
rename to go/cs/metrics/propagator.go
diff --git a/go/beacon_srv/internal/metrics/registrar.go b/go/cs/metrics/registrar.go
similarity index 100%
rename from go/beacon_srv/internal/metrics/registrar.go
rename to go/cs/metrics/registrar.go
diff --git a/go/path_srv/internal/metrics/registration.go b/go/cs/metrics/registration.go
similarity index 100%
rename from go/path_srv/internal/metrics/registration.go
rename to go/cs/metrics/registration.go
diff --git a/go/path_srv/internal/metrics/registration_test.go b/go/cs/metrics/registration_test.go
similarity index 92%
rename from go/path_srv/internal/metrics/registration_test.go
rename to go/cs/metrics/registration_test.go
index 8ebe147cf..5219c29bf 100644
--- a/go/path_srv/internal/metrics/registration_test.go
+++ b/go/cs/metrics/registration_test.go
@@ -17,8 +17,8 @@ package metrics_test
 import (
 	"testing"
 
+	"github.com/scionproto/scion/go/cs/metrics"
 	"github.com/scionproto/scion/go/lib/prom/promtest"
-	"github.com/scionproto/scion/go/path_srv/internal/metrics"
 )
 
 func TestRegistrationsLabels(t *testing.T) {
diff --git a/go/path_srv/internal/metrics/requests.go b/go/cs/metrics/requests.go
similarity index 100%
rename from go/path_srv/internal/metrics/requests.go
rename to go/cs/metrics/requests.go
diff --git a/go/path_srv/internal/metrics/requests_test.go b/go/cs/metrics/requests_test.go
similarity index 93%
rename from go/path_srv/internal/metrics/requests_test.go
rename to go/cs/metrics/requests_test.go
index 1a73ab628..e3a79053a 100644
--- a/go/path_srv/internal/metrics/requests_test.go
+++ b/go/cs/metrics/requests_test.go
@@ -17,8 +17,8 @@ package metrics_test
 import (
 	"testing"
 
+	"github.com/scionproto/scion/go/cs/metrics"
 	"github.com/scionproto/scion/go/lib/prom/promtest"
-	"github.com/scionproto/scion/go/path_srv/internal/metrics"
 )
 
 func TestRequestLabels(t *testing.T) {
diff --git a/go/path_srv/internal/metrics/revocation_test.go b/go/cs/metrics/revocation_test.go
similarity index 92%
rename from go/path_srv/internal/metrics/revocation_test.go
rename to go/cs/metrics/revocation_test.go
index 739abd42f..197003455 100644
--- a/go/path_srv/internal/metrics/revocation_test.go
+++ b/go/cs/metrics/revocation_test.go
@@ -17,8 +17,8 @@ package metrics_test
 import (
 	"testing"
 
+	"github.com/scionproto/scion/go/cs/metrics"
 	"github.com/scionproto/scion/go/lib/prom/promtest"
-	"github.com/scionproto/scion/go/path_srv/internal/metrics"
 )
 
 func TestRevocationLabels(t *testing.T) {
diff --git a/go/path_srv/internal/metrics/sync.go b/go/cs/metrics/sync.go
similarity index 100%
rename from go/path_srv/internal/metrics/sync.go
rename to go/cs/metrics/sync.go
diff --git a/go/path_srv/internal/metrics/sync_test.go b/go/cs/metrics/sync_test.go
similarity index 93%
rename from go/path_srv/internal/metrics/sync_test.go
rename to go/cs/metrics/sync_test.go
index 010087b22..46f6324ea 100644
--- a/go/path_srv/internal/metrics/sync_test.go
+++ b/go/cs/metrics/sync_test.go
@@ -17,8 +17,8 @@ package metrics_test
 import (
 	"testing"
 
+	"github.com/scionproto/scion/go/cs/metrics"
 	"github.com/scionproto/scion/go/lib/prom/promtest"
-	"github.com/scionproto/scion/go/path_srv/internal/metrics"
 )
 
 func TestSyncLabels(t *testing.T) {
diff --git a/go/beacon_srv/internal/revocation/handler_test.go b/go/cs/revocation/handler_test.go
similarity index 98%
rename from go/beacon_srv/internal/revocation/handler_test.go
rename to go/cs/revocation/handler_test.go
index 581daf79f..87a99762c 100644
--- a/go/beacon_srv/internal/revocation/handler_test.go
+++ b/go/cs/revocation/handler_test.go
@@ -22,7 +22,7 @@ import (
 
 	"github.com/golang/mock/gomock"
 
-	"github.com/scionproto/scion/go/beacon_srv/internal/revocation/mock_revocation"
+	"github.com/scionproto/scion/go/cs/revocation/mock_revocation"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl"
 	"github.com/scionproto/scion/go/lib/ctrl/ack"
diff --git a/go/path_srv/internal/segreq/db.go b/go/cs/segreq/db.go
similarity index 100%
rename from go/path_srv/internal/segreq/db.go
rename to go/cs/segreq/db.go
diff --git a/go/path_srv/internal/segreq/db_test.go b/go/cs/segreq/db_test.go
similarity index 98%
rename from go/path_srv/internal/segreq/db_test.go
rename to go/cs/segreq/db_test.go
index f63418700..d1a9e6a55 100644
--- a/go/path_srv/internal/segreq/db_test.go
+++ b/go/cs/segreq/db_test.go
@@ -24,12 +24,12 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
+	"github.com/scionproto/scion/go/cs/segreq"
+	"github.com/scionproto/scion/go/cs/segreq/mock_segreq"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/pathdb/mock_pathdb"
 	"github.com/scionproto/scion/go/lib/pathdb/query"
 	"github.com/scionproto/scion/go/lib/xtest"
-	"github.com/scionproto/scion/go/path_srv/internal/segreq"
-	"github.com/scionproto/scion/go/path_srv/internal/segreq/mock_segreq"
 )
 
 func TestPSPathDBGet(t *testing.T) {
diff --git a/go/path_srv/internal/segreq/helpers.go b/go/cs/segreq/helpers.go
similarity index 100%
rename from go/path_srv/internal/segreq/helpers.go
rename to go/cs/segreq/helpers.go
diff --git a/go/path_srv/internal/segreq/helpers_test.go b/go/cs/segreq/helpers_test.go
similarity index 97%
rename from go/path_srv/internal/segreq/helpers_test.go
rename to go/cs/segreq/helpers_test.go
index 376651b6f..f7adb9d8e 100644
--- a/go/path_srv/internal/segreq/helpers_test.go
+++ b/go/cs/segreq/helpers_test.go
@@ -23,10 +23,10 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
+	"github.com/scionproto/scion/go/cs/segreq"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/infra/mock_infra"
 	"github.com/scionproto/scion/go/lib/xtest"
-	"github.com/scionproto/scion/go/path_srv/internal/segreq"
 )
 
 func TestCoreChecker(t *testing.T) {
diff --git a/go/path_srv/internal/segreq/provider.go b/go/cs/segreq/provider.go
similarity index 100%
rename from go/path_srv/internal/segreq/provider.go
rename to go/cs/segreq/provider.go
diff --git a/go/path_srv/internal/segreq/provider_test.go b/go/cs/segreq/provider_test.go
similarity index 98%
rename from go/path_srv/internal/segreq/provider_test.go
rename to go/cs/segreq/provider_test.go
index 7980a2774..f58f5eb81 100644
--- a/go/path_srv/internal/segreq/provider_test.go
+++ b/go/cs/segreq/provider_test.go
@@ -23,13 +23,13 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
+	"github.com/scionproto/scion/go/cs/segreq"
 	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/pathdb/mock_pathdb"
 	"github.com/scionproto/scion/go/lib/pathdb/query"
 	"github.com/scionproto/scion/go/lib/revcache"
 	"github.com/scionproto/scion/go/lib/revcache/mock_revcache"
-	"github.com/scionproto/scion/go/path_srv/internal/segreq"
 )
 
 func TestSegSelector(t *testing.T) {
diff --git a/go/path_srv/internal/segreq/splitter.go b/go/cs/segreq/splitter.go
similarity index 100%
rename from go/path_srv/internal/segreq/splitter.go
rename to go/cs/segreq/splitter.go
diff --git a/go/path_srv/internal/segreq/splitter_test.go b/go/cs/segreq/splitter_test.go
similarity index 98%
rename from go/path_srv/internal/segreq/splitter_test.go
rename to go/cs/segreq/splitter_test.go
index d2c44872a..422eb6ed8 100644
--- a/go/path_srv/internal/segreq/splitter_test.go
+++ b/go/cs/segreq/splitter_test.go
@@ -23,11 +23,11 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
+	"github.com/scionproto/scion/go/cs/segreq"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/infra/mock_infra"
 	"github.com/scionproto/scion/go/lib/infra/modules/segfetcher"
 	"github.com/scionproto/scion/go/lib/xtest"
-	"github.com/scionproto/scion/go/path_srv/internal/segreq"
 )
 
 func TestSplitter(t *testing.T) {
diff --git a/go/path_srv/internal/segreq/validator.go b/go/cs/segreq/validator.go
similarity index 100%
rename from go/path_srv/internal/segreq/validator.go
rename to go/cs/segreq/validator.go
diff --git a/go/path_srv/internal/segreq/validator_test.go b/go/cs/segreq/validator_test.go
similarity index 98%
rename from go/path_srv/internal/segreq/validator_test.go
rename to go/cs/segreq/validator_test.go
index 8022567a3..c5bdbd8ef 100644
--- a/go/path_srv/internal/segreq/validator_test.go
+++ b/go/cs/segreq/validator_test.go
@@ -23,11 +23,11 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
+	"github.com/scionproto/scion/go/cs/segreq"
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/infra/mock_infra"
 	"github.com/scionproto/scion/go/lib/infra/modules/segfetcher"
 	"github.com/scionproto/scion/go/lib/xtest"
-	"github.com/scionproto/scion/go/path_srv/internal/segreq"
 )
 
 type validatorTest struct {
diff --git a/go/path_srv/internal/segutil/filter.go b/go/cs/segutil/filter.go
similarity index 100%
rename from go/path_srv/internal/segutil/filter.go
rename to go/cs/segutil/filter.go
diff --git a/go/path_srv/internal/segutil/filter_test.go b/go/cs/segutil/filter_test.go
similarity index 95%
rename from go/path_srv/internal/segutil/filter_test.go
rename to go/cs/segutil/filter_test.go
index f7116917b..6a085d396 100644
--- a/go/path_srv/internal/segutil/filter_test.go
+++ b/go/cs/segutil/filter_test.go
@@ -21,12 +21,12 @@ import (
 	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
 
+	"github.com/scionproto/scion/go/cs/segutil"
+	"github.com/scionproto/scion/go/cs/segutil/mock_segutil"
 	"github.com/scionproto/scion/go/lib/common"
 	"github.com/scionproto/scion/go/lib/ctrl/seg"
 	"github.com/scionproto/scion/go/lib/pathpol"
 	"github.com/scionproto/scion/go/lib/xtest/graph"
-	"github.com/scionproto/scion/go/path_srv/internal/segutil"
-	"github.com/scionproto/scion/go/path_srv/internal/segutil/mock_segutil"
 )
 
 func TestFilter(t *testing.T) {
diff --git a/go/godispatcher/internal/registration/svctable_test.go b/go/godispatcher/internal/registration/svctable_test.go
index 99a1cffb0..b8b2ab243 100644
--- a/go/godispatcher/internal/registration/svctable_test.go
+++ b/go/godispatcher/internal/registration/svctable_test.go
@@ -21,6 +21,8 @@ import (
 	"testing"
 
 	. "github.com/smartystreets/goconvey/convey"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 	"github.com/scionproto/scion/go/lib/addr"
 	"github.com/scionproto/scion/go/lib/xtest"
@@ -155,7 +157,6 @@ func TestSVCTableOneItem(t *testing.T) {
 			SoMsg("values", retValues, ShouldResemble, []interface{}{value})
 		})
 	})
-
 }
 
 func TestSVCTableTwoItems(t *testing.T) {
@@ -326,3 +327,113 @@ func runRandomRegistrations(count int, table SVCTable) []Reference {
 	}
 	return references
 }
+
+func TestSVCTableWildcard(t *testing.T) {
+	address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080}
+	value := "test value"
+
+	table := NewSVCTable()
+	reference, err := table.Register(addr.SvcWildcard, address, value)
+	require.NoError(t, err)
+	defer reference.Free()
+
+	testCases := []*struct {
+		Name              string
+		Address           addr.HostSVC
+		LookupResultCount int
+	}{
+		{
+			Name:              "bs",
+			Address:           addr.SvcBS.Multicast(),
+			LookupResultCount: 1,
+		},
+		{
+			Name:              "cs",
+			Address:           addr.SvcCS.Multicast(),
+			LookupResultCount: 1,
+		},
+		{
+			Name:              "ps",
+			Address:           addr.SvcPS.Multicast(),
+			LookupResultCount: 1,
+		},
+		{
+			Name:              "sig",
+			Address:           addr.SvcSIG.Multicast(),
+			LookupResultCount: 0,
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.Name, func(t *testing.T) {
+			retValues := table.Lookup(tc.Address, nil)
+			assert.Equal(t, tc.LookupResultCount, len(retValues))
+		})
+	}
+}
+
+func TestSVCTableWildcardFree(t *testing.T) {
+	address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080}
+	value := "test value"
+
+	table := NewSVCTable()
+	reference, err := table.Register(addr.SvcWildcard, address, value)
+	require.NoError(t, err)
+	reference.Free()
+
+	assert.Equal(t, 0, len(table.Lookup(addr.SvcBS, nil)))
+	assert.Equal(t, 0, len(table.Lookup(addr.SvcCS, nil)))
+	assert.Equal(t, 0, len(table.Lookup(addr.SvcPS, nil)))
+}
+
+func TestSVCTableWildcardRollback(t *testing.T) {
+	// If any SVC registration fails on a wildcard, none should remain
+	address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080}
+	value := "test value"
+
+	table := NewSVCTable()
+
+	testCases := []*struct {
+		Name                string
+		RegisteredAddress   addr.HostSVC
+		LookupResultBSCount int
+		LookupResultCSCount int
+		LookupResultPSCount int
+	}{
+		{
+			Name:                "bs",
+			RegisteredAddress:   addr.SvcBS,
+			LookupResultBSCount: 1,
+			LookupResultCSCount: 0,
+			LookupResultPSCount: 0,
+		},
+		{
+			Name:                "cs",
+			RegisteredAddress:   addr.SvcCS,
+			LookupResultBSCount: 0,
+			LookupResultCSCount: 1,
+			LookupResultPSCount: 0,
+		},
+		{
+			Name:                "ps",
+			RegisteredAddress:   addr.SvcPS,
+			LookupResultBSCount: 0,
+			LookupResultCSCount: 0,
+			LookupResultPSCount: 1,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.Name, func(t *testing.T) {
+			reference, err := table.Register(tc.RegisteredAddress, address, value)
+			require.NoError(t, err)
+			defer reference.Free()
+
+			_, err = table.Register(addr.SvcWildcard, address, value)
+			assert.Error(t, err)
+
+			assert.Equal(t, tc.LookupResultBSCount, len(table.Lookup(addr.SvcBS, nil)))
+			assert.Equal(t, tc.LookupResultCSCount, len(table.Lookup(addr.SvcCS, nil)))
+			assert.Equal(t, tc.LookupResultPSCount, len(table.Lookup(addr.SvcPS, nil)))
+		})
+	}
+}
diff --git a/go/path_srv/internal/config/config_test.go b/go/path_srv/internal/config/config_test.go
index 0e5753121..68af22b84 100644
--- a/go/path_srv/internal/config/config_test.go
+++ b/go/path_srv/internal/config/config_test.go
@@ -21,6 +21,7 @@ import (
 	"github.com/BurntSushi/toml"
 	"github.com/stretchr/testify/assert"
 
+	controlconfig "github.com/scionproto/scion/go/cs/config"
 	"github.com/scionproto/scion/go/lib/env/envtest"
 	"github.com/scionproto/scion/go/lib/infra/modules/idiscovery/idiscoverytest"
 	"github.com/scionproto/scion/go/lib/pathstorage/pathstoragetest"
@@ -46,7 +47,7 @@ func InitTestConfig(cfg *Config) {
 	InitTestPSConfig(&cfg.PS)
 }
 
-func InitTestPSConfig(cfg *PSConfig) {
+func InitTestPSConfig(cfg *controlconfig.PSConfig) {
 	cfg.SegSync = true
 	pathstoragetest.InitTestPathDBConf(&cfg.PathDB)
 	pathstoragetest.InitTestRevCacheConf(&cfg.RevCache)
@@ -59,7 +60,7 @@ func CheckTestConfig(t *testing.T, cfg *Config, id string) {
 	CheckTestPSConfig(t, &cfg.PS, id)
 }
 
-func CheckTestPSConfig(t *testing.T, cfg *PSConfig, id string) {
+func CheckTestPSConfig(t *testing.T, cfg *controlconfig.PSConfig, id string) {
 	pathstoragetest.CheckTestPathDBConf(t, &cfg.PathDB, id)
 	pathstoragetest.CheckTestRevCacheConf(t, &cfg.RevCache)
 	assert.False(t, cfg.SegSync)