diff --git a/.golangcilint.yml b/.golangcilint.yml index 32b2a7533f..ba3e6dff79 100644 --- a/.golangcilint.yml +++ b/.golangcilint.yml @@ -63,8 +63,3 @@ issues: - path: pkg/scrypto/cms linters: [goheader] - # Exceptions to errcheck for some old-ish convey tests. - - linters: [errcheck] - path: "^pkg/sock/reliable/reconnect/conn_io_test.go$|\ - ^pkg/sock/reliable/reconnect/network_test.go$|\ - ^pkg/sock/reliable/reconnect/reconnecter_test.go$" diff --git a/acceptance/app_vs_endhost_br_dispatch/BUILD.bazel b/acceptance/app_vs_endhost_br_dispatch/BUILD.bazel new file mode 100644 index 0000000000..d3554771cd --- /dev/null +++ b/acceptance/app_vs_endhost_br_dispatch/BUILD.bazel @@ -0,0 +1,9 @@ +load("//acceptance/common:topogen.bzl", "topogen_test") + +topogen_test( + name = "test", + src = "test.py", + args = ["--executable=end2end_integration:$(location //tools/end2end_integration)"], + data = ["//tools/end2end_integration"], + topo = "//acceptance/app_vs_endhost_br_dispatch/testdata:topology.topo", +) diff --git a/acceptance/app_vs_endhost_br_dispatch/test.py b/acceptance/app_vs_endhost_br_dispatch/test.py new file mode 100755 index 0000000000..602e86f7ae --- /dev/null +++ b/acceptance/app_vs_endhost_br_dispatch/test.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +# Copyright 2023 ETH Zurich + +from acceptance.common import base +from acceptance.common import scion + + +class Test(base.TestTopogen): + """ + Constructs a simple test topology with one core, two leaf ASes. + Each of them will run a different mix between BR that will replicate + the legacy endhost-port-dispatch behaviour (i.e., they will send + traffic to its own AS to the endhost default port) and + application-port-dispatch routers (i.e., they will rewrite the underlay + UDP/IP destination port with the UDP/SCION port). + + AS 1-ff00:0:1 is core. + AS 1-ff00:0:2, 1-ff00:0:3 are leaves. + + We use the shortnames AS1, AS2, etc. for the ASes above. + + AS1 contains a BR with the port rewriting configuration to the default + range. It also includes a shim dispatcher. + AS2 contains a BR with a configuration that imitates the old + behaviour, i.e., sending all traffic to default endhost port 30041. + It also includes a shim dispatcher. + AS3 contains a BR with the port rewriting configuration to the default + range. It does not include the shim dispatcher. + """ + + def setup_prepare(self): + super().setup_prepare() + + br_as_2_id = "br1-ff00_0_2-1" + + br_as_2_file = self.artifacts / "gen" / "ASff00_0_2" \ + / ("%s.toml" % br_as_2_id) + scion.update_toml({"router.dispatched_port_start": 0, + "router.dispatched_port_end": 0}, + [br_as_2_file]) + + def setup_start(self): + super().setup_start() + self.await_connectivity() + + def _run(self): + ping_test = self.get_executable("end2end_integration") + ping_test["-d", "-outDir", self.artifacts].run_fg() + + +if __name__ == "__main__": + base.main(Test) diff --git a/acceptance/app_vs_endhost_br_dispatch/testdata/BUILD.bazel b/acceptance/app_vs_endhost_br_dispatch/testdata/BUILD.bazel new file mode 100644 index 0000000000..d803c4eee5 --- /dev/null +++ b/acceptance/app_vs_endhost_br_dispatch/testdata/BUILD.bazel @@ -0,0 +1,3 @@ +exports_files([ + "topology.topo", +]) diff --git a/acceptance/app_vs_endhost_br_dispatch/testdata/topology.topo b/acceptance/app_vs_endhost_br_dispatch/testdata/topology.topo new file mode 100644 index 0000000000..68b249b07e --- /dev/null +++ b/acceptance/app_vs_endhost_br_dispatch/testdata/topology.topo @@ -0,0 +1,15 @@ +--- # Test Topology +ASes: + "1-ff00:0:1": + core: true + voting: true + authoritative: true + issuing: true + "1-ff00:0:2": + cert_issuer: 1-ff00:0:1 + "1-ff00:0:3": + cert_issuer: 1-ff00:0:1 + test_dispatcher: False +links: + - {a: "1-ff00:0:1#2", b: "1-ff00:0:2#1", linkAtoB: CHILD} + - {a: "1-ff00:0:1#3", b: "1-ff00:0:3#1", linkAtoB: CHILD} diff --git a/acceptance/common/docker.py b/acceptance/common/docker.py index c6f56fb2c8..0605214dae 100644 --- a/acceptance/common/docker.py +++ b/acceptance/common/docker.py @@ -61,8 +61,9 @@ def collect_logs(self, out_dir: str = "logs/docker"): for svc in self("config", "--services").splitlines(): # Collect logs. dst_f = out_p / "%s.log" % svc + print(svc) with open(dst_f, "w") as log_file: - cmd.docker.run(args=("logs", svc), stdout=log_file, + cmd.docker.run(args=("logs", "scion-"+svc+"-1"), stdout=log_file, stderr=subprocess.STDOUT, retcode=None) # Collect coredupms. coredump_f = out_p / "%s.coredump" % svc diff --git a/acceptance/router_benchmark/conf/topology.json b/acceptance/router_benchmark/conf/topology.json index 7e53f64275..6e48ac2cf4 100644 --- a/acceptance/router_benchmark/conf/topology.json +++ b/acceptance/router_benchmark/conf/topology.json @@ -4,6 +4,7 @@ ], "isd_as": "1-ff00:0:1", "mtu": 1400, + "dispatched_ports": "1024-65535", "border_routers": { "br1a": { "internal_addr": "10.123.10.1:30042", diff --git a/acceptance/router_multi/conf/topology.json b/acceptance/router_multi/conf/topology.json index 797f489c2a..c8b137b2c2 100644 --- a/acceptance/router_multi/conf/topology.json +++ b/acceptance/router_multi/conf/topology.json @@ -2,6 +2,7 @@ "isd_as": "1-ff00:0:1", "mtu": 1472, "attributes": [], + "dispatched_ports": "1024-65535", "border_routers": { "brA": { "internal_addr": "192.168.0.11:30001", diff --git a/acceptance/sig_short_exp_time/BUILD.bazel b/acceptance/sig_short_exp_time/BUILD.bazel index 5359d34582..8fa03036d9 100644 --- a/acceptance/sig_short_exp_time/BUILD.bazel +++ b/acceptance/sig_short_exp_time/BUILD.bazel @@ -4,7 +4,6 @@ sh_test( srcs = ["test"], data = [ "docker-compose.yml", - "//docker:dispatcher.tarball", "//docker:gateway.tarball", "//tools/udpproxy:udpproxy.tarball", ] + glob(["testdata/**"]), diff --git a/acceptance/sig_short_exp_time/docker-compose.yml b/acceptance/sig_short_exp_time/docker-compose.yml index d5a6d1869b..3286b77531 100644 --- a/acceptance/sig_short_exp_time/docker-compose.yml +++ b/acceptance/sig_short_exp_time/docker-compose.yml @@ -47,7 +47,6 @@ services: bridge1: ipv4_address: 242.254.100.2 volumes: - - vol_scion_disp_sig1-ff00_0_110:/run/shm/dispatcher:rw - ./testdata/1-ff00_0_110/dispatcher:/etc/scion/ command: [ "--config", "/etc/scion/disp.toml" ] dispatcher2: @@ -56,45 +55,46 @@ services: bridge2: ipv4_address: 242.254.200.2 volumes: - - vol_scion_disp_sig1-ff00_0_111:/run/shm/dispatcher:rw - ./testdata/1-ff00_0_111/dispatcher:/etc/scion/ command: [ "--config", "/etc/scion/disp.toml" ] sig1: cap_add: - NET_ADMIN - depends_on: - - dispatcher1 + container_name: sig1 image: scion/gateway:latest - network_mode: service:dispatcher1 + networks: + bridge1: + ipv4_address: 242.254.100.2 privileged: true volumes: - - vol_scion_disp_sig1-ff00_0_110:/run/shm/dispatcher:rw - /dev/net/tun:/dev/net/tun - ./testdata/1-ff00_0_110/sig:/etc/scion/ command: [ "--config", "/etc/scion/sig.toml" ] sig2: cap_add: - NET_ADMIN - depends_on: - - dispatcher2 + container_name: sig2 image: scion/gateway:latest - network_mode: service:dispatcher2 + networks: + bridge2: + ipv4_address: 242.254.200.2 privileged: true volumes: - - vol_scion_disp_sig1-ff00_0_111:/run/shm/dispatcher:rw - /dev/net/tun:/dev/net/tun - ./testdata/1-ff00_0_111/sig:/etc/scion/ command: [ "--config", "/etc/scion/sig.toml" ] tester1: image: alpine - network_mode: service:dispatcher1 + networks: + bridge1: + ipv4_address: 242.254.100.10 privileged: true tester2: image: alpine - network_mode: service:dispatcher2 + networks: + bridge2: + ipv4_address: 242.254.200.10 privileged: true version: '2.4' volumes: vol_logs: null - vol_scion_disp_sig1-ff00_0_110: null - vol_scion_disp_sig1-ff00_0_111: null diff --git a/acceptance/sig_short_exp_time/test b/acceptance/sig_short_exp_time/test index f258743c30..c19f3d2385 100755 --- a/acceptance/sig_short_exp_time/test +++ b/acceptance/sig_short_exp_time/test @@ -44,7 +44,7 @@ # | | # | +---------------------------------------------+ | # +---+ pathb +---- -# | 242.254.100.3:50000 <-> 242.254.200.4:50000 | +# | 242.254.100.4:50000 <-> 242.254.200.4:50000 | # +---------------------------------------------+ run_test() {(set -e diff --git a/acceptance/topo_common/topology.json b/acceptance/topo_common/topology.json index d3bfefb53b..cd5eaeafb8 100644 --- a/acceptance/topo_common/topology.json +++ b/acceptance/topo_common/topology.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:110", "mtu": 1400, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/acceptance/topo_cs_reload/BUILD.bazel b/acceptance/topo_cs_reload/BUILD.bazel index 53d8eb89e1..411df00ee9 100644 --- a/acceptance/topo_cs_reload/BUILD.bazel +++ b/acceptance/topo_cs_reload/BUILD.bazel @@ -15,7 +15,6 @@ go_test( "docker-compose.yml", "testdata/topology_reload.json", ":control.tar", - ":dispatcher.tar", ":invalid_changed_ip", ":invalid_changed_port", ":testdata/gen_crypto.sh", @@ -36,32 +35,6 @@ go_test( ], ) -# dispatcher container -oci_tarball( - name = "dispatcher.tar", - format = "docker", - image = ":dispatcher_image", - repo_tags = ["scion/" + package_name() + ":dispatcher"], -) - -oci_image( - name = "dispatcher_image", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - tars = [ - ":dispatcher_data", - ], -) - -pkg_tar( - name = "dispatcher_data", - srcs = ["testdata/disp.toml"], -) - # control container oci_tarball( name = "control.tar", diff --git a/acceptance/topo_cs_reload/docker-compose.yml b/acceptance/topo_cs_reload/docker-compose.yml index 945da70a23..4b99195829 100644 --- a/acceptance/topo_cs_reload/docker-compose.yml +++ b/acceptance/topo_cs_reload/docker-compose.yml @@ -7,23 +7,13 @@ networks: config: - subnet: 242.253.100.0/24 services: - topo_cs_reload_dispatcher: - image: scion/acceptance/topo_cs_reload:dispatcher - networks: - bridge1: - ipv4_address: 242.253.100.2 - volumes: - - vol_topo_cs_reload_disp:/run/shm/dispatcher:rw topo_cs_reload_control_srv: image: scion/acceptance/topo_cs_reload:control - depends_on: - - topo_cs_reload_dispatcher volumes: - - vol_topo_cs_reload_disp:/run/shm/dispatcher:ro - "${TOPO_CS_RELOAD_CONFIG_DIR}/certs:/certs:ro" - "${TOPO_CS_RELOAD_CONFIG_DIR}/keys:/keys:ro" - "${TOPO_CS_RELOAD_CONFIG_DIR}/crypto:/crypto:ro" - network_mode: service:topo_cs_reload_dispatcher + networks: + bridge1: + ipv4_address: 242.253.100.2 version: '2.4' -volumes: - vol_topo_cs_reload_disp: null diff --git a/acceptance/topo_cs_reload/reload_test.go b/acceptance/topo_cs_reload/reload_test.go index 1d361f89a3..eba08eb18c 100644 --- a/acceptance/topo_cs_reload/reload_test.go +++ b/acceptance/topo_cs_reload/reload_test.go @@ -100,10 +100,6 @@ func setupTest(t *testing.T) testState { s.mustExec(t, "tar", "-xf", "crypto.tar", "-C", tmpDir) // first load the docker images from bazel into the docker deamon, the // tars are in the same folder as this test runs in bazel. - s.mustExec(t, "docker", "image", "load", "-i", "dispatcher.tar/tarball.tar") - t.Cleanup(func() { - s.mustExec(t, "docker", "image", "rm", "scion/acceptance/topo_cs_reload:dispatcher") - }) s.mustExec(t, "docker", "image", "load", "-i", "control.tar/tarball.tar") t.Cleanup(func() { s.mustExec(t, "docker", "image", "rm", "scion/acceptance/topo_cs_reload:control") @@ -126,7 +122,6 @@ func (s testState) collectLogs(t *testing.T) { require.NoError(t, os.MkdirAll(fmt.Sprintf("%s/logs", outdir), os.ModePerm|os.ModeDir)) // collect logs for service, file := range map[string]string{ - "topo_cs_reload_dispatcher": "disp.log", "topo_cs_reload_control_srv": "control.log", } { cmd := exec.Command("docker", "compose", diff --git a/acceptance/topo_cs_reload/testdata/cs.toml b/acceptance/topo_cs_reload/testdata/cs.toml index d663dc7120..96cc0e380e 100644 --- a/acceptance/topo_cs_reload/testdata/cs.toml +++ b/acceptance/topo_cs_reload/testdata/cs.toml @@ -1,5 +1,4 @@ [general] -reconnect_to_dispatcher = true config_dir = "/" id = "cs1-ff00_0_110-1" diff --git a/acceptance/topo_cs_reload/testdata/disp.toml b/acceptance/topo_cs_reload/testdata/disp.toml deleted file mode 100644 index f02dc620a8..0000000000 --- a/acceptance/topo_cs_reload/testdata/disp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[dispatcher] -id = "disp_1-ff00_0_110" - -[log.console] -level = "debug" diff --git a/acceptance/topo_cs_reload/testdata/sd.toml b/acceptance/topo_cs_reload/testdata/sd.toml index 4e4dfee564..7cc184a174 100644 --- a/acceptance/topo_cs_reload/testdata/sd.toml +++ b/acceptance/topo_cs_reload/testdata/sd.toml @@ -1,5 +1,4 @@ [general] -reconnect_to_dispatcher = true config_dir = "/" id = "sd1-ff00_0_110" diff --git a/acceptance/topo_cs_reload/testdata/topology_reload.json b/acceptance/topo_cs_reload/testdata/topology_reload.json index 2db6194114..fcdf6312f6 100644 --- a/acceptance/topo_cs_reload/testdata/topology_reload.json +++ b/acceptance/topo_cs_reload/testdata/topology_reload.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:110", "mtu": 1400, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/acceptance/topo_daemon_reload/BUILD.bazel b/acceptance/topo_daemon_reload/BUILD.bazel index d0e7c73a87..71df628af9 100644 --- a/acceptance/topo_daemon_reload/BUILD.bazel +++ b/acceptance/topo_daemon_reload/BUILD.bazel @@ -8,7 +8,6 @@ go_test( data = [ "testdata/topology_reload.json", ":daemon.tar", - ":dispatcher.tar", ":docker-compose.yml", "//acceptance/topo_common:invalid_reloads", "//acceptance/topo_common:topology", @@ -24,32 +23,6 @@ go_test( ], ) -# dispatcher container -oci_tarball( - name = "dispatcher.tar", - format = "docker", - image = ":dispatcher_image", - repo_tags = ["scion/" + package_name() + ":dispatcher"], -) - -oci_image( - name = "dispatcher_image", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - tars = [ - ":dispatcher_data", - ], -) - -pkg_tar( - name = "dispatcher_data", - srcs = ["testdata/disp.toml"], -) - # daemon container oci_tarball( name = "daemon.tar", diff --git a/acceptance/topo_daemon_reload/docker-compose.yml b/acceptance/topo_daemon_reload/docker-compose.yml index 3a9ff0cdcb..ade1eb5f32 100644 --- a/acceptance/topo_daemon_reload/docker-compose.yml +++ b/acceptance/topo_daemon_reload/docker-compose.yml @@ -7,22 +7,14 @@ networks: config: - subnet: 242.254.100.0/24 services: - topo_daemon_reload_dispatcher: - container_name: topo_daemon_reload_dispatcher - image: scion/acceptance/topo_daemon_reload:dispatcher - networks: - bridge1: - ipv4_address: 242.254.100.2 - volumes: - - vol_topo_daemon_reload_disp:/run/shm/dispatcher:rw topo_daemon_reload_daemon: container_name: topo_daemon_reload_daemon image: scion/acceptance/topo_daemon_reload:daemon volumes: - - vol_topo_daemon_reload_disp:/run/shm/dispatcher:ro - vol_topo_daemon_reload_certs:/certs:ro - network_mode: service:topo_daemon_reload_dispatcher + networks: + bridge1: + ipv4_address: 242.254.100.2 version: '2.4' volumes: - vol_topo_daemon_reload_disp: null vol_topo_daemon_reload_certs: null diff --git a/acceptance/topo_daemon_reload/reload_test.go b/acceptance/topo_daemon_reload/reload_test.go index 03f38a980c..171d61eeb9 100644 --- a/acceptance/topo_daemon_reload/reload_test.go +++ b/acceptance/topo_daemon_reload/reload_test.go @@ -68,17 +68,13 @@ func TestSDTopoReload(t *testing.T) { func setupTest(t *testing.T) { // first load the docker images from bazel into the docker deamon, the // tars are in the same folder as this test runs in bazel. - mustExec(t, "docker", "image", "load", "-i", "dispatcher.tar/tarball.tar") - t.Cleanup(func() { - mustExec(t, "docker", "image", "rm", "scion/acceptance/topo_daemon_reload:dispatcher") - }) mustExec(t, "docker", "image", "load", "-i", "daemon.tar/tarball.tar") t.Cleanup(func() { mustExec(t, "docker", "image", "rm", "scion/acceptance/topo_daemon_reload:daemon") }) // now start the docker containers mustExec(t, "docker", "compose", "-f", "docker-compose.yml", - "up", "-d", "topo_daemon_reload_dispatcher", "topo_daemon_reload_daemon") + "up", "-d", "topo_daemon_reload_daemon") t.Cleanup(func() { mustExec(t, "docker", "compose", "-f", "docker-compose.yml", "down", "-v") }) // wait a bit to make sure the containers are ready. time.Sleep(time.Second / 2) @@ -92,8 +88,7 @@ func collectLogs(t *testing.T) { require.NoError(t, os.MkdirAll(fmt.Sprintf("%s/logs", outdir), os.ModePerm|os.ModeDir)) // collect logs for service, file := range map[string]string{ - "topo_daemon_reload_dispatcher": "disp.log", - "topo_daemon_reload_daemon": "daemon.log", + "topo_daemon_reload_daemon": "daemon.log", } { cmd := exec.Command("docker", "compose", "-f", "docker-compose.yml", "logs", "--no-color", diff --git a/acceptance/topo_daemon_reload/testdata/disp.toml b/acceptance/topo_daemon_reload/testdata/disp.toml deleted file mode 100644 index f02dc620a8..0000000000 --- a/acceptance/topo_daemon_reload/testdata/disp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[dispatcher] -id = "disp_1-ff00_0_110" - -[log.console] -level = "debug" diff --git a/acceptance/topo_daemon_reload/testdata/sd.toml b/acceptance/topo_daemon_reload/testdata/sd.toml index f675b0bb3a..7c4a6eeac2 100644 --- a/acceptance/topo_daemon_reload/testdata/sd.toml +++ b/acceptance/topo_daemon_reload/testdata/sd.toml @@ -1,5 +1,4 @@ [general] -reconnect_to_dispatcher = true config_dir = "/" id = "sd1-ff00_0_110" diff --git a/acceptance/topo_daemon_reload/testdata/topology_reload.json b/acceptance/topo_daemon_reload/testdata/topology_reload.json index eeddaf5a2a..8cc03affec 100644 --- a/acceptance/topo_daemon_reload/testdata/topology_reload.json +++ b/acceptance/topo_daemon_reload/testdata/topology_reload.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:110", "mtu": 1400, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/acceptance/trc_update/test.py b/acceptance/trc_update/test.py index 2edf26a0bb..3aa89cdbb9 100755 --- a/acceptance/trc_update/test.py +++ b/acceptance/trc_update/test.py @@ -46,6 +46,7 @@ class Test(base.TestTopogen): 6. Restart control servers and check connectivity again. """ + # TODO: Replace timers with the await_connectivity tool where appropriate. def _run(self): # Give some time for the topology to start. time.sleep(10) diff --git a/buf.yaml b/buf.yaml index a5d3ad80cc..a6b06a30cf 100644 --- a/buf.yaml +++ b/buf.yaml @@ -9,3 +9,5 @@ lint: - COMMENT_ENUM_VALUE - COMMENT_FIELD - COMMENT_RPC + rpc_allow_google_protobuf_empty_requests: true + rpc_allow_google_protobuf_empty_responses: true diff --git a/control/beaconing/testdata/topology-core.json b/control/beaconing/testdata/topology-core.json index 870ae77921..11284ccd2f 100644 --- a/control/beaconing/testdata/topology-core.json +++ b/control/beaconing/testdata/topology-core.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:110", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/control/beaconing/testdata/topology.json b/control/beaconing/testdata/topology.json index 534e51e2bc..3a5f69f35e 100644 --- a/control/beaconing/testdata/topology.json +++ b/control/beaconing/testdata/topology.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:111", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [], "border_routers": { "br1-ff00_0_111-1": { diff --git a/control/cmd/control/main.go b/control/cmd/control/main.go index 1e74e7265f..7b36a7e28d 100644 --- a/control/cmd/control/main.go +++ b/control/cmd/control/main.go @@ -21,6 +21,7 @@ import ( "errors" "net/http" _ "net/http/pprof" + "net/netip" "path/filepath" "strings" "sync" @@ -200,15 +201,9 @@ func realMain(ctx context.Context) error { // FIXME: readability would be improved if we could be consistent with address // representations in NetworkConfig (string or cooked, chose one). nc := infraenv.NetworkConfig{ - IA: topo.IA(), - // Public: (Historical name) The TCP/IP:port address for the control service. - Public: topo.ControlServiceAddress(globalCfg.General.ID), - ReconnectToDispatcher: globalCfg.General.ReconnectToDispatcher, + IA: topo.IA(), + Public: topo.ControlServiceAddress(globalCfg.General.ID), QUIC: infraenv.QUIC{ - // Address: the QUIC/SCION address of this service. If not - // configured, QUICStack() uses the same IP and port as - // for the public address. - Address: globalCfg.QUIC.Address, TLSVerifier: trust.NewTLSCryptoVerifier(trustDB), GetCertificate: cs.NewTLSCertificateLoader( topo.IA(), x509.ExtKeyUsageServerAuth, trustDB, globalCfg.General.ConfigDir, @@ -225,19 +220,19 @@ func realMain(ctx context.Context) error { SCIONNetworkMetrics: metrics.SCIONNetworkMetrics, SCIONPacketConnMetrics: metrics.SCIONPacketConnMetrics, MTU: topo.MTU(), + Topology: cpInfoProvider{topo: topo}, } quicStack, err := nc.QUICStack() if err != nil { return serrors.WrapStr("initializing QUIC stack", err) } - defer quicStack.RedirectCloser() tcpStack, err := nc.TCPStack() if err != nil { return serrors.WrapStr("initializing TCP stack", err) } dialer := &libgrpc.QUICDialer{ Rewriter: &onehop.AddressRewriter{ - Rewriter: nc.AddressRewriter(nil), + Rewriter: nc.AddressRewriter(), MAC: macGen(), }, Dialer: quicStack.InsecureDialer, @@ -631,7 +626,7 @@ func realMain(ctx context.Context) error { drkeyFetcher := drkeygrpc.Fetcher{ Dialer: &libgrpc.QUICDialer{ - Rewriter: nc.AddressRewriter(nil), + Rewriter: nc.AddressRewriter(), Dialer: quicStack.Dialer, }, Router: segreq.NewRouter(fetcherCfg), @@ -940,6 +935,31 @@ func (h *healther) GetCAHealth(ctx context.Context) (api.CAHealthStatus, bool) { return api.Unavailable, false } +type cpInfoProvider struct { + topo *topology.Loader +} + +func (c cpInfoProvider) LocalIA(_ context.Context) (addr.IA, error) { + return c.topo.IA(), nil +} + +func (c cpInfoProvider) PortRange(_ context.Context) (uint16, uint16, error) { + start, end := c.topo.PortRange() + return start, end, nil +} + +func (c cpInfoProvider) Interfaces(_ context.Context) (map[uint16]netip.AddrPort, error) { + ifMap := c.topo.InterfaceInfoMap() + ifsToUDP := make(map[uint16]netip.AddrPort, len(ifMap)) + for i, v := range ifMap { + if i > (1<<16)-1 { + return nil, serrors.New("invalid interface id", "id", i) + } + ifsToUDP[uint16(i)] = v.InternalAddr + } + return ifsToUDP, nil +} + func getCAHealth( ctx context.Context, caClient *caapi.Client, diff --git a/daemon/daemon.go b/daemon/daemon.go index 8fbc106938..30c92918bc 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,8 +119,11 @@ type ServerConfig struct { // NewServer constructs a daemon API server. func NewServer(cfg ServerConfig) *servers.DaemonServer { return &servers.DaemonServer{ - IA: cfg.IA, - MTU: cfg.MTU, + IA: cfg.IA, + MTU: cfg.MTU, + // TODO(JordiSubira): This will be changed in the future to fetch + // the information from the CS instead of feeding the configuration + // file into. Topology: cfg.Topology, Fetcher: cfg.Fetcher, ASInspector: cfg.Engine.Inspector, diff --git a/daemon/internal/servers/BUILD.bazel b/daemon/internal/servers/BUILD.bazel index 1d1534f2c2..9bf6640324 100644 --- a/daemon/internal/servers/BUILD.bazel +++ b/daemon/internal/servers/BUILD.bazel @@ -30,6 +30,7 @@ go_library( "@com_github_golang_protobuf//ptypes/duration", "@com_github_golang_protobuf//ptypes/timestamp", "@com_github_opentracing_opentracing_go//:go_default_library", + "@org_golang_google_protobuf//types/known/emptypb:go_default_library", "@org_golang_x_sync//singleflight:go_default_library", ], ) diff --git a/daemon/internal/servers/grpc.go b/daemon/internal/servers/grpc.go index dd2cb43bc7..c8c8258bcb 100644 --- a/daemon/internal/servers/grpc.go +++ b/daemon/internal/servers/grpc.go @@ -24,6 +24,7 @@ import ( timestamppb "github.com/golang/protobuf/ptypes/timestamp" "github.com/opentracing/opentracing-go" "golang.org/x/sync/singleflight" + "google.golang.org/protobuf/types/known/emptypb" drkey_daemon "github.com/scionproto/scion/daemon/drkey" "github.com/scionproto/scion/daemon/fetcher" @@ -49,6 +50,7 @@ type Topology interface { InterfaceIDs() []uint16 UnderlayNextHop(uint16) *net.UDPAddr ControlServiceAddresses() []*net.UDPAddr + PortRange() (uint16, uint16) } // DaemonServer handles gRPC requests to the SCION daemon. @@ -351,6 +353,19 @@ func (s *DaemonServer) notifyInterfaceDown(ctx context.Context, return &sdpb.NotifyInterfaceDownResponse{}, nil } +// PortRange returns the port range for the dispatched ports. +func (s *DaemonServer) PortRange( + _ context.Context, + _ *emptypb.Empty, +) (*sdpb.PortRangeResponse, error) { + + startPort, endPort := s.Topology.PortRange() + return &sdpb.PortRangeResponse{ + DispatchedPortStart: uint32(startPort), + DispatchedPortEnd: uint32(endPort), + }, nil +} + func (s *DaemonServer) DRKeyASHost( ctx context.Context, req *pb_daemon.DRKeyASHostRequest, diff --git a/dispatcher/BUILD.bazel b/dispatcher/BUILD.bazel index 8538e90bdd..d091d76e49 100644 --- a/dispatcher/BUILD.bazel +++ b/dispatcher/BUILD.bazel @@ -2,17 +2,10 @@ load("//tools/lint:go.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = [ - "dispatcher.go", - "table.go", - "underlay.go", - ], + srcs = ["dispatcher.go"], importpath = "github.com/scionproto/scion/dispatcher", visibility = ["//visibility:public"], deps = [ - "//dispatcher/internal/metrics:go_default_library", - "//dispatcher/internal/registration:go_default_library", - "//dispatcher/internal/respool:go_default_library", "//pkg/addr:go_default_library", "//pkg/log:go_default_library", "//pkg/private/common:go_default_library", @@ -20,25 +13,21 @@ go_library( "//pkg/slayers:go_default_library", "//pkg/slayers/path/epic:go_default_library", "//pkg/slayers/path/scion:go_default_library", - "//private/ringbuf:go_default_library", - "//private/underlay/conn:go_default_library", "@com_github_google_gopacket//:go_default_library", + "@org_golang_x_net//ipv4:go_default_library", + "@org_golang_x_net//ipv6:go_default_library", ], ) go_test( name = "go_default_test", - srcs = ["underlay_test.go"], + srcs = ["dispatcher_test.go"], embed = [":go_default_library"], deps = [ - "//dispatcher/internal/respool:go_default_library", "//pkg/addr:go_default_library", "//pkg/private/xtest:go_default_library", - "//pkg/slayers:go_default_library", - "//pkg/slayers/path:go_default_library", - "//pkg/slayers/path/scion:go_default_library", - "@com_github_golang_mock//gomock:go_default_library", - "@com_github_google_gopacket//:go_default_library", + "//pkg/snet:go_default_library", + "//pkg/snet/path:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", "@com_github_stretchr_testify//require:go_default_library", ], diff --git a/dispatcher/cmd/dispatcher/BUILD.bazel b/dispatcher/cmd/dispatcher/BUILD.bazel index f7a8f876c0..986b88784d 100644 --- a/dispatcher/cmd/dispatcher/BUILD.bazel +++ b/dispatcher/cmd/dispatcher/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") +load("//tools/lint:go.bzl", "go_library") load("//:scion.bzl", "scion_go_binary") go_library( @@ -6,21 +6,73 @@ go_library( srcs = ["main.go"], importpath = "github.com/scionproto/scion/dispatcher/cmd/dispatcher", visibility = ["//visibility:private"], - deps = [ - "//dispatcher/config:go_default_library", - "//dispatcher/mgmtapi:go_default_library", - "//dispatcher/network:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/private/util:go_default_library", - "//pkg/slayers/path:go_default_library", - "//private/app:go_default_library", - "//private/app/launcher:go_default_library", - "//private/service:go_default_library", - "@com_github_go_chi_chi_v5//:go_default_library", - "@com_github_go_chi_cors//:go_default_library", - "@org_golang_x_sync//errgroup:go_default_library", - ], + deps = select({ + "@io_bazel_rules_go//go/platform:android": [ + "//dispatcher:go_default_library", + "//dispatcher/config:go_default_library", + "//dispatcher/mgmtapi:go_default_library", + "//pkg/addr:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/slayers/path:go_default_library", + "//private/app:go_default_library", + "//private/app/launcher:go_default_library", + "//private/service:go_default_library", + "//private/topology/underlay:go_default_library", + "@com_github_go_chi_chi_v5//:go_default_library", + "@com_github_go_chi_cors//:go_default_library", + "@org_golang_x_sync//errgroup:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//dispatcher:go_default_library", + "//dispatcher/config:go_default_library", + "//dispatcher/mgmtapi:go_default_library", + "//pkg/addr:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/slayers/path:go_default_library", + "//private/app:go_default_library", + "//private/app/launcher:go_default_library", + "//private/service:go_default_library", + "//private/topology/underlay:go_default_library", + "@com_github_go_chi_chi_v5//:go_default_library", + "@com_github_go_chi_cors//:go_default_library", + "@org_golang_x_sync//errgroup:go_default_library", + ], + "@io_bazel_rules_go//go/platform:ios": [ + "//dispatcher:go_default_library", + "//dispatcher/config:go_default_library", + "//dispatcher/mgmtapi:go_default_library", + "//pkg/addr:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/slayers/path:go_default_library", + "//private/app:go_default_library", + "//private/app/launcher:go_default_library", + "//private/service:go_default_library", + "//private/topology/underlay:go_default_library", + "@com_github_go_chi_chi_v5//:go_default_library", + "@com_github_go_chi_cors//:go_default_library", + "@org_golang_x_sync//errgroup:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//dispatcher:go_default_library", + "//dispatcher/config:go_default_library", + "//dispatcher/mgmtapi:go_default_library", + "//pkg/addr:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/slayers/path:go_default_library", + "//private/app:go_default_library", + "//private/app/launcher:go_default_library", + "//private/service:go_default_library", + "//private/topology/underlay:go_default_library", + "@com_github_go_chi_chi_v5//:go_default_library", + "@com_github_go_chi_cors//:go_default_library", + "@org_golang_x_sync//errgroup:go_default_library", + ], + "//conditions:default": [], + }), ) scion_go_binary( @@ -28,18 +80,3 @@ scion_go_binary( embed = [":go_default_library"], visibility = ["//visibility:public"], ) - -go_test( - name = "go_default_test", - srcs = ["main_test.go"], - embed = [":go_default_library"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/private/xtest:go_default_library", - "//pkg/snet:go_default_library", - "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - "@com_github_stretchr_testify//require:go_default_library", - ], -) diff --git a/dispatcher/cmd/dispatcher/main.go b/dispatcher/cmd/dispatcher/main.go index ab26beac30..3eaed918e2 100644 --- a/dispatcher/cmd/dispatcher/main.go +++ b/dispatcher/cmd/dispatcher/main.go @@ -13,31 +13,34 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build linux || darwin +// +build linux darwin + package main import ( "context" "errors" - "fmt" "net" "net/http" _ "net/http/pprof" - "os" + "net/netip" "github.com/go-chi/chi/v5" "github.com/go-chi/cors" "golang.org/x/sync/errgroup" + "github.com/scionproto/scion/dispatcher" "github.com/scionproto/scion/dispatcher/config" api "github.com/scionproto/scion/dispatcher/mgmtapi" - "github.com/scionproto/scion/dispatcher/network" + "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/private/util" "github.com/scionproto/scion/pkg/slayers/path" "github.com/scionproto/scion/private/app" "github.com/scionproto/scion/private/app/launcher" "github.com/scionproto/scion/private/service" + "github.com/scionproto/scion/private/topology/underlay" ) var globalCfg config.Config @@ -53,21 +56,19 @@ func main() { } func realMain(ctx context.Context) error { - if err := util.CreateParentDirs(globalCfg.Dispatcher.ApplicationSocket); err != nil { - return serrors.WrapStr("creating directory tree for socket", err) - } - path.StrictDecoding(false) var cleanup app.Cleanup g, errCtx := errgroup.WithContext(ctx) g.Go(func() error { defer log.HandlePanic() - return RunDispatcher( - globalCfg.Dispatcher.DeleteSocket, - globalCfg.Dispatcher.ApplicationSocket, - os.FileMode(globalCfg.Dispatcher.SocketFileMode), - globalCfg.Dispatcher.UnderlayPort, + return runDispatcher( + globalCfg.Dispatcher.LocalUDPForwarding, + globalCfg.Dispatcher.ServiceAddresses, + netip.AddrPortFrom( + globalCfg.Dispatcher.UnderlayAddr, + underlay.EndhostPort, + ), ) }) @@ -116,12 +117,6 @@ func realMain(ctx context.Context) error { return globalCfg.Metrics.ServePrometheus(errCtx) }) - defer func() { - if err := deleteSocket(globalCfg.Dispatcher.ApplicationSocket); err != nil { - log.Error("deleting socket", "err", err) - } - }() - g.Go(func() error { defer log.HandlePanic() <-errCtx.Done() @@ -138,32 +133,14 @@ func realMain(ctx context.Context) error { } } -func RunDispatcher(deleteSocketFlag bool, applicationSocket string, socketFileMode os.FileMode, - underlayPort int) error { +func runDispatcher( + isDispatcher bool, + svcAddrs map[addr.Addr]netip.AddrPort, + underlayAddr netip.AddrPort, +) error { - if deleteSocketFlag { - if err := deleteSocket(globalCfg.Dispatcher.ApplicationSocket); err != nil { - return err - } - } - dispatcher := &network.Dispatcher{ - UnderlaySocket: fmt.Sprintf(":%d", underlayPort), - ApplicationSocket: applicationSocket, - SocketFileMode: socketFileMode, - } - log.Debug("Dispatcher starting", "appSocket", applicationSocket, "underlayPort", underlayPort) - return dispatcher.ListenAndServe() -} - -func deleteSocket(socket string) error { - if _, err := os.Stat(socket); err != nil { - // File does not exist, or we can't read it, nothing to delete - return nil - } - if err := os.Remove(socket); err != nil { - return err - } - return nil + log.Debug("Dispatcher starting", "localAddr", underlayAddr, "dispatcher feature", isDispatcher) + return dispatcher.ListenAndServe(isDispatcher, svcAddrs, net.UDPAddrFromAddrPort(underlayAddr)) } func requiredIPs() ([]net.IP, error) { diff --git a/dispatcher/cmd/dispatcher/main_test.go b/dispatcher/cmd/dispatcher/main_test.go deleted file mode 100644 index 86e3dc710d..0000000000 --- a/dispatcher/cmd/dispatcher/main_test.go +++ /dev/null @@ -1,487 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, 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 main - -import ( - "context" - "fmt" - "net" - "net/netip" - "os" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" -) - -const ( - defaultTimeout = 2 * time.Second - defaultWaitDuration = 200 * time.Millisecond -) - -type TestSettings struct { - ApplicationSocket string - UnderlayPort int -} - -func InitTestSettings(t *testing.T, dispatcherTestPort int) *TestSettings { - socketName, err := getSocketName("/tmp") - if err != nil { - t.Fatal(err) - } - return &TestSettings{ - ApplicationSocket: socketName, - UnderlayPort: dispatcherTestPort, - } -} - -func getSocketName(dir string) (string, error) { - dir, err := os.MkdirTemp(dir, "dispatcher") - if err != nil { - return "", err - } - return filepath.Join(dir, "server.sock"), nil -} - -type ClientAddress struct { - IA addr.IA - PublicAddress netip.Addr - PublicPort uint16 - ServiceAddress addr.SVC - UnderlayAddress *net.UDPAddr -} - -type TestCase struct { - Name string - ClientAddress *ClientAddress - TestPackets []*snet.Packet - UnderlayAddress *net.UDPAddr - ExpectedPacket *snet.Packet -} - -func genTestCases(dispatcherPort int) []*TestCase { - // Addressing information - var ( - commonIA = xtest.MustParseIA("1-ff00:0:1") - commonPublicL3Address = netip.AddrFrom4([4]byte{127, 0, 0, 1}) - commonUnderlayAddress = &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: dispatcherPort} - clientXAddress = &ClientAddress{ - IA: commonIA, - PublicAddress: commonPublicL3Address, - PublicPort: 8080, - ServiceAddress: addr.SvcNone, - UnderlayAddress: commonUnderlayAddress, - } - clientYAddress = &ClientAddress{ - IA: commonIA, - PublicAddress: commonPublicL3Address, - PublicPort: 8081, - ServiceAddress: addr.SvcCS, - UnderlayAddress: commonUnderlayAddress, - } - ) - - var testCases = []*TestCase{ - { - Name: "UDP/IPv4 packet", - ClientAddress: clientXAddress, - TestPackets: []*snet.Packet{ - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.UDPPayload{ - SrcPort: clientXAddress.PublicPort, - DstPort: clientXAddress.PublicPort, - Payload: []byte{1, 2, 3, 4}, - }, - Path: path.Empty{}, - }, - }, - }, - UnderlayAddress: clientXAddress.UnderlayAddress, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.UDPPayload{ - SrcPort: clientXAddress.PublicPort, - DstPort: clientXAddress.PublicPort, - Payload: []byte{1, 2, 3, 4}, - }, - Path: snet.RawPath{}, - }, - }, - }, - { - Name: "UDP/SVC packet", - ClientAddress: clientYAddress, - TestPackets: []*snet.Packet{ - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostSVC(clientYAddress.ServiceAddress), - }, - Payload: snet.UDPPayload{ - SrcPort: clientYAddress.PublicPort, - DstPort: clientYAddress.PublicPort, - Payload: []byte{5, 6, 7, 8}, - }, - Path: path.Empty{}, - }, - }, - }, - UnderlayAddress: clientXAddress.UnderlayAddress, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostSVC(clientYAddress.ServiceAddress), - }, - Payload: snet.UDPPayload{ - SrcPort: clientYAddress.PublicPort, - DstPort: clientYAddress.PublicPort, - Payload: []byte{5, 6, 7, 8}, - }, - Path: snet.RawPath{}, - }, - }, - }, - { - Name: "SCMP::Error, UDP quote", - ClientAddress: clientXAddress, - UnderlayAddress: clientXAddress.UnderlayAddress, - TestPackets: []*snet.Packet{ - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPDestinationUnreachable{ - Payload: MustPack(snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.UDPPayload{SrcPort: clientXAddress.PublicPort}, - Path: path.Empty{}, - }, - }), - }, - Path: path.Empty{}, - }, - }, - }, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPDestinationUnreachable{ - Payload: MustPack(snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.UDPPayload{SrcPort: clientXAddress.PublicPort}, - Path: path.Empty{}, - }, - }), - }, - Path: snet.RawPath{}, - }, - }, - }, - { - Name: "SCMP::Error, SCMP quote", - ClientAddress: clientXAddress, - UnderlayAddress: clientXAddress.UnderlayAddress, - TestPackets: []*snet.Packet{ - { - // Force a SCMP General ID registration to happen, but route it - // from nowhere so we don't get it back - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: xtest.MustParseIA("1-ff00:0:42"), // middle of nowhere - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, - Path: path.Empty{}, - }, - }, - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPDestinationUnreachable{ - Payload: MustPack(snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, - Path: path.Empty{}, - }, - }), - }, - Path: path.Empty{}, - }, - }, - }, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPDestinationUnreachable{ - Payload: MustPack(snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, - Path: path.Empty{}, - }, - }), - }, - Path: snet.RawPath{}, - }, - }, - }, - { - Name: "SCMP::General::EchoRequest", - ClientAddress: clientXAddress, - UnderlayAddress: clientYAddress.UnderlayAddress, - TestPackets: []*snet.Packet{ - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Payload: snet.SCMPEchoRequest{ - Identifier: 0xdead, - SeqNumber: 0xcafe, - Payload: []byte("hello?"), - }, - Path: path.Empty{}, - }, - }, - }, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPEchoReply{ - Identifier: 0xdead, - SeqNumber: 0xcafe, - Payload: []byte("hello?"), - }, - Path: snet.RawPath{}, - }, - }, - }, - { - Name: "SCMP::General::TraceRouteRequest", - ClientAddress: clientXAddress, - UnderlayAddress: clientYAddress.UnderlayAddress, - TestPackets: []*snet.Packet{ - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Payload: snet.SCMPTracerouteRequest{Identifier: 0xdeaf, Sequence: 0xcafd}, - Path: path.Empty{}, - }, - }, - }, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPTracerouteReply{Identifier: 0xdeaf, Sequence: 0xcafd}, - Path: snet.RawPath{}, - }, - }, - }, - } - return testCases -} - -func TestDataplaneIntegration(t *testing.T) { - dispatcherTestPort := 40032 - settings := InitTestSettings(t, dispatcherTestPort) - - go func() { - err := RunDispatcher(false, settings.ApplicationSocket, reliable.DefaultDispSocketFileMode, - settings.UnderlayPort) - require.NoError(t, err, "dispatcher error") - }() - time.Sleep(defaultWaitDuration) - - testCases := genTestCases(dispatcherTestPort) - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - RunTestCase(t, tc, settings) - }) - time.Sleep(defaultWaitDuration) - } -} - -func RunTestCase(t *testing.T, tc *TestCase, settings *TestSettings) { - dispatcherService := reliable.NewDispatcher(settings.ApplicationSocket) - ctx, cancelF := context.WithTimeout(context.Background(), defaultTimeout) - defer cancelF() - conn, _, err := dispatcherService.Register( - ctx, - tc.ClientAddress.IA, - &net.UDPAddr{ - IP: tc.ClientAddress.PublicAddress.AsSlice(), - Port: int(tc.ClientAddress.PublicPort), - }, - tc.ClientAddress.ServiceAddress, - ) - require.NoError(t, err, "unable to open socket") - // Always destroy the connection s.t. future tests aren't compromised by a - // fatal in this subtest - defer conn.Close() - - for _, packet := range tc.TestPackets { - require.NoError(t, packet.Serialize()) - fmt.Printf("sending packet: %x\n", packet.Bytes) - _, err = conn.WriteTo(packet.Bytes, tc.UnderlayAddress) - require.NoError(t, err, "unable to write message") - } - - err = conn.SetReadDeadline(time.Now().Add(defaultTimeout)) - require.NoError(t, err, "unable to set read deadline") - - rcvPkt := snet.Packet{} - rcvPkt.Prepare() - n, _, err := conn.ReadFrom(rcvPkt.Bytes) - require.NoError(t, err, "unable to read message") - rcvPkt.Bytes = rcvPkt.Bytes[:n] - - require.NoError(t, rcvPkt.Decode()) - - err = conn.Close() - require.NoError(t, err, "unable to close conn") - - assert.Equal(t, tc.ExpectedPacket.PacketInfo, rcvPkt.PacketInfo) -} - -func MustPack(pkt snet.Packet) []byte { - if err := pkt.Serialize(); err != nil { - panic(err) - } - return pkt.Bytes -} diff --git a/dispatcher/config/BUILD.bazel b/dispatcher/config/BUILD.bazel index 2e55a639c2..4b3cce9ebe 100644 --- a/dispatcher/config/BUILD.bazel +++ b/dispatcher/config/BUILD.bazel @@ -9,14 +9,12 @@ go_library( importpath = "github.com/scionproto/scion/dispatcher/config", visibility = ["//visibility:public"], deps = [ + "//pkg/addr:go_default_library", "//pkg/log:go_default_library", "//pkg/private/serrors:go_default_library", - "//pkg/private/util:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/config:go_default_library", "//private/env:go_default_library", "//private/mgmtapi:go_default_library", - "//private/topology:go_default_library", ], ) @@ -26,10 +24,8 @@ go_test( embed = [":go_default_library"], deps = [ "//pkg/log/logtest:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/env/envtest:go_default_library", "//private/mgmtapi/mgmtapitest:go_default_library", - "//private/topology:go_default_library", "@com_github_pelletier_go_toml_v2//:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", ], diff --git a/dispatcher/config/config.go b/dispatcher/config/config.go index 1fad7d9b09..4d378ad984 100644 --- a/dispatcher/config/config.go +++ b/dispatcher/config/config.go @@ -19,15 +19,14 @@ package config import ( "fmt" "io" + "net/netip" + "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/private/util" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/config" "github.com/scionproto/scion/private/env" api "github.com/scionproto/scion/private/mgmtapi" - "github.com/scionproto/scion/private/topology" ) var _ config.Config = (*Config)(nil) @@ -40,46 +39,6 @@ type Config struct { Dispatcher Dispatcher `toml:"dispatcher,omitempty"` } -// Dispatcher contains the dispatcher specific config. -type Dispatcher struct { - config.NoDefaulter - // ID of the Dispatcher (required) - ID string `toml:"id,omitempty"` - // ApplicationSocket is the local API socket (default /run/shm/dispatcher/default.sock) - ApplicationSocket string `toml:"application_socket,omitempty"` - // Socket file permissions when created; read from octal. (default 0770) - SocketFileMode util.FileMode `toml:"socket_file_mode,omitempty"` - // UnderlayPort is the native port opened by the dispatcher (default 30041) - UnderlayPort int `toml:"underlay_port,omitempty"` - // DeleteSocket specifies whether the dispatcher should delete the - // socket file prior to attempting to create a new one. - DeleteSocket bool `toml:"delete_socket,omitempty"` -} - -func (cfg *Dispatcher) Validate() error { - if cfg.ApplicationSocket == "" { - cfg.ApplicationSocket = reliable.DefaultDispPath - } - if cfg.SocketFileMode == 0 { - cfg.SocketFileMode = reliable.DefaultDispSocketFileMode - } - if cfg.UnderlayPort == 0 { - cfg.UnderlayPort = topology.EndhostPort - } - if cfg.ID == "" { - return serrors.New("id must be set") - } - return nil -} - -func (cfg *Dispatcher) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) { - config.WriteString(dst, fmt.Sprintf(dispSample, idSample)) -} - -func (cfg *Dispatcher) ConfigName() string { - return "dispatcher" -} - func (cfg *Config) InitDefaults() { config.InitAll( &cfg.Features, @@ -113,3 +72,50 @@ func (cfg *Config) Sample(dst io.Writer, path config.Path, _ config.CtxMap) { func (cfg *Config) ConfigName() string { return "dispatcher_config" } + +// Dispatcher contains the dispatcher specific config. +type Dispatcher struct { + // ID is the SCION element ID of the shim dispatcher. + ID string `toml:"id,omitempty"` + // LocalUDPForwarding specifies whether UDP forwarding is enabled for the dispatcher. + // Otherwise, it will only reply to SCMPInfo packets. + LocalUDPForwarding bool `toml:"local_udp_forwarding,omitempty"` + // ServiceAddresses is the map of IA,SVC -> underlay UDP/IP address. + // The map should be configured provided that the shim dispatcher runs colocated to such + // mapped services, e.g., the shim dispatcher runs on the same host, + // where the CS for the local IA runs. + ServiceAddresses map[addr.Addr]netip.AddrPort `toml:"service_addresses,omitempty"` + // UnderlayAddr is the IP address where the shim dispatcher listens on (default ::). + UnderlayAddr netip.Addr `toml:"underlay_addr,omitempty"` +} + +func (cfg *Dispatcher) InitDefaults() { + if cfg.UnderlayAddr == (netip.Addr{}) { + cfg.UnderlayAddr = netip.IPv6Unspecified() + } +} + +func (cfg *Dispatcher) Validate() error { + if !cfg.UnderlayAddr.IsValid() { + return serrors.New("underlay_addr is not set or it is incorrect") + } + if cfg.ID == "" { + return serrors.New("id must be set") + } + + // Process ServiceAddresses + for iaSVC := range cfg.ServiceAddresses { + if iaSVC.Host.Type() != addr.HostTypeSVC { + return serrors.New("parsed address must be SVC", "type", iaSVC.Host.Type().String()) + } + } + return nil +} + +func (cfg *Dispatcher) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) { + config.WriteString(dst, fmt.Sprintf(dispSample, idSample)) +} + +func (cfg *Dispatcher) ConfigName() string { + return "dispatcher" +} diff --git a/dispatcher/config/config_test.go b/dispatcher/config/config_test.go index 07338d9ab2..f24dea5915 100644 --- a/dispatcher/config/config_test.go +++ b/dispatcher/config/config_test.go @@ -23,10 +23,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/scionproto/scion/pkg/log/logtest" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/env/envtest" apitest "github.com/scionproto/scion/private/mgmtapi/mgmtapitest" - "github.com/scionproto/scion/private/topology" ) func TestConfigSample(t *testing.T) { @@ -44,7 +42,7 @@ func InitTestConfig(cfg *Config) { apitest.InitConfig(&cfg.API) envtest.InitTest(nil, &cfg.Metrics, nil, nil) logtest.InitTestLogging(&cfg.Logging) - cfg.Dispatcher.DeleteSocket = true + cfg.Dispatcher.InitDefaults() } func CheckTestConfig(t *testing.T, cfg *Config, id string) { @@ -52,8 +50,6 @@ func CheckTestConfig(t *testing.T, cfg *Config, id string) { envtest.CheckTest(t, nil, &cfg.Metrics, nil, nil, id) logtest.CheckTestLogging(t, &cfg.Logging, id) assert.Equal(t, id, cfg.Dispatcher.ID) - assert.Equal(t, reliable.DefaultDispPath, cfg.Dispatcher.ApplicationSocket) - assert.Equal(t, reliable.DefaultDispSocketFileMode, int(cfg.Dispatcher.SocketFileMode)) - assert.Equal(t, topology.EndhostPort, cfg.Dispatcher.UnderlayPort) - assert.False(t, cfg.Dispatcher.DeleteSocket) + assert.True(t, cfg.Dispatcher.UnderlayAddr.IsValid()) + assert.Len(t, cfg.Dispatcher.ServiceAddresses, 6) } diff --git a/dispatcher/config/sample.go b/dispatcher/config/sample.go index e105927c6e..040cf23250 100644 --- a/dispatcher/config/sample.go +++ b/dispatcher/config/sample.go @@ -21,15 +21,19 @@ const dispSample = ` # ID of the Dispatcher. (required) id = "%s" -# The local API socket. (default /run/shm/dispatcher/default.sock) -application_socket = "/run/shm/dispatcher/default.sock" +# The underlay IP address opened by the dispatcher. (default ::) +# underlay_addr = "::" -# File permissions of the ApplicationSocket socket file, in octal. (default "0770") -socket_file_mode = "0770" - -# The native port opened by the dispatcher. (default 30041) -underlay_port = 30041 - -# Remove the socket file (if it exists) on start. (default false) -delete_socket = false +# ServiceAddresses is the map of IA,SVC -> underlay UDP/IP address. +# The map should be configured provided that the shim dispatcher runs colocated to such +# mapped services, e.g., the shim dispatcher runs on the same host, +# where the CS for the local IA runs. +# For other use cases it can be ignored. +[dispatcher.service_addresses] +"1-ff00:0:110,CS" = "[fd00:f00d:cafe::7f00:14]:31000" +"1-ff00:0:110,DS" = "[fd00:f00d:cafe::7f00:14]:31000" +"1-ff00:0:120,CS" = "127.0.0.68:31008" +"1-ff00:0:120,DS" = "127.0.0.68:31008" +"1-ff00:0:130,CS" = "[fd00:f00d:cafe::7f00:2b]:31016" +"1-ff00:0:130,DS" = "[fd00:f00d:cafe::7f00:2b]:31016" ` diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 86d15a09d8..2bb71e6d89 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -1,4 +1,4 @@ -// Copyright 2020 Anapaya Systems +// Copyright 2023 ETH Zurich // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,272 +15,540 @@ package dispatcher import ( - "context" + "fmt" "net" - "time" + "net/netip" + + "github.com/google/gopacket" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" - "github.com/scionproto/scion/dispatcher/internal/registration" - "github.com/scionproto/scion/dispatcher/internal/respool" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/slayers" - "github.com/scionproto/scion/private/ringbuf" - "github.com/scionproto/scion/private/underlay/conn" + "github.com/scionproto/scion/pkg/slayers/path/epic" + "github.com/scionproto/scion/pkg/slayers/path/scion" ) -const ( - // ReceiveBufferSize is the size of receive buffers used by the dispatcher. - ReceiveBufferSize = 1 << 20 - // SendBufferSize is the size of the send buffers used by the dispatcher. - SendBufferSize = 1 << 20 -) +const ErrUnsupportedL4 common.ErrMsg = "unsupported SCION L4 protocol" -// Server is the main object allowing to create new SCION connections. +// Server is the main object allowing to forward SCION packets coming +// from legacy BR to the final endhost application and to handle SCMP +// info packets destined to this endhost. type Server struct { - // routingTable is used to register new connections. - routingTable *IATable - ipv4Conn net.PacketConn - ipv6Conn net.PacketConn + // isDispatcher indicates whether the shim acts as SCION packet + // dispatcher + isDispatcher bool + conn *net.UDPConn + // topo keeps the topology for the local AS. It can keep multiple ASes + // in case we run several topologies locally, e.g., developer environment. + + // TODO(JordiSubira): This may be taken from daemon for non self-contained + // applications. + ServiceAddresses map[addr.Addr]netip.AddrPort + buf []byte + oobuf []byte + outBuffer gopacket.SerializeBuffer + decoded []gopacket.LayerType + parser *gopacket.DecodingLayerParser + cmParser controlMessageParser + options gopacket.SerializeOptions + + scionLayer slayers.SCION + hbh slayers.HopByHopExtnSkipper + e2e slayers.EndToEndExtn + udpLayer slayers.UDP + scmpLayer slayers.SCMP } -// NewServer creates new instance of Server. Internally, it opens the dispatcher ports -// for both IPv4 and IPv6. Returns error if the ports can't be opened. -func NewServer(address string, ipv4Conn, ipv6Conn net.PacketConn) (*Server, error) { - if ipv4Conn == nil { - var err error - ipv4Conn, err = openConn("udp4", address) - if err != nil { - return nil, err - } +// NewServer creates new instance of Server. +func NewServer( + isDispatcher bool, + svcAddrs map[addr.Addr]netip.AddrPort, + conn *net.UDPConn, +) *Server { + server := Server{ + isDispatcher: isDispatcher, + ServiceAddresses: svcAddrs, + buf: make([]byte, common.SupportedMTU), + oobuf: make([]byte, 1024), + decoded: make([]gopacket.LayerType, 4), + outBuffer: gopacket.NewSerializeBuffer(), + options: gopacket.SerializeOptions{ + ComputeChecksums: true, + FixLengths: true, + }, } - if ipv6Conn == nil { - var err error - ipv6Conn, err = openConn("udp6", address) - if err != nil { - ipv4Conn.Close() - return nil, err - } + parser := gopacket.NewDecodingLayerParser( + slayers.LayerTypeSCION, + &server.scionLayer, + &server.hbh, + &server.e2e, + &server.udpLayer, + &server.scmpLayer, + ) + parser.IgnoreUnsupported = true + server.parser = parser + server.conn = conn + if isDispatcher { + server.conn, server.cmParser = setIPPktInfo(conn) } - return &Server{ - routingTable: NewIATable(32768, 65535), - ipv4Conn: ipv4Conn, - ipv6Conn: ipv6Conn, - }, nil + server.scionLayer.RecyclePaths() + server.udpLayer.SetNetworkLayerForChecksum(&server.scionLayer) + server.scmpLayer.SetNetworkLayerForChecksum(&server.scionLayer) + return &server } -// Serve starts reading packets from network and dispatching them to different connections. +// Serve starts reading packets from network and dispatching them to the end application. +// It also replies to SCMPEchoRequest and SCMPTracerouteRequest. // The function blocks and returns if there's an error or when Close has been called. -func (as *Server) Serve() error { - errChan := make(chan error) - go func() { - defer log.HandlePanic() - netToRingDataplane := &NetToRingDataplane{ - UnderlayConn: as.ipv4Conn, - RoutingTable: as.routingTable, +func (s *Server) Serve() error { + for { + n, nn, _, prevHop, err := s.conn.ReadMsgUDPAddrPort(s.buf, s.oobuf) + if err != nil { + log.Error("Reading message", "err", err) + continue } - errChan <- netToRingDataplane.Run() - }() - go func() { - defer log.HandlePanic() - netToRingDataplane := &NetToRingDataplane{ - UnderlayConn: as.ipv6Conn, - RoutingTable: as.routingTable, + + var underlay netip.Addr + if s.isDispatcher { + underlay = s.parseUnderlayAddr(s.oobuf[:nn]) + if !underlay.IsValid() { + // some error parsing the CM info from the incoming packet; + // we discard the packet and keep serving. + continue + } } - errChan <- netToRingDataplane.Run() - }() - return <-errChan -} -// Register creates a new connection. -func (as *Server) Register(ctx context.Context, ia addr.IA, address *net.UDPAddr, - svc addr.SVC) (net.PacketConn, uint16, error) { + outBuf, nextHopAddr, err := s.processMsgNextHop(s.buf[:n], underlay, prevHop) + if err != nil { + return err + } + if !nextHopAddr.IsValid() { + // some error processing the incoming packet; + // we discard the packet and keep serving. + continue + } + + m, err := s.conn.WriteToUDPAddrPort(outBuf, nextHopAddr) + if err != nil { + log.Error("writing packet out", "err", err) + continue + } + if m != len(outBuf) { + log.Error("writing packet out", "message len", len(outBuf), "written bytes", n) + } + } +} - tableEntry := newTableEntry() - ref, err := as.routingTable.Register(ia, address, nil, svc, tableEntry) +// processMsgNextHop processes the message arriving at the shim dispatcher and returns +// a byte array corresponding to the packet that needs to be forwarded. +// The input byte array `buf` is the raw incoming packet; +// `underlay` corresponds to the IP address on the outer UDP/IP header; +// `prevHop` is the address from the previous SCION hop in the local network. +// The intended nextHop address, i.e., either the end application +// or the next BR (for SCMP informational response), is returned. +// It returns a non-nil error for non-recoverable errors only. +// If the incoming packet couldn't be processed due to a recoverable error or +// incorrect address validation, the returned buffer will be nil and the address +// will be empty. +// The caller must consistently check both values. +func (s *Server) processMsgNextHop( + buf []byte, + underlay netip.Addr, + prevHop netip.AddrPort, +) ([]byte, netip.AddrPort, error) { + + err := s.parser.DecodeLayers(buf, &s.decoded) if err != nil { - return nil, 0, err + log.Error("Decoding layers", "err", err) + return nil, netip.AddrPort{}, nil } - var ovConn net.PacketConn - if address.IP.To4() == nil { - ovConn = as.ipv6Conn - } else { - ovConn = as.ipv4Conn + if len(s.decoded) < 2 { + log.Error("Unexpected packet", "layers decoded", len(s.decoded)) + return nil, netip.AddrPort{}, nil } - conn := &Conn{ - conn: ovConn, - ring: tableEntry.appIngressRing, - regReference: ref, + err = s.outBuffer.Clear() + if err != nil { + return nil, netip.AddrPort{}, err } - return conn, uint16(ref.UDPAddr().Port), nil -} -func (as *Server) Close() { - as.ipv4Conn.Close() - as.ipv6Conn.Close() -} + // If the dispatcher feature flag is disabled we only process SCMPInfo packets. + if !s.isDispatcher { + if s.decoded[len(s.decoded)-1] != slayers.LayerTypeSCMP { + log.Debug("Dispatcher feature is disabled, shim discards non-SCMPInfo packets", + "received", s.decoded[len(s.decoded)-1]) + return nil, netip.AddrPort{}, nil + } + if s.scmpLayer.TypeCode.Type() != slayers.SCMPTypeTracerouteRequest && + s.scmpLayer.TypeCode.Type() != slayers.SCMPTypeEchoRequest { + log.Debug("Dispatcher feature is disabled, shim discards non-SCMPInfo packets", + "received", s.scmpLayer.TypeCode.Type()) + return nil, netip.AddrPort{}, nil + } + } -// Conn represents a connection bound to a specific SCION port/SVC. -type Conn struct { - // conn is used to send packets. - conn net.PacketConn - // ring is used to retrieve incoming packets. - ring *ringbuf.Ring - // regReference is the reference to the registration in the routing table. - regReference registration.RegReference -} + var dstAddrPort netip.AddrPort + // Retrieve DST UDP/SCION addr and compare to underlay address if it applies, + // i.e., all cases expect SCMPInfo request messages, which are to be replied + // by the shim dispatcher itself. + switch s.decoded[len(s.decoded)-1] { + case slayers.LayerTypeSCMP: + // send response to BR + if s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeTracerouteRequest || + s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeEchoRequest { + dstAddrPort = prevHop + } else { // relay to end application + dstAddrPort, err = s.getDstSCMP() + if err != nil { + log.Error("Getting destination for SCMP message", "err", err) + return nil, netip.AddrPort{}, nil + } + if dstAddrPort.Addr().Unmap().Compare(underlay.Unmap()) != 0 { + log.Error("UDP/IP addr destination different from UDP/SCION addr", + "UDP/IP:", underlay.Unmap().String(), + "UDP/SCION:", dstAddrPort.Addr().Unmap().String()) + return nil, netip.AddrPort{}, nil + } + } + case slayers.LayerTypeSCIONUDP: + dstAddrPort, err = s.getDstSCIONUDP() + if err != nil { + log.Error("Getting destination for SCION/UDP message", "err", err) + return nil, netip.AddrPort{}, nil + } + if dstAddrPort.Addr().Unmap().Compare(underlay.Unmap()) != 0 { + log.Error("UDP/IP addr destination different from UDP/SCION addr", + "UDP/IP:", underlay.Unmap().String(), + "UDP/SCION:", dstAddrPort.Addr().Unmap().String()) + return nil, netip.AddrPort{}, nil + } + } + + var outBuf []byte + // generate SCMPInfo response + if s.decoded[len(s.decoded)-1] == slayers.LayerTypeSCMP && + (s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeTracerouteRequest || + s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeEchoRequest) { + err = s.replyToSCMPInfoRequest() + if err != nil { + log.Error("Reversing SCMP information", "err", err) + return nil, netip.AddrPort{}, nil + } + payload := gopacket.Payload(s.scmpLayer.Payload) + err = payload.SerializeTo(s.outBuffer, s.options) + if err != nil { + log.Error("Serializing payload", "err", err) + return nil, netip.AddrPort{}, nil + } + s.outBuffer.PushLayer(payload.LayerType()) + + err = s.scmpLayer.SerializeTo(s.outBuffer, s.options) + if err != nil { + log.Error("Serializing SCMP header", "err", err) + return nil, netip.AddrPort{}, nil + } + s.outBuffer.PushLayer(s.scmpLayer.LayerType()) + + if s.decoded[len(s.decoded)-2] == slayers.LayerTypeEndToEndExtn { + err = s.e2e.SerializeTo(s.outBuffer, s.options) + if err != nil { + log.Error("Serializing e2e extension", "err", err) + return nil, netip.AddrPort{}, nil + } + s.outBuffer.PushLayer(s.e2e.LayerType()) + } + err = s.scionLayer.SerializeTo(s.outBuffer, s.options) + if err != nil { + log.Error("Serializing SCION header", "err", err) + return nil, netip.AddrPort{}, nil + } + s.outBuffer.PushLayer(s.scionLayer.LayerType()) + outBuf = s.outBuffer.Bytes() + } else { //forward incoming byte array + outBuf = buf + } -func (ac *Conn) WriteTo(p []byte, addr net.Addr) (int, error) { - panic("not implemented") + return outBuf, dstAddrPort, nil } -// Write is optimized for the use by ConnHandler (avoids reparsing the packet). -func (ac *Conn) Write(pkt *respool.Packet) (int, error) { - // XXX(roosd): Ignore error since there is nothing we can do about it. - // Currently, the ID space is shared between all applications that register - // with the dispatcher. Likelihood that they overlap is very small. - // If this becomes ever a problem, we can namespace the ID per registered - // application. - _ = registerIfSCMPInfo(ac.regReference, pkt) - return pkt.SendOnConn(ac.conn, pkt.UnderlayRemote) +func (s *Server) replyToSCMPInfoRequest() error { + // Translate request to a reply. + switch s.scmpLayer.NextLayerType() { + case slayers.LayerTypeSCMPEcho: + s.scmpLayer.TypeCode = slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0) + case slayers.LayerTypeSCMPTraceroute: + s.scmpLayer.TypeCode = slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0) + default: + return serrors.New("unsupported SCMP informational message") + } + if err := s.reverseSCION(); err != nil { + return err + } + // XXX(roosd): This does not take HBH and E2E extensions into consideration. + // See: https://github.com/scionproto/scion/issues/4128 + // TODO(JordiSubira): Add support for SPAO-E2E + s.scionLayer.NextHdr = slayers.L4SCMP + return nil } -func (ac *Conn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - pkt := ac.Read() - if pkt == nil { - return 0, nil, serrors.New("Connection closed") +func (s *Server) reverseSCION() error { + // Reverse the SCION packet. + s.scionLayer.DstIA, s.scionLayer.SrcIA = s.scionLayer.SrcIA, s.scionLayer.DstIA + src, err := s.scionLayer.SrcAddr() + if err != nil { + return serrors.WrapStr("parsing source address", err) + } + dst, err := s.scionLayer.DstAddr() + if err != nil { + return serrors.WrapStr("parsing destination address", err) } - n = pkt.CopyTo(p) - addr = pkt.UnderlayRemote - pkt.Free() - return + if err := s.scionLayer.SetSrcAddr(dst); err != nil { + return serrors.WrapStr("setting source address", err) + } + if err := s.scionLayer.SetDstAddr(src); err != nil { + return serrors.WrapStr("setting destination address", err) + } + if s.scionLayer.PathType == epic.PathType { + // Received packet with EPIC path type, hence extract the SCION path + epicPath, ok := s.scionLayer.Path.(*epic.Path) + if !ok { + return serrors.New("path type and path data do not match") + } + s.scionLayer.Path = epicPath.ScionPath + s.scionLayer.PathType = scion.PathType + } + if s.scionLayer.Path, err = s.scionLayer.Path.Reverse(); err != nil { + return serrors.WrapStr("reversing path", err) + } + return nil } -// Read is optimized for the use by ConnHandler (avoids one copy). -func (ac *Conn) Read() *respool.Packet { - entries := make(ringbuf.EntryList, 1) - n, _ := ac.ring.Read(entries, true) - if n < 0 { - // Ring was closed because app shut down its data socket. - return nil +func (s *Server) getDstSCMP() (netip.AddrPort, error) { + // Check if its SCMPEcho or SCMPTraceroute reply + if s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeEchoReply { + var scmpEcho slayers.SCMPEcho + err := scmpEcho.DecodeFromBytes(s.scmpLayer.Payload, gopacket.NilDecodeFeedback) + if err != nil { + return netip.AddrPort{}, err + } + return addrPortFromBytes(s.scionLayer.RawDstAddr, scmpEcho.Identifier) + } + if s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeTracerouteReply { + var scmpTraceroute slayers.SCMPTraceroute + err := scmpTraceroute.DecodeFromBytes(s.scmpLayer.Payload, gopacket.NilDecodeFeedback) + if err != nil { + return netip.AddrPort{}, err + } + return addrPortFromBytes(s.scionLayer.RawDstAddr, scmpTraceroute.Identifier) + } + + // Drop unknown SCMP error messages. + if s.scmpLayer.NextLayerType() == gopacket.LayerTypePayload { + return netip.AddrPort{}, serrors.New("unsupported SCMP error message", + "type", s.scmpLayer.TypeCode.Type()) + } + l, err := decodeSCMP(&s.scmpLayer) + if err != nil { + return netip.AddrPort{}, err + } + if len(l) != 2 { + return netip.AddrPort{}, serrors.New("SCMP error message without payload") + } + gpkt := gopacket.NewPacket(*l[1].(*gopacket.Payload), slayers.LayerTypeSCION, + gopacket.DecodeOptions{ + NoCopy: true, + }, + ) + + // If the offending packet was UDP/SCION, use the source port to deliver. + if udp := gpkt.Layer(slayers.LayerTypeSCIONUDP); udp != nil { + port := udp.(*slayers.UDP).SrcPort + // XXX(roosd): We assume that the zero value means the UDP header is + // truncated. This flags packets of misbehaving senders as truncated, if + // they set the source port to 0. But there is no harm, since those + // packets are destined to be dropped anyway. + if port == 0 { + return netip.AddrPort{}, serrors.New("SCMP error with truncated UDP header") + } + return addrPortFromBytes(s.scionLayer.RawDstAddr, port) + } + + // If the offending packet was SCMP/SCION, and it is an echo or traceroute, + // use the Identifier to deliver. In all other cases, the message is dropped. + if scmp := gpkt.Layer(slayers.LayerTypeSCMP); scmp != nil { + + tc := scmp.(*slayers.SCMP).TypeCode + // SCMP Error messages in response to an SCMP error message are not allowed. + if !tc.InfoMsg() { + return netip.AddrPort{}, + serrors.New("SCMP error message in response to SCMP error message", + "type", tc.Type()) + } + // We only support echo and traceroute requests. + t := tc.Type() + if t != slayers.SCMPTypeEchoRequest && t != slayers.SCMPTypeTracerouteRequest { + return netip.AddrPort{}, serrors.New("unsupported SCMP info message", "type", t) + } + + var port uint16 + // Extract the port from the echo or traceroute ID field. + if echo := gpkt.Layer(slayers.LayerTypeSCMPEcho); echo != nil { + port = echo.(*slayers.SCMPEcho).Identifier + } else if tr := gpkt.Layer(slayers.LayerTypeSCMPTraceroute); tr != nil { + port = tr.(*slayers.SCMPTraceroute).Identifier + } else { + return netip.AddrPort{}, serrors.New("SCMP error with truncated payload") + } + return addrPortFromBytes(s.scionLayer.RawDstAddr, port) } - pkt := entries[0].(*respool.Packet) - return pkt + return netip.AddrPort{}, ErrUnsupportedL4 } -func (ac *Conn) Close() error { - ac.regReference.Free() - ac.ring.Close() - return nil +func (s *Server) getDstSCIONUDP() (netip.AddrPort, error) { + host, err := s.scionLayer.DstAddr() + if err != nil { + return netip.AddrPort{}, err + } + switch host.Type() { + case addr.HostTypeSVC: + hostAddr := addr.Addr{IA: s.scionLayer.DstIA, Host: host} + addrPort, ok := s.ServiceAddresses[hostAddr] + if !ok { + return netip.AddrPort{}, serrors.New("SVC destination not found", + "Host", hostAddr) + } + return addrPort, nil + case addr.HostTypeIP: + return addrPortFromBytes(s.scionLayer.RawDstAddr, s.udpLayer.DstPort) + default: + return netip.AddrPort{}, serrors.New("invalid host type", "type", host.Type().String()) + } } -func (ac *Conn) LocalAddr() net.Addr { - return ac.regReference.UDPAddr() +type controlMessageParser interface { + Destination() net.IP + Parse(b []byte) error + String() string } -func (ac *Conn) SVCAddr() addr.SVC { - return ac.regReference.SVCAddr() +type ipv4ControlMessage struct { + *ipv4.ControlMessage } -func (ac *Conn) SetDeadline(t time.Time) error { - panic("not implemented") +func (m ipv4ControlMessage) Destination() net.IP { + return m.Dst } -func (ac *Conn) SetReadDeadline(t time.Time) error { - panic("not implemented") +type ipv6ControlMessage struct { + *ipv6.ControlMessage } -func (ac *Conn) SetWriteDeadline(t time.Time) error { - panic("not implemented") +func (m ipv6ControlMessage) Destination() net.IP { + return m.Dst } -// openConn opens an underlay socket that tracks additional socket information -// such as packets dropped due to buffer full. -// -// Note that Go-style dual-stacked IPv4/IPv6 connections are not supported. If -// network is udp, it will be treated as udp4. -func openConn(network, address string) (net.PacketConn, error) { - // We cannot allow the Go standard library to open both types of sockets - // because the socket options are specific to only one socket type, so we - // degrade udp to only udp4. - if network == "udp" { - network = "udp4" - } - listeningAddress, err := net.ResolveUDPAddr(network, address) - if err != nil { - return nil, serrors.WrapStr("unable to construct UDP addr", err) - } - if network == "udp4" && listeningAddress.IP == nil { - listeningAddress.IP = net.IPv4zero - } - if network == "udp6" && listeningAddress.IP == nil { - listeningAddress.IP = net.IPv6zero +// parseUnderlayAddr returns the underlay destination address on the outer UDP/IP wrapper. +// It returns an empty address, if the control message information is not present +// or it cannot be parsed. +// This is useful for checking that this address corresponds to the address of the inner +// UDP/SCION header. This refers to the safeguard for traffic reflection as discussed in: +// https://github.com/scionproto/scion/pull/4280#issuecomment-1775177351 +func (s *Server) parseUnderlayAddr(oobuffer []byte) netip.Addr { + if err := s.cmParser.Parse(oobuffer); err != nil { + log.Error("Parsing Control Message Information", "err", err) + return netip.Addr{} } - - c, err := conn.New(listeningAddress, nil, &conn.Config{ - SendBufferSize: SendBufferSize, - ReceiveBufferSize: ReceiveBufferSize, - }) - if err != nil { - return nil, serrors.WrapStr("unable to open conn", err) + if !s.cmParser.Destination().IsUnspecified() { + pktAddr, ok := netip.AddrFromSlice(s.cmParser.Destination()) + if !ok { + log.Error("Getting DST from IP_PKTINFO", "DST", s.cmParser.Destination()) + return netip.Addr{} + } + return pktAddr } - - return &underlayConnWrapper{Conn: c}, nil + log.Error("Destination in IP_PKTINFO is unspecified") + return netip.Addr{} } -// registerIfSCMPInfo registers the ID of the SCMP request if it is an echo or -// traceroute message. -func registerIfSCMPInfo(ref registration.RegReference, pkt *respool.Packet) error { - if pkt.L4 != slayers.LayerTypeSCMP { - return nil - } - t := pkt.SCMP.TypeCode.Type() - if t != slayers.SCMPTypeEchoRequest && t != slayers.SCMPTypeTracerouteRequest { - return nil - } - id, err := extractSCMPIdentifier(&pkt.SCMP) +func ListenAndServe( + isDispatcher bool, + svcAddrs map[addr.Addr]netip.AddrPort, + addr *net.UDPAddr, +) error { + + conn, err := net.ListenUDP(addr.Network(), addr) if err != nil { return err } - // FIXME(roosd): add metrics again. - return ref.RegisterID(uint64(id)) -} + defer conn.Close() + log.Debug(fmt.Sprintf("local address: %s", conn.LocalAddr())) + dispServer := NewServer(isDispatcher, svcAddrs, conn) -// underlayConnWrapper wraps a specialized underlay conn into a net.PacketConn -// implementation. Only *net.UDPAddr addressing is supported. -type underlayConnWrapper struct { - // Conn is the wrapped underlay connection object. - conn.Conn + return dispServer.Serve() } -func (o *underlayConnWrapper) ReadFrom(p []byte) (int, net.Addr, error) { - return o.Conn.ReadFrom(p) +// decodeSCMP decodes the SCMP payload. WARNING: Decoding is done with NoCopy set. +func decodeSCMP(scmp *slayers.SCMP) ([]gopacket.SerializableLayer, error) { + gpkt := gopacket.NewPacket(scmp.Payload, scmp.NextLayerType(), + gopacket.DecodeOptions{NoCopy: true}) + layers := gpkt.Layers() + if len(layers) == 0 || len(layers) > 2 { + return nil, serrors.New("invalid number of SCMP layers", "count", len(layers)) + } + ret := make([]gopacket.SerializableLayer, len(layers)) + for i, l := range layers { + s, ok := l.(gopacket.SerializableLayer) + if !ok { + return nil, serrors.New("invalid SCMP layer, not serializable", "index", i) + } + ret[i] = s + } + return ret, nil } -func (o *underlayConnWrapper) WriteTo(p []byte, a net.Addr) (int, error) { - udpAddr, ok := a.(*net.UDPAddr) +func addrPortFromBytes(addr []byte, port uint16) (netip.AddrPort, error) { + a, ok := netip.AddrFromSlice(addr) if !ok { - return 0, serrors.New("address is not UDP", "addr", a) + return netip.AddrPort{}, serrors.New("unexpected raw address byte slice format") } - return o.Conn.WriteTo(p, udpAddr) + return netip.AddrPortFrom(a, port), nil } -func (o *underlayConnWrapper) Close() error { - return o.Conn.Close() -} - -func (o *underlayConnWrapper) LocalAddr() net.Addr { - return o.Conn.LocalAddr() -} - -func (o *underlayConnWrapper) SetDeadline(t time.Time) error { - return o.Conn.SetDeadline(t) -} +// setIPPktInfo sets the IP_PKTINFO.DST flag to the underlay socket. The IPv4 part +// covers the case for IPv4-only hosts. For hosts supporting dual stack, the IPv6 +// part handles both 6 and 4 (with mapped addresses). +// The argument conn must not be nil. The returned conn will have the flag set, +// and the returned controlMessageParser can be used as a facilitator to +// parse the OOB after reading on the conn. +func setIPPktInfo(conn *net.UDPConn) (*net.UDPConn, controlMessageParser) { + udpAddr, ok := conn.LocalAddr().(*net.UDPAddr) + if !ok { + panic(fmt.Sprintln("Connection address is not UDPAddr", + "conn", conn.LocalAddr().Network())) + } -func (o *underlayConnWrapper) SetReadDeadline(t time.Time) error { - return o.Conn.SetReadDeadline(t) -} + var cm controlMessageParser + if udpAddr.AddrPort().Addr().Unmap().Is4() { + err := ipv4.NewPacketConn(conn).SetControlMessage(ipv4.FlagDst, true) + if err != nil { + panic(fmt.Sprintf("cannot set IP_PKTINFO on socket: %s", err)) + } + cm = ipv4ControlMessage{ + ControlMessage: new(ipv4.ControlMessage), + } + } + if udpAddr.AddrPort().Addr().Unmap().Is6() { + err := ipv6.NewPacketConn(conn).SetControlMessage(ipv6.FlagDst, true) + if err != nil { + panic(fmt.Sprintf("cannot set IP_PKTINFO on socket: %s", err)) + } + cm = ipv6ControlMessage{ + ControlMessage: new(ipv6.ControlMessage), + } + } -func (o *underlayConnWrapper) SetWriteDeadline(t time.Time) error { - return o.Conn.SetWriteDeadline(t) + return conn, cm } diff --git a/dispatcher/dispatcher_test.go b/dispatcher/dispatcher_test.go new file mode 100644 index 0000000000..a3905a2712 --- /dev/null +++ b/dispatcher/dispatcher_test.go @@ -0,0 +1,390 @@ +// Copyright 2023 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dispatcher + +import ( + "net" + "net/netip" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/private/xtest" + "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" +) + +type testCase struct { + Name string + IsDispatcher bool + ClientAddrPort netip.AddrPort + DispAddrPort netip.AddrPort + Pkt *snet.Packet + ExpectedValue bool +} + +func testRunTestCase(t *testing.T, tc testCase) { + serverConn, err := net.ListenUDP("udp", net.UDPAddrFromAddrPort(tc.DispAddrPort)) + require.NoError(t, err) + defer serverConn.Close() + setIPPktInfo(serverConn) + emptyTopo := make(map[addr.Addr]netip.AddrPort) + server := NewServer(tc.IsDispatcher, emptyTopo, serverConn) + + clientConn, err := net.DialUDP( + "udp", + net.UDPAddrFromAddrPort(tc.ClientAddrPort), + net.UDPAddrFromAddrPort(tc.DispAddrPort), + ) + require.NoError(t, err) + defer clientConn.Close() + require.NoError(t, tc.Pkt.Serialize()) + _, err = clientConn.Write(tc.Pkt.Bytes) + require.NoError(t, err) + + buf := make([]byte, 1024) + oobuf := make([]byte, 1024) + n, nn, _, nextHop, err := server.conn.ReadMsgUDPAddrPort(buf, oobuf) + require.NoError(t, err) + var underlayAddr netip.Addr + if tc.IsDispatcher { + underlayAddr = server.parseUnderlayAddr(oobuf[:nn]) + require.NotNil(t, underlayAddr) + } + _, dstAddr, err := server.processMsgNextHop(buf[:n], underlayAddr, nextHop) + assert.NoError(t, err) + assert.Equal(t, tc.ExpectedValue, dstAddr.IsValid()) +} + +func TestValidateAddr(t *testing.T) { + clientAddr := netip.MustParseAddr("127.0.0.1") + clientHost := addr.HostIP(clientAddr) + clientAddrPort := netip.AddrPortFrom(clientAddr, 0) + dispIPv4Addr := netip.MustParseAddr("127.0.0.1") + dispIPv4Host := addr.HostIP(dispIPv4Addr) + dispIPv4AddrPort := netip.AddrPortFrom(dispIPv4Addr, 40032) + clientIPv6Addr := netip.MustParseAddr("::1") + clientIPv6Host := addr.HostIP(clientIPv6Addr) + clientIPv6AddrPort := netip.AddrPortFrom(clientIPv6Addr, 0) + dispIPv6Addr := netip.MustParseAddr("::1") + dispIPv6Host := addr.HostIP(dispIPv6Addr) + dispIPv6AddrPort := netip.AddrPortFrom(dispIPv6Addr, 40032) + + testCases := []testCase{ + { + Name: "valid UDP/IPv4", + IsDispatcher: true, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: dispIPv4Host, + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid UDP/IPv4", + IsDispatcher: true, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.MustParseHost("127.0.0.2"), + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + { + Name: "valid SCMP/IPv4", + IsDispatcher: true, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: dispIPv4Host, + }, + Payload: snet.SCMPDestinationUnreachable{ + Payload: MustPack(snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: dispIPv4Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: clientHost, + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid SCMP/IPv4", + IsDispatcher: true, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.MustParseHost("127.0.0.2"), + }, + Payload: snet.SCMPDestinationUnreachable{ + Payload: MustPack(snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: dispIPv4Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: clientHost, + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + { + Name: "valid UDP/IPv6", + IsDispatcher: true, + ClientAddrPort: clientIPv6AddrPort, + DispAddrPort: dispIPv6AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientIPv6Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: dispIPv6Host, + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid UDP/IPv6", + IsDispatcher: true, + ClientAddrPort: clientIPv6AddrPort, + DispAddrPort: dispIPv6AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.MustParseHost("::2"), + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + { + Name: "valid SCMP/IPv6", + IsDispatcher: true, + ClientAddrPort: clientIPv6AddrPort, + DispAddrPort: dispIPv6AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientIPv6Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: dispIPv6Host, + }, + Payload: snet.SCMPDestinationUnreachable{ + Payload: MustPack(snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: dispIPv6Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: clientIPv6Host, + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid SCMP/IPv6", + IsDispatcher: true, + ClientAddrPort: clientIPv6AddrPort, + DispAddrPort: dispIPv6AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientIPv6Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.MustParseHost("::2"), + }, + Payload: snet.SCMPDestinationUnreachable{ + Payload: MustPack(snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: dispIPv6Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: clientIPv6Host, + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + { + Name: "IPv4-mapped-IPv6 to IPv4", + IsDispatcher: true, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(netip.AddrFrom16(dispIPv4Addr.As16())), + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "isn't dispatcher", + IsDispatcher: false, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: dispIPv4Host, + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + } + for _, test := range testCases { + t.Run(test.Name, func(t *testing.T) { + testRunTestCase(t, test) + }) + } + +} + +func MustPack(pkt snet.Packet) []byte { + if err := pkt.Serialize(); err != nil { + panic(err) + } + return pkt.Bytes +} diff --git a/dispatcher/internal/metrics/BUILD.bazel b/dispatcher/internal/metrics/BUILD.bazel deleted file mode 100644 index 1acf5d718d..0000000000 --- a/dispatcher/internal/metrics/BUILD.bazel +++ /dev/null @@ -1,12 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["metrics.go"], - importpath = "github.com/scionproto/scion/dispatcher/internal/metrics", - visibility = ["//dispatcher:__subpackages__"], - deps = [ - "//pkg/private/prom:go_default_library", - "@com_github_prometheus_client_golang//prometheus:go_default_library", - ], -) diff --git a/dispatcher/internal/metrics/metrics.go b/dispatcher/internal/metrics/metrics.go deleted file mode 100644 index 37903fc63f..0000000000 --- a/dispatcher/internal/metrics/metrics.go +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "github.com/prometheus/client_golang/prometheus" - - "github.com/scionproto/scion/pkg/private/prom" -) - -// Namespace is the metrics namespace for the dispatcher. -const Namespace = "disp" - -// Packet result labels -const ( - PacketResultParseError = "parse_error" - PacketResultRouteNotFound = "route_not_found" - PacketResultOk = "ok" -) - -var ( - // M exposes all the initialized metrics for this package. - M = newMetrics() -) - -// IncomingPacket contains the labels for incoming packet metrics. -type IncomingPacket struct { - Result string -} - -// Labels returns the list of labels. -func (l IncomingPacket) Labels() []string { - return []string{"incoming_packet_result"} -} - -// Values returns the label values in the order defined by Labels. -func (l IncomingPacket) Values() []string { - return []string{l.Result} -} - -// SVC contains the labels for SVC-related metrics. -type SVC struct { - Type string -} - -// Labels returns the list of labels. -func (l SVC) Labels() []string { - return []string{"type"} -} - -// Values returns the label values in the order defined by Labels. -func (l SVC) Values() []string { - return []string{l.Type} -} - -// SCMP contains the labels for SCMP-related metrics. -type SCMP struct { - Class string - Type string -} - -// Labels returns the list of labels. -func (l SCMP) Labels() []string { - return []string{"class", "type"} -} - -// Values returns the label values in the order defined by Labels. -func (l SCMP) Values() []string { - return []string{"class", "type"} -} - -type metrics struct { - netWriteBytes prometheus.Counter - netWritePkts prometheus.Counter - netWriteErrors prometheus.Counter - netReadBytes prometheus.Counter - netReadPkts *prometheus.CounterVec - netReadParseErrors prometheus.Counter - appWriteBytes prometheus.Counter - appWritePkts prometheus.Counter - appWriteErrors prometheus.Counter - appReadBytes prometheus.Counter - appReadPkts prometheus.Counter - appReadErrors prometheus.Counter - openSockets *prometheus.GaugeVec - appConnErrors prometheus.Counter - scmpReadPkts *prometheus.CounterVec - scmpWritePkts *prometheus.CounterVec - appNotFoundErrors prometheus.Counter - appWriteSVCPkts *prometheus.CounterVec - netReadOverflows prometheus.Counter -} - -func newMetrics() metrics { - return metrics{ - netWriteBytes: prom.NewCounter(Namespace, "", "net_write_bytes_total", - "Total bytes sent on the network."), - netWritePkts: prom.NewCounter(Namespace, "", "net_write_pkts_total", - "Total packets sent on the network."), - netWriteErrors: prom.NewCounter(Namespace, "", "net_write_errors_total", - "Network packet send errors"), - netReadBytes: prom.NewCounter(Namespace, "", "net_read_bytes_total", - "Total bytes received from the network irrespective of packet outcome."), - netReadPkts: prom.NewCounterVecWithLabels(Namespace, "", "net_read_pkts_total", - "Total packets received from the network.", IncomingPacket{}), - netReadParseErrors: prom.NewCounter(Namespace, "", "net_read_parse_errors_total", - "Total network packet parse error"), - appWriteBytes: prom.NewCounter(Namespace, "", "app_write_bytes_total", - "Total bytes sent to applications."), - appWritePkts: prom.NewCounter(Namespace, "", "app_write_pkts_total", - "Total packets sent to applications."), - appWriteErrors: prom.NewCounter(Namespace, "", "app_write_errors_total", - "Send packet to applications errors."), - appReadBytes: prom.NewCounter(Namespace, "", "app_read_bytes_total", - "Total bytes read from applications."), - appReadPkts: prom.NewCounter(Namespace, "", "app_read_pkts_total", - "Total packets read from applications"), - appReadErrors: prom.NewCounter(Namespace, "", "app_read_errors_total", - "Total errors when reading packets from applications."), - openSockets: prom.NewGaugeVecWithLabels(Namespace, "", "app_sockets_open", - "Number of sockets currently opened by applications.", SVC{}), - appConnErrors: prom.NewCounter(Namespace, "", "app_conn_reg_errors_total", - "Application socket registration errors"), - scmpReadPkts: prom.NewCounterVecWithLabels(Namespace, "", "scmp_read_pkts_total", - "Total SCMP packets received from the network.", SCMP{}), - scmpWritePkts: prom.NewCounterVecWithLabels(Namespace, "", "scmp_write_pkts_total", - "Total SCMP packets received from the network.", SCMP{}), - appNotFoundErrors: prom.NewCounter(Namespace, "", "app_not_found_errors_total", - "Number of packets for which the destination application was not found."), - appWriteSVCPkts: prom.NewCounterVecWithLabels(Namespace, "", "app_write_svc_pkts_total", - "Total SVC packets delivered to applications", SVC{}), - netReadOverflows: prom.NewCounter(Namespace, "", "net_read_overflow_pkts_total", - "Total ingress packets that were dropped on the OS socket"), - } -} - -func (m metrics) NetWriteBytes() prometheus.Counter { - return m.netWriteBytes -} - -func (m metrics) NetWritePkts() prometheus.Counter { - return m.netWritePkts -} - -func (m metrics) NetReadBytes() prometheus.Counter { - return m.netReadBytes -} - -func (m metrics) NetReadPkts(labels IncomingPacket) prometheus.Counter { - return m.netReadPkts.WithLabelValues(labels.Values()...) -} - -func (m metrics) NetReadParseErrors() prometheus.Counter { - return m.netReadParseErrors -} - -func (m metrics) AppWriteBytes() prometheus.Counter { - return m.appWriteBytes -} - -func (m metrics) AppWritePkts() prometheus.Counter { - return m.appWritePkts -} - -func (m metrics) AppWriteErrors() prometheus.Counter { - return m.appWriteErrors -} - -func (m metrics) AppReadBytes() prometheus.Counter { - return m.appReadBytes -} - -func (m metrics) AppReadPkts() prometheus.Counter { - return m.appReadPkts -} - -func (m metrics) AppReadErrors() prometheus.Counter { - return m.appReadErrors -} - -func (m metrics) OpenSockets(labels SVC) prometheus.Gauge { - return m.openSockets.WithLabelValues(labels.Values()...) -} - -func (m metrics) AppConnErrors() prometheus.Counter { - return m.appConnErrors -} - -func (m metrics) NetWriteErrors() prometheus.Counter { - return m.netWriteErrors -} - -// SCMPReadPackets returns the metrics counters for SCMP packets read from the network. -func (m metrics) SCMPReadPkts(labels SCMP) prometheus.Counter { - return m.scmpReadPkts.WithLabelValues(labels.Values()...) -} - -// SCMPWritePkts returns the metrics counters for SCMP packets written to the network. -func (m metrics) SCMPWritePkts(labels SCMP) prometheus.Counter { - return m.scmpWritePkts.WithLabelValues(labels.Values()...) -} - -func (m metrics) AppNotFoundErrors() prometheus.Counter { - return m.appNotFoundErrors -} - -func (m metrics) AppWriteSVCPkts(labels SVC) prometheus.Counter { - return m.appWriteSVCPkts.WithLabelValues(labels.Values()...) -} - -func (m metrics) NetReadOverflows() prometheus.Counter { - return m.netReadOverflows -} diff --git a/dispatcher/internal/registration/BUILD.bazel b/dispatcher/internal/registration/BUILD.bazel deleted file mode 100644 index 70dc5a3245..0000000000 --- a/dispatcher/internal/registration/BUILD.bazel +++ /dev/null @@ -1,43 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "errors.go", - "iatable.go", - "portlist.go", - "scmp_table.go", - "svctable.go", - "table.go", - "udptable.go", - ], - importpath = "github.com/scionproto/scion/dispatcher/internal/registration", - visibility = ["//dispatcher:__subpackages__"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/private/common:go_default_library", - "//pkg/private/serrors:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "bench_test.go", - "generators_test.go", - "iatable_test.go", - "portlist_test.go", - "scmp_table_test.go", - "svctable_test.go", - "table_test.go", - "udptable_test.go", - ], - embed = [":go_default_library"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/private/xtest:go_default_library", - "@com_github_smartystreets_goconvey//convey:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - "@com_github_stretchr_testify//require:go_default_library", - ], -) diff --git a/dispatcher/internal/registration/bench_test.go b/dispatcher/internal/registration/bench_test.go deleted file mode 100644 index 177791c6a5..0000000000 --- a/dispatcher/internal/registration/bench_test.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "testing" - - "github.com/scionproto/scion/pkg/addr" -) - -type registerArgs struct { - ia addr.IA - public *net.UDPAddr - bind net.IP - svc addr.SVC - value interface{} -} - -func generateRegisterArgs(n int) []*registerArgs { - var data []*registerArgs - for i := 0; i < n; i++ { - newData := ®isterArgs{ - ia: getRandomIA(), - public: getRandomUDPAddress(), - bind: getRandomIPv4(), - svc: getRandomSVC(), - value: getRandomValue(), - } - data = append(data, newData) - } - return data -} - -func generateLookupPublicArgs(n int) []*net.UDPAddr { - var data []*net.UDPAddr - for i := 0; i < n; i++ { - data = append(data, getRandomUDPAddress()) - } - return data -} - -func BenchmarkRegister(b *testing.B) { - table := NewIATable(minPort, maxPort) - regData := generateRegisterArgs(b.N) - b.ResetTimer() - for n := 0; n < b.N; n++ { - _, _ = table.Register(regData[n].ia, regData[n].public, nil, - addr.SvcNone, regData[n].value) - } -} - -func BenchmarkLookupPublicIPv4(b *testing.B) { - numEntries := 1000 - table := NewIATable(minPort, maxPort) - regData := generateRegisterArgs(numEntries) - for i := 0; i < numEntries; i++ { - _, _ = table.Register(regData[i].ia, regData[i].public, nil, - addr.SvcNone, regData[i].value) - } - lookupData := generateLookupPublicArgs(b.N) - b.ResetTimer() - for n := 0; n < b.N; n++ { - table.LookupPublic(addr.MustIAFrom(1, 1), lookupData[n]) - } -} - -type lookupServiceArgs struct { - svc addr.SVC - bind net.IP -} - -func generateLookupServiceArgs(n int) []*lookupServiceArgs { - var data []*lookupServiceArgs - for i := 0; i < n; i++ { - data = append(data, &lookupServiceArgs{ - svc: getRandomSVC(), - bind: getRandomIPv4(), - }) - } - return data -} - -func BenchmarkLookupServiceIPv4(b *testing.B) { - numEntries := 1000 - table := NewIATable(minPort, maxPort) - regData := generateRegisterArgs(numEntries) - for i := 0; i < numEntries; i++ { - _, _ = table.Register(regData[i].ia, regData[i].public, regData[i].bind, - regData[i].svc, regData[i].value) - } - lookupData := generateLookupServiceArgs(b.N) - b.ResetTimer() - for n := 0; n < b.N; n++ { - table.LookupService(addr.MustIAFrom(1, 1), lookupData[n].svc, lookupData[n].bind) - } -} diff --git a/dispatcher/internal/registration/errors.go b/dispatcher/internal/registration/errors.go deleted file mode 100644 index 2ea443a6b0..0000000000 --- a/dispatcher/internal/registration/errors.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import "github.com/scionproto/scion/pkg/private/common" - -const ( - ErrNoPublicAddress common.ErrMsg = "no public address" - ErrBindWithoutSvc common.ErrMsg = "bind address without svc address" - ErrOverlappingAddress common.ErrMsg = "overlapping address" - ErrNoValue common.ErrMsg = "nil value" - ErrZeroIP common.ErrMsg = "zero address" - ErrZeroPort common.ErrMsg = "zero port" - ErrNilAddress common.ErrMsg = "nil address" - ErrSvcNone common.ErrMsg = "svc none" - ErrNoPorts common.ErrMsg = "no free ports" -) diff --git a/dispatcher/internal/registration/generators_test.go b/dispatcher/internal/registration/generators_test.go deleted file mode 100644 index ce12a84c07..0000000000 --- a/dispatcher/internal/registration/generators_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "math/rand" - "net" - "strconv" - - "github.com/scionproto/scion/pkg/addr" -) - -func getRandomUDPAddress() *net.UDPAddr { - return &net.UDPAddr{ - IP: getRandomIPv4(), - Port: getRandomPort(), - } -} - -func getRandomIPv4() net.IP { - b := byte(rand.Intn(4)) - return net.IP{10, 0, 0, b} -} - -func getRandomPort() int { - return rand.Intn(16) -} - -func getRandomValue() string { - return strconv.Itoa(rand.Intn(1 << 16)) -} - -func getRandomIA() addr.IA { - return addr.MustIAFrom( - addr.ISD(rand.Intn(3)+1), - addr.AS(rand.Intn(3)+1), - ) -} - -func getRandomSVC() addr.SVC { - switch rand.Intn(2) { - case 0: - return addr.SvcNone - default: - return addr.SvcCS - } -} diff --git a/dispatcher/internal/registration/iatable.go b/dispatcher/internal/registration/iatable.go deleted file mode 100644 index b677d09d31..0000000000 --- a/dispatcher/internal/registration/iatable.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "sync" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/common" -) - -const ( - ErrBadISD common.ErrMsg = "0 is not valid ISD" - ErrBadAS common.ErrMsg = "0 is not valid AS" -) - -// Reference tracks an object from a collection. -type Reference interface { - // Free removes the object from its parent collection, cleaning up any allocations. - Free() -} - -type RegReference interface { - Reference - // UDPAddr returns the UDP address associated with this reference - UDPAddr() *net.UDPAddr - // SVCAddr returns the SVC address associated with this reference. If no - // SVC address is associated, it returns SvcNone. - SVCAddr() addr.SVC - // RegisterID attaches an SCMP ID to this reference. The ID is released - // when the reference is freed. Registering another ID does not overwrite - // the previous; instead, multiple IDs get associated with the reference. - // SCMP messages targeted at the ID will get sent to the socket associated - // with the reference. The IA of the id is set to the IA of the reference. - RegisterID(id uint64) error -} - -// IATable manages the UDP/IP port registrations for a SCION Dispatcher. -// -// IATable is safe for concurrent use from multiple goroutines. -type IATable interface { - // Register a new entry for AS ia with the specified public, bind and - // services addresses and associate a value with the entry. Lookup calls - // for matching addresses will return the associated value. - // - // A LookupPublic call will select an entry with a matching public address. - // For IPv4, this is either a perfect match or a 0.0.0.0 entry. For IPv6, - // this is either a perfect match or a :: entry. If the public address to - // register matches an existing entry, an error is returned. Using port 0 - // for the public address will allocate a valid port. - // - // A LookupService call will select an entry with matching bind and service - // addresses. Binds for 0.0.0.0 or :: are not allowed. The port is - // inherited from the public address. To not register for a service, use a - // bind of nil and a svc of none. For more information about SVC behavior, - // see the documentation for SVCTable. - // - // To unregister from the table, free the returned reference. - Register(ia addr.IA, public *net.UDPAddr, bind net.IP, svc addr.SVC, - value interface{}) (RegReference, error) - // LookupPublic returns the value associated with the selected public - // address. Wildcard addresses are supported. If an entry is found, the - // returned boolean is set to true. Otherwise, it is set to false. - LookupPublic(ia addr.IA, public *net.UDPAddr) (interface{}, bool) - // LookupService returns the entries associated with svc and bind. - // - // If SVC is an anycast address, at most one entry is returned. The bind - // address is used to narrow down the set of possible entries. If multiple - // entries exist, one is selected arbitrarily. - // - // Note that nil bind addresses are supported for anycasts (the address is - // in this case ignored), but support for this might be dropped in the - // future. - // - // If SVC is a multicast address, more than one entry can be returned. The - // bind address is ignored in this case. - LookupService(ia addr.IA, svc addr.SVC, bind net.IP) []interface{} - // LookupID returns the entry associated with the SCMP General class ID id. - // The ID is used for SCMP Echo, TraceRoute, and RecordPath functionality. - // If an entry is found, the returned boolean is set to true. Otherwise, it - // is set to false. - LookupID(ia addr.IA, id uint64) (interface{}, bool) -} - -// NewIATable creates a new UDP/IP port registration table. -// -// If the public address in a registration contains the port 0, a free port is -// allocated between minPort and maxPort. -// -// If minPort is <= 0 or maxPort is > 65535, the function panics. -func NewIATable(minPort, maxPort int) IATable { - return newIATable(minPort, maxPort) -} - -var _ IATable = (*iaTable)(nil) - -type iaTable struct { - mtx sync.RWMutex - ia map[addr.IA]*Table - minPort int - maxPort int -} - -func newIATable(minPort, maxPort int) *iaTable { - return &iaTable{ - ia: make(map[addr.IA]*Table), - minPort: minPort, - maxPort: maxPort, - } -} - -func (t *iaTable) Register(ia addr.IA, public *net.UDPAddr, bind net.IP, svc addr.SVC, - value interface{}) (RegReference, error) { - - t.mtx.Lock() - defer t.mtx.Unlock() - if ia.ISD() == 0 { - return nil, ErrBadISD - } - if ia.AS() == 0 { - return nil, ErrBadAS - } - table, ok := t.ia[ia] - if !ok { - table = NewTable(t.minPort, t.maxPort) - t.ia[ia] = table - } - reference, err := table.Register(public, bind, svc, value) - if err != nil { - return nil, err - } - return &iaTableReference{ - table: t, - ia: ia, - entryRef: reference, - svc: svc, - value: value, - }, nil -} - -func (t *iaTable) LookupPublic(ia addr.IA, public *net.UDPAddr) (interface{}, bool) { - t.mtx.RLock() - defer t.mtx.RUnlock() - if table, ok := t.ia[ia]; ok { - return table.LookupPublic(public) - } - return nil, false -} - -func (t *iaTable) LookupService(ia addr.IA, svc addr.SVC, bind net.IP) []interface{} { - t.mtx.RLock() - defer t.mtx.RUnlock() - if table, ok := t.ia[ia]; ok { - return table.LookupService(svc, bind) - } - return nil -} - -func (t *iaTable) LookupID(ia addr.IA, id uint64) (interface{}, bool) { - t.mtx.RLock() - defer t.mtx.RUnlock() - if table, ok := t.ia[ia]; ok { - return table.LookupID(id) - } - return nil, false -} - -var _ RegReference = (*iaTableReference)(nil) - -type iaTableReference struct { - table *iaTable - ia addr.IA - entryRef *TableReference - svc addr.SVC - // value is the main table information associated with this reference - value interface{} -} - -func (r *iaTableReference) Free() { - r.table.mtx.Lock() - defer r.table.mtx.Unlock() - r.entryRef.Free() - if r.table.ia[r.ia].Size() == 0 { - delete(r.table.ia, r.ia) - } -} - -func (r *iaTableReference) UDPAddr() *net.UDPAddr { - return r.entryRef.UDPAddr() -} - -func (r *iaTableReference) SVCAddr() addr.SVC { - return r.svc -} - -func (r *iaTableReference) RegisterID(id uint64) error { - r.table.mtx.Lock() - defer r.table.mtx.Unlock() - return r.entryRef.RegisterID(id, r.value) -} diff --git a/dispatcher/internal/registration/iatable_test.go b/dispatcher/internal/registration/iatable_test.go deleted file mode 100644 index f60db2694b..0000000000 --- a/dispatcher/internal/registration/iatable_test.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/xtest" -) - -var ( - public = &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - value = "test value" - ia = xtest.MustParseIA("1-ff00:0:1") -) - -func TestIATable(t *testing.T) { - - t.Run("Given a table with one entry that is only public and no svc", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - assert.NoError(t, err) - assert.NotNil(t, ref) - t.Run("lookups for the same AS", func(t *testing.T) { - retValue, ok := table.LookupPublic(ia, public) - assert.True(t, ok) - assert.Equal(t, retValue, value) - retValues := table.LookupService(ia, addr.SvcCS, net.IP{192, 0, 2, 1}) - assert.Empty(t, retValues) - }) - - t.Run("lookups for a different AS", func(t *testing.T) { - otherIA := xtest.MustParseIA("1-ff00:0:2") - retValue, ok := table.LookupPublic(otherIA, public) - assert.False(t, ok) - assert.Nil(t, retValue) - retValues := table.LookupService(otherIA, addr.SvcCS, net.IP{192, 0, 2, 1}) - assert.Empty(t, retValues) - }) - - t.Run("calling free twice panics", func(t *testing.T) { - ref.Free() - require.Panics(t, ref.Free) - }) - }) - - t.Run("Given a table with one entry that is only public and svc", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - t.Run("lookups for the same AS works", func(t *testing.T) { - ref, err := table.Register(ia, public, nil, addr.SvcCS, value) - assert.NoError(t, err) - assert.NotNil(t, ref) - retValue, ok := table.LookupPublic(ia, public) - assert.True(t, ok) - assert.Equal(t, retValue, value) - retValues := table.LookupService(ia, addr.SvcCS, net.IP{192, 0, 2, 1}) - assert.Equal(t, retValues, []interface{}{value}) - }) - }) -} - -func TestIATableRegister(t *testing.T) { - t.Log("Given an empty table") - - t.Run("ISD zero is error", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(addr.MustIAFrom(0, 1), public, nil, addr.SvcNone, value) - assert.EqualError(t, err, ErrBadISD.Error()) - assert.Nil(t, ref) - }) - - t.Run("AS zero is error", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(addr.MustIAFrom(1, 0), public, nil, addr.SvcNone, value) - assert.EqualError(t, err, ErrBadAS.Error()) - assert.Nil(t, ref) - }) - - t.Run("for a good AS number", func(t *testing.T) { - ia := xtest.MustParseIA("1-ff00:0:1") - t.Run("already registered ports will cause error", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - _, err := table.Register(ia, public, nil, addr.SvcNone, value) - require.NoError(t, err) - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - assert.Error(t, err) - assert.Nil(t, ref) - }) - - t.Run("good ports will return success", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - assert.NoError(t, err) - assert.NotNil(t, ref) - }) - }) -} - -func TestIATableSCMPRegistration(t *testing.T) { - table := NewIATable(minPort, maxPort) - - t.Log("Given a reference to an IATable registration") - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - require.NoError(t, err) - v, ok := table.LookupID(ia, 42) - assert.False(t, ok, "Performing SCMP lookup fails") - assert.Nil(t, v) - err = ref.RegisterID(42) - assert.NoError(t, err, "Registering an SCMP ID on the reference succeeds") -} - -func TestIATableSCMPExistingRegistration(t *testing.T) { - - t.Run("Registering a second SCMP ID on the same reference succeeds", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - require.NoError(t, err) - t.Log("Given an existing SCMP General ID registration") - err = ref.RegisterID(42) - require.NoError(t, err) - - t.Log("Performing an SCMP lookup on the same IA succeeds") - retValue, ok := table.LookupID(ia, 42) - assert.True(t, ok) - assert.Equal(t, retValue, value) - - t.Log("Performing an SCMP lookup on a different IA fails") - retValue, ok = table.LookupID(xtest.MustParseIA("1-ff00:0:2"), 42) - assert.False(t, ok) - assert.Nil(t, retValue) - - t.Log("Freeing the reference makes lookup fail") - ref.Free() - retValue, ok = table.LookupID(ia, 42) - assert.False(t, ok) - assert.Nil(t, retValue) - }) - - t.Run("Registering a second SCMP ID on the same reference succeeds", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - require.NoError(t, err) - t.Log("Given an existing SCMP General ID registration") - err = ref.RegisterID(42) - require.NoError(t, err) - err = ref.RegisterID(43) - assert.NoError(t, err) - - t.Log("Freeing the reference makes lookup on first registered id fail") - ref.Free() - retValue, ok := table.LookupID(ia, 42) - assert.False(t, ok) - assert.Nil(t, retValue) - }) -} diff --git a/dispatcher/internal/registration/portlist.go b/dispatcher/internal/registration/portlist.go deleted file mode 100644 index 4b3a54e6b2..0000000000 --- a/dispatcher/internal/registration/portlist.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, 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 registration - -import ( - "container/ring" -) - -// portList is a linked list of ports with a round-robin getter. -type portList struct { - list *ring.Ring -} - -func newPortList() *portList { - return &portList{} -} - -func (l *portList) Insert(port int, v interface{}) *ring.Ring { - element := ring.New(1) - element.Value = &listItem{port: port, value: v} - if l.list == nil { - l.list = element - } else { - l.list.Link(element) - } - return element -} - -// Get returns an arbitrary object from the list. -// -// The objects are returned in round-robin fashion. Removing an element from -// the list can make the round-robin selection reset from the start. -func (l *portList) Get() interface{} { - if l.list == nil { - return nil - } - v := l.list.Value - l.list = l.list.Next() - return v.(*listItem).value -} - -func (l *portList) Find(port int) bool { - var found bool - l.list.Do( - func(p interface{}) { - if port == p.(*listItem).port { - found = true - } - }, - ) - return found -} - -func (l *portList) Remove(element *ring.Ring) { - if element.Len() == 1 { - l.list = nil - } else { - // always change the l.list since it could be that l.list == element. - l.list = element.Prev() - l.list.Unlink(1) - } -} - -func (l *portList) Len() int { - if l.list == nil { - return 0 - } - return l.list.Len() -} - -type listItem struct { - port int - value interface{} -} diff --git a/dispatcher/internal/registration/portlist_test.go b/dispatcher/internal/registration/portlist_test.go deleted file mode 100644 index 78f2952404..0000000000 --- a/dispatcher/internal/registration/portlist_test.go +++ /dev/null @@ -1,67 +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 registration - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestEmptyPortList(t *testing.T) { - pl := newPortList() - assert.Nil(t, pl.Get(), "Empty PortList should return nil on Get") - assert.False(t, pl.Find(1)) - assert.Equal(t, 0, pl.Len()) - // Remove can't be tested on empty port list since we don't have a ring to remove. -} - -func TestRemoval(t *testing.T) { - pl := newPortList() - r1 := pl.Insert(1, "1") - pl.Remove(r1) - require.Equal(t, pl.Len(), 0) - - r2 := pl.Insert(2, "2") - require.Equal(t, "2", pl.Get()) - - r1 = pl.Insert(1, "1") - r3 := pl.Insert(3, "3") - expectList(t, pl, "1", "2", "3") - - pl.Remove(r2) - expectList(t, pl, "1", "3") - r2 = pl.Insert(2, "2") - - pl.Remove(r1) - expectList(t, pl, "2", "3") - r1 = pl.Insert(1, "1") - - pl.Remove(r3) - expectList(t, pl, "1", "2") - pl.Remove(r2) - expectList(t, pl, "1") - pl.Remove(r1) - expectList(t, pl) -} - -func expectList(t *testing.T, pl *portList, expected ...string) { - var actual []string - for i := 0; i < pl.Len(); i++ { - actual = append(actual, pl.Get().(string)) - } - require.ElementsMatchf(t, actual, expected, "expected=%s actual=%s", expected, actual) -} diff --git a/dispatcher/internal/registration/scmp_table.go b/dispatcher/internal/registration/scmp_table.go deleted file mode 100644 index 9ea9fd3509..0000000000 --- a/dispatcher/internal/registration/scmp_table.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "github.com/scionproto/scion/pkg/private/serrors" -) - -// SCMPTable tracks SCMP General class IDs. -// -// Attempting to register the same ID more than once will return an error. -type SCMPTable struct { - m map[uint64]interface{} -} - -func NewSCMPTable() *SCMPTable { - return &SCMPTable{m: make(map[uint64]interface{})} -} - -func (t *SCMPTable) Lookup(id uint64) (interface{}, bool) { - value, ok := t.m[id] - return value, ok -} - -func (t *SCMPTable) Register(id uint64, value interface{}) error { - if value == nil { - return serrors.New("cannot register nil value") - } - if _, ok := t.m[id]; ok { - return serrors.New("id already registered", "id", id) - } - t.m[id] = value - return nil -} - -func (t *SCMPTable) Remove(id uint64) { - delete(t.m, id) -} diff --git a/dispatcher/internal/registration/scmp_table_test.go b/dispatcher/internal/registration/scmp_table_test.go deleted file mode 100644 index 5c376615ca..0000000000 --- a/dispatcher/internal/registration/scmp_table_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "testing" - - . "github.com/smartystreets/goconvey/convey" - "github.com/stretchr/testify/require" -) - -func TestSCMPEmptyTable(t *testing.T) { - Convey("Given an empty table", t, func() { - table := NewSCMPTable() - - Convey("Lookup for an id fails", func() { - value, ok := table.Lookup(42) - SoMsg("ok", ok, ShouldBeFalse) - SoMsg("value", value, ShouldBeNil) - }) - Convey("Adding an item succeeds", func() { - value := "test value" - err := table.Register(42, value) - So(err, ShouldBeNil) - }) - Convey("Adding an item with nil value fails", func() { - err := table.Register(42, nil) - So(err, ShouldNotBeNil) - }) - }) -} - -func TestSCMPTableWithOneItem(t *testing.T) { - Convey("Given a table with one element", t, func() { - table := NewSCMPTable() - value := "test value" - err := table.Register(42, value) - require.NoError(t, err) - Convey("Lookup for the id succeeds", func() { - retValue, ok := table.Lookup(42) - SoMsg("ok", ok, ShouldBeTrue) - SoMsg("value", retValue, ShouldEqual, value) - }) - Convey("Adding the same id fails", func() { - err := table.Register(42, "some other value") - So(err, ShouldNotBeNil) - }) - Convey("After removing the ID, lookup fails", func() { - table.Remove(42) - value, ok := table.Lookup(42) - SoMsg("ok", ok, ShouldBeFalse) - SoMsg("value", value, ShouldBeNil) - }) - }) -} diff --git a/dispatcher/internal/registration/svctable.go b/dispatcher/internal/registration/svctable.go deleted file mode 100644 index 7c93f41bd0..0000000000 --- a/dispatcher/internal/registration/svctable.go +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "container/ring" - "fmt" - "net" - - "github.com/scionproto/scion/pkg/addr" -) - -// SVCTable tracks SVC registrations. -// -// Entries are hierarchical, and conceptually look like the following: -// -// SVC CS: -// 10.2.3.4 -// :10080 -// :10081 -// 192.0.2.1 -// :20000 -// SVC PS: -// 192.0.2.2 -// :20001 -// 2001:db8::1 -// :30001 -// :30002 -// -// Call Register to add a new entry to the table. The IP and port are taken -// from the UDP address. IP must not be zero (so binding to multiple interfaces -// is not supported), and port must not be zero. -// -// Anycasting to a local application requires the service type (e.g., CS) and -// the IP. This is because for SCION, the IP is selected remotely by the border -// router. The local dispatcher then anycasts between all local ports listening on that IP. -// -// For example, in the table above, anycasting to CS-10.2.3.4 can either go to -// entry 10.2.3.4:10080 or 10.2.3.4:10081. Anycasts are chosen in round-robin -// fashion; the round-robin distribution is not strict, and can get skewed due -// to registrations and frees. -type SVCTable interface { - // Register adds a new entry for the select svc, IP address and port. Both - // IPv4 and IPv6 are supported. IP addresses 0.0.0.0 and :: are not - // supported. Port must not be 0. - // - // If an entry for the same svc, IP address and port exists, an error is - // returned and the reference is nil. - // - // To clean up resources, call Free on the returned Reference. Calling Free - // more than once will cause a panic. - Register(svc addr.SVC, address *net.UDPAddr, value interface{}) (Reference, error) - // Lookup returns the entries associated with svc and ip. - // - // If SVC is an anycast address, at most one entry is returned. The ip - // address is used in case to narrow down the set of possible entries. If - // multiple entries exist, one is selected arbitrarily. - // - // Note that nil addresses are supported for anycasts (the address is then - // ignored), but support for this might be dropped in the future. - // - // If SVC is a multicast address, more than one entry can be returned. The - // ip address is ignored in this case. - Lookup(svc addr.SVC, ip net.IP) []interface{} - String() string -} - -func NewSVCTable() SVCTable { - return newSvcTable() -} - -var _ SVCTable = (*svcTable)(nil) - -type svcTable struct { - m map[addr.SVC]unicastIpTable -} - -func newSvcTable() *svcTable { - return &svcTable{ - m: make(map[addr.SVC]unicastIpTable), - } -} - -func (t *svcTable) Register(svc addr.SVC, address *net.UDPAddr, - value interface{}) (Reference, error) { - - if err := validateUDPAddr(address); err != nil { - return nil, err - } - if svc == addr.SvcNone { - return nil, ErrSvcNone - } - // save a copy of the address to prevent callers from later affecting table - // state - address = copyUDPAddr(address) - - if svc == addr.SvcWildcard { - refCS, err := t.registerOne(addr.SvcCS, address, value) - if err != nil { - return nil, err - } - refDS, err := t.registerOne(addr.SvcDS, address, value) - if err != nil { - refCS.Free() - return nil, err - } - return &svcTableReference{ - cleanF: func() { - refCS.Free() - refDS.Free() - }, - }, nil - } - return t.registerOne(svc, address, value) -} - -func (t *svcTable) registerOne(svc addr.SVC, address *net.UDPAddr, - value interface{}) (Reference, error) { - if _, ok := t.m[svc]; !ok { - t.m[svc] = make(unicastIpTable) - } - - element, err := t.m[svc].insert(address, value) - if err != nil { - return nil, err - } - return &svcTableReference{ - cleanF: t.buildCleanupCallback(svc, address.IP, element), - }, nil -} - -func (t *svcTable) Lookup(svc addr.SVC, ip net.IP) []interface{} { - var values []interface{} - if svc.IsMulticast() { - values = t.multicast(svc) - } else { - if v, ok := t.anycast(svc, ip); ok { - values = []interface{}{v} - } - } - return values -} - -func (t *svcTable) multicast(svc addr.SVC) []interface{} { - var values []interface{} - ipTable, ok := t.m[svc.Base()] - if !ok { - return values - } - for _, v := range ipTable { - for i := 0; i < v.Len(); i++ { - values = append(values, v.Get()) - } - } - return values -} - -func (t *svcTable) anycast(svc addr.SVC, ip net.IP) (interface{}, bool) { - ipTable, ok := t.m[svc] - if !ok { - return nil, false - } - // XXX(scrye): This is a workaround s.t. a simple underlay socket - // that does not return IP-header information can still be used to - // deliver to SVC addresses. Once IP-header information is passed - // into the app, searching for nil should not return an entry. - var ports *portList - if ip == nil { - ports, ok = ipTable.any() - } else { - ports, ok = ipTable[ip.String()] - } - if !ok { - return nil, false - } - return ports.Get(), true -} - -func (t *svcTable) String() string { - return fmt.Sprintf("%v", t.m) -} - -func (t *svcTable) buildCleanupCallback(svc addr.SVC, ip net.IP, port *ring.Ring) func() { - return func() { - t.doCleanup(svc, ip, port) - } -} - -func (t *svcTable) doCleanup(svc addr.SVC, ip net.IP, port *ring.Ring) { - ipTable := t.m[svc] - portList := ipTable[ip.String()] - portList.Remove(port) - if portList.Len() == 0 { - delete(ipTable, ip.String()) - } - if len(ipTable) == 0 { - delete(t.m, svc) - } -} - -func validateUDPAddr(address *net.UDPAddr) error { - if address == nil { - return ErrNilAddress - } - if address.IP.IsUnspecified() { - return ErrZeroIP - } - if address.Port == 0 { - return ErrZeroPort - } - return nil -} - -type unicastIpTable map[string]*portList - -// insert adds an entry for address to the table, and returns a pointer to the -// entry. -func (t unicastIpTable) insert(address *net.UDPAddr, value interface{}) (*ring.Ring, error) { - var list *portList - str := address.IP.String() - list, ok := t[str] - if ok { - if list.Find(address.Port) { - return nil, ErrOverlappingAddress - } - } else { - list = newPortList() - t[str] = list - } - return list.Insert(address.Port, value), nil -} - -// any returns an arbitrary item from the table. The boolean return value is -// true if an entry was found, or false otherwise. -func (t unicastIpTable) any() (*portList, bool) { - for _, v := range t { - return v, true - } - return nil, false -} - -var _ Reference = (*svcTableReference)(nil) - -type svcTableReference struct { - freed bool - cleanF func() -} - -func (r *svcTableReference) Free() { - if r.freed { - panic("double free") - } - r.freed = true - r.cleanF() -} diff --git a/dispatcher/internal/registration/svctable_test.go b/dispatcher/internal/registration/svctable_test.go deleted file mode 100644 index f89e0c1a26..0000000000 --- a/dispatcher/internal/registration/svctable_test.go +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, 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 registration - -import ( - "fmt" - "net" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/pkg/addr" -) - -func TestSVCTableLookup(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - value := "test value" - - testCases := map[string]struct { - Svc addr.SVC - IP net.IP - Prepare func(t *testing.T, table SVCTable) - Expected []interface{} - }{ - // Empty table test cases: - "Anycast to nil address, not found": { - Svc: addr.SvcCS, - Prepare: func(*testing.T, SVCTable) {}, - }, - "Anycast to some IPv4 address, not found": { - Svc: addr.SvcCS, - IP: net.IP{10, 2, 3, 4}, - Prepare: func(*testing.T, SVCTable) {}, - }, - "Multicast to some IPv4 address, not found": { - Svc: addr.SvcCS.Multicast(), - Prepare: func(*testing.T, SVCTable) {}, - }, - - // Table with 1 entry test cases: - "anycasting to nil finds the entry": { - Svc: addr.SvcCS, - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Expected: []interface{}{value}, - }, - "multicasting to nil finds the entry": { - Svc: addr.SvcCS.Multicast(), - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Expected: []interface{}{value}, - }, - "anycasting to a different IP does not find the entry": { - Svc: addr.SvcCS, - IP: net.IP{10, 5, 6, 7}, - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - }, - "anycasting to a different SVC does not find the entry": { - Svc: addr.SvcDS, - IP: address.IP, - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - }, - "anycasting to the same SVC and IP finds the entry": { - Svc: addr.SvcCS, - IP: address.IP, - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Expected: []interface{}{value}, - }, - "multicasting to the same SVC and IP finds the entry": { - Svc: addr.SvcCS.Multicast(), - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Expected: []interface{}{value}, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - table := NewSVCTable() - tc.Prepare(t, table) - - retValues := table.Lookup(tc.Svc, tc.IP) - assert.Equal(t, tc.Expected, retValues) - }) - } -} - -func TestSVCTableRegistration(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - value := "test value" - - testCases := map[string]struct { - Prepare func(t *testing.T, table SVCTable) - // Input Register - Svc addr.SVC - Addr *net.UDPAddr - Value interface{} - // Assertions - ReferenceAssertion assert.ValueAssertionFunc - ErrAssertion assert.ErrorAssertionFunc - }{ - // Empty table test cases: - "Registering nil address fails": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcCS, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - "Registering IPv4 zero address fails": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcCS, - Addr: &net.UDPAddr{IP: net.IPv4zero, Port: address.Port}, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - "Registering IPv6 zero address fail": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcCS, - Addr: &net.UDPAddr{IP: net.IPv6zero, Port: address.Port}, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - "Registering port zero fails": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcCS, - Addr: &net.UDPAddr{IP: address.IP}, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - "Registering SvcNone fails": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcNone, - Addr: address, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - "Adding an address succeeds": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcCS, - Addr: address, - Value: value, - ReferenceAssertion: assert.NotNil, - ErrAssertion: assert.NoError, - }, - - // Table with 1 entry test cases: - "Registering the same address and different port succeeds": { - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Svc: addr.SvcCS, - Addr: &net.UDPAddr{ - IP: address.IP, - Port: address.Port + 1, - }, - Value: value, - ReferenceAssertion: assert.NotNil, - ErrAssertion: assert.NoError, - }, - "Registering the same address and same port fails": { - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Svc: addr.SvcCS, - Addr: address, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - table := NewSVCTable() - tc.Prepare(t, table) - - reference, err := table.Register(tc.Svc, tc.Addr, value) - tc.ErrAssertion(t, err) - tc.ReferenceAssertion(t, reference) - }) - } -} - -func TestSVCTableOneItemAnycast(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - diffIpSamePortAddress := &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: address.Port} - value := "test value" - otherValue := "other test value" - - prepare := func(t *testing.T) (SVCTable, Reference) { - table := NewSVCTable() - reference, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - return table, reference - } - - t.Run("Adding a second address, anycasting to first one returns correct value", - func(t *testing.T) { - table, _ := prepare(t) - _, err := table.Register(addr.SvcCS, diffIpSamePortAddress, otherValue) - assert.NoError(t, err) - retValues := table.Lookup(addr.SvcCS, address.IP) - assert.Equal(t, []interface{}{value}, retValues) - }) - t.Run("Freeing the reference yields nil on anycast", func(t *testing.T) { - table, reference := prepare(t) - reference.Free() - retValues := table.Lookup(addr.SvcCS, nil) - assert.Empty(t, retValues) - - // Check double free panicks - assert.Panics(t, func() { reference.Free() }) - - _, err := table.Register(addr.SvcCS, address, value) - assert.NoError(t, err) - }) -} -func TestSVCTableTwoItems(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - sameIpDiffPortAddress := &net.UDPAddr{IP: address.IP, Port: address.Port + 1} - value := "test value" - otherValue := "other test value" - - prepare := func(t *testing.T) SVCTable { - table := NewSVCTable() - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - _, err = table.Register(addr.SvcCS, sameIpDiffPortAddress, otherValue) - require.NoError(t, err) - return table - } - - t.Run("The anycasts will cycle between the values", func(t *testing.T) { - table := prepare(t) - retValues := table.Lookup(addr.SvcCS, address.IP) - assert.Equal(t, []interface{}{value}, retValues) - otherRetValue := table.Lookup(addr.SvcCS, address.IP) - assert.Equal(t, []interface{}{otherValue}, otherRetValue) - }) - - t.Run("A multicast will return both values", func(t *testing.T) { - table := prepare(t) - retValues := table.Lookup(addr.SvcCS.Multicast(), address.IP) - assert.Equal(t, len(retValues), 2) - }) -} - -func TestSVCTableMulticastTwoAddresses(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - diffAddress := &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: address.Port} - value := "test value" - otherValue := "other test value" - - table := NewSVCTable() - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - _, err = table.Register(addr.SvcCS, diffAddress, otherValue) - require.NoError(t, err) - - retValues := table.Lookup(addr.SvcCS.Multicast(), address.IP) - assert.ElementsMatch(t, []interface{}{otherValue, value}, retValues) -} - -func TestSVCTableStress(t *testing.T) { - registrationCount := 1000 - // Generate many random registrations, then free all - table := NewSVCTable() - references := runRandomRegistrations(registrationCount, table) - for _, ref := range references { - ref.Free() - } - // then generate some more, and free again - references = runRandomRegistrations(registrationCount, table) - for _, ref := range references { - ref.Free() - } - t.Run("Table should be empty", func(t *testing.T) { - assert.Equal(t, table.String(), "map[]") - }) -} - -func runRandomRegistrations(count int, table SVCTable) []Reference { - var references []Reference - for i := 0; i < count; i++ { - ref, err := table.Register(addr.SvcCS, getRandomUDPAddress(), getRandomValue()) - if err == nil { - references = append(references, ref) - } - } - return references -} - -func TestSVCTableFree(t *testing.T) { - ip := net.IP{10, 2, 3, 4} - prepare := func(t *testing.T) (SVCTable, []Reference) { - // Prepare a table with three entries on the same IP - table := NewSVCTable() - addressOne := &net.UDPAddr{IP: ip, Port: 10080} - refOne, err := table.Register(addr.SvcCS, addressOne, "1") - require.NoError(t, err) - addressTwo := &net.UDPAddr{IP: ip, Port: 10081} - refTwo, err := table.Register(addr.SvcCS, addressTwo, "2") - require.NoError(t, err) - addressThree := &net.UDPAddr{IP: ip, Port: 10082} - refThree, err := table.Register(addr.SvcCS, addressThree, "3") - require.NoError(t, err) - return table, []Reference{refOne, refTwo, refThree} - } - for i := 0; i < 3; i++ { - addrremainone := strconv.Itoa((i+1)%3 + 1) - addrremaintwo := strconv.Itoa((i+2)%3 + 1) - name := fmt.Sprintf("Addresses %s and %s must remain", addrremainone, addrremaintwo) - t.Run(name, func(t *testing.T) { - table, refs := prepare(t) - refs[i].Free() - retValues := table.Lookup(addr.SvcCS.Multicast(), ip) - assert.ElementsMatch(t, []interface{}{addrremainone, addrremaintwo}, retValues) - checkAnyCastCycles(t, - func() []interface{} { return table.Lookup(addr.SvcCS, ip) }, - []string{addrremainone, addrremaintwo}) - - if i == 2 { - // removing address 1, after removing address 3, should leave us with address 2 - refs[0].Free() - retValues := table.Lookup(addr.SvcCS.Multicast(), ip) - assert.ElementsMatch(t, []interface{}{"2"}, retValues) - checkAnyCastCycles(t, - func() []interface{} { return table.Lookup(addr.SvcCS, ip) }, - []string{"2"}) - } - }) - } - -} - -func checkAnyCastCycles(t *testing.T, lookup func() []interface{}, expected []string) { - t.Helper() - firstRes := lookup()[0].(string) - startIndex := -1 - for i := range expected { - if expected[i] == firstRes { - startIndex = i + 1 - break - } - } - if startIndex == -1 { - t.Fatalf("Initial value %s not in expected (%v)", firstRes, expected) - } - for cnt := 0; cnt < len(expected)+1; cnt++ { - idx := (startIndex + cnt) % len(expected) - res := lookup()[0].(string) - if res != expected[idx] { - t.Fatalf("Value %s was not expected in (%v)", res, expected) - } - } -} - -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 := map[string]struct { - Address addr.SVC - LookupResultCount int - }{ - "cs": { - Address: addr.SvcCS.Multicast(), - LookupResultCount: 1, - }, - "ds": { - Address: addr.SvcDS.Multicast(), - LookupResultCount: 1, - }, - } - for name, tc := range testCases { - t.Run(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.SvcCS, nil))) - assert.Equal(t, 0, len(table.Lookup(addr.SvcDS, 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 := map[string]struct { - RegisteredAddress addr.SVC - LookupResultCSCount int - LookupResultDSCount int - }{ - "cs": { - RegisteredAddress: addr.SvcCS, - LookupResultCSCount: 1, - LookupResultDSCount: 0, - }, - "ds": { - RegisteredAddress: addr.SvcDS, - LookupResultCSCount: 0, - LookupResultDSCount: 1, - }, - } - for name, tc := range testCases { - t.Run(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.LookupResultCSCount, len(table.Lookup(addr.SvcCS, nil))) - assert.Equal(t, tc.LookupResultDSCount, len(table.Lookup(addr.SvcDS, nil))) - }) - } -} diff --git a/dispatcher/internal/registration/table.go b/dispatcher/internal/registration/table.go deleted file mode 100644 index a0244c0dee..0000000000 --- a/dispatcher/internal/registration/table.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - - "github.com/scionproto/scion/pkg/addr" -) - -// Table manages the UDP/IP port registrations for a single AS. -// -// Table is not safe for concurrent use from multiple goroutines. -type Table struct { - udpPortTable *UDPPortTable - svcTable SVCTable - size int - // XXX(scrye): Note that SCMP General IDs are globally scoped inside an IA - // (i.e., all all hosts share the same ID namespace, and thus can collide - // with each other). Because the IDs are random, it is very unlikely for a - // collision to occur (although faulty coding can increase the chance, - // e.g., if apps start with an ID of 1 and increment from there). We should - // revisit if SCMP General IDs should be scoped to IPs. - scmpTable *SCMPTable -} - -func NewTable(minPort, maxPort int) *Table { - return &Table{ - udpPortTable: NewUDPPortTable(minPort, maxPort), - svcTable: NewSVCTable(), - scmpTable: NewSCMPTable(), - } -} - -func (t *Table) Register(public *net.UDPAddr, bind net.IP, svc addr.SVC, - value interface{}) (*TableReference, error) { - - if public == nil { - return nil, ErrNoPublicAddress - } - if bind != nil && svc == addr.SvcNone { - return nil, ErrBindWithoutSvc - } - address, err := t.udpPortTable.Insert(public, value) - if err != nil { - return nil, err - } - if bind == nil { - bind = public.IP - } - svcRef, err := t.insertSVCIfRequested(svc, bind, address.Port, value) - if err != nil { - t.udpPortTable.Remove(public) - return nil, err - } - t.size++ - return &TableReference{table: t, address: address, svcRef: svcRef}, nil -} - -func (t *Table) insertSVCIfRequested(svc addr.SVC, bind net.IP, port int, - value interface{}) (Reference, error) { - - if svc != addr.SvcNone { - bindUdpAddr := &net.UDPAddr{ - IP: bind, - Port: port, - } - return t.svcTable.Register(svc, bindUdpAddr, value) - } - return nil, nil -} - -func (t *Table) LookupPublic(address *net.UDPAddr) (interface{}, bool) { - return t.udpPortTable.Lookup(address) -} - -func (t *Table) LookupService(svc addr.SVC, bind net.IP) []interface{} { - return t.svcTable.Lookup(svc, bind) -} - -func (t *Table) Size() int { - return t.size -} - -func (t *Table) LookupID(id uint64) (interface{}, bool) { - return t.scmpTable.Lookup(id) -} - -func (t *Table) registerID(id uint64, value interface{}) error { - return t.scmpTable.Register(id, value) -} - -func (t *Table) removeID(id uint64) { - t.scmpTable.Remove(id) -} - -type TableReference struct { - table *Table - freed bool - address *net.UDPAddr - svcRef Reference - ids []uint64 -} - -func (r *TableReference) Free() { - if r.freed { - panic("double free") - } - r.freed = true - r.table.udpPortTable.Remove(r.address) - if r.svcRef != nil { - r.svcRef.Free() - } - r.table.size-- - for _, id := range r.ids { - r.table.removeID(id) - } -} - -func (r *TableReference) UDPAddr() *net.UDPAddr { - return r.address -} - -func (r *TableReference) RegisterID(id uint64, value interface{}) error { - if err := r.table.registerID(id, value); err != nil { - return err - } - r.ids = append(r.ids, id) - return nil -} diff --git a/dispatcher/internal/registration/table_test.go b/dispatcher/internal/registration/table_test.go deleted file mode 100644 index 000c06848f..0000000000 --- a/dispatcher/internal/registration/table_test.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/pkg/addr" -) - -var dummyValue = "test value" - -func TestRegister(t *testing.T) { - tests := map[string]struct { - a *net.UDPAddr - b net.IP - svc addr.SVC - af assert.ErrorAssertionFunc - }{ - "no public address fails": { - a: nil, - svc: addr.SvcNone, - af: assert.Error, - }, - "zero public IPv4 address succeeds": { - a: &net.UDPAddr{IP: net.IPv4zero, Port: 80}, - svc: addr.SvcNone, - af: assert.NoError, - }, - "zero public IPv6 address succeeds": { - a: &net.UDPAddr{IP: net.IPv6zero, Port: 80}, - svc: addr.SvcNone, - af: assert.NoError, - }, - "public address with port, no bind, no svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 5, 1}, Port: 8080}, - af: assert.NoError, - }, - "public address without port, no bind, no svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 9, 1}}, - svc: addr.SvcNone, - af: assert.NoError, - }, - "public address, bind, no svc fails": { - a: &net.UDPAddr{IP: net.IP{192, 0, 20, 1}, Port: 8880}, - b: net.IP{10, 2, 3, 4}, - svc: addr.SvcNone, - af: assert.Error, - }, - - "public address, no bind, svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 22, 1}, Port: 8889}, - svc: addr.SvcCS, - af: assert.NoError, - }, - - "zero bind IPv4 address fails": { - a: &net.UDPAddr{IP: net.IP{192, 0, 23, 1}, Port: 8888}, - b: net.IPv4zero, - svc: addr.SvcCS, - af: assert.Error, - }, - - "zero bind IPv6 address fails": { - a: &net.UDPAddr{IP: net.IP{192, 0, 23, 1}, Port: 8888}, - b: net.IPv6zero, - svc: addr.SvcCS, - af: assert.Error, - }, - "public address, bind, svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 23, 1}, Port: 8888}, - b: net.IP{10, 2, 3, 4}, - svc: addr.SvcCS, - af: assert.NoError, - }, - } - - for n, tc := range tests { - t.Run(n, func(t *testing.T) { - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - ref, err := table.Register(tc.a, tc.b, tc.svc, dummyValue) - tc.af(t, err) - if err != nil { - assert.Nil(t, ref) - return - } - assert.NotNil(t, ref) - }) - } - - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - -} - -func TestRegisterOnlyPublic(t *testing.T) { - - t.Run("Free reference, size is 0", func(t *testing.T) { - t.Log("Given a table with a public address registration") - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - public := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - ref, err := table.Register(public, nil, addr.SvcNone, dummyValue) - require.NoError(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - assert.NotNil(t, ref) - - t.Log("Lookup is successful") - retValue, ok := table.LookupPublic(public) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - ref.Free() - assert.Equal(t, table.Size(), 0, "size is 0") - assert.Panics(t, ref.Free, "Free same reference again, panic") - retValue, ok = table.LookupPublic(public) - assert.False(t, ok, "lookup should fail") - assert.Nil(t, retValue) - }) - - t.Run("Register", func(t *testing.T) { - t.Log("Given a table with a public address registration") - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - public := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - ref, err := table.Register(public, nil, addr.SvcNone, dummyValue) - require.NoError(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - assert.NotNil(t, ref) - - t.Log("Lookup is successful") - retValue, ok := table.LookupPublic(public) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - t.Log("Register same address returns error") - ref, err = table.Register(public, nil, addr.SvcNone, dummyValue) - assert.Error(t, err) - assert.Nil(t, ref) - - t.Log("Register 0.0.0.0, error due to overlap") - public = &net.UDPAddr{IP: net.IPv4zero, Port: 80} - ref, err = table.Register(public, nil, addr.SvcNone, dummyValue) - assert.Error(t, err) - assert.Nil(t, ref) - - t.Log("Register ::, success") - public = &net.UDPAddr{IP: net.IPv6zero, Port: 80} - ref, err = table.Register(public, nil, addr.SvcNone, dummyValue) - assert.NoError(t, err) - assert.NotNil(t, ref) - }) -} - -func TestRegisterPublicAndSVC(t *testing.T) { - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "size is 0") - - t.Log("Given a table with a public address registration") - p := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - _, err := table.Register(p, nil, addr.SvcCS, dummyValue) - require.NoError(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - - t.Log("Public lookup is successful") - retValue, ok := table.LookupPublic(p) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - t.Log("SVC lookup is successful (bind inherits from public)") - retValues := table.LookupService(addr.SvcCS, p.IP) - assert.Equal(t, retValues, []interface{}{dummyValue}) -} - -func TestRegisterWithBind(t *testing.T) { - table := NewTable(minPort, maxPort) - - t.Log("Given a table with a bind address registration") - p := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - bind := net.IP{10, 2, 3, 4} - ref, err := table.Register(p, bind, addr.SvcCS, dummyValue) - require.NoError(t, err) - assert.NotNil(t, ref) - assert.Equal(t, table.Size(), 1, "size is 1") - - t.Log("Public lookup is successful") - retValue, ok := table.LookupPublic(p) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - t.Log("SVC lookup is successful") - retValues := table.LookupService(addr.SvcCS, bind) - assert.Equal(t, retValues, []interface{}{dummyValue}) - - t.Log("Bind lookup on different svc fails") - retValues = table.LookupService(addr.SvcDS, bind) - assert.Empty(t, retValues) - - t.Log("Colliding binds returns error, and public port is released") - otherPublic := &net.UDPAddr{IP: net.IP{192, 0, 2, 2}, Port: 80} - _, err = table.Register(otherPublic, bind, addr.SvcCS, dummyValue) - assert.Error(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - _, err = table.Register(otherPublic, nil, addr.SvcNone, dummyValue) - assert.NoError(t, err) - - t.Log("Freeing the entry allows for reregistration") - ref.Free() - _, err = table.Register(p, bind, addr.SvcCS, dummyValue) - assert.NoError(t, err) -} diff --git a/dispatcher/internal/registration/udptable.go b/dispatcher/internal/registration/udptable.go deleted file mode 100644 index 0abdc97f16..0000000000 --- a/dispatcher/internal/registration/udptable.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "fmt" - "net" - - "github.com/scionproto/scion/pkg/private/serrors" -) - -// UDPPortTable stores port allocations for UDP/IPv4 and UDP/IPv6 sockets. -// -// Additionally, it allocates ports dynamically if the requested port is 0. -type UDPPortTable struct { - v4PortTable map[int]IPTable - v6PortTable map[int]IPTable - allocator *UDPPortAllocator -} - -func NewUDPPortTable(minPort, maxPort int) *UDPPortTable { - return NewUDPPortTableFromMap(minPort, maxPort, make(map[int]IPTable), make(map[int]IPTable)) -} - -func NewUDPPortTableFromMap(minPort, maxPort int, v4, v6 map[int]IPTable) *UDPPortTable { - return &UDPPortTable{ - v4PortTable: v4, - v6PortTable: v6, - allocator: NewUDPPortAllocator(minPort, maxPort), - } -} - -func (t *UDPPortTable) Lookup(address *net.UDPAddr) (interface{}, bool) { - if address.IP.IsUnspecified() { - return nil, false - } - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if !ok { - return nil, false - } - return ipTable.Route(address.IP) -} - -func (t *UDPPortTable) getPortTableByIP(ip net.IP) map[int]IPTable { - if ip.To4() != nil { - return t.v4PortTable - } - return t.v6PortTable -} - -func (t *UDPPortTable) overlapsWith(address *net.UDPAddr) bool { - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if !ok { - return false - } - return ipTable.OverlapsWith(address.IP) -} - -// Insert adds address into the allocation table. It will return an error if an -// entry overlaps, or if the value is nil. -func (t *UDPPortTable) Insert(address *net.UDPAddr, value interface{}) (*net.UDPAddr, error) { - if t.overlapsWith(address) { - return nil, serrors.WithCtx(ErrOverlappingAddress, "address", address) - } - if value == nil { - return nil, ErrNoValue - } - address = copyUDPAddr(address) - newAddress, err := t.computeAddressWithPort(address) - if err != nil { - return nil, err - } - t.insertUDPAddress(newAddress, value) - return newAddress, nil -} - -func (t *UDPPortTable) computeAddressWithPort(address *net.UDPAddr) (*net.UDPAddr, error) { - var err error - if address.Port == 0 { - address.Port, err = t.allocator.Allocate(address.IP, t) - } - return address, err -} - -func (t *UDPPortTable) insertUDPAddress(address *net.UDPAddr, value interface{}) { - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if !ok { - ipTable = make(IPTable) - portTable[address.Port] = ipTable - } - ipTable[address.IP.String()] = value -} - -func copyUDPAddr(address *net.UDPAddr) *net.UDPAddr { - return &net.UDPAddr{ - IP: copyIPAddr(address.IP), - Port: address.Port, - Zone: address.Zone, - } -} - -func copyIPAddr(ip net.IP) net.IP { - c := make(net.IP, len(ip)) - copy(c, ip) - return c -} - -func (t *UDPPortTable) Remove(address *net.UDPAddr) { - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if ok { - delete(ipTable, address.IP.String()) - if len(ipTable) == 0 { - delete(portTable, address.Port) - } - } -} - -// IPTable maps string representations of IP addresses to arbitrary values. -type IPTable map[string]interface{} - -// OverlapsWith returns true if ip overlaps with any entry in t. For example, -// 0.0.0.0 would overlap with any other IPv4 address. -func (t IPTable) OverlapsWith(ip net.IP) bool { - if ip.IsUnspecified() && len(t) > 0 { - return true - } - _, ok := t.Route(ip) - return ok -} - -// Route returns the object associated with destination ip. -// -// This can either be an entry matching argument ip exactly, or a zero IP -// address. -func (t IPTable) Route(ip net.IP) (interface{}, bool) { - if v, ok := t[getZeroString(ip)]; ok { - return v, true - } - if v, ok := t[ip.String()]; ok { - return v, true - } - return nil, false -} - -func getZeroString(ip net.IP) string { - if ip.To4() != nil { - return "0.0.0.0" - } else { - return "::" - } -} - -// UDPPortAllocator attempts to find a free port between a min port and a max port in -// an allocation table. Attempts wrap around when they reach max port. -// -// If no port is available, the allocation function panics. -type UDPPortAllocator struct { - minPort int - maxPort int - nextPort int -} - -// NewUDPPortAllocator returns an allocation. The function panics if min > max, or if -// min or max is not a valid port number. -func NewUDPPortAllocator(min, max int) *UDPPortAllocator { - if min <= 0 { - panic(fmt.Sprintf("bad min port value %d", min)) - } - if min > max { - panic(fmt.Sprintf("min port must be less than maxport, but %d > %d", min, max)) - } - if max >= (1 << 16) { - panic(fmt.Sprintf("max port cannot exceed %d (was %d)", (1<<16)-1, max)) - } - return &UDPPortAllocator{ - minPort: min, - maxPort: max, - nextPort: min, - } -} - -// Allocate returns the next available port for the IP address. It will panic -// if it runs out of ports. -func (a *UDPPortAllocator) Allocate(ip net.IP, t *UDPPortTable) (int, error) { - for i := a.minPort; i < a.maxPort+1; i++ { - candidate := &net.UDPAddr{ - IP: ip, - Port: a.nextPort, - } - a.nextPort++ - if a.nextPort == a.maxPort+1 { - a.nextPort = a.minPort - } - if !t.overlapsWith(candidate) { - return candidate.Port, nil - } - } - return 0, ErrNoPorts -} diff --git a/dispatcher/internal/registration/udptable_test.go b/dispatcher/internal/registration/udptable_test.go deleted file mode 100644 index 00a669291c..0000000000 --- a/dispatcher/internal/registration/udptable_test.go +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -var docIPv6AddressStr = "2001:db8::1" -var docIPv6Address = net.ParseIP(docIPv6AddressStr) - -var minPort = 1024 -var maxPort = 65535 - -func testUDPTableWithPorts(v4, v6 map[int]IPTable) *UDPPortTable { - if v4 == nil { - v4 = map[int]IPTable{} - } - if v6 == nil { - v6 = map[int]IPTable{} - } - return NewUDPPortTableFromMap(minPort, maxPort, v4, v6) -} - -func TestUDPPortTableLookup(t *testing.T) { - value := "test value" - Convey("", t, func() { - Convey("Given a non-zero IPv4 address", func() { - address := &net.UDPAddr{IP: net.IP{10, 1, 2, 3}, Port: 10080} - Convey("Lookup on an empty table returns nil", func() { - table := NewUDPPortTable(minPort, maxPort) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - Convey("Lookup on table with non-matching entries returns nil", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 10080: {"10.4.5.6": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - Convey("Lookup on table with exact match returns value", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 10080: {"10.1.2.3": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with matching 0.0.0.0 entry returns value", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 10080: {"0.0.0.0": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with non-matching 0.0.0.0 entry returns nil", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 80: {"0.0.0.0": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - }) - Convey("Lookup fails for zero IPv4 address", func() { - table := NewUDPPortTable(minPort, maxPort) - address := &net.UDPAddr{IP: net.IPv4zero, Port: 10080} - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - Convey("Given an IPv6 address", func() { - address := &net.UDPAddr{IP: docIPv6Address, Port: 10080} - Convey("Lookup on table with matching entry returns value", func() { - table := testUDPTableWithPorts(nil, map[int]IPTable{ - 10080: {docIPv6AddressStr: value}, - }) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with matching :: entry returns value", func() { - table := testUDPTableWithPorts(nil, map[int]IPTable{ - 10080: {"::": value}, - }) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with non-matching :: entry returns nil", func() { - table := testUDPTableWithPorts(nil, map[int]IPTable{ - 80: {"::": value}, - }) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - }) - }) -} - -func TestUDPPortTableInsert(t *testing.T) { - value := "Test value" - Convey("", t, func() { - Convey("Given an empty table", func() { - table := NewUDPPortTable(minPort, maxPort) - Convey("Inserting an IPv4 address with a port returns a copy of the same address", - func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(address) - SoMsg("err", err, ShouldBeNil) - SoMsg("address content", retAddress, ShouldResemble, address) - SoMsg("address not same object", retAddress, ShouldNotPointTo, address) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an IPv4 address with a 0 port returns an allocated port", - func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}} - expectedAddress := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 1024} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(expectedAddress) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, expectedAddress) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an IPv6 address with a port returns a copy of the same address", - func() { - address := &net.UDPAddr{IP: docIPv6Address, Port: 10080} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(address) - SoMsg("err", err, ShouldBeNil) - SoMsg("address content", retAddress, ShouldResemble, address) - SoMsg("address not same object", retAddress, ShouldNotPointTo, address) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an IPv6 address with a 0 port returns an allocated port", - func() { - address := &net.UDPAddr{IP: docIPv6Address} - expectedAddress := &net.UDPAddr{IP: docIPv6Address, Port: 1024} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(expectedAddress) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, expectedAddress) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an address without a value is not permitted", func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - retAddress, err := table.Insert(address, nil) - _, lookupOk := table.Lookup(address) - SoMsg("err", err, ShouldNotBeNil) - SoMsg("address", retAddress, ShouldBeNil) - SoMsg("lookup ok", lookupOk, ShouldBeFalse) - }) - Convey("Inserting a zero IPv4 address is permitted", func() { - address := &net.UDPAddr{IP: net.IPv4zero, Port: 10080} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, address) - }) - Convey("Inserting a zero IPv6 address is permitted", func() { - address := &net.UDPAddr{IP: net.IPv6zero, Port: 10080} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, address) - }) - }) - Convey("Given a table with a zero address", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 1024: {"0.0.0.0": value}}, nil) - Convey("A colliding allocation will return an error", func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 1024} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldNotBeNil) - SoMsg("address", retAddress, ShouldBeNil) - }) - }) - Convey("Given a table with a non-zero address", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 1024: {"10.0.0.0": value}}, nil) - Convey("Inserting zero IPv4 address on the same port fails", func() { - address := &net.UDPAddr{IP: net.IPv4zero, Port: 1024} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldNotBeNil) - SoMsg("address", retAddress, ShouldBeNil) - }) - Convey("Inserting zero IPv6 address on the same port succeeds", func() { - address := &net.UDPAddr{IP: net.IPv6zero, Port: 1024} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, address) - }) - }) - }) -} - -func TestUDPPortTableRemove(t *testing.T) { - value := "test value" - Convey("", t, func() { - Convey("", func() { - table := testUDPTableWithPorts( - map[int]IPTable{ - 10080: {"10.2.3.4": value}, - 10081: {"0.0.0.0": value}}, - map[int]IPTable{ - 10082: {docIPv6AddressStr: value}, - 10083: {"::": value}}, - ) - - Convey("Remove non-zero addresses", func() { - addrs := []*net.UDPAddr{ - {IP: net.IP{10, 2, 3, 4}, Port: 10080}, - {IP: docIPv6Address, Port: 10082}, - } - for _, address := range addrs { - _, lookupOk := table.Lookup(address) - SoMsg("lookup succeeds before removing", lookupOk, ShouldBeTrue) - table.Remove(address) - _, lookupOk = table.Lookup(address) - SoMsg("lookup fails after removing", lookupOk, ShouldBeFalse) - } - }) - Convey("Remove zero address", func() { - addrs := map[*net.UDPAddr]net.IP{ - {IP: net.IPv4zero, Port: 10081}: {10, 1, 2, 3}, - {IP: net.IPv6zero, Port: 10083}: docIPv6Address, - } - - for address, lookupAddress := range addrs { - _, lookupOk := table.Lookup(&net.UDPAddr{IP: lookupAddress, Port: address.Port}) - SoMsg("lookup succeeds before removing", lookupOk, ShouldBeTrue) - table.Remove(address) - _, lookupOk = table.Lookup(&net.UDPAddr{IP: lookupAddress, Port: address.Port}) - SoMsg("lookup fails after removing", lookupOk, ShouldBeFalse) - } - }) - }) - Convey("Removing non-existent entry does not panic", func() { - table := NewUDPPortTable(minPort, maxPort) - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 666} - So(func() { table.Remove(address) }, ShouldNotPanic) - }) - }) -} - -func TestUDPPortAllocator(t *testing.T) { - address := net.IP{10, 2, 3, 4} - value := "test value" - Convey("", t, func() { - Convey("Constructing an allocator with minport > maxport will panic", func() { - So(func() { NewUDPPortAllocator(10, 4) }, ShouldPanic) - }) - Convey("Constructing an allocator with a negative minport will panic", func() { - So(func() { NewUDPPortAllocator(-4, 4) }, ShouldPanic) - }) - Convey("Constructing an allocator with a minport of 0 will panic", func() { - So(func() { NewUDPPortAllocator(0, 4) }, ShouldPanic) - }) - Convey("Constructing an allocator with maxport > 65535 wil panic", func() { - So(func() { NewUDPPortAllocator(1, 65536) }, ShouldPanic) - }) - Convey("Given an allocator", func() { - allocator := NewUDPPortAllocator(1000, 1500) - table := NewUDPPortTable(minPort, maxPort) - Convey("if table is empty, first allocation gives min port", func() { - port, err := allocator.Allocate(address, table) - SoMsg("port", port, ShouldEqual, 1000) - SoMsg("err", err, ShouldBeNil) - }) - Convey("if table contains used first port, first allocation gives next port", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 1000: {"10.2.3.4": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 1001) - SoMsg("err", err, ShouldBeNil) - }) - Convey("if wildcard bind uses first port, first allocation gives next port", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 1000: {"0.0.0.0": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 1001) - SoMsg("err", err, ShouldBeNil) - }) - }) - Convey("Given an allocator with few ports", func() { - allocator := NewUDPPortAllocator(1, 3) - Convey("if all ports are taken except max, max is chosen", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 1: {"0.0.0.0": value}, - 2: {"0.0.0.0": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 3) - SoMsg("err", err, ShouldBeNil) - Convey("if first port is available, it is chosen after wrapping", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 2: {"0.0.0.0": value}, - 3: {"0.0.0.0": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 1) - SoMsg("err", err, ShouldBeNil) - }) - }) - Convey("if all ports are taken, error", func() { - table := testUDPTableWithPorts( - map[int]IPTable{ - 1: {"0.0.0.0": value}, - 2: {"0.0.0.0": value}, - 3: {"0.0.0.0": value}, - }, nil) - port, err := allocator.Allocate(address, table) - SoMsg("port", port, ShouldEqual, 0) - SoMsg("err", err, ShouldNotBeNil) - }) - }) - Convey("Given an allocator with IPv6 data", func() { - v6address := net.ParseIP(docIPv6AddressStr) - allocator := NewUDPPortAllocator(1000, 1500) - table := testUDPTableWithPorts(nil, - map[int]IPTable{ - 1000: {docIPv6AddressStr: value}, - }) - Convey("allocation skips ports correctly for IPv6", func() { - port, err := allocator.Allocate(v6address, table) - SoMsg("port", port, ShouldEqual, 1001) - SoMsg("err", err, ShouldBeNil) - }) - }) - }) -} diff --git a/dispatcher/internal/respool/BUILD.bazel b/dispatcher/internal/respool/BUILD.bazel deleted file mode 100644 index f66f9fa63e..0000000000 --- a/dispatcher/internal/respool/BUILD.bazel +++ /dev/null @@ -1,34 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "buffer.go", - "packet.go", - ], - importpath = "github.com/scionproto/scion/dispatcher/internal/respool", - visibility = ["//dispatcher:__subpackages__"], - deps = [ - "//dispatcher/internal/metrics:go_default_library", - "//pkg/private/common:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/slayers:go_default_library", - "@com_github_google_gopacket//:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["packet_test.go"], - embed = [":go_default_library"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/private/xtest:go_default_library", - "//pkg/slayers:go_default_library", - "//pkg/slayers/path:go_default_library", - "//pkg/slayers/path/scion:go_default_library", - "@com_github_google_gopacket//:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - "@com_github_stretchr_testify//require:go_default_library", - ], -) diff --git a/dispatcher/internal/respool/buffer.go b/dispatcher/internal/respool/buffer.go deleted file mode 100644 index 0c3c321729..0000000000 --- a/dispatcher/internal/respool/buffer.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package respool contains the Dispatcher's pool of free buffers/packets. -// -// FIXME(scrye): Currently the pools are elastic, but this is not ideal for -// traffic bursts. Consider converting these to fixed-size lists. -package respool - -import ( - "sync" - - "github.com/scionproto/scion/pkg/private/common" -) - -var bufferPool = sync.Pool{ - New: func() interface{} { - return make([]byte, common.SupportedMTU) - }, -} - -func GetBuffer() []byte { - b := bufferPool.Get().([]byte) - return b[:cap(b)] -} - -func PutBuffer(b []byte) { - if cap(b) == common.SupportedMTU { - bufferPool.Put(b) - } -} diff --git a/dispatcher/internal/respool/packet.go b/dispatcher/internal/respool/packet.go deleted file mode 100644 index a34dafd408..0000000000 --- a/dispatcher/internal/respool/packet.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package respool - -import ( - "net" - "sync" - - "github.com/google/gopacket" - - "github.com/scionproto/scion/dispatcher/internal/metrics" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/slayers" -) - -var packetPool = sync.Pool{ - New: func() interface{} { - return newPacket() - }, -} - -func GetPacket() *Packet { - pkt := packetPool.Get().(*Packet) - *pkt.refCount = 1 - return pkt -} - -// Packet describes a SCION packet. Fields might reference each other -// (including hidden fields), so callers should only write to freshly created -// packets, and readers should take care never to mutate data. -type Packet struct { - UnderlayRemote *net.UDPAddr - - SCION slayers.SCION - UDP slayers.UDP - SCMP slayers.SCMP - - // L4 indicates what type is at layer 4. - L4 gopacket.LayerType - - // parser is tied to the layers in this packet. - // IngoreUnsupported is set to true. - parser *gopacket.DecodingLayerParser - // buffer contains the raw slice that other fields reference - buffer []byte - - mtx sync.Mutex - refCount *int -} - -// Len returns the length of the packet. -func (p *Packet) Len() int { - return len(p.buffer) -} - -func newPacket() *Packet { - refCount := 1 - pkt := &Packet{ - buffer: GetBuffer(), - refCount: &refCount, - } - hbh := slayers.HopByHopExtnSkipper{} - e2e := slayers.EndToEndExtnSkipper{} - pkt.parser = gopacket.NewDecodingLayerParser(slayers.LayerTypeSCION, - &pkt.SCION, &hbh, &e2e, &pkt.UDP, &pkt.SCMP, - ) - pkt.parser.IgnoreUnsupported = true - return pkt -} - -// Dup increases pkt's reference count. -// -// Dup panics if it is called after the packet has been freed (i.e., it's -// reference count reached 0). -// -// Modifying a packet after the first call to Dup is racy, and callers should -// use external locking for it. -func (pkt *Packet) Dup() { - pkt.mtx.Lock() - if *pkt.refCount <= 0 { - panic("cannot reference freed packet") - } - *pkt.refCount++ - pkt.mtx.Unlock() -} - -// CopyTo copies the buffer into the provided bytearray. Returns number of bytes copied. -func (pkt *Packet) CopyTo(p []byte) int { - n := len(pkt.buffer) - p = p[:n] - copy(p, pkt.buffer) - return n -} - -// Free releases a reference to the packet. Free is safe to use from concurrent -// goroutines. -func (pkt *Packet) Free() { - pkt.mtx.Lock() - if *pkt.refCount <= 0 { - panic("reference count underflow") - } - *pkt.refCount-- - if *pkt.refCount == 0 { - pkt.reset() - pkt.mtx.Unlock() - packetPool.Put(pkt) - } else { - pkt.mtx.Unlock() - } -} - -func (pkt *Packet) DecodeFromConn(conn net.PacketConn) error { - n, readExtra, err := conn.ReadFrom(pkt.buffer) - if err != nil { - return err - } - pkt.buffer = pkt.buffer[:n] - metrics.M.NetReadBytes().Add(float64(n)) - - pkt.UnderlayRemote = readExtra.(*net.UDPAddr) - if err := pkt.decodeBuffer(); err != nil { - metrics.M.NetReadPkts( - metrics.IncomingPacket{ - Result: metrics.PacketResultParseError, - }, - ).Inc() - return err - } - return nil -} - -func (pkt *Packet) DecodeFromReliableConn(conn net.PacketConn) error { - n, readExtra, err := conn.ReadFrom(pkt.buffer) - if err != nil { - return err - } - pkt.buffer = pkt.buffer[:n] - - if readExtra == nil { - return serrors.New("missing next-hop") - } - pkt.UnderlayRemote = readExtra.(*net.UDPAddr) - return pkt.decodeBuffer() -} - -func (pkt *Packet) decodeBuffer() error { - decoded := make([]gopacket.LayerType, 0, 4) - - // Unsupported layers are ignored by the parser. - if err := pkt.parser.DecodeLayers(pkt.buffer, &decoded); err != nil { - return err - } - if len(decoded) < 2 { - return serrors.New("L4 not decoded") - } - l4 := decoded[len(decoded)-1] - if l4 != slayers.LayerTypeSCMP && l4 != slayers.LayerTypeSCIONUDP { - return serrors.New("unknown L4 layer decoded", "type", l4) - } - pkt.L4 = l4 - return nil -} - -func (pkt *Packet) SendOnConn(conn net.PacketConn, address net.Addr) (int, error) { - return conn.WriteTo(pkt.buffer, address) -} - -func (pkt *Packet) reset() { - pkt.buffer = pkt.buffer[:cap(pkt.buffer)] - pkt.UnderlayRemote = nil - pkt.L4 = 0 -} diff --git a/dispatcher/internal/respool/packet_test.go b/dispatcher/internal/respool/packet_test.go deleted file mode 100644 index f3ad215bbd..0000000000 --- a/dispatcher/internal/respool/packet_test.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2020 Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package respool - -import ( - "testing" - - "github.com/google/gopacket" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/slayers" - "github.com/scionproto/scion/pkg/slayers/path" - "github.com/scionproto/scion/pkg/slayers/path/scion" -) - -func TestDecodeBuffer(t *testing.T) { - testCases := map[string]struct { - Layers func(t *testing.T) []gopacket.SerializableLayer - Check func(t *testing.T, pkt *Packet) - ErrAssertion assert.ErrorAssertionFunc - }{ - "UDP": { - Layers: func(t *testing.T) []gopacket.SerializableLayer { - scion := scionLayer(t, slayers.L4UDP) - udp := &slayers.UDP{ - SrcPort: 1337, - DstPort: 42, - } - udp.SetNetworkLayerForChecksum(scion) - pld := gopacket.Payload("I am a payload") - return []gopacket.SerializableLayer{scion, udp, pld} - }, - Check: func(t *testing.T, pkt *Packet) { - assert.Equal(t, xtest.MustParseIA("1-ff00:0:110"), pkt.SCION.SrcIA) - assert.Equal(t, 1337, int(pkt.UDP.SrcPort)) - assert.Equal(t, slayers.LayerTypeSCIONUDP, pkt.L4) - }, - ErrAssertion: assert.NoError, - }, - "SCMP": { - Layers: func(t *testing.T) []gopacket.SerializableLayer { - scion := scionLayer(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeExternalInterfaceDown, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - scmpMsg := &slayers.SCMPExternalInterfaceDown{ - IA: xtest.MustParseIA("1-ff00:0:110"), - IfID: 42, - } - pld := gopacket.Payload("offending packet") - return []gopacket.SerializableLayer{scion, scmp, scmpMsg, pld} - }, - Check: func(t *testing.T, pkt *Packet) { - assert.Equal(t, xtest.MustParseIA("1-ff00:0:110"), pkt.SCION.SrcIA) - assert.Equal(t, slayers.SCMPTypeExternalInterfaceDown, pkt.SCMP.TypeCode.Type()) - assert.Equal(t, slayers.LayerTypeSCMP, pkt.L4) - }, - ErrAssertion: assert.NoError, - }, - "TCP": { - Layers: func(t *testing.T) []gopacket.SerializableLayer { - scion := scionLayer(t, slayers.L4TCP) - pld := gopacket.Payload("offending packet") - return []gopacket.SerializableLayer{scion, pld} - }, - ErrAssertion: assert.Error, - }, - } - for name, tc := range testCases { - name, tc := name, tc - t.Run(name, func(t *testing.T) { - opts := gopacket.SerializeOptions{ - ComputeChecksums: true, - FixLengths: true, - } - buf := gopacket.NewSerializeBuffer() - require.NoError(t, gopacket.SerializeLayers(buf, opts, tc.Layers(t)...)) - pkt := newPacket() - pkt.buffer = buf.Bytes() - err := pkt.decodeBuffer() - tc.ErrAssertion(t, err) - if err != nil { - return - } - tc.Check(t, pkt) - }) - } -} - -func scionLayer(t *testing.T, l4 slayers.L4ProtocolType) *slayers.SCION { - scion := &slayers.SCION{ - Version: 0, - TrafficClass: 0xb8, - FlowID: 0xdead, - NextHdr: l4, - PathType: scion.PathType, - SrcIA: xtest.MustParseIA("1-ff00:0:110"), - DstIA: xtest.MustParseIA("1-ff00:0:112"), - Path: &scion.Decoded{ - Base: scion.Base{ - PathMeta: scion.MetaHdr{ - CurrHF: 2, - SegLen: [3]uint8{3, 0, 0}, - }, - NumINF: 1, - NumHops: 3, - }, - InfoFields: []path.InfoField{ - {SegID: 0x111, ConsDir: true, Timestamp: 0x100}, - }, - HopFields: []path.HopField{ - {ConsIngress: 0, ConsEgress: 311}, - {ConsIngress: 131, ConsEgress: 141}, - {ConsIngress: 411, ConsEgress: 0}, - }, - }, - } - require.NoError(t, scion.SetSrcAddr(addr.MustParseHost("127.0.0.1"))) - require.NoError(t, scion.SetDstAddr(addr.MustParseHost("127.0.0.2"))) - return scion -} diff --git a/dispatcher/network/BUILD.bazel b/dispatcher/network/BUILD.bazel deleted file mode 100644 index f08a1a817a..0000000000 --- a/dispatcher/network/BUILD.bazel +++ /dev/null @@ -1,20 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "app_socket.go", - "dispatcher.go", - ], - importpath = "github.com/scionproto/scion/dispatcher/network", - visibility = ["//visibility:public"], - deps = [ - "//dispatcher:go_default_library", - "//dispatcher/internal/metrics:go_default_library", - "//dispatcher/internal/respool:go_default_library", - "//pkg/addr:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/sock/reliable:go_default_library", - ], -) diff --git a/dispatcher/network/app_socket.go b/dispatcher/network/app_socket.go deleted file mode 100644 index 2a6167de82..0000000000 --- a/dispatcher/network/app_socket.go +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package network - -import ( - "fmt" - "io" - "net" - - "github.com/scionproto/scion/dispatcher" - "github.com/scionproto/scion/dispatcher/internal/metrics" - "github.com/scionproto/scion/dispatcher/internal/respool" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable" -) - -// AppSocketServer accepts new connections coming from SCION apps, and -// hands them off to the registration + dataplane handler. -type AppSocketServer struct { - Listener *reliable.Listener - DispServer *dispatcher.Server -} - -func (s *AppSocketServer) Serve() error { - for { - conn, err := s.Listener.Accept() - if err != nil { - return err - } - pconn := conn.(net.PacketConn) - s.Handle(pconn) - } -} - -// Handle passes conn off to a per-connection state handler. -func (h *AppSocketServer) Handle(conn net.PacketConn) { - ch := &AppConnHandler{ - Conn: conn, - Logger: log.New("clientID", fmt.Sprintf("%p", conn)), - } - go func() { - defer log.HandlePanic() - ch.Handle(h.DispServer) - }() -} - -// AppConnHandler handles a single SCION application connection. -type AppConnHandler struct { - // Conn is the local socket to which the application is connected. - Conn net.PacketConn - DispConn *dispatcher.Conn - Logger log.Logger -} - -func (h *AppConnHandler) Handle(appServer *dispatcher.Server) { - h.Logger.Debug("Accepted new client") - defer h.Logger.Debug("Closed client socket") - defer h.Conn.Close() - - dispConn, err := h.doRegExchange(appServer) - if err != nil { - metrics.M.AppConnErrors().Inc() - h.Logger.Info("Registration error", "err", err) - return - } - h.DispConn = dispConn.(*dispatcher.Conn) - defer h.DispConn.Close() - svc := h.DispConn.SVCAddr().String() - metrics.M.OpenSockets(metrics.SVC{Type: svc}).Inc() - defer metrics.M.OpenSockets(metrics.SVC{Type: svc}).Dec() - - go func() { - defer log.HandlePanic() - h.RunRingToAppDataplane() - }() - - h.RunAppToNetDataplane() -} - -// doRegExchange manages an application's registration request, and returns a -// reference to registered data that should be freed at the end of the -// registration, information about allocated ring buffers and whether an error occurred. -func (h *AppConnHandler) doRegExchange(appServer *dispatcher.Server) (net.PacketConn, error) { - - b := respool.GetBuffer() - defer respool.PutBuffer(b) - - regInfo, err := h.recvRegistration(b) - if err != nil { - return nil, serrors.WrapStr("receiving registration message", err) - } - appConn, _, err := appServer.Register(nil, - regInfo.IA, regInfo.PublicAddress, regInfo.SVCAddress) - if err != nil { - return nil, serrors.WrapStr("add registration", err, "registration", regInfo) - } - udpAddr := appConn.(*dispatcher.Conn).LocalAddr().(*net.UDPAddr) - port := uint16(udpAddr.Port) - if err := h.sendConfirmation(b, &reliable.Confirmation{Port: port}); err != nil { - appConn.Close() - return nil, serrors.WrapStr("sending registration confirmation message", err) - } - h.logRegistration(regInfo.IA, udpAddr, getBindIP(regInfo.BindAddress), - regInfo.SVCAddress) - return appConn, nil -} - -func (h *AppConnHandler) logRegistration(ia addr.IA, public *net.UDPAddr, bind net.IP, - svc addr.SVC) { - - items := []interface{}{"ia", ia, "public", public} - if bind != nil { - items = append(items, "extra_bind", bind) - } - if svc != addr.SvcNone { - items = append(items, "svc", svc) - } - h.Logger.Debug("Client registered address", items...) -} - -func (h *AppConnHandler) recvRegistration(b []byte) (*reliable.Registration, error) { - n, _, err := h.Conn.ReadFrom(b) - if err != nil { - return nil, err - } - b = b[:n] - - var rm reliable.Registration - if err := rm.DecodeFromBytes(b); err != nil { - return nil, err - } - return &rm, nil -} - -func (h *AppConnHandler) sendConfirmation(b []byte, c *reliable.Confirmation) error { - n, err := c.SerializeTo(b) - if err != nil { - return err - } - b = b[:n] - - if _, err := h.Conn.WriteTo(b, nil); err != nil { - return err - } - return nil -} - -// RunAppToNetDataplane moves packets from the application's socket to the -// underlay socket. -func (h *AppConnHandler) RunAppToNetDataplane() { - - for { - pkt := respool.GetPacket() - // XXX(scrye): we don't release the reference on error conditions, and - // let the GC take care of this situation as they should be fairly - // rare. - - if err := pkt.DecodeFromReliableConn(h.Conn); err != nil { - if err == io.EOF { - h.Logger.Debug("[app->network] EOF received from client") - } else { - h.Logger.Debug("[app->network] Client connection error", "err", err) - metrics.M.AppReadErrors().Inc() - } - return - } - metrics.M.AppReadBytes().Add(float64(pkt.Len())) - metrics.M.AppReadPkts().Inc() - - n, err := h.DispConn.Write(pkt) - if err != nil { - metrics.M.NetWriteErrors().Inc() - h.Logger.Error("[app->network] Underlay socket error", "err", err) - } else { - metrics.M.NetWriteBytes().Add(float64(n)) - metrics.M.NetWritePkts().Inc() - } - pkt.Free() - } -} - -// RunRingToAppDataplane moves packets from the application's ingress ring to -// the application's socket. -func (h *AppConnHandler) RunRingToAppDataplane() { - for { - pkt := h.DispConn.Read() - if pkt == nil { - // Ring was closed because app shut down its data socket - return - } - n, err := pkt.SendOnConn(h.Conn, pkt.UnderlayRemote) - if err != nil { - metrics.M.AppWriteErrors().Inc() - h.Logger.Error("[network->app] App connection error.", "err", err) - h.Conn.Close() - return - } - metrics.M.AppWritePkts().Inc() - metrics.M.AppWriteBytes().Add(float64(n)) - pkt.Free() - } -} - -func getBindIP(address *net.UDPAddr) net.IP { - if address == nil { - return nil - } - return address.IP -} diff --git a/dispatcher/network/dispatcher.go b/dispatcher/network/dispatcher.go deleted file mode 100644 index bfbbfb53cd..0000000000 --- a/dispatcher/network/dispatcher.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package network - -import ( - "os" - - "github.com/scionproto/scion/dispatcher" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable" -) - -type Dispatcher struct { - UnderlaySocket string - ApplicationSocket string - SocketFileMode os.FileMode -} - -func (d *Dispatcher) ListenAndServe() error { - dispServer, err := dispatcher.NewServer(d.UnderlaySocket, nil, nil) - if err != nil { - return err - } - defer dispServer.Close() - - dispServerConn, err := reliable.Listen(d.ApplicationSocket) - if err != nil { - return err - } - defer dispServerConn.Close() - if err := os.Chmod(d.ApplicationSocket, d.SocketFileMode); err != nil { - return serrors.WrapStr("chmod failed", err, "socket file", d.ApplicationSocket) - } - - errChan := make(chan error) - go func() { - defer log.HandlePanic() - errChan <- dispServer.Serve() - }() - - go func() { - defer log.HandlePanic() - dispServer := &AppSocketServer{ - Listener: dispServerConn, - DispServer: dispServer, - } - errChan <- dispServer.Serve() - }() - - return <-errChan -} diff --git a/dispatcher/table.go b/dispatcher/table.go deleted file mode 100644 index e48b40af76..0000000000 --- a/dispatcher/table.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package dispatcher - -import ( - "net" - - "github.com/scionproto/scion/dispatcher/internal/registration" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/private/ringbuf" -) - -type TableEntry struct { - appIngressRing *ringbuf.Ring -} - -func newTableEntry() *TableEntry { - // Construct application ingress ring buffer - appIngressRing := ringbuf.New(128, nil, "net_to_app_ring") - return &TableEntry{ - appIngressRing: appIngressRing, - } -} - -// IATable is a type-safe convenience wrapper around a generic routing table. -type IATable struct { - registration.IATable -} - -func NewIATable(minPort, maxPort int) *IATable { - return &IATable{ - IATable: registration.NewIATable(minPort, maxPort), - } -} - -func (t *IATable) LookupPublic(ia addr.IA, public *net.UDPAddr) (*TableEntry, bool) { - e, ok := t.IATable.LookupPublic(ia, public) - if !ok { - return nil, false - } - return e.(*TableEntry), true -} - -func (t *IATable) LookupService(ia addr.IA, svc addr.SVC, bind net.IP) []*TableEntry { - ifaces := t.IATable.LookupService(ia, svc, bind) - entries := make([]*TableEntry, len(ifaces)) - for i := range ifaces { - entries[i] = ifaces[i].(*TableEntry) - } - return entries -} - -func (t *IATable) LookupID(ia addr.IA, id uint64) (*TableEntry, bool) { - e, ok := t.IATable.LookupID(ia, id) - if !ok { - return nil, false - } - return e.(*TableEntry), true -} diff --git a/dispatcher/underlay.go b/dispatcher/underlay.go deleted file mode 100644 index 4a09cc22b8..0000000000 --- a/dispatcher/underlay.go +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2020 ETH Zurich, 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 dispatcher - -import ( - "net" - - "github.com/google/gopacket" - - "github.com/scionproto/scion/dispatcher/internal/metrics" - "github.com/scionproto/scion/dispatcher/internal/respool" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/common" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/slayers" - "github.com/scionproto/scion/pkg/slayers/path/epic" - "github.com/scionproto/scion/pkg/slayers/path/scion" - "github.com/scionproto/scion/private/ringbuf" -) - -const ( - ErrUnsupportedL4 common.ErrMsg = "unsupported SCION L4 protocol" - ErrUnsupportedDestination common.ErrMsg = "unsupported destination address type" - ErrUnsupportedSCMPDestination common.ErrMsg = "unsupported SCMP destination address type" - ErrUnsupportedQuotedL4Type common.ErrMsg = "unsupported quoted L4 protocol type" - ErrMalformedL4Quote common.ErrMsg = "malformed L4 quote" -) - -// NetToRingDataplane reads SCION packets from the underlay socket, routes them -// to determine the destination process, and then enqueues the packets on the -// application's ingress ring. -// -// The rings are used to provide non-blocking IO for the underlay receiver. -type NetToRingDataplane struct { - UnderlayConn net.PacketConn - RoutingTable *IATable -} - -func (dp *NetToRingDataplane) Run() error { - for { - pkt := respool.GetPacket() - // XXX(scrye): we don't release the reference on error conditions, and - // let the GC take care of this situation as they should be fairly - // rare. - - if err := pkt.DecodeFromConn(dp.UnderlayConn); err != nil { - log.Debug("error receiving next packet from underlay conn", "err", err) - continue - } - dst, err := getDst(pkt) - if err != nil { - log.Debug("unable to route packet", "err", err) - metrics.M.NetReadPkts( - metrics.IncomingPacket{Result: metrics.PacketResultRouteNotFound}, - ).Inc() - continue - } - metrics.M.NetReadPkts(metrics.IncomingPacket{Result: metrics.PacketResultOk}).Inc() - dst.Send(dp, pkt) - } -} - -func getDst(pkt *respool.Packet) (Destination, error) { - switch pkt.L4 { - case slayers.LayerTypeSCIONUDP: - return getDstUDP(pkt) - case slayers.LayerTypeSCMP: - return getDstSCMP(pkt) - default: - return nil, serrors.WithCtx(ErrUnsupportedL4, "type", pkt.L4) - } -} - -func getDstUDP(pkt *respool.Packet) (Destination, error) { - dst, err := pkt.SCION.DstAddr() - if err != nil { - return nil, err - } - switch dst.Type() { - case addr.HostTypeIP: - return UDPDestination{ - IA: pkt.SCION.DstIA, - Public: &net.UDPAddr{ - IP: dst.IP().AsSlice(), - Port: int(pkt.UDP.DstPort), - }, - }, nil - case addr.HostTypeSVC: - return SVCDestination{ - IA: pkt.SCION.DstIA, - Svc: dst.SVC(), - }, nil - default: - return nil, serrors.WithCtx(ErrUnsupportedDestination, "type", common.TypeOf(dst)) - } -} - -func getDstSCMP(pkt *respool.Packet) (Destination, error) { - if !pkt.SCMP.TypeCode.InfoMsg() { - dst, err := getDstSCMPErr(pkt) - if err != nil { - return nil, serrors.WrapStr("delivering SCMP error message", err) - } - return dst, nil - } - return getDstSCMPInfo(pkt) -} - -func getDstSCMPInfo(pkt *respool.Packet) (Destination, error) { - t := pkt.SCMP.TypeCode.Type() - if t == slayers.SCMPTypeEchoRequest || t == slayers.SCMPTypeTracerouteRequest { - return SCMPHandler{}, nil - } - if t == slayers.SCMPTypeEchoReply || t == slayers.SCMPTypeTracerouteReply { - id, err := extractSCMPIdentifier(&pkt.SCMP) - if err != nil { - return nil, err - } - return SCMPDestination{IA: pkt.SCION.DstIA, ID: id}, nil - } - return nil, serrors.New("unsupported SCMP info message", "type", t) -} - -func getDstSCMPErr(pkt *respool.Packet) (Destination, error) { - // Drop unknown SCMP error messages. - if pkt.SCMP.NextLayerType() == gopacket.LayerTypePayload { - return nil, serrors.New("unsupported SCMP error message", "type", pkt.SCMP.TypeCode.Type()) - } - l, err := decodeSCMP(&pkt.SCMP) - if err != nil { - return nil, err - } - if len(l) != 2 { - return nil, serrors.New("SCMP error message without payload") - } - gpkt := gopacket.NewPacket(*l[1].(*gopacket.Payload), slayers.LayerTypeSCION, - gopacket.DecodeOptions{ - NoCopy: true, - }, - ) - - // If the offending packet was UDP/SCION, use the source port to deliver. - if udp := gpkt.Layer(slayers.LayerTypeSCIONUDP); udp != nil { - port := int(udp.(*slayers.UDP).SrcPort) - // XXX(roosd): We assume that the zero value means the UDP header is - // truncated. This flags packets of misbehaving senders as truncated, if - // they set the source port to 0. But there is no harm, since those - // packets are destined to be dropped anyway. - if port == 0 { - return nil, serrors.New("SCMP error with truncated UDP header") - } - dst, err := pkt.SCION.DstAddr() - if err != nil { - return nil, err - } - if dst.Type() != addr.HostTypeIP { - return nil, serrors.WithCtx(ErrUnsupportedDestination, "type", dst.Type()) - } - return UDPDestination{ - IA: pkt.SCION.DstIA, - Public: &net.UDPAddr{ - IP: dst.IP().AsSlice(), - Port: port, - }, - }, nil - } - - // If the offending packet was SCMP/SCION, and it is an echo or traceroute, - // use the Identifier to deliver. In all other cases, the message is dropped. - if scmp := gpkt.Layer(slayers.LayerTypeSCMP); scmp != nil { - - tc := scmp.(*slayers.SCMP).TypeCode - // SCMP Error messages in response to an SCMP error message are not allowed. - if !tc.InfoMsg() { - return nil, serrors.New("SCMP error message in response to SCMP error message", - "type", tc.Type()) - } - // We only support echo and traceroute requests. - t := tc.Type() - if t != slayers.SCMPTypeEchoRequest && t != slayers.SCMPTypeTracerouteRequest { - return nil, serrors.New("unsupported SCMP info message", "type", t) - } - - var id uint16 - // Extract the ID from the echo or traceroute layer. - if echo := gpkt.Layer(slayers.LayerTypeSCMPEcho); echo != nil { - id = echo.(*slayers.SCMPEcho).Identifier - } else if tr := gpkt.Layer(slayers.LayerTypeSCMPTraceroute); tr != nil { - id = tr.(*slayers.SCMPTraceroute).Identifier - } else { - return nil, serrors.New("SCMP error with truncated payload") - } - return SCMPDestination{ - IA: pkt.SCION.DstIA, - ID: id, - }, nil - } - return nil, ErrUnsupportedL4 -} - -// UDPDestination delivers packets to the app that registered for the configured -// public address. -type UDPDestination struct { - IA addr.IA - Public *net.UDPAddr -} - -func (d UDPDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) { - routingEntry, ok := dp.RoutingTable.LookupPublic(d.IA, d.Public) - if !ok { - metrics.M.AppNotFoundErrors().Inc() - log.Debug("destination address not found", "isd_as", d.IA, "udp_addr", d.Public) - return - } - sendPacket(routingEntry, pkt) -} - -// SVCDestination delivers packets to apps that registered for the configured -// service. -type SVCDestination struct { - IA addr.IA - Svc addr.SVC -} - -func (d SVCDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) { - // FIXME(scrye): This should deliver to the correct IP address, based on - // information found in the underlay IP header. - routingEntries := dp.RoutingTable.LookupService(d.IA, d.Svc, nil) - if len(routingEntries) == 0 { - metrics.M.AppNotFoundErrors().Inc() - log.Debug("destination address not found", "isd_as", d.IA, "svc", d.Svc) - return - } - // Increase reference count for all extra copies - for i := 0; i < len(routingEntries)-1; i++ { - pkt.Dup() - } - for _, routingEntry := range routingEntries { - metrics.M.AppWriteSVCPkts(metrics.SVC{Type: d.Svc.String()}).Inc() - sendPacket(routingEntry, pkt) - } -} - -type SCMPDestination struct { - IA addr.IA - ID uint16 -} - -func (d SCMPDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) { - routingEntry, ok := dp.RoutingTable.LookupID(d.IA, uint64(d.ID)) - if !ok { - metrics.M.AppNotFoundErrors().Inc() - log.Debug("destination address not found", "SCMP", d.ID) - return - } - sendPacket(routingEntry, pkt) -} - -// SCMPHandler replies to SCMP echo and traceroute requests. -type SCMPHandler struct{} - -func (h SCMPHandler) Send(dp *NetToRingDataplane, pkt *respool.Packet) { - // FIXME(roosd): introduce metrics again. - raw, err := h.reverse(pkt) - if err != nil { - log.Info("Failed to reverse SCMP packet, dropping", "err", err) - return - } - _, err = dp.UnderlayConn.WriteTo(raw, pkt.UnderlayRemote) - if err != nil { - log.Info("Unable to write to underlay socket", "err", err) - return - } - pkt.Free() -} - -func (h SCMPHandler) reverse(pkt *respool.Packet) ([]byte, error) { - l, err := decodeSCMP(&pkt.SCMP) - if err != nil { - return nil, err - } - // Translate request to a reply. - switch l[0].LayerType() { - case slayers.LayerTypeSCMPEcho: - pkt.SCMP.TypeCode = slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0) - case slayers.LayerTypeSCMPTraceroute: - pkt.SCMP.TypeCode = slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0) - default: - return nil, serrors.New("unsupported SCMP informational message") - } - if err := h.reverseSCION(pkt); err != nil { - return nil, err - } - // XXX(roosd): This does not take HBH and E2E extensions into consideration. - // See: https://github.com/scionproto/scion/issues/4128 - pkt.SCION.NextHdr = slayers.L4SCMP - // FIXME(roosd): Consider moving this to a resource pool. - buf := gopacket.NewSerializeBuffer() - pkt.SCMP.SetNetworkLayerForChecksum(&pkt.SCION) - err = gopacket.SerializeLayers( - buf, - gopacket.SerializeOptions{ - ComputeChecksums: true, - FixLengths: true, - }, - append([]gopacket.SerializableLayer{&pkt.SCION, &pkt.SCMP}, l...)..., - ) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func (h SCMPHandler) reverseSCION(pkt *respool.Packet) error { - // Reverse the SCION packet. - pkt.SCION.DstIA, pkt.SCION.SrcIA = pkt.SCION.SrcIA, pkt.SCION.DstIA - src, err := pkt.SCION.SrcAddr() - if err != nil { - return serrors.WrapStr("parsing source address", err) - } - dst, err := pkt.SCION.DstAddr() - if err != nil { - return serrors.WrapStr("parsing destination address", err) - } - if err := pkt.SCION.SetSrcAddr(dst); err != nil { - return serrors.WrapStr("setting source address", err) - } - if err := pkt.SCION.SetDstAddr(src); err != nil { - return serrors.WrapStr("setting destination address", err) - } - if pkt.SCION.PathType == epic.PathType { - // Received packet with EPIC path type, hence extract the SCION path - epicPath, ok := pkt.SCION.Path.(*epic.Path) - if !ok { - return serrors.New("path type and path data do not match") - } - pkt.SCION.Path = epicPath.ScionPath - pkt.SCION.PathType = scion.PathType - } - if pkt.SCION.Path, err = pkt.SCION.Path.Reverse(); err != nil { - return serrors.WrapStr("reversing path", err) - } - return nil -} - -func extractSCMPIdentifier(scmp *slayers.SCMP) (uint16, error) { - l, err := decodeSCMP(scmp) - if err != nil { - return 0, err - } - switch info := l[0].(type) { - case *slayers.SCMPEcho: - return info.Identifier, nil - case *slayers.SCMPTraceroute: - return info.Identifier, nil - default: - return 0, serrors.New("invalid SCMP info message", "type_code", scmp.TypeCode) - } -} - -// decodeSCMP decodes the SCMP payload. WARNING: Decoding is done with NoCopy set. -func decodeSCMP(scmp *slayers.SCMP) ([]gopacket.SerializableLayer, error) { - gpkt := gopacket.NewPacket(scmp.Payload, scmp.NextLayerType(), - gopacket.DecodeOptions{NoCopy: true}) - layers := gpkt.Layers() - if len(layers) == 0 || len(layers) > 2 { - return nil, serrors.New("invalid number of SCMP layers", "count", len(layers)) - } - ret := make([]gopacket.SerializableLayer, len(layers)) - for i, l := range layers { - s, ok := l.(gopacket.SerializableLayer) - if !ok { - return nil, serrors.New("invalid SCMP layer, not serializable", "index", i) - } - ret[i] = s - } - return ret, nil -} - -type Destination interface { - // Send takes ownership of pkt, and then sends it to the location described - // by this destination. - Send(dp *NetToRingDataplane, pkt *respool.Packet) -} - -// sendPacket puts pkt on the routing entry's ring buffer, and releases the -// reference to pkt. -func sendPacket(routingEntry *TableEntry, pkt *respool.Packet) { - // Move packet reference to other goroutine. - count, _ := routingEntry.appIngressRing.Write(ringbuf.EntryList{pkt}, false) - if count <= 0 { - // Release buffer if we couldn't transmit it to the other goroutine. - pkt.Free() - } -} diff --git a/dispatcher/underlay_test.go b/dispatcher/underlay_test.go deleted file mode 100644 index e7e72a1ed5..0000000000 --- a/dispatcher/underlay_test.go +++ /dev/null @@ -1,957 +0,0 @@ -// Copyright 2019 ETH Zurich -// Copyright 2020 ETH Zurich, 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 dispatcher - -import ( - "bytes" - "net" - "testing" - - "github.com/golang/mock/gomock" - "github.com/google/gopacket" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/dispatcher/internal/respool" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/slayers" - "github.com/scionproto/scion/pkg/slayers/path" - "github.com/scionproto/scion/pkg/slayers/path/scion" -) - -func TestGetDst(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - testCases := map[string]struct { - Pkt func(t *testing.T) *respool.Packet - ExpectedDst Destination - ErrAssertion assert.ErrorAssertionFunc - }{ - "unsupported L4": { - Pkt: func(t *testing.T) *respool.Packet { - return &respool.Packet{ - L4: 1337, - } - }, - ErrAssertion: assert.Error, - }, - "UDP/SCION with IP destination is delivered by IP": { - Pkt: func(t *testing.T) *respool.Packet { - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - UDP: slayers.UDP{ - DstPort: 1337, - }, - L4: slayers.LayerTypeSCIONUDP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: UDPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - Public: &net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1337}, - }, - ErrAssertion: assert.NoError, - }, - "UDP/SCION with SVC destination is delivered by SVC": { - Pkt: func(t *testing.T) *respool.Packet { - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - UDP: slayers.UDP{ - DstPort: 1337, - }, - L4: slayers.LayerTypeSCIONUDP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.HostSVC(addr.SvcCS))) - return pkt - }, - ExpectedDst: SVCDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - Svc: addr.SvcCS, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION EchoRequest, is sent to SCMP handler": { - Pkt: func(t *testing.T) *respool.Packet { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 13, - }, - ) - require.NoError(t, err) - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - return pkt - }, - ExpectedDst: SCMPHandler{}, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION EchoReply, is sent to SCMP destination": { - Pkt: func(t *testing.T) *respool.Packet { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 13, - }, - ) - require.NoError(t, err) - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0), - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION TracerouteRequest, is sent to SCMP handler": { - Pkt: func(t *testing.T) *respool.Packet { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - }, - ) - require.NoError(t, err) - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0), - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - return pkt - }, - ExpectedDst: SCMPHandler{}, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION TracerouteReply, is sent to SCMP destination": { - Pkt: func(t *testing.T) *respool.Packet { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - }, - ) - require.NoError(t, err) - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0), - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with offending UDP/SCION is delivered by IP": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4UDP) - udp := &slayers.UDP{ - SrcPort: 1337, - DstPort: 42, - } - udp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - udp, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPExternalInterfaceDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - IfID: 141, - }, - gopacket.Payload(buf.Bytes()), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeExternalInterfaceDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: UDPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - Public: &net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1337}, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with offending SCMP/SCION EchoRequest is delivered by ID": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - scmp, - &slayers.SCMPEcho{Identifier: 42, SeqNumber: 16}, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPInternalConnectivityDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - Ingress: 131, - Egress: 141, - }, - gopacket.Payload(buf.Bytes()), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeInternalConnectivityDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with offending SCMP/SCION TracerouteRequest is delivered by ID": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - scmp, - &slayers.SCMPTraceroute{Identifier: 42}, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPInternalConnectivityDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - Ingress: 131, - Egress: 141, - }, - gopacket.Payload(buf.Bytes()), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeInternalConnectivityDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with truncated UDP/SCION payload is delivered by IP": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4UDP) - udp := &slayers.UDP{ - SrcPort: 1337, - DstPort: 42, - } - udp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - udp, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPExternalInterfaceDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - IfID: 141, - }, - gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-20]), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeExternalInterfaceDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: UDPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - Public: &net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1337}, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with offending truncated EchoRequest is delivered by ID": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - scmp, - &slayers.SCMPEcho{Identifier: 42, SeqNumber: 16}, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPInternalConnectivityDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - Ingress: 131, - Egress: 141, - }, - // Truncate the SCMP Echo data. - gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-20]), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeInternalConnectivityDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with offending truncated TracerouteRequest is delivered by ID": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - scmp, - &slayers.SCMPTraceroute{Identifier: 42}, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPInternalConnectivityDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - Ingress: 131, - Egress: 141, - }, - // Truncate the SCMP Traceroute data. - gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-20]), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeInternalConnectivityDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with partial UDP/SCION header is dropped": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4UDP) - udp := &slayers.UDP{ - SrcPort: 1337, - DstPort: 42, - } - udp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - udp, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPExternalInterfaceDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - IfID: 141, - }, - gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-21]), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeExternalInterfaceDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ErrAssertion: assert.Error, - }, - "SCMP/SCION Error with partial EchoRequest is dropped": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - scmp, - &slayers.SCMPEcho{Identifier: 42, SeqNumber: 16}, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPInternalConnectivityDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - Ingress: 131, - Egress: 141, - }, - // Only partially include the echo request information. - gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-21]), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeInternalConnectivityDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ErrAssertion: assert.Error, - }, - } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - destination, err := getDst(tc.Pkt(t)) - tc.ErrAssertion(t, err) - assert.Equal(t, tc.ExpectedDst, destination) - }) - } -} - -func TestSCMPHandlerReverse(t *testing.T) { - testCases := map[string]struct { - L4 func(t *testing.T) slayers.SCMP - ExpectedTypeCode slayers.SCMPTypeCode - ExpectedL4 func(t *testing.T) []gopacket.SerializableLayer - }{ - "echo without data": { - L4: func(t *testing.T) slayers.SCMP { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 12, - }, - ) - require.NoError(t, err) - return slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - Checksum: 1337, - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - } - }, - ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0), - ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 12, - }, - ) - require.NoError(t, err) - pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPEcho, - gopacket.DecodeOptions{}) - echo := pkt.Layer(slayers.LayerTypeSCMPEcho) - require.NotNil(t, echo) - return []gopacket.SerializableLayer{echo.(gopacket.SerializableLayer)} - }, - }, - "echo with data": { - L4: func(t *testing.T) slayers.SCMP { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 12, - }, - gopacket.Payload("I am the payload, please don't forget about me :)"), - ) - require.NoError(t, err) - return slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - Checksum: 1337, - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - } - }, - ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0), - ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer { - pld := gopacket.Payload("I am the payload, please don't forget about me :)") - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 12, - }, - pld, - ) - require.NoError(t, err) - pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPEcho, - gopacket.DecodeOptions{}) - echo := pkt.Layer(slayers.LayerTypeSCMPEcho) - require.NotNil(t, echo) - return []gopacket.SerializableLayer{echo.(gopacket.SerializableLayer), &pld} - }, - }, - "traceroute without data": { - L4: func(t *testing.T) slayers.SCMP { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - IA: xtest.MustParseIA("1-ff00:0:110"), - Interface: 12, - }, - ) - require.NoError(t, err) - return slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0), - Checksum: 1337, - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - } - }, - ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0), - ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - IA: xtest.MustParseIA("1-ff00:0:110"), - Interface: 12, - }, - ) - require.NoError(t, err) - pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPTraceroute, - gopacket.DecodeOptions{}) - tr := pkt.Layer(slayers.LayerTypeSCMPTraceroute) - require.NotNil(t, tr) - return []gopacket.SerializableLayer{tr.(gopacket.SerializableLayer)} - }, - }, - "traceroute with data": { - L4: func(t *testing.T) slayers.SCMP { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - IA: xtest.MustParseIA("1-ff00:0:110"), - Interface: 12, - }, - gopacket.Payload("I am the payload, please don't forget about me :)"), - ) - require.NoError(t, err) - return slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0), - Checksum: 1337, - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - } - }, - ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0), - ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer { - pld := gopacket.Payload("I am the payload, please don't forget about me :)") - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - IA: xtest.MustParseIA("1-ff00:0:110"), - Interface: 12, - }, - pld, - ) - require.NoError(t, err) - pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPTraceroute, - gopacket.DecodeOptions{}) - tr := pkt.Layer(slayers.LayerTypeSCMPTraceroute) - require.NotNil(t, tr) - return []gopacket.SerializableLayer{tr.(gopacket.SerializableLayer), &pld} - }, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - // Prepare original packet - pkt := &respool.Packet{ - SCION: slayers.SCION{ - Version: 0, - TrafficClass: 0xb8, - FlowID: 0xdead, - NextHdr: slayers.L4SCMP, - PathType: scion.PathType, - SrcIA: xtest.MustParseIA("1-ff00:0:110"), - DstIA: xtest.MustParseIA("1-ff00:0:112"), - Path: &scion.Decoded{ - Base: scion.Base{ - PathMeta: scion.MetaHdr{ - CurrHF: 2, - SegLen: [3]uint8{3, 0, 0}, - }, - NumINF: 1, - NumHops: 3, - }, - InfoFields: []path.InfoField{ - {SegID: 0x111, ConsDir: true, Timestamp: 0x100}, - }, - HopFields: []path.HopField{ - {ConsIngress: 0, ConsEgress: 311, - Mac: [path.MacLen]byte{0, 0, 0, 0, 0, 0}}, - {ConsIngress: 131, ConsEgress: 141, - Mac: [path.MacLen]byte{1, 1, 1, 1, 1, 1}}, - {ConsIngress: 411, ConsEgress: 0, - Mac: [path.MacLen]byte{2, 2, 2, 2, 2, 2}}, - }, - }, - }, - SCMP: tc.L4(t), - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetSrcAddr(addr.MustParseHost("127.0.0.1"))) - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("127.0.0.2"))) - - // Reverse packet - raw, err := SCMPHandler{}.reverse(pkt) - require.NoError(t, err) - - gpkt := gopacket.NewPacket(raw, slayers.LayerTypeSCION, gopacket.DecodeOptions{}) - - t.Run("check SCION header", func(t *testing.T) { - scionL := gpkt.Layer(slayers.LayerTypeSCION).(*slayers.SCION) - expected := &slayers.SCION{ - Version: 0, - TrafficClass: 0xb8, - FlowID: 0xdead, - HdrLen: 21, - NextHdr: slayers.L4SCMP, - PayloadLen: uint16(4 + len(pkt.SCMP.Payload)), - PathType: scion.PathType, - SrcIA: xtest.MustParseIA("1-ff00:0:112"), - DstIA: xtest.MustParseIA("1-ff00:0:110"), - Path: &scion.Decoded{ - Base: scion.Base{ - PathMeta: scion.MetaHdr{ - CurrHF: 0, - SegLen: [3]uint8{3, 0, 0}, - }, - NumINF: 1, - NumHops: 3, - }, - InfoFields: []path.InfoField{ - {SegID: 0x111, ConsDir: false, Timestamp: 0x100}, - }, - HopFields: []path.HopField{ - {ConsIngress: 411, ConsEgress: 0, - Mac: [path.MacLen]byte{2, 2, 2, 2, 2, 2}}, - {ConsIngress: 131, ConsEgress: 141, - Mac: [path.MacLen]byte{1, 1, 1, 1, 1, 1}}, - {ConsIngress: 0, ConsEgress: 311, - Mac: [path.MacLen]byte{0, 0, 0, 0, 0, 0}}, - }, - }, - } - require.NoError(t, expected.SetSrcAddr(addr.MustParseHost("127.0.0.2"))) - require.NoError(t, expected.SetDstAddr(addr.MustParseHost("127.0.0.1"))) - - scionL.BaseLayer = slayers.BaseLayer{} - var decodedPath scion.Decoded - require.NoError(t, decodedPath.DecodeFromBytes(scionL.Path.(*scion.Raw).Raw)) - scionL.Path = &decodedPath - - assert.Equal(t, expected, scionL) - }) - t.Run("check L4", func(t *testing.T) { - scmp := gpkt.Layer(slayers.LayerTypeSCMP) - require.NotNil(t, scmp) - assert.Equal(t, tc.ExpectedTypeCode, scmp.(*slayers.SCMP).TypeCode) - assert.NotZero(t, scmp.(*slayers.SCMP).Checksum) - - for _, l := range tc.ExpectedL4(t) { - assert.Equal(t, l, gpkt.Layer(l.LayerType()), l.LayerType().String()) - } - }) - }) - } -} - -func newSCIONHdr(t *testing.T, l4 slayers.L4ProtocolType) *slayers.SCION { - scion := &slayers.SCION{ - NextHdr: l4, - PathType: scion.PathType, - SrcIA: xtest.MustParseIA("1-ff00:0:110"), - DstIA: xtest.MustParseIA("1-ff00:0:112"), - Path: &scion.Decoded{ - Base: scion.Base{ - PathMeta: scion.MetaHdr{ - CurrHF: 2, - SegLen: [3]uint8{3, 0, 0}, - }, - NumINF: 1, - NumHops: 3, - }, - InfoFields: []path.InfoField{ - {SegID: 0x111, ConsDir: true, Timestamp: 0x100}, - }, - HopFields: []path.HopField{ - {ConsIngress: 0, ConsEgress: 311}, - {ConsIngress: 131, ConsEgress: 141}, - {ConsIngress: 411, ConsEgress: 0}, - }, - }, - } - require.NoError(t, scion.SetSrcAddr(addr.MustParseHost("192.168.0.1"))) - require.NoError(t, scion.SetDstAddr(addr.MustParseHost("192.168.0.2"))) - return scion -} diff --git a/dist/conffiles/daemon.toml b/dist/conffiles/daemon.toml index 3abf018ab7..2401c8f1ca 100644 --- a/dist/conffiles/daemon.toml +++ b/dist/conffiles/daemon.toml @@ -1,7 +1,6 @@ [general] id = "sd" config_dir = "/etc/scion" -reconnect_to_dispatcher = true [path_db] connection = "/var/lib/scion/sd.path.db" diff --git a/dist/conffiles/dispatcher.toml b/dist/conffiles/dispatcher.toml index d6f83a1ddc..1394e70d99 100644 --- a/dist/conffiles/dispatcher.toml +++ b/dist/conffiles/dispatcher.toml @@ -1,10 +1,14 @@ [dispatcher] id = "dispatcher" -socket_file_mode = "0777" [log.console] level = "info" +# If infra SVCs need to run with old br +# [dispatcher.service_addresses] +# "1-ff00:0:110,CS" = "172.20.0.20:30252" +# "1-ff00:0:110,DS" = "172.20.0.20:30252" + # Optionally expose metrics and other local inspection endpoints. # [metrics] # prometheus = "[127.0.0.1]:30441" diff --git a/dist/openwrt/test_configs/control.toml b/dist/openwrt/test_configs/control.toml index 2b28fcb246..abb10a893f 100644 --- a/dist/openwrt/test_configs/control.toml +++ b/dist/openwrt/test_configs/control.toml @@ -1,7 +1,6 @@ [general] id = "cs-1" config_dir = "/etc/scion" -reconnect_to_dispatcher = true [trust_db] connection = "/var/lib/scion/cs-1.trust.db" diff --git a/dist/openwrt/test_configs/topology.json b/dist/openwrt/test_configs/topology.json index d141753877..3f68320221 100644 --- a/dist/openwrt/test_configs/topology.json +++ b/dist/openwrt/test_configs/topology.json @@ -3,6 +3,7 @@ "core" ], "isd_as": "1-ff00:0:a", + "dispatched_ports": "1024-65535", "mtu": 1472, "control_service": { "cs-1": { diff --git a/dist/systemd/scion-dispatcher.service b/dist/systemd/scion-dispatcher.service index 8621330f70..a5ad58aac7 100644 --- a/dist/systemd/scion-dispatcher.service +++ b/dist/systemd/scion-dispatcher.service @@ -9,7 +9,6 @@ StartLimitInterval=1s Type=simple User=scion Group=scion -ExecStartPre=/bin/rm -rf /run/shm/dispatcher/ ExecStart=/usr/bin/scion-dispatcher --config /etc/scion/dispatcher.toml LimitNOFILE=4096 Restart=on-failure diff --git a/dist/test/deb_test.sh b/dist/test/deb_test.sh index 2ae6179774..295c85097c 100755 --- a/dist/test/deb_test.sh +++ b/dist/test/deb_test.sh @@ -61,6 +61,7 @@ INNER_EOF { "isd_as": "1-ff00:0:a", "mtu": 1472, + "dispatched_ports": "1024-65535", "border_routers": { "br-1": { "internal_addr": "127.0.0.1:30001" diff --git a/gateway/BUILD.bazel b/gateway/BUILD.bazel index 902fb0db7b..b258a55983 100644 --- a/gateway/BUILD.bazel +++ b/gateway/BUILD.bazel @@ -31,8 +31,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/metrics:go_default_library", "//pkg/snet/squic:go_default_library", - "//pkg/sock/reliable:go_default_library", - "//pkg/sock/reliable/reconnect:go_default_library", "//private/app/appnet:go_default_library", "//private/periodic:go_default_library", "//private/service:go_default_library", diff --git a/gateway/cmd/gateway/BUILD.bazel b/gateway/cmd/gateway/BUILD.bazel index 565138a633..07fae88a63 100644 --- a/gateway/cmd/gateway/BUILD.bazel +++ b/gateway/cmd/gateway/BUILD.bazel @@ -15,7 +15,6 @@ go_library( "//pkg/log:go_default_library", "//pkg/private/serrors:go_default_library", "//pkg/snet/addrutil:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/app:go_default_library", "//private/app/launcher:go_default_library", "//private/service:go_default_library", diff --git a/gateway/cmd/gateway/main.go b/gateway/cmd/gateway/main.go index 8796b1863f..ca64ebc203 100644 --- a/gateway/cmd/gateway/main.go +++ b/gateway/cmd/gateway/main.go @@ -34,7 +34,6 @@ import ( "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet/addrutil" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app" "github.com/scionproto/scion/private/app/launcher" "github.com/scionproto/scion/private/service" @@ -145,7 +144,6 @@ func realMain(ctx context.Context) error { ProbeClientIP: controlAddress.IP, DataServerAddr: dataAddress, DataClientIP: dataAddress.IP, - Dispatcher: reliable.NewDispatcher(""), Daemon: daemon, RouteSourceIPv4: globalCfg.Tunnel.SrcIPv4, RouteSourceIPv6: globalCfg.Tunnel.SrcIPv6, diff --git a/gateway/control/grpc/BUILD.bazel b/gateway/control/grpc/BUILD.bazel index b020d0a8a2..8e57b2c6a5 100644 --- a/gateway/control/grpc/BUILD.bazel +++ b/gateway/control/grpc/BUILD.bazel @@ -21,7 +21,6 @@ go_library( "//pkg/proto/discovery:go_default_library", "//pkg/proto/gateway:go_default_library", "//pkg/snet:go_default_library", - "//pkg/sock/reliable:go_default_library", "@org_golang_google_grpc//codes:go_default_library", "@org_golang_google_grpc//peer:go_default_library", "@org_golang_google_grpc//status:go_default_library", diff --git a/gateway/control/grpc/probeserver.go b/gateway/control/grpc/probeserver.go index 4af7500f96..8d58dc6a6f 100644 --- a/gateway/control/grpc/probeserver.go +++ b/gateway/control/grpc/probeserver.go @@ -24,7 +24,6 @@ import ( "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" gpb "github.com/scionproto/scion/pkg/proto/gateway" - "github.com/scionproto/scion/pkg/sock/reliable" ) // ProbeDispatcher handles incoming gateway protocol messages. @@ -46,9 +45,6 @@ func (d *ProbeDispatcher) Listen(ctx context.Context, conn net.PacketConn) error default: n, addr, err := conn.ReadFrom(buf) if err != nil { - if reliable.IsDispatcherError(err) { - return err - } logger.Info("ProbeDispatcher: Error reading from connection", "err", err) // FIXME(shitz): Continuing here is only a temporary solution. Different // errors need to be handled different, for some it should break and others diff --git a/gateway/dataplane/BUILD.bazel b/gateway/dataplane/BUILD.bazel index d6049a4214..a6e554f9e7 100644 --- a/gateway/dataplane/BUILD.bazel +++ b/gateway/dataplane/BUILD.bazel @@ -30,7 +30,6 @@ go_library( "//pkg/slayers:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/ringbuf:go_default_library", "@com_github_google_gopacket//:go_default_library", "@com_github_google_gopacket//layers:go_default_library", diff --git a/gateway/dataplane/ingressserver.go b/gateway/dataplane/ingressserver.go index 3f94b548fa..cdc11c92e5 100644 --- a/gateway/dataplane/ingressserver.go +++ b/gateway/dataplane/ingressserver.go @@ -26,7 +26,6 @@ import ( "github.com/scionproto/scion/pkg/metrics" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/ringbuf" ) @@ -90,9 +89,6 @@ func (d *IngressServer) read(ctx context.Context) error { read, src, err := d.Conn.ReadFrom(frame.raw) if err != nil { logger.Error("IngressServer: Unable to read from external ingress", "err", err) - if reliable.IsDispatcherError(err) { - return serrors.WrapStr("problems speaking to dispatcher", err) - } increaseCounterMetric(d.Metrics.ReceiveExternalError, 1) frame.Release() continue diff --git a/gateway/gateway.go b/gateway/gateway.go index 7682e41a47..31d1448dfe 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -46,8 +46,6 @@ import ( gatewaypb "github.com/scionproto/scion/pkg/proto/gateway" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/squic" - "github.com/scionproto/scion/pkg/sock/reliable" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" infraenv "github.com/scionproto/scion/private/app/appnet" "github.com/scionproto/scion/private/periodic" "github.com/scionproto/scion/private/service" @@ -101,34 +99,13 @@ type PacketConnFactory struct { func (pcf PacketConnFactory) New() (net.PacketConn, error) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() - conn, err := pcf.Network.Listen(ctx, "udp", pcf.Addr, addr.SvcNone) + conn, err := pcf.Network.Listen(ctx, "udp", pcf.Addr) if err != nil { return nil, serrors.WrapStr("creating packet conn", err) } return conn, nil } -type ProbeConnFactory struct { - Dispatcher *reconnect.DispatcherService - LocalIA addr.IA - LocalIP netip.Addr -} - -func (f ProbeConnFactory) New(ctx context.Context) (net.PacketConn, error) { - pathMonitorConnection, pathMonitorPort, err := f.Dispatcher.Register( - context.Background(), - f.LocalIA, - &net.UDPAddr{IP: f.LocalIP.AsSlice()}, - addr.SvcNone, - ) - if err != nil { - return nil, serrors.WrapStr("unable to open control socket", err) - } - log.FromCtx(ctx).Debug("Path monitor connection opened on Raw UDP/SCION", - "local_ip", f.LocalIP, "local_port", pathMonitorPort) - return pathMonitorConnection, nil -} - type RoutingTableFactory struct { RoutePublisherFactory control.PublisherFactory } @@ -191,9 +168,6 @@ type Gateway struct { // DataIP is the IP that should be used for dataplane traffic. DataAddr *net.UDPAddr - // Dispatcher is the API of the SCION Dispatcher on the local host. - Dispatcher reliable.Dispatcher - // Daemon is the API of the SCION Daemon. Daemon daemon.Connector @@ -263,17 +237,15 @@ func (g *Gateway) Run(ctx context.Context) error { routePublisherFactory := createRouteManager(ctx, deviceManager) - // ************************************************************************* - // Initialize base SCION network information: IA + Dispatcher connectivity - // ************************************************************************* + // ********************************************* + // Initialize base SCION network information: IA + // ********************************************* localIA, err := g.Daemon.LocalIA(context.Background()) if err != nil { return serrors.WrapStr("unable to learn local ISD-AS number", err) } logger.Info("Learned local IA from SCION Daemon", "ia", localIA) - reconnectingDispatcher := reconnect.NewDispatcherService(g.Dispatcher) - // ************************************************************************* // Set up path monitoring. The path monitor runs an the SCION/UDP stack // using the control address and uses traceroute packets to check if paths @@ -318,20 +290,16 @@ func (g *Gateway) Run(ctx context.Context) error { RemoteWatcherFactory: &pathhealth.DefaultRemoteWatcherFactory{ Router: pathRouter, PathWatcherFactory: &pathhealth.DefaultPathWatcherFactory{ - LocalIA: localIA, - LocalIP: g.PathMonitorIP, - RevocationHandler: revocationHandler, - ConnFactory: ProbeConnFactory{ - Dispatcher: reconnectingDispatcher, - LocalIA: localIA, - LocalIP: g.PathMonitorIP, - }, + LocalIA: localIA, + LocalIP: g.PathMonitorIP, + RevocationHandler: revocationHandler, ProbeInterval: 0, // using default for now ProbesSent: probesSent, ProbesReceived: probesReceived, ProbesSendErrors: probesSendErrors, SCMPErrors: g.Metrics.SCMPErrors, SCIONPacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Topology: g.Daemon, }, PathUpdateInterval: PathUpdateInterval(ctx), PathFetchTimeout: 0, // using default for now @@ -441,22 +409,18 @@ func (g *Gateway) Run(ctx context.Context) error { // scionNetworkNoSCMP is the network for the QUIC server connection. Because SCMP errors // will cause the server's accepts to fail, we ignore SCMP. scionNetworkNoSCMP := &snet.SCIONNetwork{ - LocalIA: localIA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - // Enable transparent reconnections to the dispatcher - Dispatcher: reconnectingDispatcher, - // Discard all SCMP propagation, to avoid accept/read errors on the - // QUIC server/client. - SCMPHandler: snet.SCMPPropagationStopper{ - Handler: snet.DefaultSCMPHandler{ - RevocationHandler: revocationHandler, - SCMPErrors: g.Metrics.SCMPErrors, - }, - Log: log.FromCtx(ctx).Debug, + Topology: g.Daemon, + // Discard all SCMP propagation, to avoid accept/read errors on the + // QUIC server/client. + SCMPHandler: snet.SCMPPropagationStopper{ + Handler: snet.DefaultSCMPHandler{ + RevocationHandler: revocationHandler, + SCMPErrors: g.Metrics.SCMPErrors, }, - SCIONPacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Log: log.FromCtx(ctx).Debug, }, - Metrics: g.Metrics.SCIONNetworkMetrics, + PacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Metrics: g.Metrics.SCIONNetworkMetrics, } // Initialize the UDP/SCION QUIC conn for outgoing Gateway Discovery RPCs and outgoing Prefix @@ -465,7 +429,6 @@ func (g *Gateway) Run(ctx context.Context) error { context.TODO(), "udp", &net.UDPAddr{IP: g.ControlClientIP}, - addr.SvcNone, ) if err != nil { return serrors.WrapStr("unable to initialize client QUIC connection", err) @@ -509,18 +472,13 @@ func (g *Gateway) Run(ctx context.Context) error { // scionNetwork is the network for all SCION connections, with the exception of the QUIC server // and client connection. scionNetwork := &snet.SCIONNetwork{ - LocalIA: localIA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - // Enable transparent reconnections to the dispatcher - Dispatcher: reconnectingDispatcher, - // Forward revocations to Daemon - SCMPHandler: snet.DefaultSCMPHandler{ - RevocationHandler: revocationHandler, - SCMPErrors: g.Metrics.SCMPErrors, - }, - SCIONPacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Topology: g.Daemon, + SCMPHandler: snet.DefaultSCMPHandler{ + RevocationHandler: revocationHandler, + SCMPErrors: g.Metrics.SCMPErrors, }, - Metrics: g.Metrics.SCIONNetworkMetrics, + PacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Metrics: g.Metrics.SCIONNetworkMetrics, } remoteMonitor := &control.RemoteMonitor{ IAs: remoteIAsChannel, @@ -544,8 +502,8 @@ func (g *Gateway) Run(ctx context.Context) error { Resolver: &svc.Resolver{ LocalIA: localIA, // Reuse the network with SCMP error support. - ConnFactory: scionNetwork.Dispatcher, - LocalIP: g.ServiceDiscoveryClientIP, + Network: scionNetwork, + LocalIP: g.ServiceDiscoveryClientIP, }, SVCResolutionFraction: 1.337, }, @@ -565,7 +523,6 @@ func (g *Gateway) Run(ctx context.Context) error { context.TODO(), "udp", g.ControlServerAddr, - addr.SvcNone, ) if err != nil { return serrors.WrapStr("unable to initialize server QUIC connection", err) @@ -613,7 +570,7 @@ func (g *Gateway) Run(ctx context.Context) error { // received from the session monitors of the remote gateway. // ********************************************************************************* - probeConn, err := scionNetwork.Listen(context.TODO(), "udp", g.ProbeServerAddr, addr.SvcNone) + probeConn, err := scionNetwork.Listen(context.TODO(), "udp", g.ProbeServerAddr) if err != nil { return serrors.WrapStr("creating server probe conn", err) } @@ -811,7 +768,6 @@ func StartIngress(ctx context.Context, scionNetwork *snet.SCIONNetwork, dataAddr context.TODO(), "udp", dataAddr, - addr.SvcNone, ) if err != nil { return serrors.WrapStr("creating ingress conn", err) diff --git a/gateway/pathhealth/pathwatcher.go b/gateway/pathhealth/pathwatcher.go index 99f13d175d..ac8befb025 100644 --- a/gateway/pathhealth/pathwatcher.go +++ b/gateway/pathhealth/pathwatcher.go @@ -16,9 +16,7 @@ package pathhealth import ( "context" - "errors" "fmt" - "io" "net" "net/netip" "sync" @@ -39,22 +37,17 @@ const ( defaultProbeInterval = 500 * time.Millisecond ) -// ProbeConnFactory is used to construct net.PacketConn objects for sending and -// receiving probes. -type ProbeConnFactory interface { - New(context.Context) (net.PacketConn, error) -} - // DefaultPathWatcherFactory creates PathWatchers. type DefaultPathWatcherFactory struct { // LocalIA is the ID of the local AS. LocalIA addr.IA + // Topology is the helper class to get control-plane information for the + // local AS. + Topology snet.Topology // LocalIP is the IP address of the local host. LocalIP netip.Addr // RevocationHandler is the revocation handler. RevocationHandler snet.RevocationHandler - // ConnFactory is used to create probe connections. - ConnFactory ProbeConnFactory // Probeinterval defines the interval at which probes are sent. If it is not // set a default is used. ProbeInterval time.Duration @@ -66,8 +59,7 @@ type DefaultPathWatcherFactory struct { ProbesReceived func(remote addr.IA) metrics.Counter // ProbesSendErrors keeps track of how many time sending probes failed per // remote. - ProbesSendErrors func(remote addr.IA) metrics.Counter - + ProbesSendErrors func(remote addr.IA) metrics.Counter SCMPErrors metrics.Counter SCIONPacketConnMetrics snet.SCIONPacketConnMetrics } @@ -77,13 +69,8 @@ func (f *DefaultPathWatcherFactory) New( ctx context.Context, remote addr.IA, path snet.Path, - id uint16, ) (PathWatcher, error) { - nc, err := f.ConnFactory.New(ctx) - if err != nil { - return nil, serrors.WrapStr("creating connection for probing", err) - } pktChan := make(chan traceroutePkt, 10) createCounter := func( create func(addr.IA) metrics.Counter, remote addr.IA, @@ -93,21 +80,25 @@ func (f *DefaultPathWatcherFactory) New( } return create(remote) } + conn, err := (&snet.SCIONNetwork{ + SCMPHandler: scmpHandler{ + wrappedHandler: snet.DefaultSCMPHandler{ + RevocationHandler: f.RevocationHandler, + SCMPErrors: f.SCMPErrors, + }, + pkts: pktChan, + }, + PacketConnMetrics: f.SCIONPacketConnMetrics, + Topology: f.Topology, + }).OpenRaw(ctx, &net.UDPAddr{IP: f.LocalIP.AsSlice()}) + if err != nil { + return nil, serrors.WrapStr("creating connection for probing", err) + } return &pathWatcher{ remote: remote, probeInterval: f.ProbeInterval, - conn: &snet.SCIONPacketConn{ - Conn: nc, - SCMPHandler: scmpHandler{ - wrappedHandler: snet.DefaultSCMPHandler{ - RevocationHandler: f.RevocationHandler, - SCMPErrors: f.SCMPErrors, - }, - pkts: pktChan, - }, - Metrics: f.SCIONPacketConnMetrics, - }, - id: id, + conn: conn, + id: uint16(conn.LocalAddr().(*net.UDPAddr).Port), localAddr: snet.SCIONAddress{ IA: f.LocalIA, Host: addr.HostIP(f.LocalIP), @@ -245,11 +236,6 @@ func (w *pathWatcher) drainConn(ctx context.Context) { if ctx.Err() != nil { return } - if errors.Is(err, io.EOF) { - // dispatcher is currently down so back off. - time.Sleep(500 * time.Millisecond) - continue - } if err != nil { if _, ok := err.(*snet.OpError); ok { // ignore SCMP errors they are already dealt with in the SCMP diff --git a/gateway/pathhealth/remotewatcher.go b/gateway/pathhealth/remotewatcher.go index 7ee9baeb51..fea1a5fa3f 100644 --- a/gateway/pathhealth/remotewatcher.go +++ b/gateway/pathhealth/remotewatcher.go @@ -50,7 +50,7 @@ type PathWatcher interface { // PathWatcherFactory constructs a PathWatcher. type PathWatcherFactory interface { - New(ctx context.Context, remote addr.IA, path snet.Path, id uint16) (PathWatcher, error) + New(ctx context.Context, remote addr.IA, path snet.Path) (PathWatcher, error) } // DefaultRemoteWatcherFactory is a default factory for creating RemoteWatchers. @@ -84,7 +84,6 @@ func (f *DefaultRemoteWatcherFactory) New(remote addr.IA) RemoteWatcher { router: f.Router, pathWatcherFactory: f.PathWatcherFactory, pathWatchers: make(map[snet.PathFingerprint]*pathWatcherItem), - pathWatchersByID: make(map[uint16]*pathWatcherItem), // Set this to true so that first failure to get paths is logged. hasPaths: true, pathUpdateInterval: f.PathUpdateInterval, @@ -107,8 +106,6 @@ type remoteWatcher struct { // pathWatchers is a map of all the paths being currently monitored, indexed by path // fingerprint. pathWatchers map[snet.PathFingerprint]*pathWatcherItem - // pathWatchersByID contains the same paths as above, but indexed by SCMP Traceroute ID. - pathWatchersByID map[uint16]*pathWatcherItem // hasPaths is true if, at the moment, there is at least one path known. hasPaths bool @@ -162,7 +159,6 @@ func (w *remoteWatcher) cleanup(ctx context.Context) { } pm.cancel() delete(w.pathWatchers, fingerprint) - delete(w.pathWatchersByID, pm.id) } metrics.GaugeSet(w.pathsMonitored, float64(len(w.pathWatchers))) } @@ -221,14 +217,9 @@ func (w *remoteWatcher) updatePaths(ctx context.Context) { for fingerprint, path := range pathmap { pw, ok := w.pathWatchers[fingerprint] if !ok { - id, found := w.selectID() - if !found { - logger.Info("All traceroute IDs are occupied") - continue - } - pathW, err := w.pathWatcherFactory.New(ctx, w.remote, path, id) + pathW, err := w.pathWatcherFactory.New(ctx, w.remote, path) if err != nil { - logger.Error("Failed to create path watcher", "path", fmt.Sprint(path)) + logger.Error("Failed to create path watcher", "path", fmt.Sprint(path), "err", err) continue } pathWCtx, cancel := context.WithCancel(ctx) @@ -240,11 +231,9 @@ func (w *remoteWatcher) updatePaths(ctx context.Context) { // This is a new path, add an entry. pw = &pathWatcherItem{ pathWatcher: pathW, - id: id, cancel: cancel, } w.pathWatchers[fingerprint] = pw - w.pathWatchersByID[id] = pw } else { // If the path already exists, update it. Needed to keep expirations fresh. pw.pathWatcher.UpdatePath(path) @@ -254,21 +243,9 @@ func (w *remoteWatcher) updatePaths(ctx context.Context) { metrics.GaugeSet(w.pathsMonitored, float64(len(w.pathWatchers))) } -func (w *remoteWatcher) selectID() (uint16, bool) { - for i := 0; i < 100; i++ { - id := snet.RandomSCMPIdentifer() - if _, ok := w.pathWatchersByID[id]; !ok { - return id, true - } - } - return 0, false -} - // pathWatcherItem is an wrapper type that adds RemoteWatcher-specific data to pathWatcher. type pathWatcherItem struct { pathWatcher PathWatcher - // id is the traceroute ID for this pathWatcher - id uint16 // lastUsed is the time when the path ceased to be used. // If the path is used right now, set to time.Time{}. // Paths that are not used will be removed after a certain period of time. diff --git a/nogo.json b/nogo.json index f5a358ddae..d4a69c7f3d 100644 --- a/nogo.json +++ b/nogo.json @@ -46,7 +46,8 @@ "/in_gopkg_yaml_v2/decode.go": "https://github.com/go-yaml/yaml/pull/492", "/com_github_mattn_go_sqlite3": "", "/com_github_cloudflare_sidh": "", - "/com_github_vishvananda_netlink": "" + "/com_github_vishvananda_netlink": "", + "/com_github_spf13_pflag": "" } }, "printf": { @@ -98,7 +99,12 @@ }, "exclude_files": { "/com_github_mattn_go_sqlite3/": "", - "/com_github_google_gopacket/afpacket/": "" + "/com_github_google_gopacket/afpacket/": "", + "/org_modernc_memory/": "", + "/org_golang_x_sys/": "", + "org_modernc_libc": "", + "org_modernc_sqlite": "", + "com_github_mailru_easyjson": "" } }, "shift": { diff --git a/pkg/daemon/BUILD.bazel b/pkg/daemon/BUILD.bazel index 1d0f0d9fdc..8a8ca23be8 100644 --- a/pkg/daemon/BUILD.bazel +++ b/pkg/daemon/BUILD.bazel @@ -27,6 +27,7 @@ go_library( "//pkg/snet/path:go_default_library", "//private/topology:go_default_library", "@org_golang_google_grpc//:go_default_library", + "@org_golang_google_protobuf//types/known/emptypb:go_default_library", "@org_golang_google_protobuf//types/known/timestamppb:go_default_library", ], ) diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 24d0b6b9f8..a9ba3397ee 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -18,13 +18,12 @@ package daemon import ( "context" - "net" + "net/netip" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon/internal/metrics" "github.com/scionproto/scion/pkg/drkey" libmetrics "github.com/scionproto/scion/pkg/metrics" - "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/ctrl/path_mgmt" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" @@ -63,16 +62,18 @@ func NewService(name string) Service { // either an error occurs, or the method successfully returns. type Connector interface { // LocalIA requests from the daemon the local ISD-AS number. + // TODO: Caching this value to avoid contacting the daemon, since this never changes. LocalIA(ctx context.Context) (addr.IA, error) + // PortRange returns the beginning and the end of the SCION/UDP endhost port range, configured + // for the local IA. + PortRange(ctx context.Context) (uint16, uint16, error) + // Interfaces returns the map of interface identifiers to the underlay internal address. + Interfaces(ctx context.Context) (map[uint16]netip.AddrPort, error) // Paths requests from the daemon a set of end to end paths between the source and destination. Paths(ctx context.Context, dst, src addr.IA, f PathReqFlags) ([]snet.Path, error) // ASInfo requests from the daemon information about AS ia, the zero IA can be // use to detect the local IA. ASInfo(ctx context.Context, ia addr.IA) (ASInfo, error) - // IFInfo requests from SCION Daemon addresses and ports of interfaces. Slice - // ifs contains interface IDs of BRs. If empty, a fresh (i.e., uncached) - // answer containing all interfaces is returned. - IFInfo(ctx context.Context, ifs []common.IFIDType) (map[common.IFIDType]*net.UDPAddr, error) // SVCInfo requests from the daemon information about addresses and ports of // infrastructure services. Slice svcTypes contains a list of desired // service types. If unset, a fresh (i.e., uncached) answer containing all diff --git a/pkg/daemon/grpc.go b/pkg/daemon/grpc.go index 26ae049d7b..4cbec6036c 100644 --- a/pkg/daemon/grpc.go +++ b/pkg/daemon/grpc.go @@ -17,9 +17,11 @@ package daemon import ( "context" "net" + "net/netip" "time" "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" "github.com/scionproto/scion/pkg/addr" @@ -74,6 +76,35 @@ func (c grpcConn) LocalIA(ctx context.Context) (addr.IA, error) { return ia, nil } +func (c grpcConn) PortRange(ctx context.Context) (uint16, uint16, error) { + client := sdpb.NewDaemonServiceClient(c.conn) + response, err := client.PortRange(ctx, &emptypb.Empty{}) + if err != nil { + return 0, 0, err + } + return uint16(response.DispatchedPortStart), uint16(response.DispatchedPortEnd), nil +} + +func (c grpcConn) Interfaces(ctx context.Context) (map[uint16]netip.AddrPort, error) { + client := sdpb.NewDaemonServiceClient(c.conn) + response, err := client.Interfaces(ctx, &sdpb.InterfacesRequest{}) + if err != nil { + c.metrics.incInterface(err) + return nil, err + } + result := make(map[uint16]netip.AddrPort, len(response.Interfaces)) + for ifID, intf := range response.Interfaces { + a, err := netip.ParseAddrPort(intf.Address.Address) + if err != nil { + c.metrics.incInterface(err) + return nil, serrors.WrapStr("parsing reply", err, "raw_uri", intf.Address.Address) + } + result[uint16(ifID)] = a + } + c.metrics.incInterface(nil) + return result, nil +} + func (c grpcConn) Paths(ctx context.Context, dst, src addr.IA, f PathReqFlags) ([]snet.Path, error) { @@ -107,28 +138,6 @@ func (c grpcConn) ASInfo(ctx context.Context, ia addr.IA) (ASInfo, error) { }, nil } -func (c grpcConn) IFInfo(ctx context.Context, - _ []common.IFIDType) (map[common.IFIDType]*net.UDPAddr, error) { - - client := sdpb.NewDaemonServiceClient(c.conn) - response, err := client.Interfaces(ctx, &sdpb.InterfacesRequest{}) - if err != nil { - c.metrics.incInterface(err) - return nil, err - } - result := make(map[common.IFIDType]*net.UDPAddr) - for ifID, intf := range response.Interfaces { - a, err := net.ResolveUDPAddr("udp", intf.Address.Address) - if err != nil { - c.metrics.incInterface(err) - return nil, serrors.WrapStr("parsing reply", err, "raw_uri", intf.Address.Address) - } - result[common.IFIDType(ifID)] = a - } - c.metrics.incInterface(nil) - return result, nil -} - func (c grpcConn) SVCInfo( ctx context.Context, _ []addr.SVC, diff --git a/pkg/daemon/mock_daemon/BUILD.bazel b/pkg/daemon/mock_daemon/BUILD.bazel index 468b330d17..3423d8d8b0 100644 --- a/pkg/daemon/mock_daemon/BUILD.bazel +++ b/pkg/daemon/mock_daemon/BUILD.bazel @@ -18,7 +18,6 @@ go_library( "//pkg/addr:go_default_library", "//pkg/daemon:go_default_library", "//pkg/drkey:go_default_library", - "//pkg/private/common:go_default_library", "//pkg/private/ctrl/path_mgmt:go_default_library", "//pkg/snet:go_default_library", "@com_github_golang_mock//gomock:go_default_library", diff --git a/pkg/daemon/mock_daemon/mock.go b/pkg/daemon/mock_daemon/mock.go index 61c85508c3..05a868518c 100644 --- a/pkg/daemon/mock_daemon/mock.go +++ b/pkg/daemon/mock_daemon/mock.go @@ -6,14 +6,13 @@ package mock_daemon import ( context "context" - net "net" + netip "net/netip" reflect "reflect" gomock "github.com/golang/mock/gomock" addr "github.com/scionproto/scion/pkg/addr" daemon "github.com/scionproto/scion/pkg/daemon" drkey "github.com/scionproto/scion/pkg/drkey" - common "github.com/scionproto/scion/pkg/private/common" path_mgmt "github.com/scionproto/scion/pkg/private/ctrl/path_mgmt" snet "github.com/scionproto/scion/pkg/snet" ) @@ -115,19 +114,19 @@ func (mr *MockConnectorMockRecorder) DRKeyGetHostHostKey(arg0, arg1 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DRKeyGetHostHostKey", reflect.TypeOf((*MockConnector)(nil).DRKeyGetHostHostKey), arg0, arg1) } -// IFInfo mocks base method. -func (m *MockConnector) IFInfo(arg0 context.Context, arg1 []common.IFIDType) (map[common.IFIDType]*net.UDPAddr, error) { +// Interfaces mocks base method. +func (m *MockConnector) Interfaces(arg0 context.Context) (map[uint16]netip.AddrPort, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IFInfo", arg0, arg1) - ret0, _ := ret[0].(map[common.IFIDType]*net.UDPAddr) + ret := m.ctrl.Call(m, "Interfaces", arg0) + ret0, _ := ret[0].(map[uint16]netip.AddrPort) ret1, _ := ret[1].(error) return ret0, ret1 } -// IFInfo indicates an expected call of IFInfo. -func (mr *MockConnectorMockRecorder) IFInfo(arg0, arg1 interface{}) *gomock.Call { +// Interfaces indicates an expected call of Interfaces. +func (mr *MockConnectorMockRecorder) Interfaces(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IFInfo", reflect.TypeOf((*MockConnector)(nil).IFInfo), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Interfaces", reflect.TypeOf((*MockConnector)(nil).Interfaces), arg0) } // LocalIA mocks base method. @@ -160,6 +159,22 @@ func (mr *MockConnectorMockRecorder) Paths(arg0, arg1, arg2, arg3 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Paths", reflect.TypeOf((*MockConnector)(nil).Paths), arg0, arg1, arg2, arg3) } +// PortRange mocks base method. +func (m *MockConnector) PortRange(arg0 context.Context) (uint16, uint16, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PortRange", arg0) + ret0, _ := ret[0].(uint16) + ret1, _ := ret[1].(uint16) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// PortRange indicates an expected call of PortRange. +func (mr *MockConnectorMockRecorder) PortRange(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortRange", reflect.TypeOf((*MockConnector)(nil).PortRange), arg0) +} + // RevNotification mocks base method. func (m *MockConnector) RevNotification(arg0 context.Context, arg1 *path_mgmt.RevInfo) error { m.ctrl.T.Helper() diff --git a/pkg/experimental/hiddenpath/testdata/topology.json b/pkg/experimental/hiddenpath/testdata/topology.json index a7e182c4ea..e8981cf165 100644 --- a/pkg/experimental/hiddenpath/testdata/topology.json +++ b/pkg/experimental/hiddenpath/testdata/topology.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:111", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [], "border_routers": { "br1-ff00_0_111-1": { diff --git a/pkg/proto/daemon/daemon.pb.go b/pkg/proto/daemon/daemon.pb.go index 93b3b19c6f..75850fa7fe 100644 --- a/pkg/proto/daemon/daemon.pb.go +++ b/pkg/proto/daemon/daemon.pb.go @@ -15,6 +15,7 @@ import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" durationpb "google.golang.org/protobuf/types/known/durationpb" + emptypb "google.golang.org/protobuf/types/known/emptypb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" @@ -1066,6 +1067,61 @@ func (*NotifyInterfaceDownResponse) Descriptor() ([]byte, []int) { return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{17} } +type PortRangeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DispatchedPortStart uint32 `protobuf:"varint,1,opt,name=dispatched_port_start,json=dispatchedPortStart,proto3" json:"dispatched_port_start,omitempty"` + DispatchedPortEnd uint32 `protobuf:"varint,2,opt,name=dispatched_port_end,json=dispatchedPortEnd,proto3" json:"dispatched_port_end,omitempty"` +} + +func (x *PortRangeResponse) Reset() { + *x = PortRangeResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PortRangeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PortRangeResponse) ProtoMessage() {} + +func (x *PortRangeResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PortRangeResponse.ProtoReflect.Descriptor instead. +func (*PortRangeResponse) Descriptor() ([]byte, []int) { + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{18} +} + +func (x *PortRangeResponse) GetDispatchedPortStart() uint32 { + if x != nil { + return x.DispatchedPortStart + } + return 0 +} + +func (x *PortRangeResponse) GetDispatchedPortEnd() uint32 { + if x != nil { + return x.DispatchedPortEnd + } + return 0 +} + type DRKeyHostASRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1081,7 +1137,7 @@ type DRKeyHostASRequest struct { func (x *DRKeyHostASRequest) Reset() { *x = DRKeyHostASRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1094,7 +1150,7 @@ func (x *DRKeyHostASRequest) String() string { func (*DRKeyHostASRequest) ProtoMessage() {} func (x *DRKeyHostASRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1107,7 +1163,7 @@ func (x *DRKeyHostASRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyHostASRequest.ProtoReflect.Descriptor instead. func (*DRKeyHostASRequest) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{18} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{19} } func (x *DRKeyHostASRequest) GetValTime() *timestamppb.Timestamp { @@ -1158,7 +1214,7 @@ type DRKeyHostASResponse struct { func (x *DRKeyHostASResponse) Reset() { *x = DRKeyHostASResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1171,7 +1227,7 @@ func (x *DRKeyHostASResponse) String() string { func (*DRKeyHostASResponse) ProtoMessage() {} func (x *DRKeyHostASResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1184,7 +1240,7 @@ func (x *DRKeyHostASResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyHostASResponse.ProtoReflect.Descriptor instead. func (*DRKeyHostASResponse) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{19} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{20} } func (x *DRKeyHostASResponse) GetEpochBegin() *timestamppb.Timestamp { @@ -1223,7 +1279,7 @@ type DRKeyASHostRequest struct { func (x *DRKeyASHostRequest) Reset() { *x = DRKeyASHostRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1236,7 +1292,7 @@ func (x *DRKeyASHostRequest) String() string { func (*DRKeyASHostRequest) ProtoMessage() {} func (x *DRKeyASHostRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1249,7 +1305,7 @@ func (x *DRKeyASHostRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyASHostRequest.ProtoReflect.Descriptor instead. func (*DRKeyASHostRequest) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{20} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{21} } func (x *DRKeyASHostRequest) GetValTime() *timestamppb.Timestamp { @@ -1300,7 +1356,7 @@ type DRKeyASHostResponse struct { func (x *DRKeyASHostResponse) Reset() { *x = DRKeyASHostResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1313,7 +1369,7 @@ func (x *DRKeyASHostResponse) String() string { func (*DRKeyASHostResponse) ProtoMessage() {} func (x *DRKeyASHostResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1326,7 +1382,7 @@ func (x *DRKeyASHostResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyASHostResponse.ProtoReflect.Descriptor instead. func (*DRKeyASHostResponse) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{21} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{22} } func (x *DRKeyASHostResponse) GetEpochBegin() *timestamppb.Timestamp { @@ -1366,7 +1422,7 @@ type DRKeyHostHostRequest struct { func (x *DRKeyHostHostRequest) Reset() { *x = DRKeyHostHostRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1379,7 +1435,7 @@ func (x *DRKeyHostHostRequest) String() string { func (*DRKeyHostHostRequest) ProtoMessage() {} func (x *DRKeyHostHostRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1392,7 +1448,7 @@ func (x *DRKeyHostHostRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyHostHostRequest.ProtoReflect.Descriptor instead. func (*DRKeyHostHostRequest) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{22} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{23} } func (x *DRKeyHostHostRequest) GetValTime() *timestamppb.Timestamp { @@ -1450,7 +1506,7 @@ type DRKeyHostHostResponse struct { func (x *DRKeyHostHostResponse) Reset() { *x = DRKeyHostHostResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1463,7 +1519,7 @@ func (x *DRKeyHostHostResponse) String() string { func (*DRKeyHostHostResponse) ProtoMessage() {} func (x *DRKeyHostHostResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1476,7 +1532,7 @@ func (x *DRKeyHostHostResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyHostHostResponse.ProtoReflect.Descriptor instead. func (*DRKeyHostHostResponse) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{23} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{24} } func (x *DRKeyHostHostResponse) GetEpochBegin() *timestamppb.Timestamp { @@ -1510,169 +1566,132 @@ var file_proto_daemon_v1_daemon_proto_rawDesc = []byte{ 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x1a, 0x1a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2f, 0x76, 0x31, - 0x2f, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x92, 0x01, 0x0a, - 0x0c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, - 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x73, 0x64, 0x41, - 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x73, 0x64, 0x41, 0x73, 0x12, - 0x18, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x69, 0x64, - 0x64, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, - 0x6e, 0x22, 0x3c, 0x0a, 0x0d, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x22, - 0x94, 0x04, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x66, 0x61, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x06, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, - 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x61, 0x6e, 0x64, 0x77, - 0x69, 0x64, 0x74, 0x68, 0x18, 0x07, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x62, 0x61, 0x6e, 0x64, - 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x03, 0x67, 0x65, 0x6f, 0x18, 0x08, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, - 0x74, 0x65, 0x73, 0x52, 0x03, 0x67, 0x65, 0x6f, 0x12, 0x36, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x70, - 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x48, 0x6f, 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x0b, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x65, - 0x70, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x45, 0x70, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x52, 0x09, 0x65, 0x70, 0x69, - 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x22, 0x45, 0x0a, 0x09, 0x45, 0x70, 0x69, 0x63, 0x41, 0x75, - 0x74, 0x68, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x68, 0x76, 0x66, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x50, 0x68, 0x76, 0x66, - 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6c, 0x68, 0x76, 0x66, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x4c, 0x68, 0x76, 0x66, 0x22, 0x36, 0x0a, - 0x0d, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x15, - 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, - 0x69, 0x73, 0x64, 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x64, 0x0a, 0x0e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, - 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, - 0x75, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, - 0x75, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x22, 0x0a, 0x09, 0x41, - 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, - 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, 0x41, 0x73, 0x22, - 0x49, 0x0a, 0x0a, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, - 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, - 0x73, 0x64, 0x41, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x22, 0x13, 0x0a, 0x11, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0xc4, 0x01, 0x0a, 0x12, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x72, 0x6f, + 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x72, + 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x92, 0x01, 0x0a, 0x0c, 0x50, 0x61, + 0x74, 0x68, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x73, 0x64, 0x41, 0x73, 0x12, 0x2c, + 0x0a, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x73, + 0x64, 0x5f, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x73, 0x64, 0x41, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, + 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x22, 0x3c, + 0x0a, 0x0d, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2b, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x22, 0x94, 0x04, 0x0a, + 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x1a, 0x59, 0x0a, 0x0f, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x40, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x52, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xba, 0x01, 0x0a, 0x10, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4b, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x1a, 0x59, 0x0a, - 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x32, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x1b, 0x0a, - 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x22, 0x24, 0x0a, 0x08, 0x55, 0x6e, - 0x64, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x22, 0x43, 0x0a, 0x1a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, - 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, - 0x69, 0x73, 0x64, 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1d, 0x0a, 0x1b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, - 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, - 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, 0x54, 0x69, - 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x15, 0x0a, - 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, - 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x73, - 0x72, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, - 0x72, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x13, 0x44, 0x52, 0x4b, 0x65, 0x79, - 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, - 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x65, - 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, - 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x44, 0x52, 0x4b, 0x65, 0x79, - 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, - 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, - 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, - 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, - 0x08, 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x13, 0x44, 0x52, 0x4b, - 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x37, 0x0a, - 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, - 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xec, 0x01, 0x0a, 0x14, 0x44, 0x52, 0x4b, - 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, + 0x65, 0x12, 0x3e, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, + 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, + 0x6d, 0x74, 0x75, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x33, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x6c, 0x61, 0x74, + 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x18, 0x07, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x62, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x12, 0x31, 0x0a, 0x03, 0x67, 0x65, 0x6f, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73, + 0x52, 0x03, 0x67, 0x65, 0x6f, 0x12, 0x36, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x0a, + 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x48, 0x6f, + 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x70, 0x69, 0x63, + 0x5f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x45, + 0x70, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x52, 0x09, 0x65, 0x70, 0x69, 0x63, 0x41, 0x75, + 0x74, 0x68, 0x73, 0x22, 0x45, 0x0a, 0x09, 0x45, 0x70, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, + 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x68, 0x76, 0x66, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x50, 0x68, 0x76, 0x66, 0x12, 0x1b, 0x0a, + 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6c, 0x68, 0x76, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x4c, 0x68, 0x76, 0x66, 0x22, 0x36, 0x0a, 0x0d, 0x50, 0x61, + 0x74, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x69, + 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, + 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x64, 0x0a, 0x0e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x22, 0x0a, 0x09, 0x41, 0x53, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, 0x41, 0x73, 0x22, 0x49, 0x0a, 0x0a, + 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, + 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, 0x41, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x22, 0x13, 0x0a, 0x11, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xc4, 0x01, 0x0a, + 0x12, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, + 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x1a, 0x59, 0x0a, 0x0f, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x40, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, + 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xba, 0x01, 0x0a, 0x10, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, + 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x1a, 0x59, 0x0a, 0x0d, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, + 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x1b, 0x0a, 0x07, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x22, 0x24, 0x0a, 0x08, 0x55, 0x6e, 0x64, 0x65, 0x72, + 0x6c, 0x61, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x43, 0x0a, + 0x1a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, + 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, + 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, + 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x1d, 0x0a, 0x1b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x77, 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, + 0x64, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x65, 0x6e, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x45, 0x6e, 0x64, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x44, + 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, @@ -1684,75 +1703,126 @@ var file_proto_daemon_v1_daemon_proto_rawDesc = []byte{ 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, - 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x15, 0x44, 0x52, 0x4b, 0x65, - 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, + 0x13, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, + 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, + 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xcf, 0x01, 0x0a, + 0x12, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, + 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, + 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, + 0x01, 0x0a, 0x13, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, + 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, + 0x67, 0x69, 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x37, - 0x0a, 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, - 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x2a, 0x6c, 0x0a, 0x08, 0x4c, 0x69, 0x6e, - 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x49, - 0x52, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x48, 0x4f, 0x50, 0x10, 0x02, 0x12, - 0x16, 0x0a, 0x12, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x50, 0x45, - 0x4e, 0x5f, 0x4e, 0x45, 0x54, 0x10, 0x03, 0x32, 0xd4, 0x05, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, - 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x05, 0x50, 0x61, 0x74, - 0x68, 0x73, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x02, 0x41, 0x53, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x53, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, - 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, - 0x65, 0x73, 0x12, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, - 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, - 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x72, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, - 0x6f, 0x73, 0x74, 0x12, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, - 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, - 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x12, - 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, - 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x0d, - 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x25, 0x2e, + 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xec, + 0x01, 0x0a, 0x14, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, + 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, + 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, + 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, 0x68, + 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x48, 0x6f, + 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9f, 0x01, + 0x0a, 0x15, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, + 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, + 0x65, 0x67, 0x69, 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x2a, + 0x6c, 0x0a, 0x08, 0x4c, 0x69, 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x4c, + 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, + 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, + 0x48, 0x4f, 0x50, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x4e, 0x45, 0x54, 0x10, 0x03, 0x32, 0x9f, 0x06, + 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x48, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x02, 0x41, 0x53, 0x12, + 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x53, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0a, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, + 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x72, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, - 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2e, - 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x63, 0x69, - 0x6f, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x6b, - 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, + 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x09, 0x50, 0x6f, + 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, + 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, + 0x48, 0x6f, 0x73, 0x74, 0x12, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, + 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, + 0x12, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, + 0x74, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, + 0x0d, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x25, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, + 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x63, + 0x69, 0x6f, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x2f, 0x70, + 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1768,7 +1838,7 @@ func file_proto_daemon_v1_daemon_proto_rawDescGZIP() []byte { } var file_proto_daemon_v1_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_daemon_v1_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 26) +var file_proto_daemon_v1_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_proto_daemon_v1_daemon_proto_goTypes = []interface{}{ (LinkType)(0), // 0: proto.daemon.v1.LinkType (*PathsRequest)(nil), // 1: proto.daemon.v1.PathsRequest @@ -1789,43 +1859,45 @@ var file_proto_daemon_v1_daemon_proto_goTypes = []interface{}{ (*Underlay)(nil), // 16: proto.daemon.v1.Underlay (*NotifyInterfaceDownRequest)(nil), // 17: proto.daemon.v1.NotifyInterfaceDownRequest (*NotifyInterfaceDownResponse)(nil), // 18: proto.daemon.v1.NotifyInterfaceDownResponse - (*DRKeyHostASRequest)(nil), // 19: proto.daemon.v1.DRKeyHostASRequest - (*DRKeyHostASResponse)(nil), // 20: proto.daemon.v1.DRKeyHostASResponse - (*DRKeyASHostRequest)(nil), // 21: proto.daemon.v1.DRKeyASHostRequest - (*DRKeyASHostResponse)(nil), // 22: proto.daemon.v1.DRKeyASHostResponse - (*DRKeyHostHostRequest)(nil), // 23: proto.daemon.v1.DRKeyHostHostRequest - (*DRKeyHostHostResponse)(nil), // 24: proto.daemon.v1.DRKeyHostHostResponse - nil, // 25: proto.daemon.v1.InterfacesResponse.InterfacesEntry - nil, // 26: proto.daemon.v1.ServicesResponse.ServicesEntry - (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 28: google.protobuf.Duration - (drkey.Protocol)(0), // 29: proto.drkey.v1.Protocol + (*PortRangeResponse)(nil), // 19: proto.daemon.v1.PortRangeResponse + (*DRKeyHostASRequest)(nil), // 20: proto.daemon.v1.DRKeyHostASRequest + (*DRKeyHostASResponse)(nil), // 21: proto.daemon.v1.DRKeyHostASResponse + (*DRKeyASHostRequest)(nil), // 22: proto.daemon.v1.DRKeyASHostRequest + (*DRKeyASHostResponse)(nil), // 23: proto.daemon.v1.DRKeyASHostResponse + (*DRKeyHostHostRequest)(nil), // 24: proto.daemon.v1.DRKeyHostHostRequest + (*DRKeyHostHostResponse)(nil), // 25: proto.daemon.v1.DRKeyHostHostResponse + nil, // 26: proto.daemon.v1.InterfacesResponse.InterfacesEntry + nil, // 27: proto.daemon.v1.ServicesResponse.ServicesEntry + (*timestamppb.Timestamp)(nil), // 28: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 29: google.protobuf.Duration + (drkey.Protocol)(0), // 30: proto.drkey.v1.Protocol + (*emptypb.Empty)(nil), // 31: google.protobuf.Empty } var file_proto_daemon_v1_daemon_proto_depIdxs = []int32{ 3, // 0: proto.daemon.v1.PathsResponse.paths:type_name -> proto.daemon.v1.Path 11, // 1: proto.daemon.v1.Path.interface:type_name -> proto.daemon.v1.Interface 5, // 2: proto.daemon.v1.Path.interfaces:type_name -> proto.daemon.v1.PathInterface - 27, // 3: proto.daemon.v1.Path.expiration:type_name -> google.protobuf.Timestamp - 28, // 4: proto.daemon.v1.Path.latency:type_name -> google.protobuf.Duration + 28, // 3: proto.daemon.v1.Path.expiration:type_name -> google.protobuf.Timestamp + 29, // 4: proto.daemon.v1.Path.latency:type_name -> google.protobuf.Duration 6, // 5: proto.daemon.v1.Path.geo:type_name -> proto.daemon.v1.GeoCoordinates 0, // 6: proto.daemon.v1.Path.link_type:type_name -> proto.daemon.v1.LinkType 4, // 7: proto.daemon.v1.Path.epic_auths:type_name -> proto.daemon.v1.EpicAuths - 25, // 8: proto.daemon.v1.InterfacesResponse.interfaces:type_name -> proto.daemon.v1.InterfacesResponse.InterfacesEntry + 26, // 8: proto.daemon.v1.InterfacesResponse.interfaces:type_name -> proto.daemon.v1.InterfacesResponse.InterfacesEntry 16, // 9: proto.daemon.v1.Interface.address:type_name -> proto.daemon.v1.Underlay - 26, // 10: proto.daemon.v1.ServicesResponse.services:type_name -> proto.daemon.v1.ServicesResponse.ServicesEntry + 27, // 10: proto.daemon.v1.ServicesResponse.services:type_name -> proto.daemon.v1.ServicesResponse.ServicesEntry 15, // 11: proto.daemon.v1.ListService.services:type_name -> proto.daemon.v1.Service - 27, // 12: proto.daemon.v1.DRKeyHostASRequest.val_time:type_name -> google.protobuf.Timestamp - 29, // 13: proto.daemon.v1.DRKeyHostASRequest.protocol_id:type_name -> proto.drkey.v1.Protocol - 27, // 14: proto.daemon.v1.DRKeyHostASResponse.epoch_begin:type_name -> google.protobuf.Timestamp - 27, // 15: proto.daemon.v1.DRKeyHostASResponse.epoch_end:type_name -> google.protobuf.Timestamp - 27, // 16: proto.daemon.v1.DRKeyASHostRequest.val_time:type_name -> google.protobuf.Timestamp - 29, // 17: proto.daemon.v1.DRKeyASHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol - 27, // 18: proto.daemon.v1.DRKeyASHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp - 27, // 19: proto.daemon.v1.DRKeyASHostResponse.epoch_end:type_name -> google.protobuf.Timestamp - 27, // 20: proto.daemon.v1.DRKeyHostHostRequest.val_time:type_name -> google.protobuf.Timestamp - 29, // 21: proto.daemon.v1.DRKeyHostHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol - 27, // 22: proto.daemon.v1.DRKeyHostHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp - 27, // 23: proto.daemon.v1.DRKeyHostHostResponse.epoch_end:type_name -> google.protobuf.Timestamp + 28, // 12: proto.daemon.v1.DRKeyHostASRequest.val_time:type_name -> google.protobuf.Timestamp + 30, // 13: proto.daemon.v1.DRKeyHostASRequest.protocol_id:type_name -> proto.drkey.v1.Protocol + 28, // 14: proto.daemon.v1.DRKeyHostASResponse.epoch_begin:type_name -> google.protobuf.Timestamp + 28, // 15: proto.daemon.v1.DRKeyHostASResponse.epoch_end:type_name -> google.protobuf.Timestamp + 28, // 16: proto.daemon.v1.DRKeyASHostRequest.val_time:type_name -> google.protobuf.Timestamp + 30, // 17: proto.daemon.v1.DRKeyASHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol + 28, // 18: proto.daemon.v1.DRKeyASHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp + 28, // 19: proto.daemon.v1.DRKeyASHostResponse.epoch_end:type_name -> google.protobuf.Timestamp + 28, // 20: proto.daemon.v1.DRKeyHostHostRequest.val_time:type_name -> google.protobuf.Timestamp + 30, // 21: proto.daemon.v1.DRKeyHostHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol + 28, // 22: proto.daemon.v1.DRKeyHostHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp + 28, // 23: proto.daemon.v1.DRKeyHostHostResponse.epoch_end:type_name -> google.protobuf.Timestamp 11, // 24: proto.daemon.v1.InterfacesResponse.InterfacesEntry.value:type_name -> proto.daemon.v1.Interface 14, // 25: proto.daemon.v1.ServicesResponse.ServicesEntry.value:type_name -> proto.daemon.v1.ListService 1, // 26: proto.daemon.v1.DaemonService.Paths:input_type -> proto.daemon.v1.PathsRequest @@ -1833,19 +1905,21 @@ var file_proto_daemon_v1_daemon_proto_depIdxs = []int32{ 9, // 28: proto.daemon.v1.DaemonService.Interfaces:input_type -> proto.daemon.v1.InterfacesRequest 12, // 29: proto.daemon.v1.DaemonService.Services:input_type -> proto.daemon.v1.ServicesRequest 17, // 30: proto.daemon.v1.DaemonService.NotifyInterfaceDown:input_type -> proto.daemon.v1.NotifyInterfaceDownRequest - 21, // 31: proto.daemon.v1.DaemonService.DRKeyASHost:input_type -> proto.daemon.v1.DRKeyASHostRequest - 19, // 32: proto.daemon.v1.DaemonService.DRKeyHostAS:input_type -> proto.daemon.v1.DRKeyHostASRequest - 23, // 33: proto.daemon.v1.DaemonService.DRKeyHostHost:input_type -> proto.daemon.v1.DRKeyHostHostRequest - 2, // 34: proto.daemon.v1.DaemonService.Paths:output_type -> proto.daemon.v1.PathsResponse - 8, // 35: proto.daemon.v1.DaemonService.AS:output_type -> proto.daemon.v1.ASResponse - 10, // 36: proto.daemon.v1.DaemonService.Interfaces:output_type -> proto.daemon.v1.InterfacesResponse - 13, // 37: proto.daemon.v1.DaemonService.Services:output_type -> proto.daemon.v1.ServicesResponse - 18, // 38: proto.daemon.v1.DaemonService.NotifyInterfaceDown:output_type -> proto.daemon.v1.NotifyInterfaceDownResponse - 22, // 39: proto.daemon.v1.DaemonService.DRKeyASHost:output_type -> proto.daemon.v1.DRKeyASHostResponse - 20, // 40: proto.daemon.v1.DaemonService.DRKeyHostAS:output_type -> proto.daemon.v1.DRKeyHostASResponse - 24, // 41: proto.daemon.v1.DaemonService.DRKeyHostHost:output_type -> proto.daemon.v1.DRKeyHostHostResponse - 34, // [34:42] is the sub-list for method output_type - 26, // [26:34] is the sub-list for method input_type + 31, // 31: proto.daemon.v1.DaemonService.PortRange:input_type -> google.protobuf.Empty + 22, // 32: proto.daemon.v1.DaemonService.DRKeyASHost:input_type -> proto.daemon.v1.DRKeyASHostRequest + 20, // 33: proto.daemon.v1.DaemonService.DRKeyHostAS:input_type -> proto.daemon.v1.DRKeyHostASRequest + 24, // 34: proto.daemon.v1.DaemonService.DRKeyHostHost:input_type -> proto.daemon.v1.DRKeyHostHostRequest + 2, // 35: proto.daemon.v1.DaemonService.Paths:output_type -> proto.daemon.v1.PathsResponse + 8, // 36: proto.daemon.v1.DaemonService.AS:output_type -> proto.daemon.v1.ASResponse + 10, // 37: proto.daemon.v1.DaemonService.Interfaces:output_type -> proto.daemon.v1.InterfacesResponse + 13, // 38: proto.daemon.v1.DaemonService.Services:output_type -> proto.daemon.v1.ServicesResponse + 18, // 39: proto.daemon.v1.DaemonService.NotifyInterfaceDown:output_type -> proto.daemon.v1.NotifyInterfaceDownResponse + 19, // 40: proto.daemon.v1.DaemonService.PortRange:output_type -> proto.daemon.v1.PortRangeResponse + 23, // 41: proto.daemon.v1.DaemonService.DRKeyASHost:output_type -> proto.daemon.v1.DRKeyASHostResponse + 21, // 42: proto.daemon.v1.DaemonService.DRKeyHostAS:output_type -> proto.daemon.v1.DRKeyHostASResponse + 25, // 43: proto.daemon.v1.DaemonService.DRKeyHostHost:output_type -> proto.daemon.v1.DRKeyHostHostResponse + 35, // [35:44] is the sub-list for method output_type + 26, // [26:35] is the sub-list for method input_type 26, // [26:26] is the sub-list for extension type_name 26, // [26:26] is the sub-list for extension extendee 0, // [0:26] is the sub-list for field type_name @@ -2074,7 +2148,7 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DRKeyHostASRequest); i { + switch v := v.(*PortRangeResponse); i { case 0: return &v.state case 1: @@ -2086,7 +2160,7 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DRKeyHostASResponse); i { + switch v := v.(*DRKeyHostASRequest); i { case 0: return &v.state case 1: @@ -2098,7 +2172,7 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DRKeyASHostRequest); i { + switch v := v.(*DRKeyHostASResponse); i { case 0: return &v.state case 1: @@ -2110,7 +2184,7 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DRKeyASHostResponse); i { + switch v := v.(*DRKeyASHostRequest); i { case 0: return &v.state case 1: @@ -2122,7 +2196,7 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DRKeyHostHostRequest); i { + switch v := v.(*DRKeyASHostResponse); i { case 0: return &v.state case 1: @@ -2134,6 +2208,18 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DRKeyHostHostRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_daemon_v1_daemon_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DRKeyHostHostResponse); i { case 0: return &v.state @@ -2152,7 +2238,7 @@ func file_proto_daemon_v1_daemon_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_daemon_v1_daemon_proto_rawDesc, NumEnums: 1, - NumMessages: 26, + NumMessages: 27, NumExtensions: 0, NumServices: 1, }, @@ -2184,6 +2270,7 @@ type DaemonServiceClient interface { Interfaces(ctx context.Context, in *InterfacesRequest, opts ...grpc.CallOption) (*InterfacesResponse, error) Services(ctx context.Context, in *ServicesRequest, opts ...grpc.CallOption) (*ServicesResponse, error) NotifyInterfaceDown(ctx context.Context, in *NotifyInterfaceDownRequest, opts ...grpc.CallOption) (*NotifyInterfaceDownResponse, error) + PortRange(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*PortRangeResponse, error) DRKeyASHost(ctx context.Context, in *DRKeyASHostRequest, opts ...grpc.CallOption) (*DRKeyASHostResponse, error) DRKeyHostAS(ctx context.Context, in *DRKeyHostASRequest, opts ...grpc.CallOption) (*DRKeyHostASResponse, error) DRKeyHostHost(ctx context.Context, in *DRKeyHostHostRequest, opts ...grpc.CallOption) (*DRKeyHostHostResponse, error) @@ -2242,6 +2329,15 @@ func (c *daemonServiceClient) NotifyInterfaceDown(ctx context.Context, in *Notif return out, nil } +func (c *daemonServiceClient) PortRange(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*PortRangeResponse, error) { + out := new(PortRangeResponse) + err := c.cc.Invoke(ctx, "/proto.daemon.v1.DaemonService/PortRange", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *daemonServiceClient) DRKeyASHost(ctx context.Context, in *DRKeyASHostRequest, opts ...grpc.CallOption) (*DRKeyASHostResponse, error) { out := new(DRKeyASHostResponse) err := c.cc.Invoke(ctx, "/proto.daemon.v1.DaemonService/DRKeyASHost", in, out, opts...) @@ -2276,6 +2372,7 @@ type DaemonServiceServer interface { Interfaces(context.Context, *InterfacesRequest) (*InterfacesResponse, error) Services(context.Context, *ServicesRequest) (*ServicesResponse, error) NotifyInterfaceDown(context.Context, *NotifyInterfaceDownRequest) (*NotifyInterfaceDownResponse, error) + PortRange(context.Context, *emptypb.Empty) (*PortRangeResponse, error) DRKeyASHost(context.Context, *DRKeyASHostRequest) (*DRKeyASHostResponse, error) DRKeyHostAS(context.Context, *DRKeyHostASRequest) (*DRKeyHostASResponse, error) DRKeyHostHost(context.Context, *DRKeyHostHostRequest) (*DRKeyHostHostResponse, error) @@ -2300,6 +2397,9 @@ func (*UnimplementedDaemonServiceServer) Services(context.Context, *ServicesRequ func (*UnimplementedDaemonServiceServer) NotifyInterfaceDown(context.Context, *NotifyInterfaceDownRequest) (*NotifyInterfaceDownResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method NotifyInterfaceDown not implemented") } +func (*UnimplementedDaemonServiceServer) PortRange(context.Context, *emptypb.Empty) (*PortRangeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PortRange not implemented") +} func (*UnimplementedDaemonServiceServer) DRKeyASHost(context.Context, *DRKeyASHostRequest) (*DRKeyASHostResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DRKeyASHost not implemented") } @@ -2404,6 +2504,24 @@ func _DaemonService_NotifyInterfaceDown_Handler(srv interface{}, ctx context.Con return interceptor(ctx, in, info, handler) } +func _DaemonService_PortRange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).PortRange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.daemon.v1.DaemonService/PortRange", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).PortRange(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _DaemonService_DRKeyASHost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DRKeyASHostRequest) if err := dec(in); err != nil { @@ -2482,6 +2600,10 @@ var _DaemonService_serviceDesc = grpc.ServiceDesc{ MethodName: "NotifyInterfaceDown", Handler: _DaemonService_NotifyInterfaceDown_Handler, }, + { + MethodName: "PortRange", + Handler: _DaemonService_PortRange_Handler, + }, { MethodName: "DRKeyASHost", Handler: _DaemonService_DRKeyASHost_Handler, diff --git a/pkg/snet/BUILD.bazel b/pkg/snet/BUILD.bazel index 66f0c6f674..d9f7e410e0 100644 --- a/pkg/snet/BUILD.bazel +++ b/pkg/snet/BUILD.bazel @@ -3,9 +3,7 @@ load("//tools/lint:go.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ - "base.go", "conn.go", - "dispatcher.go", "interface.go", "packet.go", "packet_conn.go", @@ -13,6 +11,7 @@ go_library( "reader.go", "reply_pather.go", "router.go", + "scmp.go", "snet.go", "svcaddr.go", "udpaddr.go", @@ -30,8 +29,11 @@ go_library( "//pkg/private/util:go_default_library", "//pkg/slayers:go_default_library", "//pkg/slayers/path:go_default_library", + "//pkg/slayers/path/empty:go_default_library", "//pkg/slayers/path/epic:go_default_library", - "//pkg/sock/reliable:go_default_library", + "//pkg/slayers/path/onehop:go_default_library", + "//pkg/slayers/path/scion:go_default_library", + "//private/topology:go_default_library", "//private/topology/underlay:go_default_library", "@com_github_google_gopacket//:go_default_library", ], diff --git a/pkg/snet/base.go b/pkg/snet/base.go deleted file mode 100644 index a06f956f0c..0000000000 --- a/pkg/snet/base.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, 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 snet - -import ( - "net" - - "github.com/scionproto/scion/pkg/addr" -) - -type scionConnBase struct { - // Local and remote SCION addresses (IA, L3, L4) - listen *UDPAddr - remote *UDPAddr - - // svc address - svc addr.SVC - - // Reference to SCION networking context - scionNet *SCIONNetwork -} - -func (c *scionConnBase) LocalAddr() net.Addr { - return c.listen -} - -func (c *scionConnBase) RemoteAddr() net.Addr { - return c.remote -} - -func (c *scionConnBase) SVC() addr.SVC { - return c.svc -} diff --git a/pkg/snet/conn.go b/pkg/snet/conn.go index 95b9d2f13e..3ec657f299 100644 --- a/pkg/snet/conn.go +++ b/pkg/snet/conn.go @@ -16,11 +16,13 @@ package snet import ( + "context" "net" "time" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/ctrl/path_mgmt" + "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/slayers" ) @@ -42,28 +44,69 @@ var _ net.PacketConn = (*Conn)(nil) type Conn struct { conn PacketConn - scionConnBase scionConnWriter scionConnReader + + // Local and remote SCION addresses (IA, L3, L4) + local *UDPAddr + remote *UDPAddr } -func newConn(base scionConnBase, conn PacketConn, replyPather ReplyPather) *Conn { - c := &Conn{ - conn: conn, - scionConnBase: base, +// NewCookedConn returns a "cooked" Conn. The Conn object can be used to +// send/receive SCION traffic with the usual methods. +// It takes as arguments a non-nil PacketConn and a non-nil Topology parameter. +// Nil or unspecified addresses for the PacketConn object are not supported. +// This is an advanced API, that allows fine-tunning of the Conn underlay functionality. +// The general methods for obtaining a Conn object are still SCIONNetwork.Listen and +// SCIONNetwork.Dial. +func NewCookedConn( + pconn PacketConn, + topo Topology, + options ...ConnOption, +) (*Conn, error) { + o := apply(options) + localIA, err := topo.LocalIA(context.Background()) + if err != nil { + return nil, err + } + local := &UDPAddr{ + IA: localIA, + Host: pconn.LocalAddr().(*net.UDPAddr), } - c.scionConnWriter = scionConnWriter{ - base: &c.scionConnBase, - conn: conn, - buffer: make([]byte, common.SupportedMTU), + if local.Host == nil || local.Host.IP.IsUnspecified() { + return nil, serrors.New("nil or unspecified address is not supported.") } - c.scionConnReader = scionConnReader{ - base: &c.scionConnBase, - conn: conn, - buffer: make([]byte, common.SupportedMTU), - replyPather: replyPather, + start, end, err := topo.PortRange(context.Background()) + if err != nil { + return nil, err } - return c + return &Conn{ + conn: pconn, + local: local, + remote: o.remote, + scionConnWriter: scionConnWriter{ + conn: pconn, + buffer: make([]byte, common.SupportedMTU), + local: local, + remote: o.remote, + dispatchedPortStart: start, + dispatchedPortEnd: end, + }, + scionConnReader: scionConnReader{ + conn: pconn, + buffer: make([]byte, common.SupportedMTU), + replyPather: o.replyPather, + local: local, + }, + }, nil +} + +func (c *Conn) LocalAddr() net.Addr { + return c.local +} + +func (c *Conn) RemoteAddr() net.Addr { + return c.remote } func (c *Conn) SetDeadline(t time.Time) error { @@ -79,3 +122,39 @@ func (c *Conn) SetDeadline(t time.Time) error { func (c *Conn) Close() error { return c.conn.Close() } + +// ConnOption is a functional option type for configuring a Conn. +type ConnOption func(o *options) + +// WithReplyPather sets the reply pather for the connection. +// The reply pather is responsible for determining the path to send replies to. +// If the provided replyPather is not nil, it will be set as the reply pather for the connection. +func WithReplyPather(replyPather ReplyPather) ConnOption { + return func(o *options) { + if replyPather != nil { + o.replyPather = replyPather + } + } +} + +// WithRemote sets the remote address for the connection. +func WithRemote(addr *UDPAddr) ConnOption { + return func(o *options) { + o.remote = addr + } +} + +type options struct { + replyPather ReplyPather + remote *UDPAddr +} + +func apply(opts []ConnOption) options { + o := options{ + replyPather: DefaultReplyPather{}, + } + for _, option := range opts { + option(&o) + } + return o +} diff --git a/pkg/snet/interface.go b/pkg/snet/interface.go index 343ec1a442..1e333b56dd 100644 --- a/pkg/snet/interface.go +++ b/pkg/snet/interface.go @@ -18,13 +18,10 @@ package snet import ( "context" "net" - - "github.com/scionproto/scion/pkg/addr" ) type Network interface { - Listen(ctx context.Context, network string, listen *net.UDPAddr, - svc addr.SVC) (*Conn, error) - Dial(ctx context.Context, network string, listen *net.UDPAddr, remote *UDPAddr, - svc addr.SVC) (*Conn, error) + OpenRaw(ctx context.Context, addr *net.UDPAddr) (PacketConn, error) + Listen(ctx context.Context, network string, listen *net.UDPAddr) (*Conn, error) + Dial(ctx context.Context, network string, listen *net.UDPAddr, remote *UDPAddr) (*Conn, error) } diff --git a/pkg/snet/metrics/metrics.go b/pkg/snet/metrics/metrics.go index d70fe5b009..0498703bf0 100644 --- a/pkg/snet/metrics/metrics.go +++ b/pkg/snet/metrics/metrics.go @@ -78,9 +78,9 @@ func NewSCIONPacketConnMetrics(opts ...Option) snet.SCIONPacketConnMetrics { WritePackets: metrics.NewPromCounter(auto.NewCounterVec(prometheus.CounterOpts{ Name: "lib_snet_write_total_pkts", Help: "Total number of packets written"}, []string{})), - DispatcherErrors: metrics.NewPromCounter(auto.NewCounterVec(prometheus.CounterOpts{ - Name: "lib_snet_dispatcher_error_total", - Help: "Total number of dispatcher errors"}, []string{})), + UnderlayConnectionErrors: metrics.NewPromCounter(auto.NewCounterVec(prometheus.CounterOpts{ + Name: "lib_snet_underlay_error_total", + Help: "Total number of underlay connection errors"}, []string{})), ParseErrors: metrics.NewPromCounter(auto.NewCounterVec(prometheus.CounterOpts{ Name: "lib_snet_parse_error_total", Help: "Total number of parse errors"}, []string{})), diff --git a/pkg/snet/mock_snet/BUILD.bazel b/pkg/snet/mock_snet/BUILD.bazel index 5ff72264f9..9ca0f74569 100644 --- a/pkg/snet/mock_snet/BUILD.bazel +++ b/pkg/snet/mock_snet/BUILD.bazel @@ -5,7 +5,6 @@ gomock( name = "go_default_mock", out = "mock.go", interfaces = [ - "PacketDispatcherService", "Network", "PacketConn", "Path", diff --git a/pkg/snet/mock_snet/mock.go b/pkg/snet/mock_snet/mock.go index 181ac9a929..c4a1d854b1 100644 --- a/pkg/snet/mock_snet/mock.go +++ b/pkg/snet/mock_snet/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/scionproto/scion/pkg/snet (interfaces: PacketDispatcherService,Network,PacketConn,Path,PathQuerier,Router,RevocationHandler) +// Source: github.com/scionproto/scion/pkg/snet (interfaces: Network,PacketConn,Path,PathQuerier,Router,RevocationHandler) // Package mock_snet is a generated GoMock package. package mock_snet @@ -8,6 +8,7 @@ import ( context "context" net "net" reflect "reflect" + syscall "syscall" time "time" gomock "github.com/golang/mock/gomock" @@ -16,45 +17,6 @@ import ( snet "github.com/scionproto/scion/pkg/snet" ) -// MockPacketDispatcherService is a mock of PacketDispatcherService interface. -type MockPacketDispatcherService struct { - ctrl *gomock.Controller - recorder *MockPacketDispatcherServiceMockRecorder -} - -// MockPacketDispatcherServiceMockRecorder is the mock recorder for MockPacketDispatcherService. -type MockPacketDispatcherServiceMockRecorder struct { - mock *MockPacketDispatcherService -} - -// NewMockPacketDispatcherService creates a new mock instance. -func NewMockPacketDispatcherService(ctrl *gomock.Controller) *MockPacketDispatcherService { - mock := &MockPacketDispatcherService{ctrl: ctrl} - mock.recorder = &MockPacketDispatcherServiceMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockPacketDispatcherService) EXPECT() *MockPacketDispatcherServiceMockRecorder { - return m.recorder -} - -// Register mocks base method. -func (m *MockPacketDispatcherService) Register(arg0 context.Context, arg1 addr.IA, arg2 *net.UDPAddr, arg3 addr.SVC) (snet.PacketConn, uint16, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Register", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(snet.PacketConn) - ret1, _ := ret[1].(uint16) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Register indicates an expected call of Register. -func (mr *MockPacketDispatcherServiceMockRecorder) Register(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockPacketDispatcherService)(nil).Register), arg0, arg1, arg2, arg3) -} - // MockNetwork is a mock of Network interface. type MockNetwork struct { ctrl *gomock.Controller @@ -79,33 +41,48 @@ func (m *MockNetwork) EXPECT() *MockNetworkMockRecorder { } // Dial mocks base method. -func (m *MockNetwork) Dial(arg0 context.Context, arg1 string, arg2 *net.UDPAddr, arg3 *snet.UDPAddr, arg4 addr.SVC) (*snet.Conn, error) { +func (m *MockNetwork) Dial(arg0 context.Context, arg1 string, arg2 *net.UDPAddr, arg3 *snet.UDPAddr) (*snet.Conn, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Dial", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "Dial", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*snet.Conn) ret1, _ := ret[1].(error) return ret0, ret1 } // Dial indicates an expected call of Dial. -func (mr *MockNetworkMockRecorder) Dial(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockNetworkMockRecorder) Dial(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dial", reflect.TypeOf((*MockNetwork)(nil).Dial), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dial", reflect.TypeOf((*MockNetwork)(nil).Dial), arg0, arg1, arg2, arg3) } // Listen mocks base method. -func (m *MockNetwork) Listen(arg0 context.Context, arg1 string, arg2 *net.UDPAddr, arg3 addr.SVC) (*snet.Conn, error) { +func (m *MockNetwork) Listen(arg0 context.Context, arg1 string, arg2 *net.UDPAddr) (*snet.Conn, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Listen", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "Listen", arg0, arg1, arg2) ret0, _ := ret[0].(*snet.Conn) ret1, _ := ret[1].(error) return ret0, ret1 } // Listen indicates an expected call of Listen. -func (mr *MockNetworkMockRecorder) Listen(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockNetworkMockRecorder) Listen(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Listen", reflect.TypeOf((*MockNetwork)(nil).Listen), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Listen", reflect.TypeOf((*MockNetwork)(nil).Listen), arg0, arg1, arg2) +} + +// OpenRaw mocks base method. +func (m *MockNetwork) OpenRaw(arg0 context.Context, arg1 *net.UDPAddr) (snet.PacketConn, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenRaw", arg0, arg1) + ret0, _ := ret[0].(snet.PacketConn) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OpenRaw indicates an expected call of OpenRaw. +func (mr *MockNetworkMockRecorder) OpenRaw(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenRaw", reflect.TypeOf((*MockNetwork)(nil).OpenRaw), arg0, arg1) } // MockPacketConn is a mock of PacketConn interface. @@ -145,6 +122,20 @@ func (mr *MockPacketConnMockRecorder) Close() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockPacketConn)(nil).Close)) } +// LocalAddr mocks base method. +func (m *MockPacketConn) LocalAddr() net.Addr { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LocalAddr") + ret0, _ := ret[0].(net.Addr) + return ret0 +} + +// LocalAddr indicates an expected call of LocalAddr. +func (mr *MockPacketConnMockRecorder) LocalAddr() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockPacketConn)(nil).LocalAddr)) +} + // ReadFrom mocks base method. func (m *MockPacketConn) ReadFrom(arg0 *snet.Packet, arg1 *net.UDPAddr) error { m.ctrl.T.Helper() @@ -201,6 +192,21 @@ func (mr *MockPacketConnMockRecorder) SetWriteDeadline(arg0 interface{}) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockPacketConn)(nil).SetWriteDeadline), arg0) } +// SyscallConn mocks base method. +func (m *MockPacketConn) SyscallConn() (syscall.RawConn, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyscallConn") + ret0, _ := ret[0].(syscall.RawConn) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyscallConn indicates an expected call of SyscallConn. +func (mr *MockPacketConnMockRecorder) SyscallConn() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyscallConn", reflect.TypeOf((*MockPacketConn)(nil).SyscallConn)) +} + // WriteTo mocks base method. func (m *MockPacketConn) WriteTo(arg0 *snet.Packet, arg1 *net.UDPAddr) error { m.ctrl.T.Helper() diff --git a/pkg/snet/packet_conn.go b/pkg/snet/packet_conn.go index 6e257f3095..4c2f57777e 100644 --- a/pkg/snet/packet_conn.go +++ b/pkg/snet/packet_conn.go @@ -16,6 +16,8 @@ package snet import ( "net" + "net/netip" + "syscall" "time" "github.com/scionproto/scion/pkg/addr" @@ -23,6 +25,11 @@ import ( "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/path/empty" + "github.com/scionproto/scion/pkg/slayers/path/epic" + "github.com/scionproto/scion/pkg/slayers/path/onehop" + "github.com/scionproto/scion/pkg/slayers/path/scion" + "github.com/scionproto/scion/private/topology/underlay" ) // PacketConn gives applications easy access to writing and reading custom @@ -33,6 +40,8 @@ type PacketConn interface { SetReadDeadline(t time.Time) error SetWriteDeadline(t time.Time) error SetDeadline(t time.Time) error + SyscallConn() (syscall.RawConn, error) + LocalAddr() net.Addr Close() error } @@ -98,21 +107,26 @@ type SCIONPacketConnMetrics struct { ParseErrors metrics.Counter // SCMPErrors records the total number of SCMP Errors encountered. SCMPErrors metrics.Counter - // DispatcherErrors records the number of dispatcher errors encountered. - DispatcherErrors metrics.Counter + // UnderlayConnectionErrors records the number of underlay connection errors encountered. + UnderlayConnectionErrors metrics.Counter } // SCIONPacketConn gives applications full control over the content of valid SCION // packets. type SCIONPacketConn struct { // Conn is the connection to send/receive serialized packets on. - Conn net.PacketConn + Conn *net.UDPConn // SCMPHandler is invoked for packets that contain an SCMP L4. If the // handler is nil, errors are returned back to applications every time an // SCMP message is received. SCMPHandler SCMPHandler // Metrics are the metrics exported by the conn. - Metrics SCIONPacketConnMetrics + Metrics SCIONPacketConnMetrics + interfaceMap interfaceMap +} + +func (c *SCIONPacketConn) SetReadBuffer(bytes int) error { + return c.Conn.SetReadBuffer(bytes) } func (c *SCIONPacketConn) SetDeadline(d time.Time) error { @@ -139,6 +153,10 @@ func (c *SCIONPacketConn) WriteTo(pkt *Packet, ov *net.UDPAddr) error { return nil } +func (c *SCIONPacketConn) SetWriteBuffer(bytes int) error { + return c.Conn.SetWriteBuffer(bytes) +} + func (c *SCIONPacketConn) SetWriteDeadline(d time.Time) error { return c.Conn.SetWriteDeadline(d) } @@ -146,9 +164,11 @@ func (c *SCIONPacketConn) SetWriteDeadline(d time.Time) error { func (c *SCIONPacketConn) ReadFrom(pkt *Packet, ov *net.UDPAddr) error { for { // Read until we get an error or a data packet - if err := c.readFrom(pkt, ov); err != nil { + remoteAddr, err := c.readFrom(pkt) + if err != nil { return err } + *ov = *remoteAddr if scmp, ok := pkt.Payload.(SCMPPayload); ok { if c.SCMPHandler == nil { metrics.CounterInc(c.Metrics.SCMPErrors) @@ -169,41 +189,145 @@ func (c *SCIONPacketConn) ReadFrom(pkt *Packet, ov *net.UDPAddr) error { } } -func (c *SCIONPacketConn) readFrom(pkt *Packet, ov *net.UDPAddr) error { +func (c *SCIONPacketConn) SyscallConn() (syscall.RawConn, error) { + return c.Conn.SyscallConn() +} + +func (c *SCIONPacketConn) readFrom(pkt *Packet) (*net.UDPAddr, error) { pkt.Prepare() - n, lastHopNetAddr, err := c.Conn.ReadFrom(pkt.Bytes) + n, remoteAddr, err := c.Conn.ReadFrom(pkt.Bytes) if err != nil { - metrics.CounterInc(c.Metrics.DispatcherErrors) - return serrors.WrapStr("Reliable socket read error", err) + metrics.CounterInc(c.Metrics.UnderlayConnectionErrors) + return nil, serrors.WrapStr("reading underlay connection", err) } metrics.CounterAdd(c.Metrics.ReadBytes, float64(n)) metrics.CounterInc(c.Metrics.ReadPackets) pkt.Bytes = pkt.Bytes[:n] - var lastHop *net.UDPAddr - - var ok bool - lastHop, ok = lastHopNetAddr.(*net.UDPAddr) - if !ok { - return serrors.New("Invalid lastHop address Type", - "Actual", lastHopNetAddr) - } - if err := pkt.Decode(); err != nil { metrics.CounterInc(c.Metrics.ParseErrors) - return serrors.WrapStr("decoding packet", err) + return nil, serrors.WrapStr("decoding packet", err) } - if ov != nil { - *ov = *lastHop + udpRemoteAddr := remoteAddr.(*net.UDPAddr) + lastHop := udpRemoteAddr + if c.isShimDispatcher(udpRemoteAddr) { + // XXX(JordiSubira): As stated in `SCIONPacketConn.isShimDispatcher()`, we consider + // *loopback:30041* as a shim address. + // However, if in an alternative setup we find an actual endhost behind + // *loopback:30041* `SCIONPacketConn.lastHop()` should yield the right next hop address. + lastHop, err = c.lastHop(pkt) + if err != nil { + return nil, serrors.WrapStr("extracting last hop based on packet path", err) + } } - return nil + return lastHop, nil } func (c *SCIONPacketConn) SetReadDeadline(d time.Time) error { return c.Conn.SetReadDeadline(d) } +func (c *SCIONPacketConn) LocalAddr() net.Addr { + return c.Conn.LocalAddr() +} + +// isShimDispatcher checks that udpAddr corresponds to the address where the +// shim is/should listen on. The shim only forwards packets whose underlay +// IP (i.e., the address on the UDP/IP header) corresponds to the SCION Destination +// address (i.e., the address on the UDP/SCION header). Therefore, the underlay address +// for the application using SCIONPacketConn will be the same as the underlay from where +// the shim dispatcher forwards the packets. +// +// A special case is the developer setup: we use a single shim dispatcher instance +// listening on *[::]* serving all services (sometimes from multiple ASes). +// In IPv4 context, the OS will pick *loopback* as the source IP when reflecting the packet +// from the shim dispatcher to the destination endhost. Thus, we check here if the packet +// comes from *loopback:30041*. +func (c *SCIONPacketConn) isShimDispatcher(udpAddr *net.UDPAddr) bool { + localAddr := c.LocalAddr().(*net.UDPAddr) + if udpAddr.IP.Equal(localAddr.IP) || + udpAddr.IP.IsLoopback() && + udpAddr.Port == underlay.EndhostPort { + return true + } + return false +} + +func (c *SCIONPacketConn) lastHop(p *Packet) (*net.UDPAddr, error) { + rpath, ok := p.Path.(RawPath) + if !ok { + return nil, serrors.New("path type not supported", "type", common.TypeOf(p.Path)) + } + switch rpath.PathType { + case empty.PathType: + if p.Source.Host.Type() != addr.HostTypeIP { + return nil, serrors.New("unexpected source address in packet", + "type", p.Source.Host.Type().String()) + } + return &net.UDPAddr{ + IP: p.Source.Host.IP().AsSlice(), + Port: func() int { + switch p := p.PacketInfo.Payload.(type) { + case UDPPayload: + return int(p.SrcPort) + default: + // Use endhost port for SCMP and unknown payloads. + return underlay.EndhostPort + } + }(), + }, nil + case onehop.PathType: + var path onehop.Path + if err := path.DecodeFromBytes(rpath.Raw); err != nil { + return nil, err + } + ifid := path.SecondHop.ConsIngress + if !path.Info.ConsDir { + ifid = path.SecondHop.ConsEgress + } + return c.interfaceMap.get(ifid) + case epic.PathType: + var path epic.Path + if err := path.DecodeFromBytes(rpath.Raw); err != nil { + return nil, err + } + infoField, err := path.ScionPath.GetCurrentInfoField() + if err != nil { + return nil, err + } + hf, err := path.ScionPath.GetCurrentHopField() + if err != nil { + return nil, err + } + ifid := hf.ConsIngress + if !infoField.ConsDir { + ifid = hf.ConsEgress + } + return c.interfaceMap.get(ifid) + case scion.PathType: + var path scion.Raw + if err := path.DecodeFromBytes(rpath.Raw); err != nil { + return nil, err + } + infoField, err := path.GetCurrentInfoField() + if err != nil { + return nil, err + } + hf, err := path.GetCurrentHopField() + if err != nil { + return nil, err + } + ifid := hf.ConsIngress + if !infoField.ConsDir { + ifid = hf.ConsEgress + } + return c.interfaceMap.get(ifid) + default: + return nil, serrors.New("unknown path type", "type", rpath.PathType.String()) + } +} + type SerializationOptions struct { // If ComputeChecksums is true, the checksums in sent Packets are // recomputed. Otherwise, the checksum value is left intact. @@ -218,3 +342,13 @@ type SerializationOptions struct { // unchanged. InitializePaths bool } + +type interfaceMap map[uint16]netip.AddrPort + +func (m interfaceMap) get(id uint16) (*net.UDPAddr, error) { + addrPort, ok := m[id] + if !ok { + return nil, serrors.New("interface number not found", "interface", id) + } + return net.UDPAddrFromAddrPort(addrPort), nil +} diff --git a/pkg/snet/reader.go b/pkg/snet/reader.go index d09ff6926f..06de4f5a38 100644 --- a/pkg/snet/reader.go +++ b/pkg/snet/reader.go @@ -16,6 +16,7 @@ package snet import ( "net" + "net/netip" "sync" "time" @@ -32,9 +33,8 @@ type ReplyPather interface { type scionConnReader struct { replyPather ReplyPather - - base *scionConnBase - conn PacketConn + conn PacketConn + local *UDPAddr mtx sync.Mutex buffer []byte @@ -61,10 +61,7 @@ func (c *scionConnReader) Read(b []byte) (int, error) { // read returns the number of bytes read, the address that sent the bytes and // an error (if one occurred). func (c *scionConnReader) read(b []byte) (int, *UDPAddr, error) { - if c.base.scionNet == nil { - return 0, nil, serrors.New("SCION network not initialized") - } - + // TODO(JordiSubira): Add UTs for this c.mtx.Lock() defer c.mtx.Unlock() @@ -90,7 +87,21 @@ func (c *scionConnReader) read(b []byte) (int, *UDPAddr, error) { if !ok { return 0, nil, serrors.New("unexpected payload", "type", common.TypeOf(pkt.Payload)) } - n := copy(b, udp.Payload) + + // XXX(JordiSubira): We explicitly forbid nil or unspecified address in the current constructor + // for Conn. + // If this were ever to change, we would always fall into the following if statement, then + // we would like to replace this logic (e.g., using IP_PKTINFO, with its caveats). + pktAddrPort := netip.AddrPortFrom(pkt.Destination.Host.IP(), udp.DstPort) + if c.local.IA != pkt.Destination.IA || + c.local.Host.AddrPort() != pktAddrPort { + return 0, nil, serrors.New("packet is destined to a different host", + "local_isd_as", c.local.IA, + "local_host", c.local.Host, + "pkt_destination_isd_as", pkt.Destination.IA, + "pkt_destination_host", pktAddrPort, + ) + } // Extract remote address. // Copy the address data to prevent races. See @@ -104,6 +115,7 @@ func (c *scionConnReader) read(b []byte) (int, *UDPAddr, error) { Path: replyPath, NextHop: CopyUDPAddr(&lastHop), } + n := copy(b, udp.Payload) return n, remote, nil } diff --git a/pkg/snet/dispatcher.go b/pkg/snet/scmp.go similarity index 75% rename from pkg/snet/dispatcher.go rename to pkg/snet/scmp.go index 99fb8cde08..45f1940c43 100644 --- a/pkg/snet/dispatcher.go +++ b/pkg/snet/scmp.go @@ -16,10 +16,8 @@ package snet import ( "context" - "net" "time" - "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/metrics" "github.com/scionproto/scion/pkg/private/common" @@ -27,45 +25,8 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/private/util" "github.com/scionproto/scion/pkg/slayers" - "github.com/scionproto/scion/pkg/sock/reliable" ) -// PacketDispatcherService constructs SCION sockets where applications have -// fine-grained control over header fields. -type PacketDispatcherService interface { - Register(ctx context.Context, ia addr.IA, registration *net.UDPAddr, - svc addr.SVC) (PacketConn, uint16, error) -} - -var _ PacketDispatcherService = (*DefaultPacketDispatcherService)(nil) - -// DefaultPacketDispatcherService parses/serializes packets received from / -// sent to the dispatcher. -type DefaultPacketDispatcherService struct { - // Dispatcher is used to get packets from the local SCION Dispatcher process. - Dispatcher reliable.Dispatcher - // SCMPHandler is invoked for packets that contain an SCMP L4. If the - // handler is nil, errors are returned back to applications every time an - // SCMP message is received. - SCMPHandler SCMPHandler - // Metrics injected into SCIONPacketConn. - SCIONPacketConnMetrics SCIONPacketConnMetrics -} - -func (s *DefaultPacketDispatcherService) Register(ctx context.Context, ia addr.IA, - registration *net.UDPAddr, svc addr.SVC) (PacketConn, uint16, error) { - - rconn, port, err := s.Dispatcher.Register(ctx, ia, registration, svc) - if err != nil { - return nil, 0, err - } - return &SCIONPacketConn{ - Conn: rconn, - SCMPHandler: s.SCMPHandler, - Metrics: s.SCIONPacketConnMetrics, - }, port, nil -} - // RevocationHandler is called by the default SCMP Handler whenever revocations are encountered. type RevocationHandler interface { // RevokeRaw handles a revocation received as raw bytes. @@ -79,7 +40,7 @@ type SCMPHandler interface { // // If the handler returns an error value, snet will propagate the error // back to the caller. If the return value is nil, snet will reattempt to - // read a data packet from the underlying dispatcher connection. + // read a data packet from the underlying connection. // // Handlers that wish to ignore SCMP can just return nil. // @@ -131,7 +92,6 @@ func (h DefaultSCMPHandler) Handle(pkt *Packet) error { return nil } } - func (h *DefaultSCMPHandler) handleSCMPRev(typeCode slayers.SCMPTypeCode, revInfo *path_mgmt.RevInfo) error { diff --git a/pkg/snet/snet.go b/pkg/snet/snet.go index c41c269e8c..e74235ecf7 100644 --- a/pkg/snet/snet.go +++ b/pkg/snet/snet.go @@ -20,31 +20,28 @@ // Listen methods on the networking context yields connections that run in that // context. // -// A connection can be created by calling Dial or Listen; both functions -// register an address-port pair with the local dispatcher. For Dial, the +// A connection can be created by calling Dial or Listen. For Dial, the // remote address is fixed, meaning only Read and Write can be used. Attempting // to ReadFrom or WriteTo a connection created by Dial is an invalid operation. // For Listen, the remote address cannot be fixed. ReadFrom can be used to read // from the connection and find out the sender's address; and WriteTo can be // used to send a message to a chosen destination. // -// Multiple networking contexts can share the same SCIOND and/or dispatcher. +// Multiple networking contexts can share the same SCIOND. // // Write calls never return SCMP errors directly. If a write call caused an // SCMP message to be received by the Conn, it can be inspected by calling // Read. In this case, the error value is non-nil and can be type asserted to // *OpError. Method SCMP() can be called on the error to extract the SCMP // header. -// -// Important: not draining SCMP errors via Read calls can cause the dispatcher -// to shutdown the socket (see https://github.com/scionproto/scion/pull/1356). -// To prevent this on a Conn object with only Write calls, run a separate -// goroutine that continuously calls Read on the Conn. package snet import ( "context" + "errors" "net" + "net/netip" + "syscall" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" @@ -52,6 +49,13 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" ) +// Topology provides local-IA topology information +type Topology interface { + LocalIA(ctx context.Context) (addr.IA, error) + PortRange(ctx context.Context) (uint16, uint16, error) + Interfaces(ctx context.Context) (map[uint16]netip.AddrPort, error) +} + var _ Network = (*SCIONNetwork)(nil) type SCIONNetworkMetrics struct { @@ -63,92 +67,152 @@ type SCIONNetworkMetrics struct { // SCIONNetwork is the SCION networking context. type SCIONNetwork struct { - LocalIA addr.IA - Dispatcher PacketDispatcherService + // Topology provides local AS information, needed to handle sockets and + // traffic. + Topology Topology // ReplyPather is used to create reply paths when reading packets on Conn // (that implements net.Conn). If unset, the default reply pather is used, // which parses the incoming path as a path.Path and reverses it. ReplyPather ReplyPather // Metrics holds the metrics emitted by the network. Metrics SCIONNetworkMetrics + // SCMPHandler describes the network behaviour upon receiving SCMP traffic. + SCMPHandler SCMPHandler + PacketConnMetrics SCIONPacketConnMetrics +} + +// OpenRaw returns a PacketConn which listens on the specified address. +// Nil or unspecified addresses are not supported. +// If the address port is 0 a valid and free SCION/UDP port is automatically chosen. +// Otherwise, the specified port must be a valid SCION/UDP port. +func (n *SCIONNetwork) OpenRaw(ctx context.Context, addr *net.UDPAddr) (PacketConn, error) { + var pconn *net.UDPConn + var err error + if addr == nil || addr.IP.IsUnspecified() { + return nil, serrors.New("nil or unspecified address is not supported") + } + start, end, err := n.Topology.PortRange(ctx) + if err != nil { + return nil, err + } + ifAddrs, err := n.Topology.Interfaces(ctx) + if err != nil { + return nil, err + } + if addr.Port == 0 { + pconn, err = listenUDPRange(addr, start, end) + } else { + if addr.Port < int(start) || addr.Port > int(end) { + // XXX(JordiSubira): We allow listening UDP/SCION outside the endhost range, + // however, in this setup the shim dispacher is needed to receive packets, i.e., + // BRs send packet to fix port 30041 (where the shim should be listening on) and + // the shim forwards it to underlay UDP/IP port (where we bind the UDP/SCION + // socket). + log.Info("Provided port is outside the SCION/UDP range, "+ + "it will only receive packets if shim dispatcher is configured", + "start", start, "end", end, "port", addr.Port) + } + pconn, err = net.ListenUDP(addr.Network(), addr) + } + if err != nil { + return nil, err + } + return &SCIONPacketConn{ + Conn: pconn, + SCMPHandler: n.SCMPHandler, + Metrics: n.PacketConnMetrics, + interfaceMap: ifAddrs, + }, nil } -// Dial returns a SCION connection to remote. Nil values for listen are not -// supported yet. Parameter network must be "udp". The returned connection's -// Read and Write methods can be used to receive and send SCION packets. -// Remote address requires a path and the underlay net hop to be set if the +// Dial returns a SCION connection to remote. Parameter network must be "udp". +// The returned connection's Read and Write methods can be used to receive +// and send SCION packets. +// Remote address requires a path and the underlay next hop to be set if the // destination is in a remote AS. // // The context is used for connection setup, it doesn't affect the returned // connection. func (n *SCIONNetwork) Dial(ctx context.Context, network string, listen *net.UDPAddr, - remote *UDPAddr, svc addr.SVC) (*Conn, error) { + remote *UDPAddr) (*Conn, error) { + // XXX(JordiSubira): Currently Dial does not check that received packets are + // originated from the expected remote address. This should be adapted to + // check that the remote packets are originated from the expected remote address. metrics.CounterInc(n.Metrics.Dials) + if network != "udp" { + return nil, serrors.New("Unknown network", "network", network) + } if remote == nil { return nil, serrors.New("Unable to dial to nil remote") } - conn, err := n.Listen(ctx, network, listen, svc) + packetConn, err := n.OpenRaw(ctx, listen) if err != nil { return nil, err } - conn.remote = remote.Copy() - return conn, nil + log.FromCtx(ctx).Debug("UDP socket opened on", "addr", packetConn.LocalAddr(), "to", remote) + return NewCookedConn(packetConn, n.Topology, WithReplyPather(n.ReplyPather), WithRemote(remote)) } -// Listen registers listen with the dispatcher. Nil values for listen are -// not supported yet. The returned connection's ReadFrom and WriteTo methods +// Listen opens a Conn. The returned connection's ReadFrom and WriteTo methods // can be used to receive and send SCION packets with per-packet addressing. // Parameter network must be "udp". +// Nil or unspecified addresses are not supported. // // The context is used for connection setup, it doesn't affect the returned // connection. -func (n *SCIONNetwork) Listen(ctx context.Context, network string, listen *net.UDPAddr, - svc addr.SVC) (*Conn, error) { +func (n *SCIONNetwork) Listen( + ctx context.Context, + network string, + listen *net.UDPAddr, +) (*Conn, error) { metrics.CounterInc(n.Metrics.Listens) - if network != "udp" { return nil, serrors.New("Unknown network", "network", network) } - - // FIXME(scrye): If no local address is specified, we want to - // bind to the address of the outbound interface on a random - // free port. However, the current dispatcher version cannot - // expose that address. Additionally, the dispatcher does not follow - // normal operating system semantics for binding on 0.0.0.0 (it - // considers it to be a fixed address instead of a wildcard). To avoid - // misuse, disallow binding to nil or 0.0.0.0 addresses for now. - if listen == nil { - return nil, serrors.New("nil listen addr not supported") - } - if listen.IP == nil { - return nil, serrors.New("nil listen IP not supported") - } - if listen.IP.IsUnspecified() { - return nil, serrors.New("unspecified listen IP not supported") - } - conn := scionConnBase{ - scionNet: n, - svc: svc, - listen: &UDPAddr{ - IA: n.LocalIA, - Host: CopyUDPAddr(listen), - }, - } - packetConn, port, err := n.Dispatcher.Register(ctx, n.LocalIA, listen, svc) + packetConn, err := n.OpenRaw(ctx, listen) if err != nil { return nil, err } - if port != uint16(listen.Port) { - conn.listen.Host.Port = int(port) - } - log.Debug("Registered with dispatcher", "addr", conn.listen) + log.FromCtx(ctx).Debug("UDP socket openned on", "addr", packetConn.LocalAddr()) + return NewCookedConn(packetConn, n.Topology, WithReplyPather(n.ReplyPather)) +} - replyPather := n.ReplyPather - if replyPather == nil { - replyPather = DefaultReplyPather{} +func listenUDPRange(addr *net.UDPAddr, start, end uint16) (*net.UDPConn, error) { + // XXX(JordiSubira): For now, we iterate on the complete SCION/UDP + // range, in decreasing order, taking the first unused port. + // + // If the defined range, intersects with the well-known port range, i.e., + // 1-1023, we just start considering from 1024 onwards. + // The decreasing order first try to use the higher port numbers, normally used + // by ephemeral connections, letting free the lower port numbers, normally used + // by longer-lived applications, e.g., server applications. + // + // Ideally we would only take a standard ephemeral range, e.g., 32768-65535, + // Unfortunately, this range was ocuppied by the old dispatcher. + // The default range for the dispatched ports is 31000-32767. + // By configuration other port ranges may be defined and restricting to the default + // range for applications may cause problems. + // + // TODO: Replace this implementation with pseudorandom port checking. + restrictedStart := start + if start < 1024 { + restrictedStart = 1024 } - - return newConn(conn, packetConn, replyPather), nil + for port := end; port >= restrictedStart; port-- { + pconn, err := net.ListenUDP(addr.Network(), &net.UDPAddr{ + IP: addr.IP, + Port: int(port), + }) + if err == nil { + return pconn, nil + } + if errors.Is(err, syscall.EADDRINUSE) { + continue + } + return nil, err + } + return nil, serrors.WrapStr("binding to port range", syscall.EADDRINUSE, + "start", restrictedStart, "end", end) } diff --git a/pkg/snet/writer.go b/pkg/snet/writer.go index 70609f1023..f6661b1d51 100644 --- a/pkg/snet/writer.go +++ b/pkg/snet/writer.go @@ -24,12 +24,15 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/private/topology/underlay" + "github.com/scionproto/scion/private/topology" ) type scionConnWriter struct { - base *scionConnBase - conn PacketConn + conn PacketConn + local *UDPAddr + remote *UDPAddr + dispatchedPortStart uint16 + dispatchedPortEnd uint16 mtx sync.Mutex buffer []byte @@ -55,10 +58,14 @@ func (c *scionConnWriter) WriteTo(b []byte, raddr net.Addr) (int, error) { dst = SCIONAddress{IA: a.IA, Host: addr.HostIP(hostIP)} port, path = a.Host.Port, a.Path nextHop = a.NextHop - if nextHop == nil && c.base.scionNet.LocalIA.Equal(a.IA) { + if nextHop == nil && c.local.IA.Equal(a.IA) { + port := a.Host.Port + if !c.isWithinRange(port) { + port = topology.EndhostPort + } nextHop = &net.UDPAddr{ IP: a.Host.IP, - Port: underlay.EndhostPort, + Port: port, Zone: a.Host.Zone, } @@ -71,9 +78,9 @@ func (c *scionConnWriter) WriteTo(b []byte, raddr net.Addr) (int, error) { "addr", fmt.Sprintf("%v(%T)", a, a)) } - listenHostIP, ok := netip.AddrFromSlice(c.base.listen.Host.IP) + listenHostIP, ok := netip.AddrFromSlice(c.local.Host.IP) if !ok { - return 0, serrors.New("invalid listen host IP", "ip", c.base.listen.Host.IP) + return 0, serrors.New("invalid listen host IP", "ip", c.local.Host.IP) } pkt := &Packet{ @@ -81,12 +88,12 @@ func (c *scionConnWriter) WriteTo(b []byte, raddr net.Addr) (int, error) { PacketInfo: PacketInfo{ Destination: dst, Source: SCIONAddress{ - IA: c.base.scionNet.LocalIA, + IA: c.local.IA, Host: addr.HostIP(listenHostIP), }, Path: path, Payload: UDPPayload{ - SrcPort: uint16(c.base.listen.Host.Port), + SrcPort: uint16(c.local.Host.Port), DstPort: uint16(port), Payload: b, }, @@ -104,9 +111,13 @@ func (c *scionConnWriter) WriteTo(b []byte, raddr net.Addr) (int, error) { // Write sends b through a connection with fixed remote address. If the remote // address for the connection is unknown, Write returns an error. func (c *scionConnWriter) Write(b []byte) (int, error) { - return c.WriteTo(b, c.base.remote) + return c.WriteTo(b, c.remote) } func (c *scionConnWriter) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } + +func (c *scionConnWriter) isWithinRange(port int) bool { + return port >= int(c.dispatchedPortStart) && port <= int(c.dispatchedPortEnd) +} diff --git a/pkg/sock/reliable/BUILD.bazel b/pkg/sock/reliable/BUILD.bazel deleted file mode 100644 index 8a54bb26a7..0000000000 --- a/pkg/sock/reliable/BUILD.bazel +++ /dev/null @@ -1,41 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "errors.go", - "frame.go", - "packetizer.go", - "registration.go", - "reliable.go", - "util.go", - ], - importpath = "github.com/scionproto/scion/pkg/sock/reliable", - visibility = ["//visibility:public"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/common:go_default_library", - "//pkg/private/prom:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/sock/reliable/internal/metrics:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "errors_test.go", - "frame_test.go", - "packetizer_test.go", - "registration_test.go", - ], - embed = [":go_default_library"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/private/mocks/net/mock_net:go_default_library", - "//pkg/private/xtest:go_default_library", - "@com_github_golang_mock//gomock:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - ], -) diff --git a/pkg/sock/reliable/errors.go b/pkg/sock/reliable/errors.go deleted file mode 100644 index 33a0ff9b99..0000000000 --- a/pkg/sock/reliable/errors.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018 ETH Zurich, 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 reliable - -import ( - "errors" - "io" - "syscall" - - "github.com/scionproto/scion/pkg/private/common" -) - -// Possible errors -var ( - ErrNoAddress common.ErrMsg = "no address found" - ErrNoPort common.ErrMsg = "missing port" - ErrPayloadTooLong common.ErrMsg = "payload too long" - ErrIncompleteFrameHeader common.ErrMsg = "incomplete frame header" - ErrBadFrameLength common.ErrMsg = "bad frame length" - ErrBadCookie common.ErrMsg = "bad cookie" - ErrBadAddressType common.ErrMsg = "bad address type" - ErrIncompleteAddress common.ErrMsg = "incomplete IP address" - ErrIncompletePort common.ErrMsg = "incomplete UDP port" - ErrIncompleteMessage common.ErrMsg = "incomplete message" - ErrBadLength common.ErrMsg = "bad length" - ErrBufferTooSmall common.ErrMsg = "buffer too small" -) - -func IsDispatcherError(err error) bool { - // On Linux, the following errors should prompt a reconnect: - // - An EOF, when a Read happens to a connection that was closed at the - // other end, and there is no outstanding outgoing data. - // - An EPIPE, when a Write happens to a connection that was closed at - // the other end. - // - An ECONNRESET, when a Read happens to a connection that was - // closed at the other end, and there is outstanding outgoing data. An - // ECONNRESET may be followed by EOF on repeated attempts. - // All other errors can be immediately propagated back to the application. - return errors.Is(err, io.EOF) || - errors.Is(err, syscall.EPIPE) || - errors.Is(err, syscall.ECONNRESET) -} diff --git a/pkg/sock/reliable/errors_test.go b/pkg/sock/reliable/errors_test.go deleted file mode 100644 index 097db199e2..0000000000 --- a/pkg/sock/reliable/errors_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2022 SCION Association -// -// 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 reliable_test - -import ( - "fmt" - "io" - "net" - "os" - "syscall" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/scionproto/scion/pkg/sock/reliable" -) - -func TestIsDispatcherError(t *testing.T) { - cases := map[string]struct { - err error - expected bool - }{ - "nil": { - err: nil, - expected: false, - }, - "io.EOF": { - err: io.EOF, - expected: true, - }, - "io.EOF wrapped": { - err: fmt.Errorf("aha, end of the file %w", io.EOF), - expected: true, - }, - "syscall EPIPE": { - err: syscall.EPIPE, - expected: true, - }, - "OpError EPIPE": { - err: &net.OpError{Err: &os.SyscallError{Err: syscall.EPIPE}}, - expected: true, - }, - "Wrapped OpError EPIPE": { - err: fmt.Errorf("foo %w", - &net.OpError{Err: &os.SyscallError{Err: syscall.ECONNRESET}}), - expected: true, - }, - "OpError ECONNRESET": { - err: &net.OpError{Err: &os.SyscallError{Err: syscall.ECONNRESET}}, - expected: true, - }, - "OpError other errno": { - err: &net.OpError{Err: &os.SyscallError{Err: syscall.EACCES}}, - expected: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := reliable.IsDispatcherError(c.err) - assert.Equal(t, c.expected, actual, c.err) - }) - } -} diff --git a/pkg/sock/reliable/frame.go b/pkg/sock/reliable/frame.go deleted file mode 100644 index 31b8b938af..0000000000 --- a/pkg/sock/reliable/frame.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "encoding/binary" - "net" - - "github.com/scionproto/scion/pkg/private/serrors" -) - -// UnderlayPacket contains metadata about a SCION packet going through the -// reliable socket framing protocol. -type UnderlayPacket struct { - Address *net.UDPAddr - Payload []byte -} - -func (p *UnderlayPacket) SerializeTo(b []byte) (int, error) { - var f frame - f.Cookie = expectedCookie - f.AddressType = byte(getAddressType(p.Address)) - f.Length = uint32(len(p.Payload)) - if p.Address != nil { - if err := f.insertAddress(p.Address); err != nil { - return 0, err - } - } - f.Payload = p.Payload - return f.SerializeTo(b) -} - -func (p *UnderlayPacket) DecodeFromBytes(b []byte) error { - var f frame - if err := f.DecodeFromBytes(b); err != nil { - return err - } - if f.Cookie != expectedCookie { - return ErrBadCookie - } - p.Address = f.extractAddress() - p.Payload = f.Payload - return nil -} - -// frame describes the wire format of the reliable socket framing protocol. -type frame struct { - Cookie uint64 - AddressType byte - Length uint32 - Address []byte - Port []byte - Payload []byte -} - -func (f *frame) SerializeTo(b []byte) (int, error) { - totalLength := f.length() - if totalLength > len(b) { - return 0, serrors.WithCtx(ErrBufferTooSmall, "have", len(b), "want", totalLength) - } - binary.BigEndian.PutUint64(b, f.Cookie) - b[8] = f.AddressType - binary.BigEndian.PutUint32(b[9:], f.Length) - copy(b[13:], f.Address) - copy(b[13+len(f.Address):], f.Port) - copy(b[13+len(f.Address)+len(f.Port):], f.Payload) - return totalLength, nil -} - -func (f *frame) DecodeFromBytes(data []byte) error { - if len(data) < f.headerLength() { - return ErrIncompleteFrameHeader - } - f.Cookie = binary.BigEndian.Uint64(data) - f.AddressType = data[8] - f.Length = binary.BigEndian.Uint32(data[9:]) - offset := 13 - addressType := hostAddrType(f.AddressType) - if !isValidReliableSockDestination(addressType) { - return serrors.WithCtx(ErrBadAddressType, "type", addressType) - } - addrLen := getAddressLength(addressType) - portLen := getPortLength(addressType) - if len(data[offset:]) < addrLen { - return ErrIncompleteAddress - } - f.Address = data[offset : offset+addrLen] - offset += addrLen - if len(data[offset:]) < portLen { - return ErrIncompletePort - } - f.Port = data[offset : offset+portLen] - offset += portLen - f.Payload = data[offset:] - if len(f.Payload) != int(f.Length) { - return ErrBadLength - } - return nil -} - -// length returns the total length of the frame (including payload). -func (f *frame) length() int { - return f.headerLength() + len(f.Address) + len(f.Port) + len(f.Payload) -} - -// header length returns the length of the fixed size start of the frame -// (cookie, address type and payload length field). -func (f *frame) headerLength() int { - return 8 + 1 + 4 -} - -func (f *frame) insertAddress(address *net.UDPAddr) error { - if address.IP == nil || address.IP.IsUnspecified() { - return ErrNoAddress - } - if address.Port == 0 { - return ErrNoPort - } - f.Address = []byte(normalizeIP(address.IP)) - f.Port = make([]byte, 2) - binary.BigEndian.PutUint16(f.Port, uint16(address.Port)) - return nil -} - -func (f *frame) extractAddress() *net.UDPAddr { - t := hostAddrType(f.AddressType) - if t == hostTypeIPv4 || t == hostTypeIPv6 { - return &net.UDPAddr{ - IP: net.IP(f.Address), - Port: int(binary.BigEndian.Uint16(f.Port)), - } - } - return nil -} diff --git a/pkg/sock/reliable/frame_test.go b/pkg/sock/reliable/frame_test.go deleted file mode 100644 index a93dfe0554..0000000000 --- a/pkg/sock/reliable/frame_test.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "net" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestUnderlayPacketSerializeTo(t *testing.T) { - type TestCase struct { - Name string - Packet *UnderlayPacket - ExpectedData []byte - ExpectedError error - } - testCases := []TestCase{ - { - Name: "none type address, no data", - Packet: &UnderlayPacket{}, - ExpectedData: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 0, 0, 0, 0, 0}, - }, - { - Name: "empty IP address", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{}, - }, - ExpectedError: ErrNoAddress, - ExpectedData: []byte{}, - }, - { - Name: "IPv4 host, with address, no port, no data", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("1.2.3.4")}, - }, - ExpectedError: ErrNoPort, - ExpectedData: []byte{}, - }, - { - Name: "IPv4 host, with address, with port, no data", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("10.2.3.4"), Port: 80}, - }, - ExpectedData: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 0, - 10, 2, 3, 4, 0, 80}, - }, - { - Name: "IPv6 host, with address, with port, no data", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80}, - }, - ExpectedData: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 2, 0, 0, 0, 0, - 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 80}, - }, - { - Name: "IPv4 host, with address, big port, no data", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("10.2.3.4"), Port: 0x1234}, - }, - ExpectedData: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 0, - 10, 2, 3, 4, 0x12, 0x34}, - }, - { - Name: "long payload", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 80}, - Payload: make([]byte, 2000), - }, - ExpectedError: ErrBufferTooSmall, - ExpectedData: []byte{}, - }, - { - Name: "good payload", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("10.2.3.4"), Port: 80}, - Payload: []byte{10, 5, 6, 7}, - }, - ExpectedData: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 4, - 10, 2, 3, 4, 0, 80, 10, 5, 6, 7}, - }, - } - t.Run("Different packets serialize correctly", func(t *testing.T) { - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - b := make([]byte, 1500) - n, err := tc.Packet.SerializeTo(b) - assert.ErrorIs(t, err, tc.ExpectedError) - assert.Equal(t, tc.ExpectedData, b[:n]) - }) - } - }) -} - -func TestUnderlayPacketDecodeFromBytes(t *testing.T) { - type TestCase struct { - Name string - Buffer []byte - ExpectedPacket UnderlayPacket - ExpectedError error - } - testCases := []TestCase{ - { - Name: "incomplete header", - Buffer: []byte{0xaa}, - ExpectedError: ErrIncompleteFrameHeader, - }, - { - Name: "bad cookie", - Buffer: []byte{0xaa, 0xbb, 0xaa, 0xbb, 0xaa, 0xbb, 0xaa, 0xbb, 0, 0, 0, 0, 0}, - ExpectedError: ErrBadCookie, - }, - { - Name: "bad address type", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 3, 0, 0, 0, 0}, - ExpectedError: ErrBadAddressType, - }, - { - Name: "incomplete address", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 0, - 10, 2, 3}, - ExpectedError: ErrIncompleteAddress, - }, - { - Name: "incomplete port", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 0, - 10, 2, 3, 4, 0}, - ExpectedError: ErrIncompletePort, - }, - { - Name: "bad length (underflow)", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 0, - 10, 2, 3, 4, 0, 80, 42}, - ExpectedError: ErrBadLength, - }, - { - Name: "bad length (overflow)", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 2, - 10, 2, 3, 4, 0, 80, 42}, - ExpectedError: ErrBadLength, - }, - { - Name: "good packet (none type address)", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 0, 0, 0, 0, 1, 42}, - ExpectedPacket: UnderlayPacket{ - Payload: []byte{42}, - }, - }, - { - Name: "good packet (IPv4)", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 1, - 10, 2, 3, 4, 0, 80, 42}, - ExpectedPacket: UnderlayPacket{ - Address: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - Payload: []byte{42}, - }, - }, - { - Name: "good packet (IPv6)", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 2, 0, 0, 0, 1, - 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 80, 42}, - ExpectedPacket: UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80}, - Payload: []byte{42}, - }, - }, - } - t.Run("Different packets decode correctly", func(t *testing.T) { - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - var p UnderlayPacket - err := p.DecodeFromBytes(tc.Buffer) - assert.ErrorIs(t, err, tc.ExpectedError) - assert.Equal(t, tc.ExpectedPacket, p) - }) - } - }) -} diff --git a/pkg/sock/reliable/internal/metrics/BUILD.bazel b/pkg/sock/reliable/internal/metrics/BUILD.bazel deleted file mode 100644 index 6ca88bea68..0000000000 --- a/pkg/sock/reliable/internal/metrics/BUILD.bazel +++ /dev/null @@ -1,21 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["metrics.go"], - importpath = "github.com/scionproto/scion/pkg/sock/reliable/internal/metrics", - visibility = ["//pkg/sock/reliable:__subpackages__"], - deps = [ - "//pkg/private/prom:go_default_library", - "@com_github_prometheus_client_golang//prometheus:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["metrics_test.go"], - deps = [ - ":go_default_library", - "//pkg/private/prom/promtest:go_default_library", - ], -) diff --git a/pkg/sock/reliable/internal/metrics/metrics.go b/pkg/sock/reliable/internal/metrics/metrics.go deleted file mode 100644 index 774869b257..0000000000 --- a/pkg/sock/reliable/internal/metrics/metrics.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "github.com/prometheus/client_golang/prometheus" - - "github.com/scionproto/scion/pkg/private/prom" -) - -// Namespace is the metrics namespace for the metrics in this package. -const Namespace = "lib" - -const sub = "reliable" - -var ( - // M exposes all the initialized metrics for this package. - M = newMetrics() -) - -// DialLabels contains the labels for Dial calls. -type DialLabels struct { - Result string -} - -// Labels returns the list of labels. -func (l DialLabels) Labels() []string { - return []string{prom.LabelResult} -} - -// Values returns the label values in the order defined by Labels. -func (l DialLabels) Values() []string { - return []string{l.Result} -} - -// RegisterLabels contains the labels for Register calls. -type RegisterLabels struct { - Result string - SVC string -} - -// Labels returns the list of labels. -func (l RegisterLabels) Labels() []string { - return []string{prom.LabelResult, "svc"} -} - -// Values returns the label values in the order defined by Labels. -func (l RegisterLabels) Values() []string { - return []string{l.Result, l.SVC} -} - -// IOLabels contains the labels for Read and Write calls. -type IOLabels struct { - Result string -} - -// Labels returns the list of labels. -func (l IOLabels) Labels() []string { - return []string{prom.LabelResult} -} - -// Values returns the label values in the order defined by Labels. -func (l IOLabels) Values() []string { - return []string{l.Result} -} - -type metrics struct { - dials *prometheus.CounterVec - registers *prometheus.CounterVec - reads *prometheus.HistogramVec - writes *prometheus.HistogramVec -} - -func newMetrics() metrics { - return metrics{ - dials: prom.NewCounterVecWithLabels(Namespace, sub, "dials_total", - "Total number of Dial calls.", DialLabels{}), - registers: prom.NewCounterVecWithLabels(Namespace, sub, "registers_total", - "Total number of Register calls.", RegisterLabels{}), - reads: prom.NewHistogramVecWithLabels(Namespace, sub, "reads_total", - "Total number of Read calls", IOLabels{}, prom.DefaultSizeBuckets), - writes: prom.NewHistogramVecWithLabels(Namespace, sub, "writes_total", - "Total number of Write calls", IOLabels{}, prom.DefaultSizeBuckets), - } -} - -func (m metrics) Dials(l DialLabels) prometheus.Counter { - return m.dials.WithLabelValues(l.Values()...) -} - -func (m metrics) Registers(l RegisterLabels) prometheus.Counter { - return m.registers.WithLabelValues(l.Values()...) -} - -func (m metrics) Reads(l IOLabels) prometheus.Observer { - return m.reads.WithLabelValues(l.Values()...) -} - -func (m metrics) Writes(l IOLabels) prometheus.Observer { - return m.writes.WithLabelValues(l.Values()...) -} diff --git a/pkg/sock/reliable/internal/metrics/metrics_test.go b/pkg/sock/reliable/internal/metrics/metrics_test.go deleted file mode 100644 index 4df6f08e97..0000000000 --- a/pkg/sock/reliable/internal/metrics/metrics_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics_test - -import ( - "testing" - - "github.com/scionproto/scion/pkg/private/prom/promtest" - "github.com/scionproto/scion/pkg/sock/reliable/internal/metrics" -) - -func TestLabels(t *testing.T) { - promtest.CheckLabelsStruct(t, metrics.DialLabels{}) - promtest.CheckLabelsStruct(t, metrics.RegisterLabels{}) - promtest.CheckLabelsStruct(t, metrics.IOLabels{}) -} diff --git a/pkg/sock/reliable/mock_reliable/BUILD.bazel b/pkg/sock/reliable/mock_reliable/BUILD.bazel deleted file mode 100644 index baaefd8ff1..0000000000 --- a/pkg/sock/reliable/mock_reliable/BUILD.bazel +++ /dev/null @@ -1,21 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") -load("@io_bazel_rules_go//go:def.bzl", "gomock") - -gomock( - name = "go_default_mock", - out = "mock.go", - interfaces = ["Dispatcher"], - library = "//pkg/sock/reliable:go_default_library", - package = "mock_reliable", -) - -go_library( - name = "go_default_library", - srcs = ["mock.go"], - importpath = "github.com/scionproto/scion/pkg/sock/reliable/mock_reliable", - visibility = ["//visibility:public"], - deps = [ - "//pkg/addr:go_default_library", - "@com_github_golang_mock//gomock:go_default_library", - ], -) diff --git a/pkg/sock/reliable/mock_reliable/mock.go b/pkg/sock/reliable/mock_reliable/mock.go deleted file mode 100644 index 6dd9215859..0000000000 --- a/pkg/sock/reliable/mock_reliable/mock.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/scionproto/scion/pkg/sock/reliable (interfaces: Dispatcher) - -// Package mock_reliable is a generated GoMock package. -package mock_reliable - -import ( - context "context" - net "net" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - addr "github.com/scionproto/scion/pkg/addr" -) - -// MockDispatcher is a mock of Dispatcher interface. -type MockDispatcher struct { - ctrl *gomock.Controller - recorder *MockDispatcherMockRecorder -} - -// MockDispatcherMockRecorder is the mock recorder for MockDispatcher. -type MockDispatcherMockRecorder struct { - mock *MockDispatcher -} - -// NewMockDispatcher creates a new mock instance. -func NewMockDispatcher(ctrl *gomock.Controller) *MockDispatcher { - mock := &MockDispatcher{ctrl: ctrl} - mock.recorder = &MockDispatcherMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockDispatcher) EXPECT() *MockDispatcherMockRecorder { - return m.recorder -} - -// Register mocks base method. -func (m *MockDispatcher) Register(arg0 context.Context, arg1 addr.IA, arg2 *net.UDPAddr, arg3 addr.SVC) (net.PacketConn, uint16, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Register", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(net.PacketConn) - ret1, _ := ret[1].(uint16) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Register indicates an expected call of Register. -func (mr *MockDispatcherMockRecorder) Register(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockDispatcher)(nil).Register), arg0, arg1, arg2, arg3) -} diff --git a/pkg/sock/reliable/packetizer.go b/pkg/sock/reliable/packetizer.go deleted file mode 100644 index 38485bf395..0000000000 --- a/pkg/sock/reliable/packetizer.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "encoding/binary" - "net" - - "github.com/scionproto/scion/pkg/private/common" - "github.com/scionproto/scion/pkg/private/serrors" -) - -// ReadPacketizer splits a stream of reliable socket frames into packets. -// -// FIXME(scrye): This will be deleted when we move to SEQPACKET. -type ReadPacketizer struct { - buffer [common.SupportedMTU]byte - data []byte - freeSpace []byte - conn net.Conn -} - -func NewReadPacketizer(conn net.Conn) *ReadPacketizer { - packetizer := &ReadPacketizer{conn: conn} - packetizer.freeSpace = packetizer.buffer[:] - packetizer.data = packetizer.buffer[0:0] - return packetizer -} - -func (r *ReadPacketizer) Read(b []byte) (int, error) { - for { - if packet := r.haveNextPacket(r.data); packet != nil { - if len(packet) > len(b) { - return 0, serrors.WithCtx(ErrBufferTooSmall, - "have", len(b), "want", len(packet)) - } - copy(b, packet) - r.deleteData(len(packet)) - return len(packet), nil - } - n, err := r.conn.Read(r.freeSpace) - if err != nil { - return 0, err - } - r.addData(n) - } -} - -func (r *ReadPacketizer) deleteData(count int) { - copy(r.buffer[:], r.buffer[count:r.availableData()]) - r.updateSlices(r.availableData() - count) -} - -func (r *ReadPacketizer) addData(count int) { - r.updateSlices(r.availableData() + count) -} - -func (r *ReadPacketizer) availableData() int { - return len(r.data) -} - -func (r *ReadPacketizer) updateSlices(availableData int) { - r.data = r.buffer[:availableData] - r.freeSpace = r.buffer[availableData:] -} - -// haveNextPacket returns a slice with the next packet in b, or nil, if a full -// packet is not available. -func (reader *ReadPacketizer) haveNextPacket(b []byte) []byte { - if len(b) < 13 { - return nil - } - rcvdAddrType := b[8] - payloadLength := binary.BigEndian.Uint32(b[9:13]) - addressLength := getAddressLength(hostAddrType(rcvdAddrType)) - portLength := getPortLength(hostAddrType(rcvdAddrType)) - totalLength := 13 + addressLength + portLength + int(payloadLength) - if len(b) < totalLength { - return nil - } - return b[:totalLength] -} - -// WriteStreamer sends a packet via a stream. It is guaranteed to block until -// the whole packet has been sent (or an error occurred). -// -// FIXME(scrye): This will be delete when we move to SEQPACKET. -type WriteStreamer struct { - conn net.Conn -} - -func NewWriteStreamer(conn net.Conn) *WriteStreamer { - return &WriteStreamer{conn: conn} -} - -func (writer *WriteStreamer) Write(b []byte) error { - var err error - for bytesWritten, n := 0, 0; bytesWritten != len(b); bytesWritten += n { - n, err = writer.conn.Write(b[bytesWritten:]) - if err != nil { - return err - } - } - return nil -} diff --git a/pkg/sock/reliable/packetizer_test.go b/pkg/sock/reliable/packetizer_test.go deleted file mode 100644 index cda6ad3903..0000000000 --- a/pkg/sock/reliable/packetizer_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "testing" - - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" - - "github.com/scionproto/scion/pkg/private/mocks/net/mock_net" -) - -func TestReadPacketizer(t *testing.T) { - // FIXME(scrye): This will get deleted when we move from to SEQPACKET. - t.Run("Packetizer should extract multiple packets from an input stream", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - data := []byte{ - 0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 2, 0, 0, 0, 1, - 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 80, 42, - 0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 2, 0, 0, 0, 1, - 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 80, 42, - } - conn := mock_net.NewMockConn(ctrl) - conn.EXPECT().Read(gomock.Any()).DoAndReturn( - func(b []byte) (int, error) { - max := 5 - if max > len(data) { - max = len(data) - } - n := copy(b, data[:max]) - data = data[n:] - return n, nil - }).AnyTimes() - packetizer := NewReadPacketizer(conn) - b := make([]byte, 128) - n, err := packetizer.Read(b) - assert.NoError(t, err, "first packet err") - assert.Equal(t, 32, n, "first packet size") - n, err = packetizer.Read(b) - assert.NoError(t, err, "second packet err") - assert.Equal(t, 32, n, "second packet err") - }) -} - -func TestWriteStreamer(t *testing.T) { - // FIXME(scrye): This will get deleted when we move from to SEQPACKET. - t.Run("Streamer should do repeated calls to send a full message", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - data := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} - conn := mock_net.NewMockConn(ctrl) - gomock.InOrder( - conn.EXPECT().Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}).Return(4, nil), - conn.EXPECT().Write([]byte{5, 6, 7, 8, 9, 10}).Return(4, nil), - conn.EXPECT().Write([]byte{9, 10}).Return(2, nil), - ) - streamer := NewWriteStreamer(conn) - err := streamer.Write(data) - assert.NoError(t, err) - }) -} diff --git a/pkg/sock/reliable/reconnect/BUILD.bazel b/pkg/sock/reliable/reconnect/BUILD.bazel deleted file mode 100644 index 0ef4bfcc84..0000000000 --- a/pkg/sock/reliable/reconnect/BUILD.bazel +++ /dev/null @@ -1,48 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "conn.go", - "doc.go", - "errors.go", - "io.go", - "network.go", - "reconnecter.go", - "util.go", - ], - importpath = "github.com/scionproto/scion/pkg/sock/reliable/reconnect", - visibility = ["//visibility:public"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/sock/reliable:go_default_library", - "//pkg/sock/reliable/reconnect/internal/metrics:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "conn_io_test.go", - "main_test.go", - "network_test.go", - "reconnecter_test.go", - "util_test.go", - ], - deps = [ - ":go_default_library", - "//pkg/addr:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/mocks/net/mock_net:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/private/xtest:go_default_library", - "//pkg/snet:go_default_library", - "//pkg/sock/reliable/mock_reliable:go_default_library", - "//pkg/sock/reliable/reconnect/mock_reconnect:go_default_library", - "@com_github_golang_mock//gomock:go_default_library", - "@com_github_smartystreets_goconvey//convey:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - ], -) diff --git a/pkg/sock/reliable/reconnect/conn.go b/pkg/sock/reliable/reconnect/conn.go deleted file mode 100644 index 866bc06294..0000000000 --- a/pkg/sock/reliable/reconnect/conn.go +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import ( - "context" - "net" - "sync" - "time" - - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable" -) - -var _ net.PacketConn = (*PacketConn)(nil) - -type PacketConn struct { - // connMtx protects read/write access to connection information. connMtx must - // not be held when running methods on the connection. - connMtx sync.Mutex - dispConn net.PacketConn - - // readMtx is used to ensure only one reader enters the main I/O loop. - readMtx sync.Mutex - // writeMtx is used to ensure only one writer enters the main I/O loop. - writeMtx sync.Mutex - // spawnReconnecterMtx is used to ensure a single goroutine starts the - // reconnecter. This must be acquired with either readMtx or writeMtx - // taken. - spawnReconnecterMtx sync.Mutex - - writeDeadlineMtx sync.Mutex - writeDeadline time.Time - - readDeadlineMtx sync.Mutex - readDeadline time.Time - - dispatcherState *State - reconnecter Reconnecter - deadlineChangedEvent chan struct{} - // fatalError is written to by the async reconnecter on fatal errors, and then closed - fatalError chan error - // closeCh is closed when Close() is called, thus starting clean-up - closeCh chan struct{} - // closeMtx is used to guarantee that a single goroutine enters Close - closeMtx sync.Mutex -} - -func NewPacketConn(dispConn net.PacketConn, reconnecter Reconnecter) *PacketConn { - return &PacketConn{ - dispConn: dispConn, - dispatcherState: NewState(), - reconnecter: reconnecter, - deadlineChangedEvent: make(chan struct{}, 1), - fatalError: make(chan error, 1), - closeCh: make(chan struct{}), - } -} - -func (conn *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { - op := &ReadFromOperation{} - op.buffer = b - err := conn.DoIO(op) - return op.numBytes, op.address, err -} - -func (conn *PacketConn) WriteTo(b []byte, address net.Addr) (int, error) { - op := &WriteToOperation{} - op.buffer = b - op.address = address - err := conn.DoIO(op) - return op.numBytes, err -} - -func (conn *PacketConn) DoIO(op IOOperation) error { - conn.lockMutexForOpType(op) - defer conn.unlockMutexForOpType(op) - var err error -Loop: - for { - deadline := conn.getDeadlineForOpType(op) - select { - case <-conn.closeCh: - return ErrClosed - case <-conn.dispatcherState.Up(): - err = op.Do(conn.getConn()) - if err != nil { - if reliable.IsDispatcherError(err) && !conn.isClosing() { - conn.spawnAsyncReconnecterOnce() - continue - } else { - return err - } - } - break Loop - case err := <-conn.fatalError: - return err - case <-conn.deadlineChangedEvent: - case <-returnOnDeadline(deadline): - return ErrDispatcherDead - } - } - return nil -} - -func (conn *PacketConn) lockMutexForOpType(op IOOperation) { - if op.IsWrite() { - conn.writeMtx.Lock() - } else { - conn.readMtx.Lock() - } -} - -func (conn *PacketConn) unlockMutexForOpType(op IOOperation) { - if op.IsWrite() { - conn.writeMtx.Unlock() - } else { - conn.readMtx.Unlock() - } -} - -func (conn *PacketConn) spawnAsyncReconnecterOnce() { - conn.spawnReconnecterMtx.Lock() - select { - case <-conn.dispatcherState.Up(): - conn.dispatcherState.SetDown() - go func() { - defer log.HandlePanic() - conn.asyncReconnectWrapper() - }() - default: - } - conn.spawnReconnecterMtx.Unlock() -} - -func (conn *PacketConn) asyncReconnectWrapper() { - newConn, err := conn.Reconnect() - if err != nil { - conn.fatalError <- err - close(conn.fatalError) - return - } - if err := serrors.Join( - newConn.SetReadDeadline(conn.getReadDeadline()), - newConn.SetWriteDeadline(conn.getWriteDeadline()), - ); err != nil { - conn.fatalError <- err - close(conn.fatalError) - return - } - conn.setConn(newConn) - conn.dispatcherState.SetUp() -} - -// Reconnect is only used internally and should never be called from outside -// the package. -func (conn *PacketConn) Reconnect() (net.PacketConn, error) { - newConn, _, err := conn.reconnecter.Reconnect(context.Background()) - if err != nil { - return nil, err - } - return newConn, nil -} - -func (conn *PacketConn) Close() error { - conn.closeMtx.Lock() - defer conn.closeMtx.Unlock() - if conn.isClosing() { - panic("double close") - } - close(conn.closeCh) - conn.reconnecter.Stop() - // Once Stop() returns, it is guaranteed that snetConn is never recreated - // by the reconnecter. - err := conn.getConn().Close() - return err -} - -func (conn *PacketConn) isClosing() bool { - select { - case <-conn.closeCh: - return true - default: - return false - } -} - -func (conn *PacketConn) LocalAddr() net.Addr { - return conn.getConn().LocalAddr() -} - -func (conn *PacketConn) SetWriteDeadline(deadline time.Time) error { - conn.writeDeadlineMtx.Lock() - err := conn.getConn().SetWriteDeadline(deadline) - conn.writeDeadline = deadline - select { - case conn.deadlineChangedEvent <- struct{}{}: - default: - // The channel contains an event already, so we are guaranteed the - // channel reader sees the new deadline. - } - conn.writeDeadlineMtx.Unlock() - return err -} - -func (conn *PacketConn) SetReadDeadline(deadline time.Time) error { - conn.readDeadlineMtx.Lock() - err := conn.getConn().SetReadDeadline(deadline) - conn.readDeadline = deadline - select { - case conn.deadlineChangedEvent <- struct{}{}: - default: - // The channel contains an event already, so we are guaranteed the - // channel reader sees the new deadline. - } - conn.readDeadlineMtx.Unlock() - return err -} - -func (conn *PacketConn) SetDeadline(deadline time.Time) error { - return serrors.Join( - conn.SetWriteDeadline(deadline), - conn.SetReadDeadline(deadline), - ) -} - -func (conn *PacketConn) getDeadlineForOpType(op IOOperation) time.Time { - if op.IsWrite() { - return conn.getWriteDeadline() - } - return conn.getReadDeadline() -} - -func (conn *PacketConn) getWriteDeadline() time.Time { - conn.writeDeadlineMtx.Lock() - deadline := conn.writeDeadline - conn.writeDeadlineMtx.Unlock() - return deadline -} - -func (conn *PacketConn) getReadDeadline() time.Time { - conn.readDeadlineMtx.Lock() - deadline := conn.readDeadline - conn.readDeadlineMtx.Unlock() - return deadline -} - -func (conn *PacketConn) getConn() net.PacketConn { - conn.connMtx.Lock() - c := conn.dispConn - conn.connMtx.Unlock() - return c -} - -func (conn *PacketConn) setConn(newConn net.PacketConn) { - conn.connMtx.Lock() - conn.dispConn = newConn - conn.connMtx.Unlock() -} - -func returnOnDeadline(deadline time.Time) <-chan time.Time { - var deadlineChannel <-chan time.Time - if !deadline.IsZero() { - deadlineChannel = time.After(time.Until(deadline)) - } - return deadlineChannel -} diff --git a/pkg/sock/reliable/reconnect/conn_io_test.go b/pkg/sock/reliable/reconnect/conn_io_test.go deleted file mode 100644 index 3e618b13fd..0000000000 --- a/pkg/sock/reliable/reconnect/conn_io_test.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect_test - -import ( - "context" - "net" - "testing" - "time" - - "github.com/golang/mock/gomock" - . "github.com/smartystreets/goconvey/convey" - "github.com/stretchr/testify/assert" - - "github.com/scionproto/scion/pkg/private/mocks/net/mock_net" - "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect/mock_reconnect" -) - -func TestPacketConnIO(t *testing.T) { - Convey("Given an underlying connection, a reconnecter and an IO operation", t, func() { - ctrl := gomock.NewController(&xtest.PanickingReporter{T: t}) - defer ctrl.Finish() - mockConn := mock_net.NewMockPacketConn(ctrl) - mockReconnecter := mock_reconnect.NewMockReconnecter(ctrl) - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - mockIO := mock_reconnect.NewMockIOOperation(ctrl) - mockIO.EXPECT().IsWrite().Return(true).AnyTimes() - Convey("IO must reconnect after dispatcher error, and do op on new conn", func() { - connFromReconnect := mock_net.NewMockPacketConn(ctrl) - connFromReconnect.EXPECT().SetWriteDeadline(Any()).Return(nil).AnyTimes() - connFromReconnect.EXPECT().SetReadDeadline(Any()).Return(nil).AnyTimes() - gomock.InOrder( - mockIO.EXPECT().Do(mockConn).Return(dispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()).Return(connFromReconnect, uint16(0), nil), - mockIO.EXPECT().Do(connFromReconnect).Return(nil), - ) - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldBeNil) - }) - Convey("IO must return a nil error if successful", func() { - mockIO.EXPECT().Do(mockConn).Return(nil) - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldBeNil) - }) - Convey("IO must return non-dispatcher errors", func() { - mockIO.EXPECT().Do(mockConn).Return(writeNonDispatcherError) - err := packetConn.DoIO(mockIO) - assert.ErrorIs(t, err, writeNonDispatcherError) - }) - Convey("IO must return an error if reconnect got an error from the dispatcher", func() { - // If reconnection failed while the dispatcher was up (e.g., - // requested port is no longer available, registration message was - // malformed) the caller must be informed because reattempts will - // probably get the same error again. - gomock.InOrder( - mockIO.EXPECT().Do(mockConn).Return(writeDispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()). - Return(nil, uint16(0), connectErrorFromDispatcher), - ) - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldNotBeNil) - }) - Convey("IO returns dispatcher dead if write deadline reached when disconnected", func() { - mockConn.EXPECT().SetWriteDeadline(Any()).Return(nil).AnyTimes() - mockConn.EXPECT().SetReadDeadline(Any()).Return(nil).AnyTimes() - gomock.InOrder( - mockIO.EXPECT().Do(mockConn).Return(writeDispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()).DoAndReturn( - func(_ context.Context) (net.PacketConn, uint16, error) { - time.Sleep(tickerMultiplier(4)) - return mockConn, uint16(0), nil - }), - ) - packetConn.SetWriteDeadline(time.Now().Add(tickerMultiplier(2))) - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldEqual, reconnect.ErrDispatcherDead) - }) - Convey("SetWriteDeadline in the past unblocks a blocked writer", func() { - mockConn.EXPECT().SetWriteDeadline(Any()).Return(nil).AnyTimes() - mockConn.EXPECT().SetReadDeadline(Any()).Return(nil).AnyTimes() - gomock.InOrder( - mockIO.EXPECT().Do(mockConn).Return(writeDispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()).DoAndReturn( - func(_ context.Context) (net.PacketConn, uint16, error) { - time.Sleep(tickerMultiplier(6)) - return mockConn, uint16(0), nil - }), - ) - // Set a deadline that is sufficient to Reconnect. We later move - // the deadline in the past, thus cancelling the write prior to the - // Reconnect completing. - packetConn.SetWriteDeadline(time.Now().Add(tickerMultiplier(10))) - go func() { - // Give write time to block on the existing deadline - time.Sleep(tickerMultiplier(2)) - packetConn.SetWriteDeadline(time.Now().Add(tickerMultiplier(-1))) - }() - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldEqual, reconnect.ErrDispatcherDead) - }) - Convey("SetReadDeadline in the past unblocks a blocked reader", func() { - mockConn.EXPECT().SetWriteDeadline(Any()).Return(nil).AnyTimes() - mockConn.EXPECT().SetReadDeadline(Any()).Return(nil).AnyTimes() - mockIO := mock_reconnect.NewMockIOOperation(ctrl) - mockIO.EXPECT().IsWrite().Return(false).AnyTimes() - gomock.InOrder( - mockIO.EXPECT().Do(mockConn).Return(writeDispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()).DoAndReturn( - func(_ context.Context) (net.PacketConn, uint16, error) { - time.Sleep(tickerMultiplier(6)) - return mockConn, uint16(0), nil - }), - ) - // Set a deadline that is sufficient to Reconnect. We later move - // the deadline in the past, thus cancelling the write prior to the - // Reconnect completing. - packetConn.SetReadDeadline(time.Now().Add(tickerMultiplier(10))) - go func() { - // Give write time to block on the existing deadline - time.Sleep(tickerMultiplier(2)) - packetConn.SetReadDeadline(time.Now().Add(tickerMultiplier(-1))) - }() - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldEqual, reconnect.ErrDispatcherDead) - }) - Convey("After reconnect, IO deadline is inherited by the new connection", func() { - deadline := time.Now().Add(tickerMultiplier(1)) - connFromReconnect := mock_net.NewMockPacketConn(ctrl) - gomock.InOrder( - mockConn.EXPECT().SetWriteDeadline(deadline).Return(nil), - mockIO.EXPECT().Do(mockConn).Return(dispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()).Return(connFromReconnect, uint16(0), nil), - connFromReconnect.EXPECT().SetReadDeadline(time.Time{}).Return(nil), - connFromReconnect.EXPECT().SetWriteDeadline(deadline).Return(nil), - mockIO.EXPECT().Do(connFromReconnect).Return(nil), - ) - packetConn.SetWriteDeadline(deadline) - packetConn.DoIO(mockIO) - }) - }) -} - -func TestPacketConnAddrs(t *testing.T) { - Convey("Given a packet conn running on an underlying connection with a reconnecter", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockConn := mock_net.NewMockPacketConn(ctrl) - mockReconnecter := mock_reconnect.NewMockReconnecter(ctrl) - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - Convey("Local address must call the same function on the underlying connection", func() { - mockConn.EXPECT().LocalAddr().Return(localAddr) - address := packetConn.LocalAddr() - SoMsg("address", address, ShouldEqual, localAddr) - }) - }) -} - -func TestPacketConnReadWrite(t *testing.T) { - Convey("Given a packet conn running on an underlying connection with a reconnecter", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockConn := mock_net.NewMockPacketConn(ctrl) - mockReconnecter := mock_reconnect.NewMockReconnecter(ctrl) - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - Convey("Writes on packet conn must call the same function on the underlying conn", func() { - buffer := []byte{1, 2, 3} - Convey("WriteTo", func() { - mockConn.EXPECT().WriteTo(buffer, remoteAddr).Return(len(buffer), nil) - n, err := packetConn.WriteTo(buffer, remoteAddr) - SoMsg("n", n, ShouldEqual, len(buffer)) - SoMsg("err", err, ShouldBeNil) - }) - }) - Convey("Reads on packet conn must call the same function on the underlying conn", func() { - buffer := make([]byte, 3) - readData := []byte{4, 5} - mockReadFunc := func(b []byte) (int, *snet.UDPAddr, error) { - copy(b, readData) - return len(readData), remoteAddr, nil - } - Convey("ReadFrom", func() { - mockConn.EXPECT().ReadFrom(buffer).DoAndReturn(mockReadFunc) - n, remoteAddress, err := packetConn.ReadFrom(buffer) - SoMsg("n", n, ShouldEqual, len(readData)) - SoMsg("address", remoteAddress, ShouldEqual, remoteAddr) - SoMsg("buffer", buffer[:n], ShouldResemble, readData) - SoMsg("err", err, ShouldBeNil) - }) - }) - }) -} - -func TestPacketConnConcurrentReadWrite(t *testing.T) { - Convey("Given a server blocked in reading, writes still go through", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockConn := mock_net.NewMockPacketConn(ctrl) - mockReconnecter := mock_reconnect.NewMockReconnecter(ctrl) - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - mockConn.EXPECT().ReadFrom(Any()).DoAndReturn( - func(_ []byte) (int, net.Addr, error) { - // Keep the read blocked "forever" - time.Sleep(tickerMultiplier(50)) - return 3, nil, nil - }, - ) - mockConn.EXPECT().WriteTo(Any(), Any()) - - barrierCh := make(chan struct{}) - go func() { - buffer := make([]byte, 3) - packetConn.ReadFrom(buffer) - }() - time.Sleep(tickerMultiplier(2)) - go func() { - packetConn.WriteTo(testBuffer, nil) - close(barrierCh) - }() - xtest.AssertReadReturnsBefore(t, barrierCh, tickerMultiplier(3)) - }) -} - -func TestPacketConnClose(t *testing.T) { - Convey("Given a packet conn running on an underlying connection with a reconnecter", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockConn := mock_net.NewMockPacketConn(ctrl) - mockReconnecter := mock_reconnect.NewMockReconnecter(ctrl) - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - Convey("Calling close on packet conn calls close on underlying conn", func() { - mockReconnecter.EXPECT().Stop().AnyTimes() - mockConn.EXPECT().Close() - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - packetConn.Close() - }) - Convey("Calling close while blocked in IO does not cause a reconnect attempt", func() { - mockReconnecter.EXPECT().Stop().AnyTimes() - mockIO := mock_reconnect.NewMockIOOperation(ctrl) - mockIO.EXPECT().IsWrite().Return(true).AnyTimes() - mockIO.EXPECT().Do(mockConn).DoAndReturn( - func(_ net.PacketConn) error { - time.Sleep(tickerMultiplier(2)) - return writeDispatcherError - }) - mockConn.EXPECT().Close() - go func() { - packetConn.DoIO(mockIO) - }() - time.Sleep(tickerMultiplier(1)) - packetConn.Close() - // Wait for mocked IO to finish (note that real IO would be - // unblocked immediately by the go runtime) - time.Sleep(tickerMultiplier(10)) - }) - Convey("Calling close while IO is blocked waiting for reconnect unblocks waiter", func() { - mockReconnecter.EXPECT().Stop().AnyTimes() - mockReconnecter.EXPECT(). - Reconnect(Any()). - DoAndReturn(func(_ context.Context) (net.PacketConn, uint16, error) { - select {} - }) - mockIO := mock_reconnect.NewMockIOOperation(ctrl) - mockIO.EXPECT().IsWrite().Return(true).AnyTimes() - mockIO.EXPECT().Do(mockConn).Return(writeDispatcherError) - mockConn.EXPECT().Close() - barrierCh := make(chan struct{}) - go func() { - packetConn.DoIO(mockIO) - close(barrierCh) - }() - time.Sleep(tickerMultiplier(1)) - packetConn.Close() - select { - case <-barrierCh: - case <-time.After(tickerMultiplier(20)): - t.Fatalf("goroutine took too long to finish") - } - }) - Convey("Calling close twice panics", func() { - mockReconnecter.EXPECT().Stop().AnyTimes() - mockConn.EXPECT().Close() - packetConn.Close() - SoMsg("close panic", func() { packetConn.Close() }, ShouldPanicWith, "double close") - }) - Convey("Calling close shuts down the reconnecting goroutine (if any)", func() { - mockReconnecter.EXPECT().Stop() - mockConn.EXPECT().Close() - packetConn.Close() - }) - }) -} diff --git a/pkg/sock/reliable/reconnect/doc.go b/pkg/sock/reliable/reconnect/doc.go deleted file mode 100644 index 9c49116e1b..0000000000 --- a/pkg/sock/reliable/reconnect/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package reconnect implements transparent logic for reconnecting to the -// dispatcher. -package reconnect diff --git a/pkg/sock/reliable/reconnect/errors.go b/pkg/sock/reliable/reconnect/errors.go deleted file mode 100644 index a43a47f8d1..0000000000 --- a/pkg/sock/reliable/reconnect/errors.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import "github.com/scionproto/scion/pkg/private/serrors" - -var ( - ErrDispatcherDead = serrors.New("dispatcher dead") - // FIXME(scrye): Change this s.t. it's serrors.IsTimeout compatible. - ErrReconnecterTimeoutExpired = serrors.New("timeout expired") - ErrReconnecterStopped = serrors.New("stop method was called") - ErrClosed = serrors.New("closed") -) diff --git a/pkg/sock/reliable/reconnect/internal/metrics/BUILD.bazel b/pkg/sock/reliable/reconnect/internal/metrics/BUILD.bazel deleted file mode 100644 index 4de0d23ca7..0000000000 --- a/pkg/sock/reliable/reconnect/internal/metrics/BUILD.bazel +++ /dev/null @@ -1,12 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["metrics.go"], - importpath = "github.com/scionproto/scion/pkg/sock/reliable/reconnect/internal/metrics", - visibility = ["//pkg/sock/reliable/reconnect:__subpackages__"], - deps = [ - "//pkg/private/prom:go_default_library", - "@com_github_prometheus_client_golang//prometheus:go_default_library", - ], -) diff --git a/pkg/sock/reliable/reconnect/internal/metrics/metrics.go b/pkg/sock/reliable/reconnect/internal/metrics/metrics.go deleted file mode 100644 index 542fdef45e..0000000000 --- a/pkg/sock/reliable/reconnect/internal/metrics/metrics.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "github.com/prometheus/client_golang/prometheus" - - "github.com/scionproto/scion/pkg/private/prom" -) - -// Namespace is the metrics namespace for the metrics in this package. -const Namespace = "lib" - -const sub = "reconnect" - -var ( - // M exposes all the initialized metrics for this package. - M = newMetrics() -) - -type metrics struct { - timeouts prometheus.Counter - retries prometheus.Counter -} - -func newMetrics() metrics { - return metrics{ - timeouts: prom.NewCounter(Namespace, sub, "timeouts_total", - "Total number of reconnection attempt timeouts"), - retries: prom.NewCounter(Namespace, sub, "retries_total", - "Total number of reconnection attempt retries"), - } -} - -// Timeouts returns a counter for timeout errors. -func (m metrics) Timeouts() prometheus.Counter { - return m.timeouts -} - -// Retries returns a counter for individual reconnection attempts. -func (m metrics) Retries() prometheus.Counter { - return m.retries -} diff --git a/pkg/sock/reliable/reconnect/io.go b/pkg/sock/reliable/reconnect/io.go deleted file mode 100644 index c3a828d58b..0000000000 --- a/pkg/sock/reliable/reconnect/io.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import "net" - -// IOOperation provides an abstraction around any Conn reads and writes. Types -// that implement this interface contain the Read/Write arguments and return -// values as fields, thus allowing the reconnection loop to run any I/O -// function without caring what it is. -type IOOperation interface { - // Runs the I/O operation on conn - Do(conn net.PacketConn) error - // IsWrite returns true for types implementing write operations - IsWrite() bool -} - -type BaseOperation struct { - buffer []byte //nolint:golint,structcheck - numBytes int //nolint:golint,structcheck -} - -type WriteOperation struct { - BaseOperation -} - -func (_ *WriteOperation) IsWrite() bool { - return true -} - -type WriteToOperation struct { - WriteOperation - address net.Addr -} - -func (op *WriteToOperation) Do(conn net.PacketConn) error { - n, err := conn.WriteTo(op.buffer, op.address) - op.numBytes = n - return err -} - -type ReadOperation struct { - BaseOperation -} - -func (_ *ReadOperation) IsWrite() bool { - return false -} - -type ReadFromOperation struct { - ReadOperation - address net.Addr -} - -func (op *ReadFromOperation) Do(conn net.PacketConn) error { - n, address, err := conn.ReadFrom(op.buffer) - op.numBytes = n - op.address = address - return err -} diff --git a/pkg/sock/reliable/reconnect/main_test.go b/pkg/sock/reliable/reconnect/main_test.go deleted file mode 100644 index c0362bf9f2..0000000000 --- a/pkg/sock/reliable/reconnect/main_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect_test - -import ( - "context" - "fmt" - "net" - "os" - "syscall" - "testing" - "time" - - "github.com/golang/mock/gomock" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" -) - -var ( - Any = gomock.Any -) - -var ( - localNoPortAddr = MustParseSnet("1-ff00:0:1,[192.168.0.1]:0") - localAddr = MustParseSnet("1-ff00:0:1,[192.168.0.1]:80") - remoteAddr = MustParseSnet("2-ff00:0:2,[172.16.0.1]:80") - svc = addr.SvcNone - testBuffer = []byte{1, 2, 3} -) - -var ( - dispatcherError = &net.OpError{Err: os.NewSyscallError("write", syscall.ECONNRESET)} - writeDispatcherError = &net.OpError{Err: os.NewSyscallError("write", syscall.EPIPE)} - writeNonDispatcherError = serrors.New("Misc error") - connectErrorFromDispatcher = serrors.New("Port unavailable") -) - -func MustParseSnet(str string) *snet.UDPAddr { - var a snet.UDPAddr - if err := a.Set(str); err != nil { - panic(fmt.Sprintf("bad snet string %v, err=%v", str, err)) - } - return &a -} - -// tickerMultiplier computes durations relative to the default reconnect -// ticking interval. This is needed for some timing tests that need sleep -// values to stay fairly close to the ticking interval. -func tickerMultiplier(multiplier time.Duration) time.Duration { - return multiplier * reconnect.DefaultTickerInterval -} - -func ctxMultiplier(multiplier time.Duration) context.Context { - ctx, cancelF := context.WithTimeout(context.Background(), tickerMultiplier(multiplier)) - _ = cancelF - return ctx -} - -func TestMain(m *testing.M) { - // Inject a smaller timeout s.t. tests run quickly - reconnect.DefaultTickerInterval = 10 * time.Millisecond - log.Discard() - os.Exit(m.Run()) -} diff --git a/pkg/sock/reliable/reconnect/mock_reconnect/BUILD.bazel b/pkg/sock/reliable/reconnect/mock_reconnect/BUILD.bazel deleted file mode 100644 index 76ef55fecc..0000000000 --- a/pkg/sock/reliable/reconnect/mock_reconnect/BUILD.bazel +++ /dev/null @@ -1,21 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") -load("@io_bazel_rules_go//go:def.bzl", "gomock") - -gomock( - name = "go_default_mock", - out = "mock.go", - interfaces = [ - "IOOperation", - "Reconnecter", - ], - library = "//pkg/sock/reliable/reconnect:go_default_library", - package = "mock_reconnect", -) - -go_library( - name = "go_default_library", - srcs = ["mock.go"], - importpath = "github.com/scionproto/scion/pkg/sock/reliable/reconnect/mock_reconnect", - visibility = ["//visibility:public"], - deps = ["@com_github_golang_mock//gomock:go_default_library"], -) diff --git a/pkg/sock/reliable/reconnect/mock_reconnect/mock.go b/pkg/sock/reliable/reconnect/mock_reconnect/mock.go deleted file mode 100644 index 61dff8f55b..0000000000 --- a/pkg/sock/reliable/reconnect/mock_reconnect/mock.go +++ /dev/null @@ -1,115 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/scionproto/scion/pkg/sock/reliable/reconnect (interfaces: IOOperation,Reconnecter) - -// Package mock_reconnect is a generated GoMock package. -package mock_reconnect - -import ( - context "context" - net "net" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" -) - -// MockIOOperation is a mock of IOOperation interface. -type MockIOOperation struct { - ctrl *gomock.Controller - recorder *MockIOOperationMockRecorder -} - -// MockIOOperationMockRecorder is the mock recorder for MockIOOperation. -type MockIOOperationMockRecorder struct { - mock *MockIOOperation -} - -// NewMockIOOperation creates a new mock instance. -func NewMockIOOperation(ctrl *gomock.Controller) *MockIOOperation { - mock := &MockIOOperation{ctrl: ctrl} - mock.recorder = &MockIOOperationMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockIOOperation) EXPECT() *MockIOOperationMockRecorder { - return m.recorder -} - -// Do mocks base method. -func (m *MockIOOperation) Do(arg0 net.PacketConn) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Do", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// Do indicates an expected call of Do. -func (mr *MockIOOperationMockRecorder) Do(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockIOOperation)(nil).Do), arg0) -} - -// IsWrite mocks base method. -func (m *MockIOOperation) IsWrite() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsWrite") - ret0, _ := ret[0].(bool) - return ret0 -} - -// IsWrite indicates an expected call of IsWrite. -func (mr *MockIOOperationMockRecorder) IsWrite() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsWrite", reflect.TypeOf((*MockIOOperation)(nil).IsWrite)) -} - -// MockReconnecter is a mock of Reconnecter interface. -type MockReconnecter struct { - ctrl *gomock.Controller - recorder *MockReconnecterMockRecorder -} - -// MockReconnecterMockRecorder is the mock recorder for MockReconnecter. -type MockReconnecterMockRecorder struct { - mock *MockReconnecter -} - -// NewMockReconnecter creates a new mock instance. -func NewMockReconnecter(ctrl *gomock.Controller) *MockReconnecter { - mock := &MockReconnecter{ctrl: ctrl} - mock.recorder = &MockReconnecterMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockReconnecter) EXPECT() *MockReconnecterMockRecorder { - return m.recorder -} - -// Reconnect mocks base method. -func (m *MockReconnecter) Reconnect(arg0 context.Context) (net.PacketConn, uint16, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Reconnect", arg0) - ret0, _ := ret[0].(net.PacketConn) - ret1, _ := ret[1].(uint16) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Reconnect indicates an expected call of Reconnect. -func (mr *MockReconnecterMockRecorder) Reconnect(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reconnect", reflect.TypeOf((*MockReconnecter)(nil).Reconnect), arg0) -} - -// Stop mocks base method. -func (m *MockReconnecter) Stop() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "Stop") -} - -// Stop indicates an expected call of Stop. -func (mr *MockReconnecterMockRecorder) Stop() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockReconnecter)(nil).Stop)) -} diff --git a/pkg/sock/reliable/reconnect/network.go b/pkg/sock/reliable/reconnect/network.go deleted file mode 100644 index 84358f2837..0000000000 --- a/pkg/sock/reliable/reconnect/network.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, 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 reconnect - -import ( - "context" - "errors" - "net" - "time" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/sock/reliable" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect/internal/metrics" -) - -// DispatcherService is a dispatcher wrapper that creates conns -// with transparent reconnection capabilities. Connections created by -// DispatcherService also validate that dispatcher registrations do -// not change addresses. -// -// Callers interested in providing their own reconnection callbacks and -// validating the new connection themselves should use the connection -// constructors directly. -type DispatcherService struct { - dispatcher reliable.Dispatcher -} - -// NewDispatcherService adds transparent reconnection capabilities -// to dispatcher connections. -func NewDispatcherService(dispatcher reliable.Dispatcher) *DispatcherService { - return &DispatcherService{dispatcher: dispatcher} -} - -func (pn *DispatcherService) Register(ctx context.Context, ia addr.IA, public *net.UDPAddr, - svc addr.SVC) (net.PacketConn, uint16, error) { - - // Perform initial connection to allocate port. We use a reconnecter here - // to set up the initial connection using the same retry logic we use when - // losing the connection to the dispatcher. - reconnecter := pn.newReconnecterFromListenArgs(ctx, ia, public, svc) - conn, port, err := reconnecter.Reconnect(ctx) - if err != nil { - return nil, 0, err - } - - updatePort := func(a *net.UDPAddr, port int) *net.UDPAddr { - if a == nil { - return nil - } - return &net.UDPAddr{ - IP: append(a.IP[:0:0], a.IP...), - Port: port, - } - } - newPublic := updatePort(public, int(port)) - reconnecter = pn.newReconnecterFromListenArgs(ctx, ia, newPublic, svc) - return NewPacketConn(conn, reconnecter), port, nil -} - -func (pn *DispatcherService) newReconnecterFromListenArgs(ctx context.Context, ia addr.IA, - public *net.UDPAddr, svc addr.SVC) *TickingReconnecter { - - // f represents individual connection attempts - f := func(timeout time.Duration) (net.PacketConn, uint16, error) { - metrics.M.Retries().Inc() - ctx := context.Background() - if timeout != 0 { - var cancelF context.CancelFunc - ctx, cancelF = context.WithTimeout(ctx, timeout) - defer cancelF() - } - conn, port, err := pn.dispatcher.Register(ctx, ia, public, svc) - if errors.Is(err, ErrReconnecterTimeoutExpired) { - metrics.M.Timeouts().Inc() - } - return conn, port, err - } - return NewTickingReconnecter(f) -} diff --git a/pkg/sock/reliable/reconnect/network_test.go b/pkg/sock/reliable/reconnect/network_test.go deleted file mode 100644 index 9c34cd4200..0000000000 --- a/pkg/sock/reliable/reconnect/network_test.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, 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 reconnect_test - -import ( - "context" - "net" - "os" - "syscall" - "testing" - - "github.com/golang/mock/gomock" - . "github.com/smartystreets/goconvey/convey" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/mocks/net/mock_net" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable/mock_reliable" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" -) - -func TestReconnect(t *testing.T) { - Convey("Reconnections must conserve local address", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockDispatcher := mock_reliable.NewMockDispatcher(ctrl) - Convey("Given a mocked underlying connection with local", func() { - mockConn := mock_net.NewMockPacketConn(ctrl) - Convey("Allocated ports are reused on subsequent attempts", func() { - mockDispatcher.EXPECT(). - Register(context.Background(), localAddr.IA, localNoPortAddr.Host, svc). - Return(mockConn, uint16(80), nil) - - want := &net.UDPAddr{ - IP: append(localNoPortAddr.Host.IP[:0:0], localNoPortAddr.Host.IP...), - Port: 80, - } - - mockDispatcher.EXPECT(). - Register(context.Background(), localAddr.IA, want, svc). - Return(mockConn, uint16(80), nil) - - network := reconnect.NewDispatcherService(mockDispatcher) - packetConn, _, _ := network.Register(context.Background(), localAddr.IA, - localNoPortAddr.Host, svc) - packetConn.(*reconnect.PacketConn).Reconnect() - }) - }) - }) -} - -func TestNetworkFatalError(t *testing.T) { - Convey("Given a network running over an underlying mocked network", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - err := serrors.New("Not dispatcher dead error, e.g., malformed register msg") - mockNetwork := mock_reliable.NewMockDispatcher(ctrl) - network := reconnect.NewDispatcherService(mockNetwork) - Convey("The network returns non-dispatcher dial errors from the mock", func() { - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), err) - _, _, err := network.Register(context.Background(), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldNotBeNil) - }) - Convey("The network returns non-dispatcher listen errors from the mock", func() { - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), err) - _, _, err := network.Register(context.Background(), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldNotBeNil) - }) - }) -} - -func TestNetworkDispatcherDeadError(t *testing.T) { - dispatcherError := &net.OpError{Err: os.NewSyscallError("connect", syscall.ECONNREFUSED)} - Convey("Listen and Dial should reattempt to connect on dispatcher down errors", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockNetwork := mock_reliable.NewMockDispatcher(ctrl) - network := reconnect.NewDispatcherService(mockNetwork) - Convey("Dial tries to reconnect if no timeout set", func() { - mockConn := mock_net.NewMockPacketConn(ctrl) - gomock.InOrder( - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), dispatcherError). - Times(2), - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(mockConn, uint16(0), nil), - ) - _, _, err := network.Register(context.Background(), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldBeNil) - }) - Convey("Dial only retries for limited time if timeout set", func() { - gomock.InOrder( - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), dispatcherError). - MinTimes(2).MaxTimes(5), - ) - _, _, err := network.Register(ctxMultiplier(4), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldNotBeNil) - }) - Convey("Listen tries to reconnect if no timeout set", func() { - mockConn := mock_net.NewMockPacketConn(ctrl) - gomock.InOrder( - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), dispatcherError). - Times(2), - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(mockConn, uint16(0), nil), - ) - _, _, err := network.Register(context.Background(), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldBeNil) - }) - Convey("Listen only retries for limited time if timeout set", func() { - gomock.InOrder( - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), dispatcherError). - MinTimes(3).MaxTimes(5), - ) - _, _, err := network.Register(ctxMultiplier(4), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldNotBeNil) - }) - }) -} diff --git a/pkg/sock/reliable/reconnect/reconnecter.go b/pkg/sock/reliable/reconnect/reconnecter.go deleted file mode 100644 index d5d36cec69..0000000000 --- a/pkg/sock/reliable/reconnect/reconnecter.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import ( - "context" - "errors" - "net" - "os" - "sync" - "time" - - "github.com/scionproto/scion/pkg/log" -) - -// Use a var here to allow tests to inject shorter intervals for fast testing. -var ( - DefaultTickerInterval = time.Second -) - -type Reconnecter interface { - Reconnect(ctx context.Context) (net.PacketConn, uint16, error) - Stop() -} - -var _ Reconnecter = (*TickingReconnecter)(nil) - -type TickingReconnecter struct { - mtx sync.Mutex - // XXX(scrye): reconnectF does not support cancellation because adding - // context-aware dials in reliable socket is tricky. This can make stopping - // the reconnecter take significant time, depending on the timeout of the - // reconnection function. - reconnectF func(timeout time.Duration) (net.PacketConn, uint16, error) - stopping *AtomicBool -} - -// NewTickingReconnecter creates a new dispatcher reconnecter. Calling -// Reconnect in turn calls f periodically to obtain a new connection to the -// dispatcher, -func NewTickingReconnecter( - f func(timeout time.Duration) (net.PacketConn, uint16, error)) *TickingReconnecter { - - return &TickingReconnecter{ - reconnectF: f, - stopping: &AtomicBool{}, - } -} - -// Reconnect repeatedly attempts to reestablish a connection to the dispatcher, -// subject to timeout. Attempts that receive dispatcher connection errors are -// followed by reattempts. Critical errors (e.g., port mismatches) return -// immediately. -func (r *TickingReconnecter) Reconnect(ctx context.Context) (net.PacketConn, uint16, error) { - r.mtx.Lock() - defer r.mtx.Unlock() - start := time.Now() - t := time.NewTicker(DefaultTickerInterval) - defer t.Stop() - - var timeout time.Duration - if deadline, ok := ctx.Deadline(); ok { - timeout = time.Until(deadline) - } - - timeoutExpired := afterTimeout(timeout) - for r.stopping.IsFalse() { - newTimeout, ok := getNewTimeout(timeout, start) - if !ok { - return nil, 0, ErrReconnecterTimeoutExpired - } - conn, port, err := r.reconnectF(newTimeout) - var sysErr *os.SyscallError - switch { - case errors.As(err, &sysErr): - // Wait until next tick to retry. If the overall timeout expires - // before the next tick, return immediately with an error. - // time.Ticker will ensure that no more than one attempt is made - // per interval (even if the reconnection function takes longer - // than the interval). - log.Debug("Registering with dispatcher failed, retrying...") - select { - case <-t.C: - case <-timeoutExpired: - return nil, 0, ErrReconnecterTimeoutExpired - } - continue - case err != nil: - return nil, 0, err - default: - return conn, port, nil - } - } - return nil, 0, ErrReconnecterStopped -} - -// Stop shuts down the reconnection attempt (if any), and waits for the -// reconnecting goroutine to finish. -// -// It is safe to call Stop while Reconnect is running. -func (r *TickingReconnecter) Stop() { - r.stopping.Set(true) - // Grab lock to make sure the reconnection function finished - r.mtx.Lock() - r.mtx.Unlock() -} - -func getNewTimeout(timeout time.Duration, start time.Time) (time.Duration, bool) { - if timeout == 0 { - return 0, true - } - newTimeout := timeout - time.Since(start) - if newTimeout > 0 { - return newTimeout, true - } - return 0, false -} - -// afterTimeout waits for the timeout to elapse and then sends the current -// time on the returned channel. If the timeout is 0, the current time is never -// sent. -func afterTimeout(timeout time.Duration) <-chan time.Time { - var timeoutExpired <-chan time.Time - if timeout != 0 { - timeoutExpired = time.After(timeout) - } - return timeoutExpired -} diff --git a/pkg/sock/reliable/reconnect/reconnecter_test.go b/pkg/sock/reliable/reconnect/reconnecter_test.go deleted file mode 100644 index e196f69d79..0000000000 --- a/pkg/sock/reliable/reconnect/reconnecter_test.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect_test - -import ( - "context" - "net" - "testing" - "time" - - . "github.com/smartystreets/goconvey/convey" - - "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" -) - -// newErrorReconnF returns a dispatcher error after the duration elapses. -func newErrorReconnF(sleep time.Duration) func(time.Duration) (net.PacketConn, uint16, error) { - return func(_ time.Duration) (net.PacketConn, uint16, error) { - time.Sleep(sleep) - // return dispatcher error s.t. reconnecter reattempts - return nil, 0, dispatcherError - } -} - -func TestTickingReconnectorStop(t *testing.T) { - Convey("Calling stop terminates a reconnect running in the background", t, func() { - reconnecter := reconnect.NewTickingReconnecter(newErrorReconnF(tickerMultiplier(1))) - barrierCh := make(chan struct{}) - Convey("Stop returns immediately if a reconnect is not running", func() { - go func() { - stopAfter(reconnecter, 0) - close(barrierCh) - }() - xtest.AssertReadReturnsBefore(t, barrierCh, tickerMultiplier(2)) - }) - Convey("Stop causes reconnect to return right after the current attempt finishes", func() { - // Note that because it is not possible right now to interrupt the - // listen/dial step of a reconnection, the soonest we can return - // after a Stop() is after the next Listen/Dial returns - go func() { - reconnectWithoutTimeoutAfter(reconnecter, 0) - close(barrierCh) - }() - go stopAfter(reconnecter, tickerMultiplier(1)) - xtest.AssertReadReturnsBefore(t, barrierCh, tickerMultiplier(4)) - }) - Convey("Error must be non-nil when timing out due to stop", func() { - var err error - go func() { - err = reconnectWithoutTimeoutAfter(reconnecter, tickerMultiplier(1)) - close(barrierCh) - }() - go reconnecter.Stop() - xtest.AssertReadReturnsBefore(t, barrierCh, tickerMultiplier(4)) - SoMsg("err", err, ShouldEqual, reconnect.ErrReconnecterStopped) - }) - }) - Convey("Given a reconnection function that takes a long time", t, func() { - reconnecter := reconnect.NewTickingReconnecter(newErrorReconnF(tickerMultiplier(4))) - barrierCh := make(chan struct{}) - Convey("Stop waits for a running reconnection attempt to finish before returning", func() { - go func() { - reconnectWithoutTimeoutAfter(reconnecter, 0) - }() - go func() { - stopAfter(reconnecter, tickerMultiplier(1)) - close(barrierCh) - }() - xtest.AssertReadReturnsBetween(t, barrierCh, tickerMultiplier(3), tickerMultiplier(8)) - }) - }) -} - -func reconnectWithoutTimeoutAfter(reconnecter *reconnect.TickingReconnecter, - sleepAtStart time.Duration) error { - - time.Sleep(sleepAtStart) - _, _, err := reconnecter.Reconnect(context.Background()) - return err -} - -func stopAfter(reconnecter *reconnect.TickingReconnecter, sleepAtStart time.Duration) { - time.Sleep(sleepAtStart) - reconnecter.Stop() -} diff --git a/pkg/sock/reliable/reconnect/util.go b/pkg/sock/reliable/reconnect/util.go deleted file mode 100644 index 5ab8eefae9..0000000000 --- a/pkg/sock/reliable/reconnect/util.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import ( - "sync" -) - -type AtomicBool struct { - m sync.Mutex - v bool -} - -func (f *AtomicBool) Set(v bool) { - f.m.Lock() - f.v = v - f.m.Unlock() -} - -func (f *AtomicBool) IsTrue() bool { - f.m.Lock() - result := f.v - f.m.Unlock() - return result -} - -func (f *AtomicBool) IsFalse() bool { - return !f.IsTrue() -} - -// A State objects encodes an up or down state in a way that can be used -// directly in selects. Note that not all methods are safe for concurrent use -// (see their documentation for more information). -type State struct { - ch chan struct{} -} - -// NewState returns a new state. The state is initially set to up. -func NewState() *State { - s := &State{ch: make(chan struct{})} - s.SetUp() - return s -} - -// SetDown sets the state to down. -// -// It is not safe to call SetDown concurrently with other methods. -func (s *State) SetDown() { - s.ch = make(chan struct{}) -} - -// SetUp sets the state to up. -// -// It is safe to call SetUp concurrently with Up. -func (s *State) SetUp() { - close(s.ch) -} - -// Up yields a channel that will be closed once SetUp() is called. -// -// It is safe to call SetUp concurrently with Up. -func (s *State) Up() <-chan struct{} { - return s.ch -} diff --git a/pkg/sock/reliable/reconnect/util_test.go b/pkg/sock/reliable/reconnect/util_test.go deleted file mode 100644 index e9341817ae..0000000000 --- a/pkg/sock/reliable/reconnect/util_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect_test - -import ( - "testing" - - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" -) - -// TestState tests that State check returns immediately after creating a new object. -func TestState(t *testing.T) { - s := reconnect.NewState() - select { - case <-s.Up(): - default: - t.Fatalf("Expected method to return immediately, but it didn't") - } -} diff --git a/pkg/sock/reliable/registration.go b/pkg/sock/reliable/registration.go deleted file mode 100644 index e4e7c87c99..0000000000 --- a/pkg/sock/reliable/registration.go +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, 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 reliable - -import ( - "encoding/binary" - "net" - - "github.com/scionproto/scion/pkg/addr" -) - -type CommandBitField uint8 - -const ( - CmdBindAddress CommandBitField = 0x04 - CmdEnableSCMP CommandBitField = 0x02 - CmdAlwaysOn CommandBitField = 0x01 -) - -// Registration contains metadata for a SCION Dispatcher registration message. -type Registration struct { - IA addr.IA - PublicAddress *net.UDPAddr - BindAddress *net.UDPAddr - SVCAddress addr.SVC -} - -func (r *Registration) SerializeTo(b []byte) (int, error) { - if r.PublicAddress == nil || r.PublicAddress.IP == nil { - return 0, ErrNoAddress - } - - var msg registrationMessage - msg.Command = CmdAlwaysOn | CmdEnableSCMP - msg.L4Proto = 17 - msg.IA = uint64(r.IA) - msg.PublicData.SetFromUDPAddr(r.PublicAddress) - if r.BindAddress != nil { - msg.Command |= CmdBindAddress - var bindAddress registrationAddressField - msg.BindData = &bindAddress - bindAddress.SetFromUDPAddr(r.BindAddress) - } - if r.SVCAddress != addr.SvcNone { - buffer := make([]byte, 2) - binary.BigEndian.PutUint16(buffer, uint16(r.SVCAddress)) - msg.SVC = buffer - } - return msg.SerializeTo(b) -} - -func (r *Registration) DecodeFromBytes(b []byte) error { - var msg registrationMessage - err := msg.DecodeFromBytes(b) - if err != nil { - return err - } - - r.IA = addr.IA(msg.IA) - r.PublicAddress = &net.UDPAddr{ - IP: net.IP(msg.PublicData.Address), - Port: int(msg.PublicData.Port), - } - - if len(msg.SVC) == 0 { - r.SVCAddress = addr.SvcNone - } else { - r.SVCAddress = addr.SVC(binary.BigEndian.Uint16(msg.SVC)) - } - if (msg.Command & CmdBindAddress) != 0 { - r.BindAddress = &net.UDPAddr{ - IP: net.IP(msg.BindData.Address), - Port: int(msg.BindData.Port), - } - } - return nil -} - -// registrationMessage is the wire format for a SCION Dispatcher registration -// message. -type registrationMessage struct { - Command CommandBitField - L4Proto uint8 - IA uint64 - PublicData registrationAddressField - BindData *registrationAddressField - SVC []byte -} - -func (m *registrationMessage) SerializeTo(b []byte) (int, error) { - if len(b) < 13 { - return 0, ErrBufferTooSmall - } - b[0] = byte(m.Command) - b[1] = m.L4Proto - binary.BigEndian.PutUint64(b[2:], m.IA) - offset := 10 - if _, err := m.PublicData.SerializeTo(b[offset:]); err != nil { - return 0, err - } - offset += m.PublicData.length() - if m.BindData != nil { - if _, err := m.BindData.SerializeTo(b[offset:]); err != nil { - return 0, err - } - offset += m.BindData.length() - } - copy(b[offset:], m.SVC) - offset += len(m.SVC) - return offset, nil -} - -func (l *registrationMessage) DecodeFromBytes(b []byte) error { - if len(b) < 13 { - return ErrIncompleteMessage - } - l.Command = CommandBitField(b[0]) - l.L4Proto = b[1] - l.IA = binary.BigEndian.Uint64(b[2:]) - offset := 10 - if err := l.PublicData.DecodeFromBytes(b[offset:]); err != nil { - return err - } - offset += l.PublicData.length() - if (l.Command & CmdBindAddress) != 0 { - l.BindData = ®istrationAddressField{} - if err := l.BindData.DecodeFromBytes(b[offset:]); err != nil { - return err - } - offset += l.BindData.length() - } - switch len(b[offset:]) { - case 0: - return nil - case 2: - l.SVC = b[offset:] - return nil - default: - return ErrPayloadTooLong - } -} - -type registrationAddressField struct { - Port uint16 - AddressType byte - Address []byte -} - -func (l *registrationAddressField) SerializeTo(b []byte) (int, error) { - if len(b) < l.length() { - return 0, ErrBufferTooSmall - } - binary.BigEndian.PutUint16(b, l.Port) - b[2] = l.AddressType - copy(b[3:], l.Address) - return l.length(), nil -} - -func (l *registrationAddressField) DecodeFromBytes(b []byte) error { - if len(b) < 3 { - return ErrIncompleteMessage - } - l.Port = binary.BigEndian.Uint16(b[:2]) - l.AddressType = b[2] - if !isValidReliableSockDestination(hostAddrType(l.AddressType)) { - return ErrBadAddressType - } - addressLength := getAddressLength(hostAddrType(l.AddressType)) - if len(b[3:]) < addressLength { - return ErrIncompleteAddress - } - l.Address = b[3 : 3+addressLength] - return nil -} - -func (l *registrationAddressField) SetFromUDPAddr(u *net.UDPAddr) { - l.Port = uint16(u.Port) - l.AddressType = byte(getIPAddressType(u.IP)) - l.Address = normalizeIP(u.IP) -} - -func (l *registrationAddressField) length() int { - if l == nil { - return 0 - } - return 2 + 1 + len(l.Address) -} - -type Confirmation struct { - Port uint16 -} - -func (c *Confirmation) SerializeTo(b []byte) (int, error) { - if len(b) < 2 { - return 0, ErrBufferTooSmall - } - binary.BigEndian.PutUint16(b, c.Port) - return 2, nil -} - -func (c *Confirmation) DecodeFromBytes(b []byte) error { - if len(b) < 2 { - return ErrIncompletePort - } - c.Port = binary.BigEndian.Uint16(b) - return nil -} diff --git a/pkg/sock/reliable/registration_test.go b/pkg/sock/reliable/registration_test.go deleted file mode 100644 index 0cf96e4164..0000000000 --- a/pkg/sock/reliable/registration_test.go +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "net" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/xtest" -) - -func TestRegistrationMessageSerializeTo(t *testing.T) { - type TestCase struct { - Name string - Registration *Registration - ExpectedError error - ExpectedData []byte - } - testCases := []TestCase{ - { - Name: "nil public address", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - SVCAddress: addr.SvcNone, - }, - ExpectedData: []byte{}, - ExpectedError: ErrNoAddress, - }, - { - Name: "nil public address IP", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{Port: 80}, - SVCAddress: addr.SvcNone, - }, - ExpectedData: []byte{}, - ExpectedError: ErrNoAddress, - }, - { - Name: "public IPv4 address only", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - SVCAddress: addr.SvcNone, - }, - ExpectedData: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, 0, 80, 1, - 10, 2, 3, 4}, - }, - { - Name: "public IPv6 address only", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80}, - SVCAddress: addr.SvcNone, - }, - ExpectedData: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 2, 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, - }, - { - Name: "public address with bind", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - BindAddress: &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: 81}, - SVCAddress: addr.SvcNone, - }, - ExpectedData: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, 0, 81, 1, 10, 5, 6, 7}, - }, - { - Name: "public IPv4 address with SVC", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - SVCAddress: addr.SvcCS, - }, - ExpectedData: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, 0, - 80, 1, 10, 2, 3, 4, 0x00, 0x02}, - }, - { - Name: "public address with bind and SVC", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - BindAddress: &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: 81}, - SVCAddress: addr.SvcCS, - }, - ExpectedData: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 5, 6, 7, 0, 2}, - }, - } - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - b := make([]byte, 1500) - n, err := tc.Registration.SerializeTo(b) - assert.ErrorIs(t, err, tc.ExpectedError) - assert.Equal(t, tc.ExpectedData, b[:n]) - }) - } -} - -func TestRegistrationMessageDecodeFromBytes(t *testing.T) { - type TestCase struct { - Name string - Data []byte - ExpectedError error - ExpectedRegistration Registration - } - testCases := []TestCase{ - { - Name: "incomplete message", - Data: []byte{0x03, 17, 0, 1}, - ExpectedError: ErrIncompleteMessage, - }, - { - Name: "incomplete address", - Data: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 0, 0}, - ExpectedError: ErrIncompleteAddress, - }, - { - Name: "bad address type", - Data: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 9, 10, 2, 3, 4}, - ExpectedError: ErrBadAddressType, - }, - { - Name: "public IPv4 address only", - Data: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4}, - ExpectedRegistration: Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - SVCAddress: addr.SvcNone, - }, - }, - { - Name: "public IPv6 address only", - Data: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 2, 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, - ExpectedRegistration: Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80}, - SVCAddress: addr.SvcNone, - }, - }, - { - Name: "public address with bind", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 5, 6, 7}, - ExpectedRegistration: Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - BindAddress: &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: 81}, - SVCAddress: addr.SvcNone, - }, - }, - { - Name: "incomplete bind starting information", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81}, - ExpectedError: ErrIncompleteMessage, - }, - { - Name: "incomplete bind address", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 0, 0}, - ExpectedError: ErrIncompleteAddress, - }, - { - Name: "bad bind address type", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 0, 0, 1, - 0, 81, 9, 10, 0, 0, 2}, - ExpectedError: ErrBadAddressType, - }, - { - Name: "public IPv6 address with bind", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 2, 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 81, 2, 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, - }, - ExpectedRegistration: Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80}, - BindAddress: &net.UDPAddr{IP: net.ParseIP("2001:db8::2"), Port: 81}, - SVCAddress: addr.SvcNone, - }, - }, - { - Name: "excess of 1 byte is error", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 5, 6, 7, - 42}, - ExpectedError: ErrPayloadTooLong, - }, - { - Name: "excess of 3 bytes (or more) is error", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 5, 6, 7, - 42, 42, 42}, - ExpectedError: ErrPayloadTooLong, - }, - { - Name: "excess of 2 bytes is SVC address", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 5, 6, 7, - 0x00, 0x02}, - ExpectedRegistration: Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - BindAddress: &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: 81}, - SVCAddress: addr.SvcCS, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - var r Registration - err := r.DecodeFromBytes(tc.Data) - assert.ErrorIs(t, err, tc.ExpectedError) - assert.Equal(t, tc.ExpectedRegistration, r) - }) - } -} - -func TestConfirmationMessageSerializeTo(t *testing.T) { - confirmation := &Confirmation{Port: 0xaabb} - t.Run("bad buffer", func(t *testing.T) { - b := make([]byte, 1) - n, err := confirmation.SerializeTo(b) - assert.ErrorIs(t, err, ErrBufferTooSmall) - assert.Zero(t, n) - }) - t.Run("success", func(t *testing.T) { - b := make([]byte, 1500) - n, err := confirmation.SerializeTo(b) - assert.NoError(t, err) - assert.Equal(t, []byte{0xaa, 0xbb}, b[:n]) - }) -} - -func TestConfirmationDecodeFromBytes(t *testing.T) { - var confirmation Confirmation - t.Run("bad buffer", func(t *testing.T) { - b := []byte{0xaa} - err := confirmation.DecodeFromBytes(b) - assert.ErrorIs(t, err, ErrIncompletePort) - assert.Equal(t, Confirmation{}, confirmation) - }) - t.Run("success", func(t *testing.T) { - b := []byte{0xaa, 0xbb} - err := confirmation.DecodeFromBytes(b) - assert.NoError(t, err) - assert.Equal(t, Confirmation{Port: 0xaabb}, confirmation) - }) -} diff --git a/pkg/sock/reliable/reliable.go b/pkg/sock/reliable/reliable.go deleted file mode 100644 index b3b3ed1484..0000000000 --- a/pkg/sock/reliable/reliable.go +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright 2017 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package reliable implements the SCION ReliableSocket protocol -// -// Servers should first call Listen on a UNIX socket address, and then call -// Accept on the received Listener. -// -// Clients should either call: -// -// Dial, if they do not want to register a receiving address with the remote end -// (e.g., when connecting to SCIOND); -// Register, to register the address argument with the remote end -// (e.g., when connecting to a dispatcher). -// -// ReliableSocket common header message format: -// -// 8-bytes: COOKIE (0xde00ad01be02ef03) -// 1-byte: ADDR TYPE (NONE=0, IPv4=1, IPv6=2, SVC=3) -// 4-byte: data length -// var-byte: Destination address (0 bytes for SCIOND API) -// +2-byte: If destination address not NONE, destination port -// var-byte: Payload -// -// ReliableSocket registration message format: -// -// 13-bytes: [Common header with address type NONE] -// 1-byte: Command (bit mask with 0x04=Bind address, 0x02=SCMP enable, 0x01 always set) -// 1-byte: L4 Proto (IANA number) -// 8-bytes: ISD-AS -// 2-bytes: L4 port -// 1-byte: Address type -// var-byte: Address -// +2-bytes: L4 bind port \ -// +1-byte: Address type ) (optional bind address) -// +var-byte: Bind Address / -// +2-bytes: SVC (optional SVC type) -// -// To communicate with SCIOND, clients must first connect to SCIOND's UNIX socket. Messages -// for SCIOND must set the ADDR TYPE field in the common header to NONE. The payload contains -// the query for SCIOND (e.g., a request for paths to a SCION destination). The reply header -// contains the same fields, and the reply payload contains the query answer. -// -// To receive messages from remote SCION hosts, hosts can register their address and -// port with the SCION dispatcher. The common header of a registration message uses an address -// of type NONE. The payload contains the address type of the registered address, the address -// itself and the layer 4 port. -// -// To send messages to remote SCION hosts, hosts fill in the common header -// with the address type, the address and the layer 4 port of the remote host. -// -// Reads and writes to the connection are thread safe. -package reliable - -import ( - "context" - "fmt" - "math" - "net" - "sync" - "time" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/common" - "github.com/scionproto/scion/pkg/private/prom" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable/internal/metrics" -) - -var ( - expectedCookie = uint64(0xde00ad01be02ef03) -) - -const ( - // DefaultDispPath contains the system default for a dispatcher socket. - DefaultDispPath = "/run/shm/dispatcher/default.sock" - // DefaultDispSocketFileMode allows read/write to the user and group only. - DefaultDispSocketFileMode = 0770 -) - -// Dispatcher controls how SCION applications open sockets in the SCION world. -type Dispatcher interface { - // Register connects to a SCION Dispatcher's UNIX socket. Future messages for the address in AS - // ia which arrive at the dispatcher can be read by calling Read on the returned connection. - Register(ctx context.Context, ia addr.IA, address *net.UDPAddr, - svc addr.SVC) (net.PacketConn, uint16, error) -} - -// NewDispatcher creates a new dispatcher API endpoint on top of a UNIX -// STREAM reliable socket. If name is empty, the default dispatcher path is -// chosen. -func NewDispatcher(name string) Dispatcher { - if name == "" { - name = DefaultDispPath - } - return &dispatcherService{Address: name} -} - -type dispatcherService struct { - Address string -} - -func (d *dispatcherService) Register(ctx context.Context, ia addr.IA, public *net.UDPAddr, - svc addr.SVC) (net.PacketConn, uint16, error) { - - return registerMetricsWrapper(ctx, d.Address, ia, public, svc) -} - -var _ net.Conn = (*Conn)(nil) -var _ net.PacketConn = (*Conn)(nil) - -// Conn implements the ReliableSocket framing protocol over UNIX sockets. -type Conn struct { - *net.UnixConn - - readMutex sync.Mutex - readBuffer []byte - readPacketizer *ReadPacketizer - - writeMutex sync.Mutex - writeBuffer []byte - writeStreamer *WriteStreamer -} - -func newConn(c net.Conn) *Conn { - conn := c.(*net.UnixConn) - return &Conn{ - UnixConn: c.(*net.UnixConn), - writeBuffer: make([]byte, common.SupportedMTU), - writeStreamer: NewWriteStreamer(conn), - readBuffer: make([]byte, common.SupportedMTU), - readPacketizer: NewReadPacketizer(conn), - } -} - -// Dial connects to the UNIX socket specified by address. -// -// The provided context must be non-nil. If the context expires before the connection is complete, -// an error is returned. Once successfully connected, any expiration of the context will not affect -// the connection. -func Dial(ctx context.Context, address string) (*Conn, error) { - dialer := net.Dialer{} - c, err := dialer.DialContext(ctx, "unix", address) - metrics.M.Dials(metrics.DialLabels{Result: labelResult(err)}).Inc() - if err != nil { - return nil, err - } - return newConn(c), nil -} - -func registerMetricsWrapper(ctx context.Context, dispatcher string, ia addr.IA, - public *net.UDPAddr, svc addr.SVC) (*Conn, uint16, error) { - - conn, port, err := register(ctx, dispatcher, ia, public, svc) - labels := metrics.RegisterLabels{Result: labelResult(err), SVC: svc.BaseString()} - metrics.M.Registers(labels).Inc() - return conn, port, err -} - -func register(ctx context.Context, dispatcher string, ia addr.IA, public *net.UDPAddr, - svc addr.SVC) (*Conn, uint16, error) { - - reg := &Registration{ - IA: ia, - PublicAddress: public, - SVCAddress: svc, - } - - conn, err := Dial(ctx, dispatcher) - if err != nil { - return nil, 0, err - } - - type RegistrationReturn struct { - port uint16 - err error - } - resultChannel := make(chan RegistrationReturn) - go func() { - defer log.HandlePanic() - - // If a timeout was specified, make reads and writes return if deadline exceeded. - if deadline, ok := ctx.Deadline(); ok { - if err := conn.SetDeadline(deadline); err != nil { - resultChannel <- RegistrationReturn{err: err} - return - } - } - - port, err := registrationExchange(conn, reg) - resultChannel <- RegistrationReturn{port: port, err: err} - }() - - select { - case registrationReturn := <-resultChannel: - if registrationReturn.err != nil { - conn.Close() - return nil, 0, registrationReturn.err - } - if public.Port < 0 || public.Port > math.MaxUint16 { - return nil, 0, serrors.New(fmt.Sprintf("invalid port, range [0 - %v]", math.MaxUint16), - "requested", public.Port) - } - if public.Port != 0 && public.Port != int(registrationReturn.port) { - conn.Close() - return nil, 0, serrors.New("port mismatch", "requested", public.Port, - "received", registrationReturn.port) - } - // Disable deadline to not affect future I/O - err = conn.SetDeadline(time.Time{}) - return conn, registrationReturn.port, err - case <-ctx.Done(): - // Unblock registration worker I/O - conn.Close() - // Wait for registration worker to finish before exiting. Worker should exit quickly - // because all pending I/O immediately times out. - <-resultChannel - // The returned values aren't needed, we already decided to error out when the connection - // was closed. Note that the registration might succeed in the short window of time between - // the context being marked as done (canceled) and the I/O getting informed of the new - // deadline. - return nil, 0, serrors.WrapStr("timed out during dispatcher registration", ctx.Err()) - } -} - -func registrationExchange(conn *Conn, reg *Registration) (uint16, error) { - b := make([]byte, 1500) - n, err := reg.SerializeTo(b) - if err != nil { - return 0, err - } - _, err = conn.WriteTo(b[:n], nil) - if err != nil { - return 0, err - } - - n, _, err = conn.ReadFrom(b) - if err != nil { - conn.Close() - return 0, err - } - - var c Confirmation - err = c.DecodeFromBytes(b[:n]) - if err != nil { - conn.Close() - return 0, err - } - return c.Port, nil - -} - -// ReadFrom works similarly to Read. In addition to Read, it also returns the last hop -// (usually, the border router) which sent the message. -func (conn *Conn) ReadFrom(buf []byte) (int, net.Addr, error) { - n, addr, err := conn.readFrom(buf) - metrics.M.Reads(metrics.IOLabels{Result: labelResult(err)}).Observe(float64(n)) - return n, addr, err -} - -func (conn *Conn) readFrom(buf []byte) (int, net.Addr, error) { - conn.readMutex.Lock() - defer conn.readMutex.Unlock() - - n, err := conn.readPacketizer.Read(conn.readBuffer) - if err != nil { - return 0, nil, err - } - var p UnderlayPacket - if err := p.DecodeFromBytes(conn.readBuffer[:n]); err != nil { - return 0, nil, err - } - var underlayAddr *net.UDPAddr - if p.Address != nil { - underlayAddr = &net.UDPAddr{ - IP: append(p.Address.IP[:0:0], p.Address.IP...), - Port: p.Address.Port, - } - } - if len(buf) < len(p.Payload) { - return 0, nil, serrors.New("buffer too small") - } - copy(buf, p.Payload) - return len(p.Payload), underlayAddr, nil -} - -// WriteTo blocks until it sends buf as a single framed message through conn. -// The ReliableSocket message header will contain the address and port information in dst. -// On error, the number of bytes returned is meaningless. On success, the number of bytes -// is always len(buf). -func (conn *Conn) WriteTo(buf []byte, dst net.Addr) (int, error) { - n, err := conn.writeTo(buf, dst) - metrics.M.Writes(metrics.IOLabels{Result: labelResult(err)}).Observe(float64(n)) - return n, err -} - -func (conn *Conn) writeTo(buf []byte, dst net.Addr) (int, error) { - conn.writeMutex.Lock() - defer conn.writeMutex.Unlock() - - var udpAddr *net.UDPAddr - if dst != nil { - var ok bool - udpAddr, ok = dst.(*net.UDPAddr) - if !ok { - return 0, serrors.New("unsupported address type, must be UDP", - "address", fmt.Sprintf("%#v", dst)) - } - } - p := &UnderlayPacket{Address: udpAddr, Payload: buf} - n, err := p.SerializeTo(conn.writeBuffer) - if err != nil { - return 0, err - } - err = conn.writeStreamer.Write(conn.writeBuffer[:n]) - if err != nil { - return 0, err - } - return len(buf), nil -} - -// Read blocks until it reads the next framed message payload from conn and stores it in buf. -// The first return value contains the number of payload bytes read. -// buf must be large enough to fit the entire message. No addressing data is returned, -// only the payload. On error, the number of bytes returned is meaningless. -func (conn *Conn) Read(buf []byte) (int, error) { - n, _, err := conn.ReadFrom(buf) - return n, err -} - -// Listener listens on Unix sockets and returns Conn sockets on Accept(). -type Listener struct { - *net.UnixListener -} - -// Listen listens on UNIX socket laddr. -func Listen(laddr string) (*Listener, error) { - l, err := net.Listen("unix", laddr) - if err != nil { - return nil, serrors.WrapStr("Unable to listen on address", err, "addr", laddr) - } - return &Listener{l.(*net.UnixListener)}, nil -} - -// Accept returns sockets which implement the SCION ReliableSocket protocol for reading -// and writing. -func (listener *Listener) Accept() (net.Conn, error) { - c, err := listener.UnixListener.Accept() - if err != nil { - return nil, err - } - return newConn(c), nil -} - -func (listener *Listener) String() string { - return fmt.Sprintf("&{addr: %v}", listener.UnixListener.Addr()) -} - -func labelResult(err error) string { - switch { - case err == nil: - return prom.Success - case serrors.IsTimeout(err): - return prom.ErrTimeout - default: - return prom.ErrNotClassified - } -} diff --git a/pkg/sock/reliable/util.go b/pkg/sock/reliable/util.go deleted file mode 100644 index f0e49b0f5c..0000000000 --- a/pkg/sock/reliable/util.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "net" -) - -type hostAddrType uint8 - -const ( - hostTypeNone = iota - hostTypeIPv4 - hostTypeIPv6 - hostTypeSVC -) - -func getAddressType(address *net.UDPAddr) hostAddrType { - if address == nil || address.IP == nil { - return hostTypeNone - } - return getIPAddressType(address.IP) -} - -func getIPAddressType(ip net.IP) hostAddrType { - if ip.To4() != nil { - return hostTypeIPv4 - } - return hostTypeIPv6 -} - -// normalizeIP returns a 4-byte slice for an IPv4 address, and 16-byte slice -// for an IPv6 address. -func normalizeIP(ip net.IP) net.IP { - if ip := ip.To4(); ip != nil { - return ip - } - return ip -} - -func isValidReliableSockDestination(t hostAddrType) bool { - return t == hostTypeNone || t == hostTypeIPv4 || t == hostTypeIPv6 -} - -func getAddressLength(t hostAddrType) int { - switch t { - case hostTypeNone: - return 0 - case hostTypeIPv4: - return 4 - case hostTypeIPv6: - return 16 - case hostTypeSVC: - return 2 - } - return 0 -} - -func getPortLength(t hostAddrType) int { - if t == hostTypeIPv4 || t == hostTypeIPv6 { - return 2 - } - return 0 -} diff --git a/private/app/appnet/BUILD.bazel b/private/app/appnet/BUILD.bazel index 6762e1fd67..ab84c7c0fa 100644 --- a/private/app/appnet/BUILD.bazel +++ b/private/app/appnet/BUILD.bazel @@ -18,8 +18,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", "//pkg/snet/squic:go_default_library", - "//pkg/sock/reliable:go_default_library", - "//pkg/sock/reliable/reconnect:go_default_library", "//private/env:go_default_library", "//private/svc:go_default_library", "//private/trust:go_default_library", diff --git a/private/app/appnet/infraenv.go b/private/app/appnet/infraenv.go index e1f73c73c2..b18a7db2dc 100644 --- a/private/app/appnet/infraenv.go +++ b/private/app/appnet/infraenv.go @@ -25,8 +25,6 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" - "errors" - "fmt" "math/big" "net" "time" @@ -39,8 +37,6 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/squic" - "github.com/scionproto/scion/pkg/sock/reliable" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" "github.com/scionproto/scion/private/env" "github.com/scionproto/scion/private/svc" "github.com/scionproto/scion/private/trust" @@ -48,9 +44,6 @@ import ( // QUIC contains the QUIC configuration for control-plane speakers. type QUIC struct { - // Address is the UDP address to start the QUIC server on. - Address string - GetCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error) GetClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error) TLSVerifier *trust.TLSCryptoVerifier @@ -64,12 +57,7 @@ type NetworkConfig struct { // Public is the Internet-reachable address in the case where the service // is behind NAT. Public *net.UDPAddr - // ReconnectToDispatcher sets up sockets that automatically reconnect if - // the dispatcher closes the connection (e.g., if the dispatcher goes - // down). - ReconnectToDispatcher bool - // QUIC contains configuration details for QUIC servers. If the listening - // address is the empty string, then no QUIC socket is opened. + // QUIC contains configuration details for QUIC servers. QUIC QUIC // SVCResolver is used to discover the underlay addresses of intra-AS SVC // servers. @@ -81,10 +69,13 @@ type NetworkConfig struct { SCMPHandler snet.SCMPHandler // Metrics injected into SCIONNetwork. SCIONNetworkMetrics snet.SCIONNetworkMetrics - // Metrics injected into DefaultPacketDispatcherService. + // Metrics injected into SCIONPacketConn. SCIONPacketConnMetrics snet.SCIONPacketConnMetrics // MTU of the local AS MTU uint16 + // Topology is the helper class to get control-plane information for the + // local AS. + Topology snet.Topology } // QUICStack contains everything to run a QUIC based RPC stack. @@ -92,7 +83,6 @@ type QUICStack struct { Listener *squic.ConnListener InsecureDialer *squic.ConnDialer Dialer *squic.ConnDialer - RedirectCloser func() } func (nc *NetworkConfig) TCPStack() (net.Listener, error) { @@ -104,9 +94,6 @@ func (nc *NetworkConfig) TCPStack() (net.Listener, error) { } func (nc *NetworkConfig) QUICStack() (*QUICStack, error) { - if nc.QUIC.Address == "" { - nc.QUIC.Address = nc.Public.String() - } client, server, err := nc.initQUICSockets() if err != nil { @@ -127,18 +114,6 @@ func (nc *NetworkConfig) QUICStack() (*QUICStack, error) { return nil, serrors.WrapStr("listening QUIC/SCION", err) } - serverAddr, ok := server.LocalAddr().(*snet.UDPAddr) - if !ok { - return nil, serrors.New("unexpected server address type", - "type", fmt.Sprintf("%T", server.LocalAddr()), - ) - } - - cancel, err := nc.initSvcRedirect(serverAddr.Host.String()) - if err != nil { - return nil, serrors.WrapStr("starting service redirection", err) - } - insecureClientTLSConfig := &tls.Config{ InsecureSkipVerify: true, NextProtos: []string{"SCION"}, @@ -164,7 +139,6 @@ func (nc *NetworkConfig) QUICStack() (*QUICStack, error) { Transport: clientTransport, TLSConfig: clientTLSConfig, }, - RedirectCloser: cancel, }, nil } @@ -215,147 +189,81 @@ func GenerateTLSConfig() (*tls.Config, error) { // AddressRewriter initializes path and svc resolvers for infra servers. // -// The connection factory is used to open sockets for SVC resolution requests. -// If the connection factory is nil, the default connection factory is used. -func (nc *NetworkConfig) AddressRewriter( - connFactory snet.PacketDispatcherService) *AddressRewriter { - - if connFactory == nil { - connFactory = &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), - SCMPHandler: nc.SCMPHandler, - } - } +// The connector is used to open sockets for SVC resolution requests. +// If the connector is nil, the default connection factory is used. +func (nc *NetworkConfig) AddressRewriter() *AddressRewriter { return &AddressRewriter{ Router: &snet.BaseRouter{Querier: IntraASPathQuerier{IA: nc.IA, MTU: nc.MTU}}, SVCRouter: nc.SVCResolver, Resolver: &svc.Resolver{ - LocalIA: nc.IA, - ConnFactory: connFactory, - LocalIP: nc.Public.IP, + LocalIA: nc.IA, + Network: &snet.SCIONNetwork{ + Topology: nc.Topology, + SCMPHandler: nc.SCMPHandler, + Metrics: nc.SCIONNetworkMetrics, + PacketConnMetrics: nc.SCIONPacketConnMetrics, + }, + LocalIP: nc.Public.IP, }, SVCResolutionFraction: 1.337, } } -// initSvcRedirect creates the main control-plane UDP socket. SVC anycasts will be -// delivered to this socket, which replies to SVC resolution requests. The -// address will be included as the QUIC address in SVC resolution replies. -func (nc *NetworkConfig) initSvcRedirect(quicAddress string) (func(), error) { +func (nc *NetworkConfig) initQUICSockets() (net.PacketConn, net.PacketConn, error) { reply := &svc.Reply{ Transports: map[svc.Transport]string{ - svc.QUIC: quicAddress, + svc.QUIC: nc.Public.String(), }, } svcResolutionReply, err := reply.Marshal() if err != nil { - return nil, serrors.WrapStr("building SVC resolution reply", err) + return nil, nil, serrors.WrapStr("building SVC resolution reply", err) } - dispatcherService := reliable.NewDispatcher("") - if nc.ReconnectToDispatcher { - dispatcherService = reconnect.NewDispatcherService(dispatcherService) - } - packetDispatcher := svc.NewResolverPacketDispatcher( - &snet.DefaultPacketDispatcherService{ - Dispatcher: dispatcherService, - SCMPHandler: nc.SCMPHandler, - SCIONPacketConnMetrics: nc.SCIONPacketConnMetrics, - }, - &svc.BaseHandler{ - Message: svcResolutionReply, - }, - ) - network := &snet.SCIONNetwork{ - LocalIA: nc.IA, - Dispatcher: packetDispatcher, - Metrics: nc.SCIONNetworkMetrics, + serverNet := &snet.SCIONNetwork{ + Topology: nc.Topology, + // XXX(roosd): This is essential, the server must not read SCMP + // errors. Otherwise, the accept loop will always return that error + // on every subsequent call to accept. + SCMPHandler: ignoreSCMP{}, + PacketConnMetrics: nc.SCIONPacketConnMetrics, } - - // The service resolution address gets a dynamic port. In reality, neither the - // address nor the port are needed to address the resolver, but the dispatcher still - // requires them and checks unicity. At least a dynamic port is allowed. - srAddr := &net.UDPAddr{IP: nc.Public.IP, Port: 0} - conn, err := network.Listen(context.Background(), "udp", srAddr, addr.SvcWildcard) + pconn, err := serverNet.OpenRaw(context.Background(), nc.Public) if err != nil { - log.Info("Listen failed", "err", err) - return nil, serrors.WrapStr("listening on SCION", err, "addr", srAddr) + return nil, nil, serrors.WrapStr("creating server raw PacketConn", err) } - - ctx, cancel := context.WithCancel(context.Background()) - go func() { - defer log.HandlePanic() - buf := make([]byte, 1500) - done := ctx.Done() - for { - select { - case <-done: - return - default: - // All the resolution logic is "hidden" in the - // svc.ResolverPacketDispatcher. Here, we just need to Read to - // drive this. Ignore errors from reading, just keep going. - _, err := conn.Read(buf) - if errors.Is(err, svc.ErrHandler) { - log.Debug("Error handling SVC request", "err", err) - } else if errors.Is(err, net.ErrClosed) { - log.Error("SVC resolution socket was closed", "err", err) - return - } - } - } - }() - return cancel, nil -} - -func (nc *NetworkConfig) initQUICSockets() (net.PacketConn, net.PacketConn, error) { - dispatcherService := reliable.NewDispatcher("") - if nc.ReconnectToDispatcher { - dispatcherService = reconnect.NewDispatcherService(dispatcherService) - } - - serverNet := &snet.SCIONNetwork{ - LocalIA: nc.IA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - Dispatcher: dispatcherService, - // XXX(roosd): This is essential, the server must not read SCMP - // errors. Otherwise, the accept loop will always return that error - // on every subsequent call to accept. - SCMPHandler: ignoreSCMP{}, - SCIONPacketConnMetrics: nc.SCIONPacketConnMetrics, + resolvedPacketConn := &svc.ResolverPacketConn{ + PacketConn: pconn, + Source: snet.SCIONAddress{ + IA: nc.IA, + Host: addr.HostIP(nc.Public.AddrPort().Addr()), + }, + Handler: &svc.BaseHandler{ + Message: svcResolutionReply, }, - Metrics: nc.SCIONNetworkMetrics, - } - serverAddr, err := net.ResolveUDPAddr("udp", nc.QUIC.Address) - if err != nil { - return nil, nil, serrors.WrapStr("parsing server QUIC address", err) } - server, err := serverNet.Listen(context.Background(), "udp", serverAddr, addr.SvcNone) + server, err := snet.NewCookedConn(resolvedPacketConn, nc.Topology) if err != nil { return nil, nil, serrors.WrapStr("creating server connection", err) } clientNet := &snet.SCIONNetwork{ - LocalIA: nc.IA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - Dispatcher: dispatcherService, - // Discard all SCMP propagation, to avoid read errors on the QUIC - // client. - SCMPHandler: snet.SCMPPropagationStopper{ - Handler: nc.SCMPHandler, - Log: log.Debug, - }, - SCIONPacketConnMetrics: nc.SCIONPacketConnMetrics, + Topology: nc.Topology, + // Discard all SCMP propagation, to avoid read errors on the QUIC + // client. + SCMPHandler: snet.SCMPPropagationStopper{ + Handler: nc.SCMPHandler, + Log: log.Debug, }, - Metrics: nc.SCIONNetworkMetrics, + Metrics: nc.SCIONNetworkMetrics, + PacketConnMetrics: nc.SCIONPacketConnMetrics, } - // Let the dispatcher decide on the port for the client connection. clientAddr := &net.UDPAddr{ - IP: serverAddr.IP, - Zone: serverAddr.Zone, + IP: nc.Public.IP, + Zone: nc.Public.Zone, } - client, err := clientNet.Listen(context.Background(), "udp", clientAddr, addr.SvcNone) + client, err := clientNet.Listen(context.Background(), "udp", clientAddr) if err != nil { return nil, nil, serrors.WrapStr("creating client connection", err) } diff --git a/private/app/env/env.go b/private/app/env/env.go index cc134e5ad3..8b3a743ab1 100644 --- a/private/app/env/env.go +++ b/private/app/env/env.go @@ -49,8 +49,6 @@ type General struct { // DefaultIA is the ISD-AS that will be used by default as a source AS in case multiple SCION // ASes are available on the host. DefaultIA addr.IA `json:"default_isd_as,omitempty"` - // DispatcherSocket is the path to the dispatcher socket. - DispatcherSocket string `json:"dispatcher_socket,omitempty"` } func (g *General) Validate() error { diff --git a/private/app/env/env_test.go b/private/app/env/env_test.go index 74cf2705bc..bb6c58f305 100644 --- a/private/app/env/env_test.go +++ b/private/app/env/env_test.go @@ -33,8 +33,7 @@ func TestSCION(t *testing.T) { Input: ` { "general": { - "default_isd_as": "1-ff00:0:1", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-ff00:0:1" }, "ases": { "1-ff00:0:1": { @@ -50,8 +49,7 @@ func TestSCION(t *testing.T) { Input: ` { "general": { - "default_isd_as": "1-ff00:0:1", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-ff00:0:1" }, "ases": { "invalid-ia": { @@ -67,8 +65,7 @@ func TestSCION(t *testing.T) { Input: ` { "general": { - "default_isd_as": "1-0", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-0" }, "ases": { "1-ff00:0:1": { @@ -84,8 +81,7 @@ func TestSCION(t *testing.T) { Input: ` { "general": { - "default_isd_as": "1-ff00:0:1", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-ff00:0:1" }, "ases": { "1-ff00:0:1": { @@ -123,8 +119,7 @@ func TestGeneral(t *testing.T) { "valid": { Input: ` { - "default_isd_as": "1-ff00:0:1", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-ff00:0:1" } `, parseError: assert.NoError, @@ -133,8 +128,7 @@ func TestGeneral(t *testing.T) { "parse error": { Input: ` { - "default_isd_as": "invalid", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "invalid" } `, parseError: assert.Error, @@ -143,8 +137,7 @@ func TestGeneral(t *testing.T) { "validation error": { Input: ` { - "default_isd_as": "1-0", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-0" } `, parseError: assert.NoError, diff --git a/private/app/flag/BUILD.bazel b/private/app/flag/BUILD.bazel index 2dddf09adb..fca4f76dcb 100644 --- a/private/app/flag/BUILD.bazel +++ b/private/app/flag/BUILD.bazel @@ -14,7 +14,6 @@ go_library( "//pkg/daemon:go_default_library", "//pkg/private/serrors:go_default_library", "//pkg/private/util:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/app/env:go_default_library", "@com_github_spf13_pflag//:go_default_library", ], @@ -31,7 +30,6 @@ go_test( "//pkg/addr:go_default_library", "//pkg/daemon:go_default_library", "//pkg/private/xtest:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/app/env:go_default_library", "@com_github_spf13_pflag//:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", diff --git a/private/app/flag/env.go b/private/app/flag/env.go index a585a9017c..9f5820fe00 100644 --- a/private/app/flag/env.go +++ b/private/app/flag/env.go @@ -27,13 +27,11 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app/env" ) const ( - defaultDaemon = daemon.DefaultAPIAddress - defaultDispatcher = reliable.DefaultDispPath + defaultDaemon = daemon.DefaultAPIAddress defaultEnvironmentFile = "/etc/scion/environment.json" ) @@ -77,15 +75,12 @@ func (v *ipVal) Type() string { return "ip" } func (v *ipVal) String() string { return netip.Addr(*v).String() } // SCIONEnvironment can be used to access the common SCION configuration values, -// like the SCION daemon address, the dispatcher socket address and the local IP -// as well as the local ISD-AS. +// like the SCION daemon address and the local IP as well as the local ISD-AS. type SCIONEnvironment struct { sciondFlag *pflag.Flag sciondEnv *string ia addr.IA iaFlag *pflag.Flag - dispFlag *pflag.Flag - dispEnv *string local netip.Addr localEnv *netip.Addr localFlag *pflag.Flag @@ -104,13 +99,10 @@ func (e *SCIONEnvironment) Register(flagSet *pflag.FlagSet) { defer e.mtx.Unlock() sciond := defaultDaemon - dispatcher := defaultDispatcher e.sciondFlag = flagSet.VarPF((*stringVal)(&sciond), "sciond", "", "SCION Daemon address.") e.iaFlag = flagSet.VarPF((*iaVal)(&e.ia), "isd-as", "", "The local ISD-AS to use.") - e.dispFlag = flagSet.VarPF((*stringVal)(&dispatcher), "dispatcher", "", - "Path to the dispatcher socket") e.localFlag = flagSet.VarPF((*ipVal)(&e.local), "local", "l", "Local IP address to listen on.") } @@ -161,9 +153,6 @@ func (e *SCIONEnvironment) loadEnv() error { if d, ok := os.LookupEnv("SCION_DAEMON"); ok { e.sciondEnv = &d } - if d, ok := os.LookupEnv("SCION_DISPATCHER"); ok { - e.dispEnv = &d - } if l, ok := os.LookupEnv("SCION_LOCAL_ADDR"); ok { a, err := netip.ParseAddr(l) if err != nil { @@ -200,29 +189,7 @@ func (e *SCIONEnvironment) Daemon() string { return defaultDaemon } -// Dispatcher returns the path to the SCION dispatcher socket. The value is -// loaded from one of the following sources with the precedence as listed: -// 1. Command line flag -// 2. Environment variable -// 3. Environment configuration file -// 4. Default value. -func (e *SCIONEnvironment) Dispatcher() string { - e.mtx.Lock() - defer e.mtx.Unlock() - - if e.dispFlag != nil && e.dispFlag.Changed { - return e.dispFlag.Value.String() - } - if e.dispEnv != nil { - return *e.dispEnv - } - if s := e.file.General.DispatcherSocket; s != "" { - return s - } - return defaultDispatcher -} - -// Local returns the local IP to listen on. The value is loaded from one of the +// Local returns the loca IP to listen on. The value is loaded from one of the // following sources with the precedence as listed: // 1. Command line flag // 2. Environment variable diff --git a/private/app/flag/env_test.go b/private/app/flag/env_test.go index cb5d9737cb..262f0efc33 100644 --- a/private/app/flag/env_test.go +++ b/private/app/flag/env_test.go @@ -27,7 +27,6 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon" "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app/env" "github.com/scionproto/scion/private/app/flag" ) @@ -40,8 +39,7 @@ func TestSCIONEnvironment(t *testing.T) { t.Cleanup(func() { os.Remove(fName) }) e := env.SCION{ General: env.General{ - DispatcherSocket: "/test/dispatcher_file.socket", - DefaultIA: xtest.MustParseIA("1-ff00:0:110"), + DefaultIA: xtest.MustParseIA("1-ff00:0:110"), }, ASes: map[addr.IA]env.AS{ xtest.MustParseIA("1-ff00:0:110"): { @@ -58,14 +56,12 @@ func TestSCIONEnvironment(t *testing.T) { } setupEnv := func(t *testing.T) { tempEnv(t, "SCION_DAEMON", "scion_env:1234") - tempEnv(t, "SCION_DISPATCHER", "/test/dispatcher_env.socket") tempEnv(t, "SCION_LOCAL_ADDR", "10.0.42.0") } noEnv := func(t *testing.T) {} setupFlags := func(t *testing.T, fs *pflag.FlagSet) { err := fs.Parse([]string{ "--sciond", "scion:1234", - "--dispatcher", "/test/dispatcher.socket", "--local", "10.0.0.42", }) require.NoError(t, err) @@ -82,52 +78,46 @@ func TestSCIONEnvironment(t *testing.T) { local netip.Addr }{ "no flag, no file, no env, defaults only": { - flags: noFlags, - env: noEnv, - file: noFile, - daemon: daemon.DefaultAPIAddress, - dispatcher: reliable.DefaultDispPath, - local: netip.Addr{}, + flags: noFlags, + env: noEnv, + file: noFile, + daemon: daemon.DefaultAPIAddress, + local: netip.Addr{}, }, "flag values set": { - flags: setupFlags, - env: noEnv, - file: noFile, - daemon: "scion:1234", - dispatcher: "/test/dispatcher.socket", - local: netip.MustParseAddr("10.0.0.42"), + flags: setupFlags, + env: noEnv, + file: noFile, + daemon: "scion:1234", + local: netip.MustParseAddr("10.0.0.42"), }, "env values set": { - flags: noFlags, - env: setupEnv, - file: noFile, - daemon: "scion_env:1234", - dispatcher: "/test/dispatcher_env.socket", - local: netip.MustParseAddr("10.0.42.0"), + flags: noFlags, + env: setupEnv, + file: noFile, + daemon: "scion_env:1234", + local: netip.MustParseAddr("10.0.42.0"), }, "file values set": { - flags: noFlags, - env: noEnv, - file: setupFile, - daemon: "scion_file:1234", - dispatcher: "/test/dispatcher_file.socket", - local: netip.Addr{}, + flags: noFlags, + env: noEnv, + file: setupFile, + daemon: "scion_file:1234", + local: netip.Addr{}, }, "all set, flag precedence": { - flags: setupFlags, - env: setupEnv, - file: setupFile, - daemon: "scion:1234", - dispatcher: "/test/dispatcher.socket", - local: netip.MustParseAddr("10.0.0.42"), + flags: setupFlags, + env: setupEnv, + file: setupFile, + daemon: "scion:1234", + local: netip.MustParseAddr("10.0.0.42"), }, "env set, file set, env precedence": { - flags: noFlags, - env: setupEnv, - file: setupFile, - daemon: "scion_env:1234", - dispatcher: "/test/dispatcher_env.socket", - local: netip.MustParseAddr("10.0.42.0"), + flags: noFlags, + env: setupEnv, + file: setupFile, + daemon: "scion_env:1234", + local: netip.MustParseAddr("10.0.42.0"), }, } for name, tc := range testCases { @@ -140,7 +130,6 @@ func TestSCIONEnvironment(t *testing.T) { tc.file(t, &env) require.NoError(t, env.LoadExternalVars()) assert.Equal(t, tc.daemon, env.Daemon()) - assert.Equal(t, tc.dispatcher, env.Dispatcher()) assert.Equal(t, tc.local, env.Local()) }) } diff --git a/private/app/path/path.go b/private/app/path/path.go index b89e53a09a..96666805e3 100644 --- a/private/app/path/path.go +++ b/private/app/path/path.go @@ -143,9 +143,8 @@ func filterUnhealthy( DstIA: remote, LocalIA: cfg.LocalIA, LocalIP: cfg.LocalIP, - ID: snet.RandomSCMPIdentifer(), SCIONPacketConnMetrics: cfg.SCIONPacketConnMetrics, - Dispatcher: cfg.Dispatcher, + Topology: sd, }.GetStatuses(subCtx, nonEmptyPaths, pathprobe.WithEPIC(epic)) if err != nil { return nil, serrors.WrapStr("probing paths", err) @@ -305,9 +304,8 @@ func (cs ColorScheme) Path(path snet.Path) string { } type ProbeConfig struct { - LocalIA addr.IA - LocalIP net.IP - Dispatcher string + LocalIA addr.IA + LocalIP net.IP // Metrics injected into Prober. SCIONPacketConnMetrics snet.SCIONPacketConnMetrics diff --git a/private/app/path/pathprobe/BUILD.bazel b/private/app/path/pathprobe/BUILD.bazel index 281d5880ce..8d64118959 100644 --- a/private/app/path/pathprobe/BUILD.bazel +++ b/private/app/path/pathprobe/BUILD.bazel @@ -14,7 +14,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/addrutil:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", "@org_golang_x_sync//errgroup:go_default_library", ], ) diff --git a/private/app/path/pathprobe/paths.go b/private/app/path/pathprobe/paths.go index 951d27d59d..62df6c737a 100644 --- a/private/app/path/pathprobe/paths.go +++ b/private/app/path/pathprobe/paths.go @@ -36,7 +36,6 @@ import ( "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/addrutil" snetpath "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" ) // StatusName defines the different states a path can be in. @@ -104,13 +103,11 @@ type Prober struct { // an appropriate local IP endpoint depending on the path that should be probed. Note, LocalIP // should not be set, unless you know what you are doing. LocalIP net.IP - // ID is the SCMP traceroute ID used by the Prober. - ID uint16 - // Dispatcher is the path to the dispatcher socket. Leaving this empty uses - // the default dispatcher socket value. - Dispatcher string - // Metrics injected into snet.DefaultPacketDispatcherService. + // Metrics injected into snet.DefaultConnector. SCIONPacketConnMetrics snet.SCIONPacketConnMetrics + // Topology is the helper class to get control-plane information for the + // local AS. + Topology snet.Topology } type options struct { @@ -164,11 +161,11 @@ func (p Prober) GetStatuses(ctx context.Context, paths []snet.Path, statuses[key] = status } - // Instantiate dispatcher service - disp := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(p.Dispatcher), - SCMPHandler: &scmpHandler{}, - SCIONPacketConnMetrics: p.SCIONPacketConnMetrics, + // Instantiate network + sn := &snet.SCIONNetwork{ + SCMPHandler: &scmpHandler{}, + PacketConnMetrics: p.SCIONPacketConnMetrics, + Topology: p.Topology, } // Resolve all the local IPs per path. We will open one connection @@ -199,8 +196,7 @@ func (p Prober) GetStatuses(ctx context.Context, paths []snet.Path, g.Go(func() error { defer log.HandlePanic() - conn, _, err := disp.Register(ctx, p.LocalIA, - &net.UDPAddr{IP: localIP.AsSlice()}, addr.SvcNone) + conn, err := sn.OpenRaw(ctx, &net.UDPAddr{IP: localIP.AsSlice()}) if err != nil { return serrors.WrapStr("creating packet conn", err, "local", localIP) } @@ -246,7 +242,7 @@ func (p Prober) GetStatuses(ctx context.Context, paths []snet.Path, }, Path: alertPath, Payload: snet.SCMPTracerouteRequest{ - Identifier: p.ID, + Identifier: uint16(conn.LocalAddr().(*net.UDPAddr).Port), Sequence: uint16(atomic.AddInt32(&seq, 1)), }, }, diff --git a/private/env/env.go b/private/env/env.go index 1c59a1822f..584de18020 100644 --- a/private/env/env.go +++ b/private/env/env.go @@ -79,9 +79,6 @@ type General struct { ID string `toml:"id,omitempty"` // ConfigDir for loading extra files (currently, only topology.json and staticInfoConfig.json) ConfigDir string `toml:"config_dir,omitempty"` - // ReconnectToDispatcher can be set to true to enable transparent dispatcher - // reconnects. - ReconnectToDispatcher bool `toml:"reconnect_to_dispatcher,omitempty"` } // InitDefaults sets the default value for Topology if not already set. diff --git a/private/env/envtest/config.go b/private/env/envtest/config.go index ff792bf85e..bdc6aae320 100644 --- a/private/env/envtest/config.go +++ b/private/env/envtest/config.go @@ -44,10 +44,7 @@ func InitTest(general *env.General, metrics *env.Metrics, } } -func InitTestGeneral(cfg *env.General) { - cfg.ReconnectToDispatcher = true -} - +func InitTestGeneral(cfg *env.General) {} func InitTestMetrics(cfg *env.Metrics) {} func InitTestTracing(cfg *env.Tracing) { @@ -80,7 +77,6 @@ func CheckTestGeneral(t *testing.T, cfg *env.General, id string) { assert.Equal(t, id, cfg.ID) assert.Equal(t, "/etc/scion", cfg.ConfigDir) assert.Equal(t, filepath.Join(cfg.ConfigDir, env.TopologyFile), cfg.Topology()) - assert.False(t, cfg.ReconnectToDispatcher) } func CheckTestMetrics(t *testing.T, cfg *env.Metrics) { diff --git a/private/env/sample.go b/private/env/sample.go index b242e7ca08..edccf9e179 100644 --- a/private/env/sample.go +++ b/private/env/sample.go @@ -20,9 +20,6 @@ id = "%s" # Directory for loading AS information, certs, keys, path policy, topology. config_dir = "/etc/scion" - -# Enable the snetproxy reconnecter. (default false) -reconnect_to_dispatcher = false ` const featuresSample = ` diff --git a/private/svc/resolver.go b/private/svc/resolver.go index fb01fb173f..8e32a3e910 100644 --- a/private/svc/resolver.go +++ b/private/svc/resolver.go @@ -57,10 +57,9 @@ func init() { // Resolver performs SVC address resolution. type Resolver struct { + Network snet.Network // LocalIA is the local AS. LocalIA addr.IA - // ConnFactory is used to open ports for SVC resolution messages. - ConnFactory snet.PacketDispatcherService // LocalIP is the default L3 address for connections originating from this process. LocalIP net.IP // RoundTripper performs the request/reply exchange for SVC resolutions. If @@ -84,7 +83,7 @@ func (r *Resolver) LookupSVC(ctx context.Context, p snet.Path, svc addr.SVC) (*R return nil, serrors.New("invalid local IP", "ip", r.LocalIP) } - conn, port, err := r.ConnFactory.Register(ctx, r.LocalIA, u, addr.SvcNone) + conn, err := r.Network.OpenRaw(ctx, u) if err != nil { ext.Error.Set(span, true) return nil, serrors.Wrap(errRegistration, err) @@ -108,7 +107,7 @@ func (r *Resolver) LookupSVC(ctx context.Context, p snet.Path, svc addr.SVC) (*R }, Path: p.Dataplane(), Payload: snet.UDPPayload{ - SrcPort: port, + SrcPort: uint16(conn.LocalAddr().(*net.UDPAddr).Port), Payload: requestPayload, }, }, diff --git a/private/svc/resolver_test.go b/private/svc/resolver_test.go index 86a47bf579..f8b26d2d89 100644 --- a/private/svc/resolver_test.go +++ b/private/svc/resolver_test.go @@ -48,14 +48,13 @@ func TestResolver(t *testing.T) { mockPath.EXPECT().Destination().Return(dstIA).AnyTimes() t.Run("If opening up port fails, return error and no reply", func(t *testing.T) { - mockPacketDispatcherService := mock_snet.NewMockPacketDispatcherService(ctrl) - mockPacketDispatcherService.EXPECT().Register(gomock.Any(), gomock.Any(), - gomock.Any(), gomock.Any()). - Return(nil, uint16(0), errors.New("no conn")) + mockNet := mock_snet.NewMockNetwork(ctrl) + mockNet.EXPECT().OpenRaw(gomock.Any(), gomock.Any()). + Return(nil, errors.New("no conn")) resolver := &svc.Resolver{ - LocalIA: srcIA, - ConnFactory: mockPacketDispatcherService, - LocalIP: net.IP{0, 0, 0, 0}, + LocalIA: srcIA, + LocalIP: xtest.MustParseIP(t, "127.0.0.1"), + Network: mockNet, } reply, err := resolver.LookupSVC(context.Background(), mockPath, addr.SvcCS) @@ -64,12 +63,13 @@ func TestResolver(t *testing.T) { }) t.Run("Local machine information is used to build conns", func(t *testing.T) { - mockPacketDispatcherService := mock_snet.NewMockPacketDispatcherService(ctrl) + mockNet := mock_snet.NewMockNetwork(ctrl) mockConn := mock_snet.NewMockPacketConn(ctrl) - mockPacketDispatcherService.EXPECT().Register(gomock.Any(), srcIA, - &net.UDPAddr{IP: net.IP{192, 0, 2, 1}}, - addr.SvcNone).Return(mockConn, uint16(42), nil) - mockConn.EXPECT().Close() + mockConn.EXPECT().LocalAddr().Return(&net.UDPAddr{ + IP: net.IP{192, 0, 2, 1}, Port: 30001}) + mockNet.EXPECT().OpenRaw(gomock.Any(), &net.UDPAddr{ + IP: net.IP{192, 0, 2, 1}}).Return(mockConn, nil) + mockConn.EXPECT().Close().Return(nil) mockRoundTripper := mock_svc.NewMockRoundTripper(ctrl) mockRoundTripper.EXPECT().RoundTrip(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do( @@ -80,7 +80,7 @@ func TestResolver(t *testing.T) { resolver := &svc.Resolver{ LocalIA: srcIA, - ConnFactory: mockPacketDispatcherService, + Network: mockNet, LocalIP: net.IP{192, 0, 2, 1}, RoundTripper: mockRoundTripper, } diff --git a/private/svc/svc.go b/private/svc/svc.go index e7451f25c1..c757538fe6 100644 --- a/private/svc/svc.go +++ b/private/svc/svc.go @@ -16,11 +16,10 @@ package svc import ( - "context" "net" - "net/netip" "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" @@ -40,102 +39,42 @@ const ( Forward ) -// NewResolverPacketDispatcher creates a dispatcher service that returns -// sockets with built-in SVC address resolution capabilities. -// -// RequestHandler results during connection read operations are handled in the -// following way: -// - on error result, the error is sent back to the reader -// - on forwarding result, the packet is sent back to the app for processing. -// - on handled result, the packet is discarded after processing, and a new -// read is attempted from the connection, and the entire decision process -// repeats. -func NewResolverPacketDispatcher(d snet.PacketDispatcherService, - h RequestHandler) *ResolverPacketDispatcher { - - return &ResolverPacketDispatcher{dispService: d, handler: h} -} - -var _ snet.PacketDispatcherService = (*ResolverPacketDispatcher)(nil) - -// ResolverPacketDispatcher is a dispatcher service that returns sockets with -// built-in SVC address resolution capabilities. Every packet received with a -// destination SVC address is intercepted inside the socket, and sent to an SVC -// resolution handler which responds back to the client. -// -// Redirected packets are not returned by the connection, so they cannot be -// seen via ReadFrom. After redirecting a packet, the connection attempts to -// read another packet before returning, until a non SVC packet is received or -// an error occurs. -type ResolverPacketDispatcher struct { - dispService snet.PacketDispatcherService - handler RequestHandler -} - -func (d *ResolverPacketDispatcher) Register(ctx context.Context, ia addr.IA, - registration *net.UDPAddr, svc addr.SVC) (snet.PacketConn, uint16, error) { - - registrationIP, ok := netip.AddrFromSlice(registration.IP) - if !ok { - return nil, 0, serrors.New("invalid registration IP", "ip", registration.IP) - } - c, port, err := d.dispService.Register(ctx, ia, registration, svc) - if err != nil { - return nil, 0, err - } - packetConn := &resolverPacketConn{ - PacketConn: c, - source: snet.SCIONAddress{ - IA: ia, - Host: addr.HostIP(registrationIP), - }, - handler: d.handler, - } - return packetConn, port, err -} - -// resolverPacketConn redirects SVC destination packets to SVC resolution +// ResolverPacketConn redirects SVC destination packets to SVC resolution // handler logic. -type resolverPacketConn struct { +type ResolverPacketConn struct { // PacketConn is the conn to receive and send packets. snet.PacketConn - // source contains the address from which packets should be sent. - source snet.SCIONAddress - // handler handles packets for SVC destinations. - handler RequestHandler + // Source contains the address from which packets should be sent. + Source snet.SCIONAddress + // Handler handles packets for SVC destinations. + Handler RequestHandler } -func (c *resolverPacketConn) ReadFrom(pkt *snet.Packet, ov *net.UDPAddr) error { +func (c *ResolverPacketConn) ReadFrom(pkt *snet.Packet, ov *net.UDPAddr) error { for { if err := c.PacketConn.ReadFrom(pkt, ov); err != nil { return err } - // XXX(scrye): destination address is guaranteed to not be nil if pkt.Destination.Host.Type() != addr.HostTypeSVC { // Normal packet, return to caller because data is already parsed and ready return nil } - svc := pkt.Destination.Host.SVC() - - // Multicasts do not trigger SVC resolution logic - if svc.IsMulticast() { - return nil - } - // XXX(scrye): This might block, causing the read to wait for the // write to go through. The solution would be to run the logic in a // goroutine, but because UDP writes rarely block, the current // solution should be good enough for now. r := &Request{ Conn: c.PacketConn, - Source: c.source, + Source: c.Source, Packet: pkt, Underlay: ov, } - switch result, err := c.handler.Handle(r); result { + switch result, err := c.Handler.Handle(r); result { case Error: - return serrors.Wrap(ErrHandler, err) + // We do not propagate error to caller, to avoid the connection fails, + // e.g., within QUIC layer. + log.Error("Error handling SVC request", "err", err) case Forward: return nil default: diff --git a/private/svc/svc_test.go b/private/svc/svc_test.go index f6aed72334..0d33ba53e7 100644 --- a/private/svc/svc_test.go +++ b/private/svc/svc_test.go @@ -25,6 +25,7 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/private/xtest" "github.com/scionproto/scion/pkg/slayers/path/empty" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/mock_snet" @@ -35,28 +36,29 @@ import ( func TestSVCResolutionServer(t *testing.T) { testCases := map[string]struct { - DispService func(ctrl *gomock.Controller) snet.PacketDispatcherService + Network func(ctrl *gomock.Controller) snet.Network ReqHandler func(ctrl *gomock.Controller) svc.RequestHandler - ErrRegister assert.ErrorAssertionFunc + ErrOpen assert.ErrorAssertionFunc ErrConnRead assert.ErrorAssertionFunc }{ - "Underlying dispatcher service fails to set up underlying conn": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(nil, uint16(0), errors.New("conn error")) - return s + "Underlying service fails to set up underlying conn": { + Network: func(ctrl *gomock.Controller) snet.Network { + c := mock_snet.NewMockNetwork(ctrl) + c.EXPECT().OpenRaw(gomock.Any(), gomock.Any()).Return(nil, errors.New("conn error")) + return c }, ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { return mock_svc.NewMockRequestHandler(ctrl) }, - ErrRegister: assert.Error, + ErrOpen: assert.Error, }, - "If handler fails, caller sees error": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + "If handler fails, caller doesn't see an error": { + Network: func(ctrl *gomock.Controller) snet.Network { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) - mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( + firstCall := mockPacketConn.EXPECT().ReadFrom( + gomock.Any(), + gomock.Any(), + ).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { pkt.Destination = snet.SCIONAddress{ Host: addr.HostSVC(addr.SvcCS), @@ -64,121 +66,95 @@ func TestSVCResolutionServer(t *testing.T) { return nil }, ) - - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(mockPacketConn, uint16(1337), nil) - return s - }, - ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { - h := mock_svc.NewMockRequestHandler(ctrl) - h.EXPECT().Handle(gomock.Any()).Return(svc.Error, errors.New("err")).AnyTimes() - return h - }, - ErrRegister: assert.NoError, - ErrConnRead: assert.Error, - }, - "If handler returns forward, caller sees data": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { - mockPacketConn := mock_snet.NewMockPacketConn(ctrl) - mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( + mockPacketConn.EXPECT().ReadFrom( + gomock.Any(), + gomock.Any(), + ).After(firstCall).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { pkt.Destination = snet.SCIONAddress{ - Host: addr.HostSVC(addr.SvcCS), + Host: addr.MustParseHost("127.0.0.1"), } return nil }, ) - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(mockPacketConn, uint16(1337), nil) - return s + c := mock_snet.NewMockNetwork(ctrl) + c.EXPECT().OpenRaw(gomock.Any(), gomock.Any()).Return(mockPacketConn, nil) + return c }, ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { h := mock_svc.NewMockRequestHandler(ctrl) - h.EXPECT().Handle(gomock.Any()).Return(svc.Forward, nil).AnyTimes() + h.EXPECT().Handle(gomock.Any()).Return(svc.Error, errors.New("err")) return h }, - ErrRegister: assert.NoError, + ErrOpen: assert.NoError, ErrConnRead: assert.NoError, }, - "return from conn with no error next internal read yields data": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + "If non-SVC addr, caller receives request": { + Network: func(ctrl *gomock.Controller) snet.Network { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { pkt.Destination = snet.SCIONAddress{ - Host: addr.MustParseHost("192.168.0.1"), + Host: addr.MustParseHost("127.0.0.1"), } return nil }, ) - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(mockPacketConn, uint16(1337), nil) - return s + c := mock_snet.NewMockNetwork(ctrl) + c.EXPECT().OpenRaw(gomock.Any(), gomock.Any()).Return(mockPacketConn, nil) + return c }, ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { - h := mock_svc.NewMockRequestHandler(ctrl) - h.EXPECT().Handle(gomock.Any()).Return(svc.Handled, nil).AnyTimes() - return h + return mock_svc.NewMockRequestHandler(ctrl) }, - ErrRegister: assert.NoError, + ErrOpen: assert.NoError, ErrConnRead: assert.NoError, }, - "return from socket with error if next internal read fails": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + "handled first, keep reading forwards following packet to caller": { + Network: func(ctrl *gomock.Controller) snet.Network { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { - return serrors.New("forced exit") + pkt.Destination = snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + } + return nil }, - ) + ).AnyTimes() - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(mockPacketConn, uint16(1337), nil) - return s + c := mock_snet.NewMockNetwork(ctrl) + c.EXPECT().OpenRaw(gomock.Any(), gomock.Any()).Return(mockPacketConn, nil) + return c }, ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { h := mock_svc.NewMockRequestHandler(ctrl) - h.EXPECT().Handle(gomock.Any()).Return(svc.Handled, nil).AnyTimes() + firstCall := h.EXPECT().Handle(gomock.Any()).Return(svc.Handled, nil) + h.EXPECT().Handle(gomock.Any()).After(firstCall).Return(svc.Forward, nil) return h }, - ErrRegister: assert.NoError, - ErrConnRead: assert.Error, + ErrOpen: assert.NoError, + ErrConnRead: assert.NoError, }, - "Multicast SVC packets get delivered to caller": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + "Return from socket with error if next internal read fails": { + Network: func(ctrl *gomock.Controller) snet.Network { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { - pkt.Destination = snet.SCIONAddress{ - Host: addr.HostSVC(addr.SvcCS.Multicast()), - } - return nil + return serrors.New("forced exit") }, ) - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(mockPacketConn, uint16(1337), nil) - return s + c := mock_snet.NewMockNetwork(ctrl) + c.EXPECT().OpenRaw(gomock.Any(), gomock.Any()).Return(mockPacketConn, nil) + return c }, ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { - h := mock_svc.NewMockRequestHandler(ctrl) - h.EXPECT().Handle(gomock.Any()).Return(svc.Handled, nil).AnyTimes() - return h + return mock_svc.NewMockRequestHandler(ctrl) }, - ErrRegister: assert.NoError, - ErrConnRead: assert.NoError, + ErrOpen: assert.NoError, + ErrConnRead: assert.Error, }, } for name, tc := range testCases { @@ -189,21 +165,24 @@ func TestSVCResolutionServer(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - disp := svc.NewResolverPacketDispatcher(tc.DispService(ctrl), tc.ReqHandler(ctrl)) - conn, port, err := disp.Register(context.Background(), 0, - &net.UDPAddr{IP: net.ParseIP("198.51.100.1")}, - addr.SvcCS) - - tc.ErrRegister(t, err) + pconn, err := tc.Network(ctrl).OpenRaw( + context.Background(), + &net.UDPAddr{IP: xtest.MustParseIP(t, "127.0.0.1")}, + ) + tc.ErrOpen(t, err) if err != nil { - assert.Nil(t, conn) - assert.Zero(t, port) + assert.Nil(t, pconn) return } else { - assert.NotNil(t, conn) - assert.Equal(t, port, uint16(1337)) + assert.NotNil(t, pconn) + } + + resolvedPacketConn := &svc.ResolverPacketConn{ + PacketConn: pconn, + Handler: tc.ReqHandler(ctrl), } - err = conn.ReadFrom(&snet.Packet{}, &net.UDPAddr{}) + + err = resolvedPacketConn.ReadFrom(&snet.Packet{}, &net.UDPAddr{}) tc.ErrConnRead(t, err) }) } @@ -222,6 +201,9 @@ func TestDefaultHandler(t *testing.T) { "path cannot be reversed": { InputPacket: &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Path: path.SCION{ Raw: []byte{0x00, 0x01, 0x02, 0x03}, }, @@ -233,6 +215,9 @@ func TestDefaultHandler(t *testing.T) { "empty UDP payload, success": { InputPacket: &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Payload: snet.UDPPayload{}, Path: snet.RawPath{}, }, @@ -250,6 +235,9 @@ func TestDefaultHandler(t *testing.T) { "UDP payload with ports": { InputPacket: &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Payload: snet.UDPPayload{SrcPort: 42, DstPort: 73}, Path: snet.RawPath{}, }, @@ -267,6 +255,9 @@ func TestDefaultHandler(t *testing.T) { ReplyPayload: []byte{1, 2, 3, 4}, InputPacket: &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Payload: snet.UDPPayload{}, Path: snet.RawPath{}, }, @@ -284,6 +275,9 @@ func TestDefaultHandler(t *testing.T) { ReplySource: snet.SCIONAddress{Host: addr.MustParseHost("192.168.0.1")}, InputPacket: &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Payload: snet.UDPPayload{}, Path: snet.RawPath{}, }, @@ -334,6 +328,9 @@ func TestDefaultHandler(t *testing.T) { conn := mock_snet.NewMockPacketConn(ctrl) packet := &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Payload: snet.UDPPayload{}, Path: snet.RawPath{}, }, diff --git a/private/topology/interface.go b/private/topology/interface.go index 9680d08bf8..5100f70438 100644 --- a/private/topology/interface.go +++ b/private/topology/interface.go @@ -40,6 +40,9 @@ type Topology interface { Core() bool // InterfaceIDs returns all interface IDS from the local AS. InterfaceIDs() []common.IFIDType + // PortRange returns the first and last ports of the port range (both included), + // in which endhost listen for SCION/UDP application using the UDP/IP underlay. + PortRange() (uint16, uint16) // PublicAddress gets the public address of a server with the requested type and name, and nil // if no such server exists. @@ -149,6 +152,10 @@ func (t *topologyS) InterfaceIDs() []common.IFIDType { return intfs } +func (t *topologyS) PortRange() (uint16, uint16) { + return t.Topology.DispatchedPortStart, t.Topology.DispatchedPortEnd +} + func (t *topologyS) UnderlayNextHop(ifid common.IFIDType) (*net.UDPAddr, bool) { ifInfo, ok := t.Topology.IFInfoMap[ifid] if !ok { diff --git a/private/topology/json/json.go b/private/topology/json/json.go index 0af0c692ec..175cd2e344 100644 --- a/private/topology/json/json.go +++ b/private/topology/json/json.go @@ -71,10 +71,11 @@ func (as *Attributes) UnmarshalJSON(b []byte) error { // Topology is the JSON type for the entire AS topology file. type Topology struct { - Timestamp int64 `json:"timestamp,omitempty"` - TimestampHuman string `json:"timestamp_human,omitempty"` - IA string `json:"isd_as"` - MTU int `json:"mtu"` + Timestamp int64 `json:"timestamp,omitempty"` + TimestampHuman string `json:"timestamp_human,omitempty"` + IA string `json:"isd_as"` + MTU int `json:"mtu"` + EndhostPortRange string `json:"dispatched_ports"` // Attributes specify whether this is a core AS or not. Attributes Attributes `json:"attributes"` BorderRouters map[string]*BRInfo `json:"border_routers,omitempty"` diff --git a/private/topology/json/json_test.go b/private/topology/json/json_test.go index 17e5637048..4968571bec 100644 --- a/private/topology/json/json_test.go +++ b/private/topology/json/json_test.go @@ -37,11 +37,12 @@ var ( func TestLoadRawFromFile(t *testing.T) { referenceTopology := &jsontopo.Topology{ - Timestamp: 168562800, - TimestampHuman: "May 6 00:00:00 CET 1975", - IA: "6-ff00:0:362", - MTU: 1472, - Attributes: []jsontopo.Attribute{jsontopo.AttrCore}, + Timestamp: 168562800, + TimestampHuman: "May 6 00:00:00 CET 1975", + IA: "6-ff00:0:362", + MTU: 1472, + EndhostPortRange: "1024-65535", + Attributes: []jsontopo.Attribute{jsontopo.AttrCore}, BorderRouters: map[string]*jsontopo.BRInfo{ "borderrouter6-f00:0:362-1": { InternalAddr: "10.1.0.1:0", diff --git a/private/topology/json/testdata/topology-deprecated-attrs.json b/private/topology/json/testdata/topology-deprecated-attrs.json index 16e9f352e8..ff264e76d6 100644 --- a/private/topology/json/testdata/topology-deprecated-attrs.json +++ b/private/topology/json/testdata/topology-deprecated-attrs.json @@ -3,6 +3,7 @@ "timestamp_human": "May 6 00:00:00 CET 1975", "isd_as": "6-ff00:0:362", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [ "authoritative", "core", diff --git a/private/topology/json/testdata/topology.json b/private/topology/json/testdata/topology.json index 02817faa64..4a8a8fc3df 100644 --- a/private/topology/json/testdata/topology.json +++ b/private/topology/json/testdata/topology.json @@ -3,6 +3,7 @@ "timestamp_human": "May 6 00:00:00 CET 1975", "isd_as": "6-ff00:0:362", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/private/topology/mock_topology/mock.go b/private/topology/mock_topology/mock.go index 66d237aed7..5efe3794b3 100644 --- a/private/topology/mock_topology/mock.go +++ b/private/topology/mock_topology/mock.go @@ -182,6 +182,21 @@ func (mr *MockTopologyMockRecorder) Multicast(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Multicast", reflect.TypeOf((*MockTopology)(nil).Multicast), arg0) } +// PortRange mocks base method. +func (m *MockTopology) PortRange() (uint16, uint16) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PortRange") + ret0, _ := ret[0].(uint16) + ret1, _ := ret[1].(uint16) + return ret0, ret1 +} + +// PortRange indicates an expected call of PortRange. +func (mr *MockTopologyMockRecorder) PortRange() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortRange", reflect.TypeOf((*MockTopology)(nil).PortRange)) +} + // PublicAddress mocks base method. func (m *MockTopology) PublicAddress(arg0 addr.SVC, arg1 string) *net.UDPAddr { m.ctrl.T.Helper() diff --git a/private/topology/reload.go b/private/topology/reload.go index 74f974681f..1ec5d4b9e1 100644 --- a/private/topology/reload.go +++ b/private/topology/reload.go @@ -148,6 +148,13 @@ func (l *Loader) InterfaceIDs() []uint16 { return ids } +func (l *Loader) PortRange() (uint16, uint16) { + l.mtx.Lock() + defer l.mtx.Unlock() + + return l.topo.PortRange() +} + func (l *Loader) ControlServiceAddresses() []*net.UDPAddr { l.mtx.Lock() defer l.mtx.Unlock() diff --git a/private/topology/testdata/basic.json b/private/topology/testdata/basic.json index 7107b480da..a926651e1c 100644 --- a/private/topology/testdata/basic.json +++ b/private/topology/testdata/basic.json @@ -3,6 +3,7 @@ "timestamp_human": "1975-05-06 01:02:03.000000+0000", "isd_as": "1-ff00:0:311", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [], "border_routers": { "br1-ff00:0:311-1": { diff --git a/private/topology/testdata/core.json b/private/topology/testdata/core.json index 312043e2f1..a13778ee3d 100644 --- a/private/topology/testdata/core.json +++ b/private/topology/testdata/core.json @@ -3,6 +3,7 @@ "timestamp_human": "May 6 00:00:00 CET 1975", "isd_as": "6-ff00:0:362", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/private/topology/topology.go b/private/topology/topology.go index b7c6ce443f..0590743209 100644 --- a/private/topology/topology.go +++ b/private/topology/topology.go @@ -23,17 +23,22 @@ import ( "net/netip" "os" "sort" + "strconv" + "strings" "time" "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" jsontopo "github.com/scionproto/scion/private/topology/json" "github.com/scionproto/scion/private/topology/underlay" ) -// EndhostPort is the underlay port that the dispatcher binds to on non-routers. -const EndhostPort = underlay.EndhostPort +const ( + // EndhostPort is the underlay port that SCION binds to on non-routers. + EndhostPort = underlay.EndhostPort +) // ErrAddressNotFound indicates the address was not found. var ErrAddressNotFound = serrors.New("address not found") @@ -56,10 +61,12 @@ type ( // there is again a sorted slice of names of the servers that provide the service. // Additionally, there is a map from those names to TopoAddr structs. RWTopology struct { - Timestamp time.Time - IA addr.IA - IsCore bool - MTU int + Timestamp time.Time + IA addr.IA + IsCore bool + MTU int + DispatchedPortStart uint16 + DispatchedPortEnd uint16 BR map[string]BRInfo IFInfoMap IfInfoMap @@ -201,6 +208,11 @@ func (t *RWTopology) populateMeta(raw *jsontopo.Topology) error { } t.MTU = raw.MTU + t.DispatchedPortStart, t.DispatchedPortEnd, err = validatePortRange(raw.EndhostPortRange) + if err != nil { + return err + } + isCore := false for _, attr := range raw.Attributes { if attr == jsontopo.AttrCore { @@ -212,6 +224,37 @@ func (t *RWTopology) populateMeta(raw *jsontopo.Topology) error { return nil } +func validatePortRange(portRange string) (uint16, uint16, error) { + if portRange == "" || portRange == "-" { + log.Debug("Empty port range defined") + return 0, 0, nil + } + if portRange == "all" || portRange == "ALL" { + log.Debug("\"all\" port range defined") + return uint16(1), uint16(65535), nil + } + ports := strings.Split(portRange, "-") + if len(ports) != 2 { + return 0, 0, serrors.New("invalid format: expected startPort-endPort", "got", portRange) + } + startPort, errStart := strconv.ParseUint(ports[0], 10, 16) + endPort, errEnd := strconv.ParseUint(ports[1], 10, 16) + if errStart != nil || errEnd != nil { + return 0, 0, serrors.New("invalid port numbers", "got", portRange) + } + if startPort < 1 { + return 0, 0, serrors.New("invalid value for start port", "start port", startPort) + } + if endPort < 1 { + return 0, 0, serrors.New("invalid value for end port", "end port", endPort) + } + if startPort > endPort { + return 0, 0, serrors.New("start port is bigger than end port for the SCION port range", + "start port", startPort, "end port", endPort) + } + return uint16(startPort), uint16(endPort), nil +} + func (t *RWTopology) populateBR(raw *jsontopo.Topology) error { for name, rawBr := range raw.BorderRouters { if rawBr.InternalAddr == "" { @@ -372,10 +415,12 @@ func (t *RWTopology) Copy() *RWTopology { return nil } return &RWTopology{ - Timestamp: t.Timestamp, - IA: t.IA, - MTU: t.MTU, - IsCore: t.IsCore, + Timestamp: t.Timestamp, + IA: t.IA, + MTU: t.MTU, + IsCore: t.IsCore, + DispatchedPortStart: t.DispatchedPortStart, + DispatchedPortEnd: t.DispatchedPortEnd, BR: copyBRMap(t.BR), IFInfoMap: t.IFInfoMap.copy(), diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index 534c2e0115..a6fa30c23b 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -39,7 +39,7 @@ const ( ) const ( - // EndhostPort is the underlay port that the dispatcher binds to on non-routers. Subject to + // EndhostPort is the underlay port that SCION binds to on non-routers. Subject to // change during standardisation. EndhostPort = 30041 ) diff --git a/proto/daemon/v1/BUILD.bazel b/proto/daemon/v1/BUILD.bazel index cf258adada..8e53589e34 100644 --- a/proto/daemon/v1/BUILD.bazel +++ b/proto/daemon/v1/BUILD.bazel @@ -9,6 +9,7 @@ proto_library( deps = [ "//proto/drkey/v1:drkey", "@com_google_protobuf//:duration_proto", + "@com_google_protobuf//:empty_proto", "@com_google_protobuf//:timestamp_proto", ], ) diff --git a/proto/daemon/v1/daemon.proto b/proto/daemon/v1/daemon.proto index 3fb8632000..fff158a11a 100644 --- a/proto/daemon/v1/daemon.proto +++ b/proto/daemon/v1/daemon.proto @@ -20,6 +20,7 @@ package proto.daemon.v1; import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; +import "google/protobuf/empty.proto"; import "proto/drkey/v1/drkey.proto"; service DaemonService { @@ -35,6 +36,8 @@ service DaemonService { rpc Services(ServicesRequest) returns (ServicesResponse) {} // Inform the SCION Daemon of a revocation. rpc NotifyInterfaceDown(NotifyInterfaceDownRequest) returns (NotifyInterfaceDownResponse) {} + // Returns the endhost portRange defined in the local AS. + rpc PortRange(google.protobuf.Empty) returns (PortRangeResponse) {} // DRKeyASHost returns a key that matches the request. rpc DRKeyASHost (DRKeyASHostRequest) returns (DRKeyASHostResponse) {} // DRKeyHostAS returns a key that matches the request. @@ -200,6 +203,13 @@ message NotifyInterfaceDownRequest { message NotifyInterfaceDownResponse {}; +message PortRangeResponse { + // The lowest port in the SCION/UDP dispatched port range. + uint32 dispatched_port_start = 1; + // The highest port in the SCION/UDP dispatched port range. + uint32 dispatched_port_end = 2; +} + message DRKeyHostASRequest{ // Point in time where requested key is valid. google.protobuf.Timestamp val_time = 1; diff --git a/router/cmd/router/main.go b/router/cmd/router/main.go index 835b066f12..6a43e58796 100644 --- a/router/cmd/router/main.go +++ b/router/cmd/router/main.go @@ -56,14 +56,17 @@ func realMain(ctx context.Context) error { } g, errCtx := errgroup.WithContext(ctx) metrics := router.NewMetrics() + dp := &router.Connector{ DataPlane: router.DataPlane{ Metrics: metrics, ExperimentalSCMPAuthentication: globalCfg.Features.ExperimentalSCMPAuthentication, }, - ReceiveBufferSize: globalCfg.Router.ReceiveBufferSize, - SendBufferSize: globalCfg.Router.SendBufferSize, - BFD: globalCfg.Router.BFD, + ReceiveBufferSize: globalCfg.Router.ReceiveBufferSize, + SendBufferSize: globalCfg.Router.SendBufferSize, + BFD: globalCfg.Router.BFD, + DispatchedPortStart: globalCfg.Router.DispatchedPortStart, + DispatchedPortEnd: globalCfg.Router.DispatchedPortEnd, } iaCtx := &control.IACtx{ Config: controlConfig, diff --git a/router/config/config.go b/router/config/config.go index 01a0e8291e..eeb0308fa1 100644 --- a/router/config/config.go +++ b/router/config/config.go @@ -48,6 +48,12 @@ type RouterConfig struct { NumSlowPathProcessors int `toml:"num_slow_processors,omitempty"` BatchSize int `toml:"batch_size,omitempty"` BFD BFD `toml:"bfd,omitempty"` + // TODO: These two values were introduced to override the port range for + // configured router in the context of acceptance tests. However, this + // introduces two sources for the port configuration. We should remove this + // and adapt the acceptance tests. + DispatchedPortStart *int `toml:"dispatched_port_start,omitempty"` + DispatchedPortEnd *int `toml:"dispatched_port_end,omitempty"` } // BFD configuration. Unfortunately cannot be shared with topology.BFD @@ -79,7 +85,27 @@ func (cfg *RouterConfig) Validate() error { if cfg.NumSlowPathProcessors < 1 { return serrors.New("Provided router config is invalid. NumSlowPathProcessors < 1") } - + if cfg.DispatchedPortStart != nil { + if cfg.DispatchedPortEnd == nil { + return serrors.New("provided router config is invalid. " + + "EndHostEndPort is nil; EndHostStartPort isn't") + } + if *cfg.DispatchedPortStart < 0 { + return serrors.New("provided router config is invalid. EndHostStartPort < 0") + } + if *cfg.DispatchedPortEnd >= (1 << 16) { + return serrors.New("provided router config is invalid. EndHostEndPort > 2**16 -1") + } + if *cfg.DispatchedPortStart > *cfg.DispatchedPortEnd { + return serrors.New("provided router config is invalid. " + + "EndHostStartPort > DispatchedPortEnd") + } + } else { + if cfg.DispatchedPortEnd != nil { + return serrors.New("provided router config is invalid. " + + "EndHostStartPort is nil; EndHostEndPort isn't") + } + } return nil } diff --git a/router/connector.go b/router/connector.go index 78adffb0cf..e3d205287f 100644 --- a/router/connector.go +++ b/router/connector.go @@ -23,7 +23,6 @@ import ( "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/private/topology" "github.com/scionproto/scion/private/underlay/conn" "github.com/scionproto/scion/router/config" "github.com/scionproto/scion/router/control" @@ -40,9 +39,11 @@ type Connector struct { externalInterfaces map[uint16]control.ExternalInterface siblingInterfaces map[uint16]control.SiblingInterface - ReceiveBufferSize int - SendBufferSize int - BFD config.BFD + ReceiveBufferSize int + SendBufferSize int + BFD config.BFD + DispatchedPortStart *int + DispatchedPortEnd *int } var errMultiIA = serrors.New("different IA not allowed") @@ -141,25 +142,25 @@ func (c *Connector) AddExternalInterface(localIfID common.IFIDType, link control } // AddSvc adds the service address for the given ISD-AS. -func (c *Connector) AddSvc(ia addr.IA, svc addr.SVC, ip net.IP) error { +func (c *Connector) AddSvc(ia addr.IA, svc addr.SVC, a *net.UDPAddr) error { c.mtx.Lock() defer c.mtx.Unlock() - log.Debug("Adding service", "isd_as", ia, "svc", svc, "ip", ip) + log.Debug("Adding service", "isd_as", ia, "svc", svc, "address", a) if !c.ia.Equal(ia) { return serrors.WithCtx(errMultiIA, "current", c.ia, "new", ia) } - return c.DataPlane.AddSvc(svc, &net.UDPAddr{IP: ip, Port: topology.EndhostPort}) + return c.DataPlane.AddSvc(svc, a) } // DelSvc deletes the service entry for the given ISD-AS and IP pair. -func (c *Connector) DelSvc(ia addr.IA, svc addr.SVC, ip net.IP) error { +func (c *Connector) DelSvc(ia addr.IA, svc addr.SVC, a *net.UDPAddr) error { c.mtx.Lock() defer c.mtx.Unlock() - log.Debug("Deleting service", "isd_as", ia, "svc", svc, "ip", ip) + log.Debug("Deleting service", "isd_as", ia, "svc", svc, "address", a) if !c.ia.Equal(ia) { return serrors.WithCtx(errMultiIA, "current", c.ia, "new", ia) } - return c.DataPlane.DelSvc(svc, &net.UDPAddr{IP: ip, Port: topology.EndhostPort}) + return c.DataPlane.DelSvc(svc, a) } // SetKey sets the key for the given ISD-AS at the given index. @@ -232,3 +233,16 @@ func (c *Connector) applyBFDDefaults(cfg control.BFD) control.BFD { } return cfg } + +func (c *Connector) SetPortRange(start, end uint16) { + c.mtx.Lock() + defer c.mtx.Unlock() + if c.DispatchedPortStart != nil { + start = uint16(*c.DispatchedPortStart) + } + if c.DispatchedPortEnd != nil { + end = uint16(*c.DispatchedPortEnd) + } + log.Debug("Endhost port range configuration", "startPort", start, "endPort", end) + c.DataPlane.SetPortRange(start, end) +} diff --git a/router/control/conf.go b/router/control/conf.go index 46be29aff0..7c4ecd8bc3 100644 --- a/router/control/conf.go +++ b/router/control/conf.go @@ -34,9 +34,10 @@ type Dataplane interface { CreateIACtx(ia addr.IA) error AddInternalInterface(ia addr.IA, local netip.AddrPort) error AddExternalInterface(localIfID common.IFIDType, info LinkInfo, owned bool) error - AddSvc(ia addr.IA, svc addr.SVC, ip net.IP) error - DelSvc(ia addr.IA, svc addr.SVC, ip net.IP) error + AddSvc(ia addr.IA, svc addr.SVC, a *net.UDPAddr) error + DelSvc(ia addr.IA, svc addr.SVC, a *net.UDPAddr) error SetKey(ia addr.IA, index int, key []byte) error + SetPortRange(start, end uint16) } // BFD is the configuration for the BFD sessions. @@ -128,6 +129,7 @@ func ConfigDataplane(dp Dataplane, cfg *Config) error { return err } } + // Add internal interfaces if cfg.BR != nil { if cfg.BR.InternalAddr != (netip.AddrPort{}) { @@ -144,6 +146,8 @@ func ConfigDataplane(dp Dataplane, cfg *Config) error { if err := confServices(dp, cfg); err != nil { return err } + // Set Endhost port range + dp.SetPortRange(cfg.Topo.PortRange()) return nil } @@ -222,7 +226,7 @@ func confServices(dp Dataplane, cfg *Config) error { return nil } for _, svc := range svcTypes { - addrs, err := cfg.Topo.UnderlayMulticast(svc) + addrs, err := cfg.Topo.Multicast(svc) if err != nil { // XXX assumption is that any error means there are no addresses for the SVC type continue @@ -232,7 +236,7 @@ func confServices(dp Dataplane, cfg *Config) error { return addrs[i].IP.String() < addrs[j].IP.String() }) for _, a := range addrs { - if err := dp.AddSvc(cfg.IA, svc, a.IP); err != nil { + if err := dp.AddSvc(cfg.IA, svc, a); err != nil { return err } } diff --git a/router/control/testdata/topology.json b/router/control/testdata/topology.json index 91e0399cb7..2d3a1739e7 100644 --- a/router/control/testdata/topology.json +++ b/router/control/testdata/topology.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:110", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/router/dataplane.go b/router/dataplane.go index ff387ce187..76a92e4349 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -20,6 +20,7 @@ import ( "context" "crypto/rand" "crypto/subtle" + "encoding/binary" "errors" "fmt" "hash" @@ -90,22 +91,24 @@ type BatchConn interface { // from multiple sockets, performs routing, and sends them to their destinations // (after updating the path, if that is needed). type DataPlane struct { - interfaces map[uint16]BatchConn - external map[uint16]BatchConn - linkTypes map[uint16]topology.LinkType - neighborIAs map[uint16]addr.IA - peerInterfaces map[uint16]uint16 - internal BatchConn - internalIP netip.Addr - internalNextHops map[uint16]*net.UDPAddr - svc *services - macFactory func() hash.Hash - bfdSessions map[uint16]bfdSession - localIA addr.IA - mtx sync.Mutex - running bool - Metrics *Metrics - forwardingMetrics map[uint16]interfaceMetrics + interfaces map[uint16]BatchConn + external map[uint16]BatchConn + linkTypes map[uint16]topology.LinkType + neighborIAs map[uint16]addr.IA + peerInterfaces map[uint16]uint16 + internal BatchConn + internalIP netip.Addr + internalNextHops map[uint16]*net.UDPAddr + svc *services + macFactory func() hash.Hash + bfdSessions map[uint16]bfdSession + localIA addr.IA + mtx sync.Mutex + running bool + Metrics *Metrics + forwardingMetrics map[uint16]interfaceMetrics + dispatchedPortStart uint16 + dispatchedPortEnd uint16 ExperimentalSCMPAuthentication bool @@ -196,6 +199,11 @@ func (d *DataPlane) SetKey(key []byte) error { return nil } +func (d *DataPlane) SetPortRange(start, end uint16) { + d.dispatchedPortStart = start + d.dispatchedPortEnd = end +} + // AddInternalInterface sets the interface the data-plane will use to // send/receive traffic in the local AS. This can only be called once; future // calls will return an error. This can only be called on a not yet running @@ -1570,7 +1578,7 @@ func (p *scionPacketProcessor) verifyCurrentMAC() (processResult, error) { } func (p *scionPacketProcessor) resolveInbound() (*net.UDPAddr, processResult, error) { - a, err := p.d.resolveLocalDst(p.scionLayer) + a, err := p.d.resolveLocalDst(p.scionLayer, p.lastLayer) switch { case errors.Is(err, noSVCBackend): log.Debug("SCMP: no SVC backend") @@ -1966,14 +1974,18 @@ func (p *scionPacketProcessor) processOHP() (processResult, error) { if err := updateSCIONLayer(p.rawPkt, s, p.buffer); err != nil { return processResult{}, err } - a, err := p.d.resolveLocalDst(s) + a, err := p.d.resolveLocalDst(s, p.lastLayer) if err != nil { return processResult{}, err } return processResult{OutAddr: a, OutPkt: p.rawPkt}, nil } -func (d *DataPlane) resolveLocalDst(s slayers.SCION) (*net.UDPAddr, error) { +func (d *DataPlane) resolveLocalDst( + s slayers.SCION, + lastLayer gopacket.DecodingLayer, +) (*net.UDPAddr, error) { + dst, err := s.DstAddr() if err != nil { // TODO parameter problem. @@ -1987,16 +1999,165 @@ func (d *DataPlane) resolveLocalDst(s slayers.SCION) (*net.UDPAddr, error) { if !ok { return nil, noSVCBackend } + // if SVC address is outside the configured port range we send to the fix + // port. + if uint16(a.Port) < d.dispatchedPortStart || uint16(a.Port) > d.dispatchedPortEnd { + a.Port = topology.EndhostPort + } return a, nil case addr.HostTypeIP: - return addEndhostPort(dst.IP().AsSlice()), nil + // Parse UPD port and rewrite underlay IP/UDP port + return d.addEndhostPort(lastLayer, dst.IP().AsSlice()) default: panic("unexpected address type returned from DstAddr") } } -func addEndhostPort(dst net.IP) *net.UDPAddr { - return &net.UDPAddr{IP: dst, Port: topology.EndhostPort} +func (d *DataPlane) addEndhostPort( + lastLayer gopacket.DecodingLayer, + dst []byte, +) (*net.UDPAddr, error) { + + // Parse UPD port and rewrite underlay IP/UDP port + l4Type := nextHdr(lastLayer) + switch l4Type { + case slayers.L4UDP: + if len(lastLayer.LayerPayload()) < 8 { + // TODO(JordiSubira): Treat this as a parameter problem + return nil, serrors.New("SCION/UDP header len too small", "legth", + len(lastLayer.LayerPayload())) + } + port := binary.BigEndian.Uint16(lastLayer.LayerPayload()[2:]) + if port < d.dispatchedPortStart || port > d.dispatchedPortEnd { + port = topology.EndhostPort + } + return &net.UDPAddr{IP: dst, Port: int(port)}, nil + case slayers.L4SCMP: + var scmpLayer slayers.SCMP + err := scmpLayer.DecodeFromBytes(lastLayer.LayerPayload(), gopacket.NilDecodeFeedback) + if err != nil { + // TODO(JordiSubira): Treat this as a parameter problem. + return nil, serrors.WrapStr("decoding SCMP layer for extracting endhost dst port", err) + } + port, err := getDstPortSCMP(&scmpLayer) + if err != nil { + // TODO(JordiSubira): Treat this as a parameter problem. + return nil, serrors.WrapStr("getting dst port from SCMP message", err) + } + // if the SCMP dst port is outside the range, we send it to the EndhostPort + if port < d.dispatchedPortStart || port > d.dispatchedPortEnd { + port = topology.EndhostPort + } + return &net.UDPAddr{IP: dst, Port: int(port)}, nil + default: + log.Debug("msg", "protocol", l4Type) + return &net.UDPAddr{IP: dst, Port: topology.EndhostPort}, nil + } +} + +func getDstPortSCMP(scmp *slayers.SCMP) (uint16, error) { + // XXX(JordiSubira): This implementation is far too slow for the dataplane. + // We should reimplement this with fewer helpers and memory allocations, since + // our sole goal is to parse the L4 port or identifier in the offending packets. + if scmp.TypeCode.Type() == slayers.SCMPTypeEchoRequest || + scmp.TypeCode.Type() == slayers.SCMPTypeTracerouteRequest { + return topology.EndhostPort, nil + } + if scmp.TypeCode.Type() == slayers.SCMPTypeEchoReply { + var scmpEcho slayers.SCMPEcho + err := scmpEcho.DecodeFromBytes(scmp.Payload, gopacket.NilDecodeFeedback) + if err != nil { + return 0, err + } + return scmpEcho.Identifier, nil + } + if scmp.TypeCode.Type() == slayers.SCMPTypeTracerouteReply { + var scmpTraceroute slayers.SCMPTraceroute + err := scmpTraceroute.DecodeFromBytes(scmp.Payload, gopacket.NilDecodeFeedback) + if err != nil { + return 0, err + } + return scmpTraceroute.Identifier, nil + } + + // Drop unknown SCMP error messages. + if scmp.NextLayerType() == gopacket.LayerTypePayload { + return 0, serrors.New("unsupported SCMP error message", + "type", scmp.TypeCode.Type()) + } + l, err := decodeSCMP(scmp) + if err != nil { + return 0, err + } + if len(l) != 2 { + return 0, serrors.New("SCMP error message without payload") + } + gpkt := gopacket.NewPacket(*l[1].(*gopacket.Payload), slayers.LayerTypeSCION, + gopacket.DecodeOptions{ + NoCopy: true, + }, + ) + + // If the offending packet was UDP/SCION, use the source port to deliver. + if udp := gpkt.Layer(slayers.LayerTypeSCIONUDP); udp != nil { + port := udp.(*slayers.UDP).SrcPort + // XXX(roosd): We assume that the zero value means the UDP header is + // truncated. This flags packets of misbehaving senders as truncated, if + // they set the source port to 0. But there is no harm, since those + // packets are destined to be dropped anyway. + if port == 0 { + return 0, serrors.New("SCMP error with truncated UDP header") + } + return port, nil + } + + // If the offending packet was SCMP/SCION, and it is an echo or traceroute, + // use the Identifier to deliver. In all other cases, the message is dropped. + if scmp := gpkt.Layer(slayers.LayerTypeSCMP); scmp != nil { + + tc := scmp.(*slayers.SCMP).TypeCode + // SCMP Error messages in response to an SCMP error message are not allowed. + if !tc.InfoMsg() { + return 0, serrors.New("SCMP error message in response to SCMP error message", + "type", tc.Type()) + } + // We only support echo and traceroute requests. + t := tc.Type() + if t != slayers.SCMPTypeEchoRequest && t != slayers.SCMPTypeTracerouteRequest { + return 0, serrors.New("unsupported SCMP info message", "type", t) + } + + var port uint16 + // Extract the port from the echo or traceroute ID field. + if echo := gpkt.Layer(slayers.LayerTypeSCMPEcho); echo != nil { + port = echo.(*slayers.SCMPEcho).Identifier + } else if tr := gpkt.Layer(slayers.LayerTypeSCMPTraceroute); tr != nil { + port = tr.(*slayers.SCMPTraceroute).Identifier + } else { + return 0, serrors.New("SCMP error with truncated payload") + } + return port, nil + } + return 0, serrors.New("unknown SCION SCMP content") +} + +// decodeSCMP decodes the SCMP payload. WARNING: Decoding is done with NoCopy set. +func decodeSCMP(scmp *slayers.SCMP) ([]gopacket.SerializableLayer, error) { + gpkt := gopacket.NewPacket(scmp.Payload, scmp.NextLayerType(), + gopacket.DecodeOptions{NoCopy: true}) + layers := gpkt.Layers() + if len(layers) == 0 || len(layers) > 2 { + return nil, serrors.New("invalid number of SCMP layers", "count", len(layers)) + } + ret := make([]gopacket.SerializableLayer, len(layers)) + for i, l := range layers { + s, ok := l.(gopacket.SerializableLayer) + if !ok { + return nil, serrors.New("invalid SCMP layer, not serializable", "index", i) + } + ret[i] = s + } + return ret, nil } // TODO(matzf) this function is now only used to update the OneHop-path. diff --git a/router/dataplane_internal_test.go b/router/dataplane_internal_test.go index 9fa22fb4a2..7b9914e7f7 100644 --- a/router/dataplane_internal_test.go +++ b/router/dataplane_internal_test.go @@ -42,7 +42,9 @@ import ( "github.com/scionproto/scion/router/mock_router" ) -var testKey = []byte("testkey_xxxxxxxx") +var ( + testKey = []byte("testkey_xxxxxxxx") +) // TestReceiver sets up a mocked batchConn, starts the receiver that reads from // this batchConn and forwards it to the processing routines channels. We verify @@ -444,8 +446,7 @@ func TestSlowPathProcessing(t *testing.T) { nil, mock_router.NewMockBatchConn(ctrl), fakeInternalNextHops, map[addr.SVC][]*net.UDPAddr{}, - xtest.MustParseIA("1-ff00:0:110"), - nil, testKey) + xtest.MustParseIA("1-ff00:0:110"), nil, testKey) }, mockMsg: func() []byte { spkt := prepBaseMsg(t, payload, 0) diff --git a/router/dataplane_test.go b/router/dataplane_test.go index 105a0ef6a8..7750bb1861 100644 --- a/router/dataplane_test.go +++ b/router/dataplane_test.go @@ -52,7 +52,11 @@ import ( "github.com/scionproto/scion/router/mock_router" ) -var metrics = router.GetMetrics() +var ( + metrics = router.GetMetrics() + srcUDPPort = 50001 + dstUDPPort = 50002 +) func TestDataPlaneAddInternalInterface(t *testing.T) { internalIP := netip.MustParseAddr("198.51.100.1") @@ -652,7 +656,7 @@ func TestProcessPkt(t *testing.T) { dpath.HopFields[2].Mac = computeMAC(t, key, dpath.InfoFields[0], dpath.HopFields[2]) ret := toMsg(t, spkt, dpath) if afterProcessing { - ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: topology.EndhostPort} + ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: dstUDPPort} ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil } return ret @@ -1247,7 +1251,7 @@ func TestProcessPkt(t *testing.T) { addr.SvcCS: { &net.UDPAddr{ IP: net.ParseIP("10.0.200.200").To4(), - Port: topology.EndhostPort, + Port: dstUDPPort, }, }, }, @@ -1267,7 +1271,7 @@ func TestProcessPkt(t *testing.T) { ret := toMsg(t, spkt, dpath) if afterProcessing { ret.Addr = &net.UDPAddr{IP: net.ParseIP("10.0.200.200").To4(), - Port: topology.EndhostPort} + Port: dstUDPPort} ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil } return ret @@ -1285,7 +1289,7 @@ func TestProcessPkt(t *testing.T) { map[addr.SVC][]*net.UDPAddr{ addr.SvcCS: {&net.UDPAddr{ IP: net.ParseIP("172.0.2.10"), - Port: topology.EndhostPort, + Port: dstUDPPort, }}, }, xtest.MustParseIA("1-ff00:0:110"), @@ -1330,7 +1334,7 @@ func TestProcessPkt(t *testing.T) { ret := toMsg(t, spkt, dpath) ret.Addr = &net.UDPAddr{ IP: net.ParseIP("172.0.2.10"), - Port: topology.EndhostPort, + Port: dstUDPPort, } ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil return ret @@ -1390,7 +1394,7 @@ func TestProcessPkt(t *testing.T) { map[addr.SVC][]*net.UDPAddr{ addr.SvcCS: {&net.UDPAddr{ IP: net.ParseIP("172.0.2.10"), - Port: topology.EndhostPort, + Port: dstUDPPort, }}, }, xtest.MustParseIA("1-ff00:0:110"), nil, key) @@ -1596,9 +1600,13 @@ func toMsg(t *testing.T, spkt *slayers.SCION, dpath path.Path) *ipv4.Message { ret := &ipv4.Message{} spkt.Path = dpath buffer := gopacket.NewSerializeBuffer() + scionudpLayer := &slayers.UDP{} + scionudpLayer.SrcPort = uint16(srcUDPPort) + scionudpLayer.DstPort = uint16(dstUDPPort) + scionudpLayer.SetNetworkLayerForChecksum(spkt) payload := []byte("actualpayloadbytes") err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{FixLengths: true}, - spkt, gopacket.Payload(payload)) + spkt, scionudpLayer, gopacket.Payload(payload)) require.NoError(t, err) raw := buffer.Bytes() ret.Buffers = make([][]byte, 1) @@ -1619,7 +1627,7 @@ func prepBaseMsg(now time.Time) (*slayers.SCION, *scion.Decoded) { DstIA: xtest.MustParseIA("4-ff00:0:411"), SrcIA: xtest.MustParseIA("2-ff00:0:222"), Path: &scion.Raw{}, - PayloadLen: 18, + PayloadLen: 26, // scionudpLayer + len("actualpayloadbytes") } dpath := &scion.Decoded{ @@ -1697,7 +1705,7 @@ func toIP(t *testing.T, spkt *slayers.SCION, path path.Path, afterProcessing boo require.NoError(t, spkt.SetDstAddr(dst)) ret := toMsg(t, spkt, path) if afterProcessing { - ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: topology.EndhostPort} + ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: dstUDPPort} ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil } return ret diff --git a/router/export_test.go b/router/export_test.go index f8134b4a3d..96faeed914 100644 --- a/router/export_test.go +++ b/router/export_test.go @@ -25,6 +25,11 @@ import ( "github.com/scionproto/scion/private/topology" ) +var ( + dispatchedPortStart = 1024 + dispatchedPortEnd = 1<<16 - 1 +) + var metrics = NewMetrics() func GetMetrics() *Metrics { @@ -50,15 +55,17 @@ func NewDP( key []byte) *DataPlane { dp := &DataPlane{ - localIA: local, - external: external, - linkTypes: linkTypes, - neighborIAs: neighbors, - internalNextHops: internalNextHops, - svc: &services{m: svc}, - internal: internal, - internalIP: netip.MustParseAddr("198.51.100.1"), - Metrics: metrics, + localIA: local, + external: external, + linkTypes: linkTypes, + neighborIAs: neighbors, + internalNextHops: internalNextHops, + dispatchedPortStart: uint16(dispatchedPortStart), + dispatchedPortEnd: uint16(dispatchedPortEnd), + svc: &services{m: svc}, + internal: internal, + internalIP: netip.MustParseAddr("198.51.100.1"), + Metrics: metrics, } if err := dp.SetKey(key); err != nil { panic(err) diff --git a/scion-pki/certs/BUILD.bazel b/scion-pki/certs/BUILD.bazel index 21de49c53f..82aaab42ac 100644 --- a/scion-pki/certs/BUILD.bazel +++ b/scion-pki/certs/BUILD.bazel @@ -32,7 +32,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/addrutil:go_default_library", "//pkg/snet/squic:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/app:go_default_library", "//private/app/appnet:go_default_library", "//private/app/command:go_default_library", diff --git a/scion-pki/certs/renew.go b/scion-pki/certs/renew.go index 4bb546a182..bb7f5449e8 100644 --- a/scion-pki/certs/renew.go +++ b/scion-pki/certs/renew.go @@ -45,7 +45,6 @@ import ( "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/addrutil" "github.com/scionproto/scion/pkg/snet/squic" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app" infraenv "github.com/scionproto/scion/private/app/appnet" "github.com/scionproto/scion/private/app/command" @@ -264,11 +263,9 @@ The template is expressed in JSON. A valid example:: return err } daemonAddr := envFlags.Daemon() - dispatcher := envFlags.Dispatcher() localIP := net.IP(envFlags.Local().AsSlice()) log.Debug("Resolved SCION environment flags", "daemon", daemonAddr, - "dispatcher", dispatcher, "local", localIP, ) @@ -420,12 +417,11 @@ The template is expressed in JSON. A valid example:: } r := renewer{ - LocalIA: info.IA, - LocalIP: localIP, - Daemon: sd, - Disatcher: dispatcher, - Timeout: flags.timeout, - StdErr: cmd.ErrOrStderr(), + LocalIA: info.IA, + LocalIP: localIP, + Daemon: sd, + Timeout: flags.timeout, + StdErr: cmd.ErrOrStderr(), PathOptions: func() []path.Option { pathOpts := []path.Option{ path.WithInteractive(flags.interactive), @@ -435,9 +431,8 @@ The template is expressed in JSON. A valid example:: } if !flags.noProbe { pathOpts = append(pathOpts, path.WithProbing(&path.ProbeConfig{ - LocalIA: info.IA, - LocalIP: localIP, - Dispatcher: dispatcher, + LocalIA: info.IA, + LocalIP: localIP, })) } return pathOpts @@ -742,19 +737,16 @@ func (r *renewer) requestRemote( } sn := &snet.SCIONNetwork{ - LocalIA: local.IA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(r.Disatcher), - SCMPHandler: snet.SCMPPropagationStopper{ - Handler: snet.DefaultSCMPHandler{ - RevocationHandler: daemon.RevHandler{Connector: r.Daemon}, - }, - Log: log.FromCtx(ctx).Debug, + Topology: r.Daemon, + SCMPHandler: snet.SCMPPropagationStopper{ + Handler: snet.DefaultSCMPHandler{ + RevocationHandler: daemon.RevHandler{Connector: r.Daemon}, }, + Log: log.FromCtx(ctx).Debug, }, } - conn, err := sn.Listen(ctx, "udp", local.Host, addr.SvcNone) + conn, err := sn.Listen(ctx, "udp", local.Host) if err != nil { return nil, serrors.WrapStr("dialing", err) } @@ -767,9 +759,9 @@ func (r *renewer) requestRemote( }, SVCRouter: svcRouter{Connector: r.Daemon}, Resolver: &svc.Resolver{ - LocalIA: local.IA, - ConnFactory: sn.Dispatcher, - LocalIP: local.Host.IP, + LocalIA: local.IA, + Network: sn, + LocalIP: local.Host.IP, }, SVCResolutionFraction: 1, }, diff --git a/scion.sh b/scion.sh index 0f4851be1e..2d92c5d14f 100755 --- a/scion.sh +++ b/scion.sh @@ -24,9 +24,6 @@ cmd_topology() { echo "Create topology, configuration, and execution files." tools/topogen.py "$@" - if is_docker_be; then - ./tools/quiet ./tools/dc run utils_chowner - fi } cmd_topodot() { @@ -90,22 +87,10 @@ cmd_mstart() { run_setup() { tools/set_ipv6_addr.py -a - # Ensure base dir for dispatcher socket exists; on ubuntu this symbolic link to /dev/shm always exists. - if [ ! -d /run/shm/ ]; then - sudo ln -s /dev/shm /run/shm; - fi - # Create dispatcher dir or change owner - local disp_dir="/run/shm/dispatcher" - [ -d "$disp_dir" ] || mkdir "$disp_dir" - [ $(stat -c "%U" "$disp_dir") == "$LOGNAME" ] || { sudo -p "Fixing ownership of $disp_dir - [sudo] password for %p: " chown $LOGNAME: "$disp_dir"; } } run_teardown() { tools/set_ipv6_addr.py -d - local disp_dir="/run/shm/dispatcher" - if [ -e "$disp_dir" ]; then - find "$disp_dir" -xdev -mindepth 1 -print0 | xargs -r0 rm -v - fi } stop_scion() { diff --git a/scion/cmd/scion/BUILD.bazel b/scion/cmd/scion/BUILD.bazel index 4e60f134cb..db17255736 100644 --- a/scion/cmd/scion/BUILD.bazel +++ b/scion/cmd/scion/BUILD.bazel @@ -24,7 +24,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/addrutil:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/app:go_default_library", "//private/app/command:go_default_library", "//private/app/flag:go_default_library", diff --git a/scion/cmd/scion/ping.go b/scion/cmd/scion/ping.go index df6bf02ac1..d5a7ec3bf1 100644 --- a/scion/cmd/scion/ping.go +++ b/scion/cmd/scion/ping.go @@ -33,7 +33,6 @@ import ( "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/addrutil" snetpath "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app" "github.com/scionproto/scion/private/app/flag" "github.com/scionproto/scion/private/app/path" @@ -131,11 +130,9 @@ On other errors, ping will exit with code 2. return err } daemonAddr := envFlags.Daemon() - dispatcher := envFlags.Dispatcher() localIP := net.IP(envFlags.Local().AsSlice()) log.Debug("Resolved SCION environment flags", "daemon", daemonAddr, - "dispatcher", dispatcher, "local", localIP, ) @@ -167,9 +164,8 @@ On other errors, ping will exit with code 2. } if flags.healthyOnly { opts = append(opts, path.WithProbing(&path.ProbeConfig{ - LocalIA: info.IA, - LocalIP: localIP, - Dispatcher: dispatcher, + LocalIA: info.IA, + LocalIP: localIP, })) } path, err := path.Choose(traceCtx, sd, remote.IA, opts...) @@ -265,7 +261,7 @@ On other errors, ping will exit with code 2. } stats, err := ping.Run(ctx, ping.Config{ - Dispatcher: reliable.NewDispatcher(dispatcher), + Topology: sd, Attempts: count, Interval: flags.interval, Timeout: flags.timeout, diff --git a/scion/cmd/scion/showpaths.go b/scion/cmd/scion/showpaths.go index b0cd4480c9..55141ea5a1 100644 --- a/scion/cmd/scion/showpaths.go +++ b/scion/cmd/scion/showpaths.go @@ -99,11 +99,9 @@ On other errors, showpaths will exit with code 2. } flags.cfg.Daemon = envFlags.Daemon() - flags.cfg.Dispatcher = envFlags.Dispatcher() flags.cfg.Local = net.IP(envFlags.Local().AsSlice()) log.Debug("Resolved SCION environment flags", "daemon", flags.cfg.Daemon, - "dispatcher", flags.cfg.Dispatcher, "local", flags.cfg.Local, ) diff --git a/scion/cmd/scion/traceroute.go b/scion/cmd/scion/traceroute.go index 0bdf6a5233..8520d6a368 100644 --- a/scion/cmd/scion/traceroute.go +++ b/scion/cmd/scion/traceroute.go @@ -33,7 +33,6 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/addrutil" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app" "github.com/scionproto/scion/private/app/flag" "github.com/scionproto/scion/private/app/path" @@ -107,11 +106,9 @@ On other errors, traceroute will exit with code 2. return err } daemonAddr := envFlags.Daemon() - dispatcher := envFlags.Dispatcher() localIP := net.IP(envFlags.Local().AsSlice()) log.Debug("Resolved SCION environment flags", "daemon", daemonAddr, - "dispatcher", dispatcher, "local", localIP, ) @@ -184,7 +181,7 @@ On other errors, traceroute will exit with code 2. var stats traceroute.Stats var updates []traceroute.Update cfg := traceroute.Config{ - Dispatcher: reliable.NewDispatcher(dispatcher), + Topology: sd, Remote: remote, MTU: path.Metadata().MTU, Local: local, diff --git a/scion/ping/BUILD.bazel b/scion/ping/BUILD.bazel index 964b11c68e..a828aadeea 100644 --- a/scion/ping/BUILD.bazel +++ b/scion/ping/BUILD.bazel @@ -15,7 +15,6 @@ go_library( "//pkg/private/serrors:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/topology/underlay:go_default_library", ], ) diff --git a/scion/ping/ping.go b/scion/ping/ping.go index 8fc46e9b3c..af0a983d1a 100644 --- a/scion/ping/ping.go +++ b/scion/ping/ping.go @@ -22,12 +22,10 @@ import ( "sync" "time" - "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/topology/underlay" ) @@ -74,9 +72,12 @@ func (s State) String() string { // Config configures the ping run. type Config struct { - Dispatcher reliable.Dispatcher - Local *snet.UDPAddr - Remote *snet.UDPAddr + Local *snet.UDPAddr + Remote *snet.UDPAddr + + // Topology is the helper class to get control-plane information for the + // local AS. + Topology snet.Topology // Attempts is the number of pings to send. Attempts uint16 @@ -103,22 +104,25 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { } replies := make(chan reply, 10) - - id := snet.RandomSCMPIdentifer() - svc := snet.DefaultPacketDispatcherService{ - Dispatcher: cfg.Dispatcher, - SCMPHandler: scmpHandler{ - id: id, - replies: replies, - }, + scmpHandler := &scmpHandler{ + replies: replies, + } + sn := &snet.SCIONNetwork{ + SCMPHandler: scmpHandler, + Topology: cfg.Topology, } - conn, port, err := svc.Register(ctx, cfg.Local.IA, cfg.Local.Host, addr.SvcNone) + conn, err := sn.OpenRaw(ctx, cfg.Local.Host) if err != nil { return Stats{}, err } local := cfg.Local.Copy() - local.Host.Port = int(port) + local.Host = conn.LocalAddr().(*net.UDPAddr) + + // we set the identifier on the handler to the same value as + // the udp port + id := local.Host.Port + scmpHandler.SetId(id) // we need to have at least 8 bytes to store the request time in the // payload. @@ -131,7 +135,7 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { timeout: cfg.Timeout, pldSize: cfg.PayloadSize, pld: make([]byte, cfg.PayloadSize), - id: id, + id: uint16(id), conn: conn, local: local, replies: replies, @@ -320,6 +324,10 @@ func (h scmpHandler) Handle(pkt *snet.Packet) error { return nil } +func (h *scmpHandler) SetId(id int) { + h.id = uint16(id) +} + func (h scmpHandler) handle(pkt *snet.Packet) (snet.SCMPEchoReply, error) { if pkt.Payload == nil { return snet.SCMPEchoReply{}, serrors.New("no v2 payload found") diff --git a/scion/showpaths/config.go b/scion/showpaths/config.go index bae04756f1..dcc4bdcad1 100644 --- a/scion/showpaths/config.go +++ b/scion/showpaths/config.go @@ -38,9 +38,6 @@ type Config struct { // Sequence is a string of space separated Hop Predicates that is used for // filtering. Sequence string - // Dispatcher is the path to the dispatcher socket. Leaving this empty uses - // the default dispatcher socket value. - Dispatcher string // Epic filters paths for which EPIC is not available, and when probing, the // EPIC path type header is used. Epic bool diff --git a/scion/showpaths/showpaths.go b/scion/showpaths/showpaths.go index 2e950e0b69..85b6c0b833 100644 --- a/scion/showpaths/showpaths.go +++ b/scion/showpaths/showpaths.go @@ -352,11 +352,10 @@ func Run(ctx context.Context, dst addr.IA, cfg Config) (*Result, error) { if !cfg.NoProbe { p := pathprobe.FilterEmptyPaths(paths) statuses, err = pathprobe.Prober{ - DstIA: dst, - LocalIA: localIA, - LocalIP: cfg.Local, - ID: snet.RandomSCMPIdentifer(), - Dispatcher: cfg.Dispatcher, + DstIA: dst, + LocalIA: localIA, + LocalIP: cfg.Local, + Topology: sdConn, }.GetStatuses(ctx, p, pathprobe.WithEPIC(cfg.Epic)) if err != nil { return nil, serrors.WrapStr("getting statuses", err) diff --git a/scion/traceroute/BUILD.bazel b/scion/traceroute/BUILD.bazel index b58bd9fe4a..0fda29f297 100644 --- a/scion/traceroute/BUILD.bazel +++ b/scion/traceroute/BUILD.bazel @@ -13,6 +13,5 @@ go_library( "//pkg/slayers/path/scion:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", ], ) diff --git a/scion/traceroute/traceroute.go b/scion/traceroute/traceroute.go index 2893ab983d..bcecbb9e04 100644 --- a/scion/traceroute/traceroute.go +++ b/scion/traceroute/traceroute.go @@ -28,7 +28,6 @@ import ( "github.com/scionproto/scion/pkg/slayers/path/scion" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" ) // Update contains the information for a single hop. @@ -56,8 +55,8 @@ type Stats struct { // Config configures the traceroute run. type Config struct { - Dispatcher reliable.Dispatcher Local *snet.UDPAddr + Topology snet.Topology MTU uint16 PathEntry snet.Path PayloadSize uint @@ -99,18 +98,17 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { if _, isEmpty := cfg.PathEntry.Dataplane().(path.Empty); isEmpty { return Stats{}, serrors.New("empty path is not allowed for traceroute") } - id := snet.RandomSCMPIdentifer() replies := make(chan reply, 10) - dispatcher := snet.DefaultPacketDispatcherService{ - Dispatcher: cfg.Dispatcher, + sn := &snet.SCIONNetwork{ SCMPHandler: scmpHandler{replies: replies}, + Topology: cfg.Topology, } - conn, port, err := dispatcher.Register(ctx, cfg.Local.IA, cfg.Local.Host, addr.SvcNone) + conn, err := sn.OpenRaw(ctx, cfg.Local.Host) if err != nil { return Stats{}, err } local := cfg.Local.Copy() - local.Host.Port = int(port) + local.Host = conn.LocalAddr().(*net.UDPAddr) t := tracerouter{ probesPerHop: cfg.ProbesPerHop, timeout: cfg.Timeout, @@ -120,7 +118,7 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { replies: replies, errHandler: cfg.ErrHandler, updateHandler: cfg.UpdateHandler, - id: id, + id: uint16(conn.LocalAddr().(*net.UDPAddr).Port), path: cfg.PathEntry, epic: cfg.EPIC, } diff --git a/tools/braccept/cases/child_to_internal.go b/tools/braccept/cases/child_to_internal.go index 88d5fcdcb9..58b5c1c0c2 100644 --- a/tools/braccept/cases/child_to_internal.go +++ b/tools/braccept/cases/child_to_internal.go @@ -33,7 +33,11 @@ import ( ) // ChildToInternalHost tests traffic from a child to an AS host. -func ChildToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { +func ChildToInternalHost( + artifactsDir string, + mac hash.Hash, +) runner.Case { + const endhostPort = 21000 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -106,7 +110,7 @@ func ChildToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { scionudp := &slayers.UDP{} scionudp.SrcPort = 2345 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(endhostPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -127,8 +131,7 @@ func ChildToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { // IP4: Src=192.168.0.11 Dst=192.168.0.51 Checksum=0 ip.SrcIP = net.IP{192, 168, 0, 11} ip.DstIP = net.IP{192, 168, 0, 51} - // UDP: Src=30001 Dst=30041 - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(scionudp.DstPort) sp.InfoFields[0].UpdateSegID(sp.HopFields[1].Mac) if err := gopacket.SerializeLayers(want, options, @@ -149,7 +152,11 @@ func ChildToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { // ChildToInternalHostShortcut tests traffic from a child to an AS host with a // short-cut path. I.e., a path where only a partial path segment is used. -func ChildToInternalHostShortcut(artifactsDir string, mac hash.Hash) runner.Case { +func ChildToInternalHostShortcut( + artifactsDir string, + mac hash.Hash, +) runner.Case { + const endhostPort = 21000 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -213,7 +220,7 @@ func ChildToInternalHostShortcut(artifactsDir string, mac hash.Hash) runner.Case scionudp := &slayers.UDP{} scionudp.SrcPort = 2345 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(endhostPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -234,8 +241,7 @@ func ChildToInternalHostShortcut(artifactsDir string, mac hash.Hash) runner.Case // IP4: Src=192.168.0.11 Dst=192.168.0.51 Checksum=0 ip.SrcIP = net.IP{192, 168, 0, 11} ip.DstIP = net.IP{192, 168, 0, 51} - // UDP: Src=30001 Dst=30041 - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(endhostPort) sp.InfoFields[0].UpdateSegID(sp.HopFields[1].Mac) if err := gopacket.SerializeLayers(want, options, diff --git a/tools/braccept/cases/onehop.go b/tools/braccept/cases/onehop.go index 56890a6139..be28bb0f3b 100644 --- a/tools/braccept/cases/onehop.go +++ b/tools/braccept/cases/onehop.go @@ -34,7 +34,11 @@ import ( ) // IncomingOneHop tests one-hop being sent from the remote AS to the local AS. -func IncomingOneHop(artifactsDir string, mac hash.Hash) runner.Case { +func IncomingOneHop( + artifactsDir string, + mac hash.Hash, +) runner.Case { + const endhostPort = 21000 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -88,7 +92,7 @@ func IncomingOneHop(artifactsDir string, mac hash.Hash) runner.Case { scionudp := &slayers.UDP{} scionudp.SrcPort = 2345 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(endhostPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -107,7 +111,7 @@ func IncomingOneHop(artifactsDir string, mac hash.Hash) runner.Case { ethernet.DstMAC = net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0xbe, 0xef} ip.SrcIP = net.IP{192, 168, 0, 11} ip.DstIP = net.IP{192, 168, 0, 71} - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(endhostPort) // Second hop in OHP should have been set by BR. ohp.SecondHop.ConsIngress = 131 ohp.SecondHop.Mac = path.MAC(mac, ohp.Info, ohp.SecondHop, nil) diff --git a/tools/braccept/cases/parent_to_internal.go b/tools/braccept/cases/parent_to_internal.go index cdc77ac649..2892915dc9 100644 --- a/tools/braccept/cases/parent_to_internal.go +++ b/tools/braccept/cases/parent_to_internal.go @@ -33,7 +33,11 @@ import ( ) // ParentToInternalHost test traffic from a parent to an AS host. -func ParentToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { +func ParentToInternalHost( + artifactsDir string, + mac hash.Hash, +) runner.Case { + const endhostPort = 21000 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -101,7 +105,7 @@ func ParentToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { scionudp := &slayers.UDP{} scionudp.SrcPort = 2354 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(endhostPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -120,7 +124,7 @@ func ParentToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { ethernet.DstMAC = net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0xbe, 0xef} ip.SrcIP = net.IP{192, 168, 0, 11} ip.DstIP = net.IP{192, 168, 0, 51} - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(scionudp.DstPort) if err := gopacket.SerializeLayers(want, options, ethernet, ip, udp, scionL, scionudp, gopacket.Payload(payload), @@ -140,7 +144,11 @@ func ParentToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { // ParentToInternalHostMultiSegment test traffic from a parent to an AS host // where two path segments are involved. -func ParentToInternalHostMultiSegment(artifactsDir string, mac hash.Hash) runner.Case { +func ParentToInternalHostMultiSegment( + artifactsDir string, + mac hash.Hash, +) runner.Case { + const endhostPort = 21000 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -215,7 +223,7 @@ func ParentToInternalHostMultiSegment(artifactsDir string, mac hash.Hash) runner scionudp := &slayers.UDP{} scionudp.SrcPort = 2354 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(endhostPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -234,7 +242,7 @@ func ParentToInternalHostMultiSegment(artifactsDir string, mac hash.Hash) runner ethernet.DstMAC = net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0xbe, 0xef} ip.SrcIP = net.IP{192, 168, 0, 11} ip.DstIP = net.IP{192, 168, 0, 51} - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(endhostPort) if err := gopacket.SerializeLayers(want, options, ethernet, ip, udp, scionL, scionudp, gopacket.Payload(payload), diff --git a/tools/braccept/cases/svc.go b/tools/braccept/cases/svc.go index 220522a031..b3adfea429 100644 --- a/tools/braccept/cases/svc.go +++ b/tools/braccept/cases/svc.go @@ -34,6 +34,7 @@ import ( // SVC tests resolution of SVC addresses. func SVC(artifactsDir string, mac hash.Hash) runner.Case { + const csPort = 20007 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -103,7 +104,7 @@ func SVC(artifactsDir string, mac hash.Hash) runner.Case { } scionudp := &slayers.UDP{} scionudp.SrcPort = 2345 - scionudp.DstPort = 53 + scionudp.DstPort = 6789 scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -125,8 +126,7 @@ func SVC(artifactsDir string, mac hash.Hash) runner.Case { ip.SrcIP = net.IP{192, 168, 0, 11} // CS address from the topology file. ip.DstIP = net.IP{192, 168, 0, 71} - // UDP: Src=30001 Dst=30041 - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(csPort) sp.InfoFields[0].UpdateSegID(sp.HopFields[1].Mac) if err := gopacket.SerializeLayers(want, options, diff --git a/tools/braccept/main.go b/tools/braccept/main.go index dbc8a37172..cddeed995c 100644 --- a/tools/braccept/main.go +++ b/tools/braccept/main.go @@ -167,7 +167,7 @@ func loadKey(artifactsDir string) (hash.Hash, error) { // registerScionPorts registers the following UDP ports in gopacket such as SCION is the // next layer. In other words, map the following ports to expect SCION as the payload. func registerScionPorts() { - layers.RegisterUDPPortLayerType(layers.UDPPort(30041), slayers.LayerTypeSCION) + layers.RegisterUDPPortLayerType(layers.UDPPort(53), slayers.LayerTypeSCION) for i := 30000; i < 30010; i++ { layers.RegisterUDPPortLayerType(layers.UDPPort(i), slayers.LayerTypeSCION) } diff --git a/tools/end2end/BUILD.bazel b/tools/end2end/BUILD.bazel index 5007b8ff4b..6e44df874a 100644 --- a/tools/end2end/BUILD.bazel +++ b/tools/end2end/BUILD.bazel @@ -16,8 +16,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/metrics:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", - "//private/topology:go_default_library", "//private/tracing:go_default_library", "//tools/integration:go_default_library", "//tools/integration/integrationlib:go_default_library", diff --git a/tools/end2end/main.go b/tools/end2end/main.go index 8dff7dad6a..d16578140d 100644 --- a/tools/end2end/main.go +++ b/tools/end2end/main.go @@ -29,7 +29,6 @@ import ( "flag" "fmt" "net" - "net/netip" "os" "time" @@ -45,8 +44,6 @@ import ( "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/metrics" snetpath "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" - "github.com/scionproto/scion/private/topology" "github.com/scionproto/scion/private/tracing" libint "github.com/scionproto/scion/tools/integration" integration "github.com/scionproto/scion/tools/integration/integrationlib" @@ -137,27 +134,26 @@ func (s server) run() { sdConn := integration.SDConn() defer sdConn.Close() - connFactory := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), + sn := &snet.SCIONNetwork{ SCMPHandler: snet.DefaultSCMPHandler{ RevocationHandler: daemon.RevHandler{Connector: sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + PacketConnMetrics: scionPacketConnMetrics, + Topology: sdConn, } - conn, port, err := connFactory.Register(context.Background(), integration.Local.IA, - integration.Local.Host, addr.SvcNone) + conn, err := sn.Listen(context.Background(), "udp", integration.Local.Host) if err != nil { integration.LogFatal("Error listening", "err", err) } defer conn.Close() + localAddr := conn.LocalAddr().(*snet.UDPAddr) if len(os.Getenv(libint.GoIntegrationEnv)) > 0 { // Needed for integration test ready signal. - fmt.Printf("Port=%d\n", port) + fmt.Printf("Port=%d\n", localAddr.Host.Port) fmt.Printf("%s%s\n\n", libint.ReadySignal, integration.Local.IA) } - log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host, port)) - + log.Info("Listening", "local", fmt.Sprintf("%v:%d", localAddr.Host.IP, localAddr.Host.Port)) // Receive ping message for { if err := s.handlePing(conn); err != nil { @@ -166,26 +162,17 @@ func (s server) run() { } } -func (s server) handlePing(conn snet.PacketConn) error { - var p snet.Packet - var ov net.UDPAddr - if err := readFrom(conn, &p, &ov); err != nil { +func (s server) handlePing(conn *snet.Conn) error { + rawPld := make([]byte, common.MaxMTU) + n, clientAddr, err := readFrom(conn, rawPld) + if err != nil { return serrors.WrapStr("reading packet", err) } - udp, ok := p.Payload.(snet.UDPPayload) - if !ok { - return serrors.New("unexpected payload received", - "source", p.Source, - "destination", p.Destination, - "type", common.TypeOf(p.Payload), - ) - } + var pld Ping - if err := json.Unmarshal(udp.Payload, &pld); err != nil { + if err := json.Unmarshal(rawPld[:n], &pld); err != nil { return serrors.New("invalid payload contents", - "source", p.Source, - "destination", p.Destination, - "data", string(udp.Payload), + "data", string(rawPld), ) } @@ -206,17 +193,16 @@ func (s server) handlePing(conn snet.PacketConn) error { tracing.Error(span, err) return err } - + clientUDPAddr := clientAddr.(*snet.UDPAddr) if pld.Message != ping || !pld.Server.Equal(integration.Local.IA) { return withTag(serrors.New("unexpected data in payload", - "source", p.Source, - "destination", p.Destination, + "remote", clientUDPAddr, "data", pld, )) } - log.Info(fmt.Sprintf("Ping received from %s, sending pong.", p.Source)) + log.Info(fmt.Sprintf("Ping received from %v, sending pong.", clientUDPAddr)) raw, err := json.Marshal(Pong{ - Client: p.Source.IA, + Client: clientUDPAddr.IA, Server: integration.Local.IA, Message: pong, Trace: pld.Trace, @@ -224,36 +210,19 @@ func (s server) handlePing(conn snet.PacketConn) error { if err != nil { return withTag(serrors.WrapStr("packing pong", err)) } - - p.Destination, p.Source = p.Source, p.Destination - p.Payload = snet.UDPPayload{ - DstPort: udp.SrcPort, - SrcPort: udp.DstPort, - Payload: raw, - } - // reverse path - rpath, ok := p.Path.(snet.RawPath) - if !ok { - return serrors.New("unexpected path", "type", common.TypeOf(p.Path)) - } - replypather := snet.DefaultReplyPather{} - replyPath, err := replypather.ReplyPath(rpath) - if err != nil { - return serrors.WrapStr("creating reply path", err) - } - p.Path = replyPath // Send pong - if err := conn.WriteTo(&p, &ov); err != nil { + if _, err := conn.WriteTo(raw, clientUDPAddr); err != nil { return withTag(serrors.WrapStr("sending reply", err)) } - log.Info("Sent pong to", "client", p.Destination) + log.Info("Sent pong to", "client", clientUDPAddr) return nil } type client struct { - conn snet.PacketConn - port uint16 - sdConn daemon.Connector + network *snet.SCIONNetwork + conn *snet.Conn + sdConn daemon.Connector + errorPaths map[snet.PathFingerprint]struct{} } @@ -262,25 +231,20 @@ func (c *client) run() int { log.Info("Starting", "pair", pair) defer log.Info("Finished", "pair", pair) defer integration.Done(integration.Local.IA, remote.IA) - connFactory := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), + c.sdConn = integration.SDConn() + defer c.sdConn.Close() + c.network = &snet.SCIONNetwork{ SCMPHandler: snet.DefaultSCMPHandler{ - RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()}, + RevocationHandler: daemon.RevHandler{Connector: c.sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + PacketConnMetrics: scionPacketConnMetrics, + Topology: c.sdConn, } - - var err error - c.conn, c.port, err = connFactory.Register(context.Background(), integration.Local.IA, - integration.Local.Host, addr.SvcNone) - if err != nil { - integration.LogFatal("Unable to listen", "err", err) - } - log.Info("Send on", "local", - fmt.Sprintf("%v,[%v]:%d", integration.Local.IA, integration.Local.Host.IP, c.port)) - c.sdConn = integration.SDConn() - defer c.sdConn.Close() + log.Info("Send", "local", + fmt.Sprintf("%v,[%v] -> %v,[%v]", + integration.Local.IA, integration.Local.Host, + remote.IA, remote.Host)) c.errorPaths = make(map[snet.PathFingerprint]struct{}) return integration.AttemptRepeatedly("End2End", c.attemptRequest) } @@ -310,10 +274,12 @@ func (c *client) attemptRequest(n int) bool { } // Send ping - if err := c.ping(ctx, n, path); err != nil { + close, err := c.ping(ctx, n, path) + if err != nil { logger.Error("Could not send packet", "err", withTag(err)) return false } + defer close() // Receive pong if err := c.pong(ctx); err != nil { logger.Error("Error receiving pong", "err", withTag(err)) @@ -325,56 +291,33 @@ func (c *client) attemptRequest(n int) bool { return true } -func (c *client) ping(ctx context.Context, n int, path snet.Path) error { +func (c *client) ping(ctx context.Context, n int, path snet.Path) (func(), error) { rawPing, err := json.Marshal(Ping{ Server: remote.IA, Message: ping, Trace: tracing.IDFromCtx(ctx), }) if err != nil { - return serrors.WrapStr("packing ping", err) + return nil, serrors.WrapStr("packing ping", err) } - if err := c.conn.SetWriteDeadline(getDeadline(ctx)); err != nil { - return serrors.WrapStr("setting write deadline", err) + log.FromCtx(ctx).Info("Dialing", "remote", remote) + c.conn, err = c.network.Dial(ctx, "udp", integration.Local.Host, &remote) + if err != nil { + return nil, serrors.WrapStr("dialing conn", err) } - if remote.NextHop == nil { - remote.NextHop = &net.UDPAddr{ - IP: remote.Host.IP, - Port: topology.EndhostPort, - } + if err := c.conn.SetWriteDeadline(getDeadline(ctx)); err != nil { + return nil, serrors.WrapStr("setting write deadline", err) } - - remoteHostIP, ok := netip.AddrFromSlice(remote.Host.IP) - if !ok { - return serrors.New("invalid remote host IP", "ip", remote.Host.IP) + log.Info("sending ping", "attempt", n, "remote", c.conn.RemoteAddr()) + if _, err := c.conn.Write(rawPing); err != nil { + return nil, err } - localHostIP, ok := netip.AddrFromSlice(integration.Local.Host.IP) - if !ok { - return serrors.New("invalid local host IP", "ip", integration.Local.Host.IP) - } - pkt := &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Destination: snet.SCIONAddress{ - IA: remote.IA, - Host: addr.HostIP(remoteHostIP), - }, - Source: snet.SCIONAddress{ - IA: integration.Local.IA, - Host: addr.HostIP(localHostIP), - }, - Path: remote.Path, - Payload: snet.UDPPayload{ - SrcPort: c.port, - DstPort: uint16(remote.Host.Port), - Payload: rawPing, - }, - }, - } - log.Info("sending ping", "attempt", n, "path", path) - if err := c.conn.WriteTo(pkt, remote.NextHop); err != nil { - return err + closer := func() { + if err := c.conn.Close(); err != nil { + log.Error("Unable to close connection", "err", err) + } } - return nil + return closer, nil } func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) { @@ -436,20 +379,15 @@ func (c *client) pong(ctx context.Context) error { if err := c.conn.SetReadDeadline(getDeadline(ctx)); err != nil { return serrors.WrapStr("setting read deadline", err) } - var p snet.Packet - var ov net.UDPAddr - if err := readFrom(c.conn, &p, &ov); err != nil { + rawPld := make([]byte, common.MaxMTU) + n, serverAddr, err := readFrom(c.conn, rawPld) + if err != nil { return serrors.WrapStr("reading packet", err) } - udp, ok := p.Payload.(snet.UDPPayload) - if !ok { - return serrors.New("unexpected payload received", "type", common.TypeOf(p.Payload)) - } - var pld Pong - if err := json.Unmarshal(udp.Payload, &pld); err != nil { - return serrors.WrapStr("unpacking pong", err, "data", string(udp.Payload)) + if err := json.Unmarshal(rawPld[:n], &pld); err != nil { + return serrors.WrapStr("unpacking pong", err, "data", string(rawPld)) } expected := Pong{ @@ -460,7 +398,7 @@ func (c *client) pong(ctx context.Context) error { if pld.Client != expected.Client || pld.Server != expected.Server || pld.Message != pong { return serrors.New("unexpected contents received", "data", pld, "expected", expected) } - log.Info("Received pong", "server", p.Source) + log.Info("Received pong", "server", serverAddr) return nil } @@ -472,14 +410,14 @@ func getDeadline(ctx context.Context) time.Time { return dl } -func readFrom(conn snet.PacketConn, pkt *snet.Packet, ov *net.UDPAddr) error { - err := conn.ReadFrom(pkt, ov) +func readFrom(conn *snet.Conn, pld []byte) (int, net.Addr, error) { + n, remoteAddr, err := conn.ReadFrom(pld) // Attach more context to error var opErr *snet.OpError if !(errors.As(err, &opErr) && opErr.RevInfo() != nil) { - return err + return n, remoteAddr, err } - return serrors.WithCtx(err, + return n, remoteAddr, serrors.WithCtx(err, "isd_as", opErr.RevInfo().IA(), "interface", opErr.RevInfo().IfID, ) diff --git a/tools/end2end_integration/main.go b/tools/end2end_integration/main.go index 43efd2e093..5b4ff4cdf2 100644 --- a/tools/end2end_integration/main.go +++ b/tools/end2end_integration/main.go @@ -320,7 +320,7 @@ func clientTemplate(progressSock string) integration.Cmd { // remote[ISD/AS] is specified, h2:h2 and h1:h1. Not all combinations yield something useful... // caveat emptor. func getPairs() ([]integration.IAPair, error) { - pairs := integration.IAPairs(integration.DispAddr) + pairs := integration.IAPairs(integration.CSAddr) if subset == "all" { return pairs, nil } diff --git a/tools/end2endblast/BUILD.bazel b/tools/end2endblast/BUILD.bazel index e89d3c45cc..9a691e0751 100644 --- a/tools/end2endblast/BUILD.bazel +++ b/tools/end2endblast/BUILD.bazel @@ -16,7 +16,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/metrics:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/topology:go_default_library", "//tools/integration:go_default_library", "//tools/integration/integrationlib:go_default_library", diff --git a/tools/end2endblast/main.go b/tools/end2endblast/main.go index 74d04ef854..585eeeda44 100644 --- a/tools/end2endblast/main.go +++ b/tools/end2endblast/main.go @@ -40,7 +40,6 @@ import ( "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/metrics" snetpath "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/topology" libint "github.com/scionproto/scion/tools/integration" integration "github.com/scionproto/scion/tools/integration/integrationlib" @@ -124,28 +123,26 @@ func (s *server) run() { sdConn := integration.SDConn() defer sdConn.Close() - connFactory := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), + sn := &snet.SCIONNetwork{ SCMPHandler: snet.DefaultSCMPHandler{ RevocationHandler: daemon.RevHandler{Connector: sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + PacketConnMetrics: scionPacketConnMetrics, + Topology: sdConn, } - - conn, port, err := connFactory.Register(context.Background(), integration.Local.IA, - integration.Local.Host, addr.SvcNone) + conn, err := sn.OpenRaw(context.Background(), integration.Local.Host) if err != nil { integration.LogFatal("Error listening", "err", err) } defer conn.Close() + localAddr := conn.LocalAddr().(*net.UDPAddr) if len(os.Getenv(libint.GoIntegrationEnv)) > 0 { // Needed for integration test ready signal. - fmt.Printf("Port=%d\n", port) + fmt.Printf("Port=%d\n", localAddr.Port) fmt.Printf("%s%s\n\n", libint.ReadySignal, integration.Local.IA) } - - log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host, port)) + log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host.IP, localAddr.Port)) // Receive ping message for { @@ -239,23 +236,23 @@ func (c *client) run() int { log.Info("Starting", "pair", pair) defer log.Info("Finished", "pair", pair) defer integration.Done(integration.Local.IA, remote.IA) - connFactory := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), + sn := &snet.SCIONNetwork{ SCMPHandler: snet.DefaultSCMPHandler{ - RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()}, + RevocationHandler: daemon.RevHandler{Connector: c.sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + PacketConnMetrics: scionPacketConnMetrics, + Topology: c.sdConn, } var err error - c.conn, c.port, err = connFactory.Register(context.Background(), integration.Local.IA, - integration.Local.Host, addr.SvcNone) + c.conn, err = sn.OpenRaw(context.Background(), integration.Local.Host) if err != nil { integration.LogFatal("Unable to listen", "err", err) } + port := c.conn.LocalAddr().(*net.UDPAddr).Port log.Info("Send on", "local", - fmt.Sprintf("%v,[%v]:%d", integration.Local.IA, integration.Local.Host.IP, c.port)) + fmt.Sprintf("%v,[%v]:%d", integration.Local.IA, integration.Local.Host.IP, port)) c.sdConn = integration.SDConn() defer c.sdConn.Close() diff --git a/tools/integration/integration.go b/tools/integration/integration.go index 991b708e72..518bef3de7 100644 --- a/tools/integration/integration.go +++ b/tools/integration/integration.go @@ -218,12 +218,8 @@ func generateAllSrcDst(hostAddr HostAddr, unique bool) []IAPair { type HostAddr func(ia addr.IA) *snet.UDPAddr -// 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 -// connecting to the dispatcher of the services. -var DispAddr HostAddr = func(ia addr.IA) *snet.UDPAddr { +// CSAddr reads the CS host Addr from the topology for the specified IA. +var CSAddr HostAddr = func(ia addr.IA) *snet.UDPAddr { if a := loadAddr(ia); a != nil { return a } diff --git a/tools/integration/integrationlib/common.go b/tools/integration/integrationlib/common.go index b215a97ae3..f9b2388a3b 100644 --- a/tools/integration/integrationlib/common.go +++ b/tools/integration/integrationlib/common.go @@ -70,6 +70,8 @@ func addFlags() error { if err != nil { return serrors.WrapStr("reading scion environment", err) } + // TODO(JordiSubira): Make this flag optional and consider the same case as Unspecified + // if it isn't explicitly set. flag.Var(&Local, "local", "(Mandatory) address to listen on") flag.StringVar(&Mode, "mode", ModeClient, "Run in "+ModeClient+" or "+ModeServer+" mode") flag.StringVar(&Progress, "progress", "", "Socket to write progress to") diff --git a/tools/scion_integration/main.go b/tools/scion_integration/main.go index 8964b6c416..5f993754f0 100644 --- a/tools/scion_integration/main.go +++ b/tools/scion_integration/main.go @@ -110,7 +110,7 @@ func realMain() int { } log.Info(fmt.Sprintf("Run scion %s tests:", tc.Name)) in := integration.NewBinaryIntegration(tc.Name, integration.WrapperCmd, tc.Args, nil) - pairs := tc.Pairs(integration.DispAddr) + pairs := tc.Pairs(integration.CSAddr) err := integration.RunUnaryTests(in, pairs, integration.DefaultRunTimeout, tc.OutputCheck) if err != nil { log.Error(fmt.Sprintf("Error during scion %s tests", tc.Name), "err", err) diff --git a/tools/topology/config.py b/tools/topology/config.py index aa70bfbb2e..fffff91be5 100644 --- a/tools/topology/config.py +++ b/tools/topology/config.py @@ -31,6 +31,7 @@ DEFAULT_MTU, DEFAULT6_NETWORK, NETWORKS_FILE, + DEFAULT_DISPATCHED_PORTS, ) from topology.scion_addr import ISD_AS from topology.util import write_file @@ -85,6 +86,7 @@ def _read_defaults(self, network): self.subnet_gen4 = SubnetGenerator(DEFAULT_NETWORK, self.args.docker) self.subnet_gen6 = SubnetGenerator(DEFAULT6_NETWORK, self.args.docker) self.default_mtu = defaults.get("mtu", DEFAULT_MTU) + self.dispatched_ports = defaults.get("dispatched_ports", DEFAULT_DISPATCHED_PORTS) def generate_all(self): """ @@ -139,7 +141,8 @@ def _generate_topology(self): def _topo_args(self): return TopoGenArgs(self.args, self.topo_config, self.subnet_gen4, - self.subnet_gen6, self.default_mtu) + self.subnet_gen6, self.default_mtu, + self.dispatched_ports) def _generate_supervisor(self, topo_dicts): args = self._supervisor_args(topo_dicts) diff --git a/tools/topology/defines.py b/tools/topology/defines.py index bef592788c..f5c9b3bdf2 100644 --- a/tools/topology/defines.py +++ b/tools/topology/defines.py @@ -33,6 +33,8 @@ #: Default SCION router UDP port. SCION_ROUTER_PORT = 50000 +DEFAULT_DISPATCHED_PORTS = "31000-32767" + #: Default MTU - assumes overlay is ipv4+udp DEFAULT_MTU = 1500 - 20 - 8 #: IPv6 min value diff --git a/tools/topology/docker.py b/tools/topology/docker.py index 00a69962b3..ba59b9db58 100644 --- a/tools/topology/docker.py +++ b/tools/topology/docker.py @@ -175,7 +175,6 @@ def _control_service_conf(self, topo_id, topo, base): 'volumes': [ self._cache_vol(), '%s:/etc/scion:ro' % base, - self._disp_vol(k), ], 'command': ['--config', '/etc/scion/%s.toml' % k] } @@ -189,14 +188,10 @@ def _dispatcher_conf(self, topo_id, topo, base): 'networks': {}, 'user': self.user, 'volumes': [], - 'depends_on': { - 'utils_chowner': { - 'condition': 'service_started' - }, - }, } - keys = (list(topo.get("control_service", {})) + - ["tester_%s" % topo_id.file_fmt()]) + keys = list(topo.get("control_service", {})) + if topo.get("test_dispatcher"): + keys.append("tester_%s" % topo_id.file_fmt()) for disp_id in keys: entry = copy.deepcopy(base_entry) net_key = disp_id @@ -208,7 +203,6 @@ def _dispatcher_conf(self, topo_id, topo, base): entry['networks'][self.bridges[net['net']]] = { '%s_address' % ipv: ip } - entry['volumes'].append(self._disp_vol(disp_id)) conf = '%s:/etc/scion:rw' % base entry['volumes'].append(conf) entry['command'] = [ @@ -216,8 +210,6 @@ def _dispatcher_conf(self, topo_id, topo, base): ] self.dc_conf['services']['disp_%s' % disp_id] = entry - self.dc_conf['volumes'][self._disp_vol(disp_id).split(':') - [0]] = None def _sciond_conf(self, topo_id, base): name = sciond_name(topo_id) @@ -235,7 +227,6 @@ def _sciond_conf(self, topo_id, base): 'user': self.user, 'volumes': [ - self._disp_vol(disp_id), self._cache_vol(), '%s:/etc/scion:ro' % base ], @@ -248,8 +239,5 @@ def _sciond_conf(self, topo_id, base): } self.dc_conf['services'][name] = entry - def _disp_vol(self, disp_id): - return 'vol_disp_%s:/run/shm/dispatcher:rw' % disp_id - def _cache_vol(self): return self.output_base + '/gen-cache:/share/cache:rw' diff --git a/tools/topology/docker_utils.py b/tools/topology/docker_utils.py index 90d4041f51..6bd9be0b21 100644 --- a/tools/topology/docker_utils.py +++ b/tools/topology/docker_utils.py @@ -49,49 +49,40 @@ def __init__(self, args): self.output_base = os.environ.get('SCION_OUTPUT_BASE', os.getcwd()) def generate(self): - self._utils_conf() for topo_id in self.args.topo_dicts: self._test_conf(topo_id) if self.args.sig: self._sig_testing_conf() return self.dc_conf - def _utils_conf(self): - entry_chown = { - 'image': 'busybox', - 'network_mode': 'none', - 'volumes': [ - '/etc/passwd:/etc/passwd:ro', - '/etc/group:/etc/group:ro' - ], - 'command': 'chown -R ' + self.user + ' /mnt/volumes' - } - for volume in self.dc_conf['volumes']: - entry_chown['volumes'].append('%s:/mnt/volumes/%s' % (volume, volume)) - self.dc_conf['services']['utils_chowner'] = entry_chown - def _test_conf(self, topo_id): cntr_base = '/share' name = 'tester_%s' % topo_id.file_fmt() entry = { 'image': docker_image(self.args, 'tester'), - 'depends_on': ['disp_%s' % name], 'privileged': True, 'entrypoint': 'sh tester.sh', 'environment': {}, # 'user': self.user, 'volumes': [ - 'vol_disp_%s:/run/shm/dispatcher:rw' % name, self.output_base + '/logs:' + cntr_base + '/logs:rw', self.output_base + '/gen:' + cntr_base + '/gen:rw', self.output_base + '/gen-certs:' + cntr_base + '/gen-certs:rw' ], - 'network_mode': 'service:disp_%s' % name, } net = self.args.networks[name][0] ipv = 'ipv4' if ipv not in net: ipv = 'ipv6' + ip = str(net[ipv]) + if 'disp_%s' % name in self.dc_conf['services']: + entry['depends_on'] = ['disp_%s' % name] + entry.update({'network_mode': 'service:disp_%s' % name}) + else: + entry['networks'] = {} + entry['networks'][self.args.bridges[net['net']]] = { + '%s_address' % ipv: ip + } disp_net = self.args.networks[name][0] entry['environment']['SCION_LOCAL_ADDR'] = str(disp_net[ipv]) sciond_net = self.args.networks['sd%s' % topo_id.file_fmt()][0] diff --git a/tools/topology/go.py b/tools/topology/go.py index dcc307e2a2..72a1902097 100644 --- a/tools/topology/go.py +++ b/tools/topology/go.py @@ -113,7 +113,6 @@ def _build_control_service_conf(self, topo_id, ia, base, name, infra_elem, ca): 'general': { 'id': name, 'config_dir': config_dir, - 'reconnect_to_dispatcher': True, }, 'log': self._log_entry(name), 'trust_db': { @@ -148,7 +147,6 @@ def _build_sciond_conf(self, topo_id, ia, base): 'general': { 'id': name, 'config_dir': config_dir, - 'reconnect_to_dispatcher': True, }, 'log': self._log_entry(name), 'trust_db': { @@ -177,13 +175,13 @@ def generate_disp(self): else: elem_dir = os.path.join(self.args.output_dir, "dispatcher") config_file_path = os.path.join(elem_dir, DISP_CONFIG_NAME) - write_file(config_file_path, toml.dumps(self._build_disp_conf("dispatcher"))) + write_file(config_file_path, toml.dumps(self._build_disp_conf( + "dispatcher"))) def _gen_disp_docker(self): for topo_id, topo in self.args.topo_dicts.items(): base = topo_id.base_dir(self.args.output_dir) elem_ids = ['sig_%s' % topo_id.file_fmt()] + \ - list(topo.get("border_routers", {})) + \ list(topo.get("control_service", {})) + \ ['tester_%s' % topo_id.file_fmt()] for k in elem_ids: @@ -196,9 +194,11 @@ def _build_disp_conf(self, name, topo_id=None): self.args.networks, DISP_PROM_PORT, name) api_addr = prom_addr_dispatcher(self.args.docker, topo_id, self.args.networks, DISP_PROM_PORT+700, name) - return { + srv_addresses = self._build_srv_addresses(self.args.docker, name, topo_id) + tomlDict = { 'dispatcher': { 'id': name, + 'local_udp_forwarding': True, }, 'log': self._log_entry(name), 'metrics': { @@ -209,6 +209,26 @@ def _build_disp_conf(self, name, topo_id=None): 'addr': api_addr, }, } + if len(srv_addresses) > 1: + tomlDict["dispatcher"]["service_addresses"] = srv_addresses + return tomlDict + + def _build_srv_addresses(self, docker, name, topo_id): + srv_addresses = dict() + if docker: + if name.startswith("disp_cs"): + topo = self.args.topo_dicts.get(topo_id) + cs_addresses = list(topo.get("control_service", {}).values()) + srv_addresses[str(topo_id)+",CS"] = cs_addresses[0]["addr"] + ds_addresses = list(topo.get("discovery_service", {}).values()) + srv_addresses[str(topo_id)+",DS"] = ds_addresses[0]["addr"] + else: + for topo_id, topo in self.args.topo_dicts.items(): + cs_addresses = list(topo.get("control_service", {}).values()) + srv_addresses[str(topo_id)+",CS"] = cs_addresses[0]["addr"] + ds_addresses = list(topo.get("discovery_service", {}).values()) + srv_addresses[str(topo_id)+",DS"] = ds_addresses[0]["addr"] + return srv_addresses def _tracing_entry(self): docker_ip = docker_host(self.args.docker) diff --git a/tools/topology/net.py b/tools/topology/net.py index 7968301f7f..7ac4aa4135 100644 --- a/tools/topology/net.py +++ b/tools/topology/net.py @@ -176,8 +176,11 @@ def _exclude_net(self, alloc, net): class PortGenerator(object): + # XXX(JordiSubira): We keep this in the default range. If the configured range, + # doesn't include the 31000-32767 range, the services will be able to operate + # with the shim dispatcher. def __init__(self): - self.iter = iter(range(31000, 35000)) + self.iter = iter(range(31000, 32767)) self._ports = defaultdict(lambda: next(self.iter)) def register(self, id_: str) -> int: diff --git a/tools/topology/sig.py b/tools/topology/sig.py index 8a965aa997..cec64fc57d 100644 --- a/tools/topology/sig.py +++ b/tools/topology/sig.py @@ -71,16 +71,10 @@ def _dispatcher_conf(self, topo_id, base): # Create dispatcher config entry = { 'image': docker_image(self.args, 'dispatcher'), - 'depends_on': { - 'utils_chowner': { - 'condition': 'service_started' - }, - }, 'user': self.user, 'networks': {}, 'volumes': [ - self._disp_vol(topo_id), '%s:/etc/scion:rw' % base, ], 'command': @@ -97,8 +91,6 @@ def _dispatcher_conf(self, topo_id, base): } self.dc_conf['services']['disp_sig_%s' % topo_id.file_fmt()] = entry - vol_name = 'vol_disp_sig_%s' % topo_id.file_fmt() - self.dc_conf['volumes'][vol_name] = None def _sig_dc_conf(self, topo_id, base): setup_name = 'sig_setup_%s' % topo_id.file_fmt() @@ -122,7 +114,6 @@ def _sig_dc_conf(self, topo_id, base): }, 'cap_add': ['NET_ADMIN'], 'volumes': [ - self._disp_vol(topo_id), '/dev/net/tun:/dev/net/tun', '%s:/etc/scion' % base, ], @@ -183,6 +174,3 @@ def _sig_toml(self, topo_id, topo): path = os.path.join(topo_id.base_dir(self.args.output_dir), SIG_CONFIG_NAME) write_file(path, toml.dumps(sig_conf)) - - def _disp_vol(self, topo_id): - return 'vol_disp_sig_%s:/run/shm/dispatcher:rw' % topo_id.file_fmt() diff --git a/tools/topology/topo.py b/tools/topology/topo.py index c67663a498..9fb39e7ce0 100644 --- a/tools/topology/topo.py +++ b/tools/topology/topo.py @@ -66,7 +66,8 @@ def __init__(self, topo_config, subnet_gen4: SubnetGenerator, subnet_gen6: SubnetGenerator, - default_mtu: int): + default_mtu: int, + dispatched_ports: str): """ :param ArgsBase args: Contains the passed command line arguments. :param dict topo_config: The parsed topology config. @@ -81,6 +82,7 @@ def __init__(self, ADDR_TYPE_6: subnet_gen6, } self.default_mtu = default_mtu + self.dispatched_ports = dispatched_ports self.port_gen = PortGenerator() @@ -242,6 +244,16 @@ def _generate_as_topo(self, topo_id, as_conf): 'attributes': attributes, 'isd_as': str(topo_id), 'mtu': mtu, + # XXX(JordiSubira): This key is used internally later on, to decide + # whether to create a dispatcher container collocated with the tester + # container. + # + # Correcter/nicer would be to pass the ConfigGenerator.topo_config + # via the DockerGenArgs to DockerGenerator and check the test_dispatcher + # flag for the individual AS in DockerGenerator.generate before the call + # to self._gen_topo + 'test_dispatcher': as_conf.get('test_dispatcher', True), + 'dispatched_ports': as_conf.get('dispatched_ports', self.args.dispatched_ports), } for i in SCION_SERVICE_NAMES: self.topo_dicts[topo_id][i] = {}