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/hidden_paths/test.py b/acceptance/hidden_paths/test.py index 3056096671..5bff81f10e 100755 --- a/acceptance/hidden_paths/test.py +++ b/acceptance/hidden_paths/test.py @@ -2,6 +2,7 @@ # Copyright 2020 Anapaya Systems +import time import http.server import threading @@ -52,6 +53,19 @@ class Test(base.TestTopogen): http_server_port = 9099 + _testers = { + "2": "tester_1-ff00_0_2", + "3": "tester_1-ff00_0_3", + "4": "tester_1-ff00_0_4", + "5": "tester_1-ff00_0_5", + } + _ases = { + "2": "1-ff00:0:2", + "3": "1-ff00:0:3", + "4": "1-ff00:0:4", + "5": "1-ff00:0:5", + } + def setup_prepare(self): super().setup_prepare() @@ -65,6 +79,15 @@ def setup_prepare(self): "4": "172.20.0.65", "5": "172.20.0.73", } + # TODO(JordiSubira): These addresses will change if the topology file is modified. + # In any case, after rebasing this branch this probably does not work anymore, since + # SVC resolution and address setup is modified. + control_addresses = { + "2": "172.20.0.51:30090", + "3": "172.20.0.59:30090", + "4": "172.20.0.67:30090", + "5": "172.20.0.75:30090", + } # Each AS participating in hidden paths has their own hidden paths configuration file. hp_configs = { "2": "hp_groups_as2_as5.yml", @@ -92,16 +115,13 @@ def setup_prepare(self): # even though some don't need the registration service. as_dir_path = self.artifacts / "gen" / ("ASff00_0_%s" % as_number) - # The hidden_segment services are behind the same server as the control_service. - topology_file = as_dir_path / "topology.json" - control_service_addr = scion.load_from_json( - 'control_service.%s.addr' % control_id, [topology_file]) topology_update = { "hidden_segment_lookup_service.%s.addr" % control_id: - control_service_addr, + control_addresses[as_number], "hidden_segment_registration_service.%s.addr" % control_id: - control_service_addr, + control_addresses[as_number], } + topology_file = as_dir_path / "topology.json" scion.update_json(topology_update, [topology_file]) def setup_start(self): @@ -111,11 +131,10 @@ def setup_start(self): server_thread.start() self._server = server - super().setup_start() - self.await_connectivity() self._server.shutdown() # by now configuration must have been downloaded everywhere + def _run(self): # Group 3 self._showpaths_bidirectional("2", "3") diff --git a/acceptance/old_new_br/BUILD.bazel b/acceptance/old_new_br/BUILD.bazel new file mode 100644 index 0000000000..f9b14f228f --- /dev/null +++ b/acceptance/old_new_br/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/old_new_br/testdata:topology.topo", +) diff --git a/acceptance/old_new_br/test.py b/acceptance/old_new_br/test.py new file mode 100755 index 0000000000..81e052a949 --- /dev/null +++ b/acceptance/old_new_br/test.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +# Copyright 2023 ETH Zurich + +import time + +from acceptance.common import base +from acceptance.common import scion +# from plumbum import local + + +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 old behaviour (i.e., they will send traffic to its own AS to the + endhost default port) and routers with the new behaviour (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 reassembles 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.endhost_start_port": 0, + "router.endhost_end_port": 0}, + [br_as_2_file]) + + def setup_start(self): + super().setup_start() + time.sleep(10) # TODO(JordiSubira): Replace with self.await_connectivity() after rebasing + + 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/old_new_br/testdata/BUILD.bazel b/acceptance/old_new_br/testdata/BUILD.bazel new file mode 100644 index 0000000000..d803c4eee5 --- /dev/null +++ b/acceptance/old_new_br/testdata/BUILD.bazel @@ -0,0 +1,3 @@ +exports_files([ + "topology.topo", +]) diff --git a/acceptance/old_new_br/testdata/topology.topo b/acceptance/old_new_br/testdata/topology.topo new file mode 100644 index 0000000000..68b249b07e --- /dev/null +++ b/acceptance/old_new_br/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/router_benchmark/conf/topology.json b/acceptance/router_benchmark/conf/topology.json index 53c7cfb01f..e005f1bd14 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, + "endhost_port_range": "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 3f917f69eb..fdb2bb11b9 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": [], + "endhost_port_range": "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 854ba9f37c..4da04a6619 100644 --- a/acceptance/sig_short_exp_time/BUILD.bazel +++ b/acceptance/sig_short_exp_time/BUILD.bazel @@ -6,8 +6,6 @@ sh_test( srcs = ["test"], data = [ "docker-compose.yml", - ":dispatcher1.tar", - ":dispatcher2.tar", ":sig1.tar", ":sig2.tar", ":udpproxy.tar", @@ -24,28 +22,6 @@ container_image( base = "//tools/udpproxy", ) -container_image( - name = "dispatcher1", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - files = ["testdata/1-ff00_0_110/dispatcher/disp.toml"], -) - -container_image( - name = "dispatcher2", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - files = ["testdata/1-ff00_0_111/dispatcher/disp.toml"], -) - container_image( name = "sig1", base = "//docker:posix_gateway", diff --git a/acceptance/sig_short_exp_time/docker-compose.yml b/acceptance/sig_short_exp_time/docker-compose.yml index 65fc288fd1..338d4a8255 100644 --- a/acceptance/sig_short_exp_time/docker-compose.yml +++ b/acceptance/sig_short_exp_time/docker-compose.yml @@ -62,38 +62,38 @@ services: cap_add: - NET_ADMIN container_name: sig1 - depends_on: - - dispatcher1 image: bazel/acceptance/sig_short_exp_time:sig1 - 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 sig2: cap_add: - NET_ADMIN container_name: sig2 - depends_on: - - dispatcher2 image: bazel/acceptance/sig_short_exp_time:sig2 - 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 tester1: container_name: tester1 image: alpine - network_mode: service:dispatcher1 + networks: + bridge1: + ipv4_address: 242.254.100.10 privileged: true tester2: container_name: 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 55bd704850..f2ddfc0592 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/sig_short_exp_time/testdata/1-ff00_0_110/dispatcher/disp.toml b/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/dispatcher/disp.toml index f02dc620a8..6c11642ad6 100644 --- a/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/dispatcher/disp.toml +++ b/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/dispatcher/disp.toml @@ -2,4 +2,4 @@ id = "disp_1-ff00_0_110" [log.console] -level = "debug" +level = "debug" \ No newline at end of file diff --git a/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/dispatcher/disp.toml b/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/dispatcher/disp.toml index 1054942819..9984dfec2a 100644 --- a/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/dispatcher/disp.toml +++ b/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/dispatcher/disp.toml @@ -2,4 +2,4 @@ id = "disp_1-ff00_0_111" [log.console] -level = "debug" +level = "debug" \ No newline at end of file diff --git a/acceptance/topo_common/topology.json b/acceptance/topo_common/topology.json index 200ceb7b96..3a66b2146d 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, + "endhost_port_range": "1024-65535", "attributes": [ "core" ], diff --git a/acceptance/topo_cs_reload/BUILD.bazel b/acceptance/topo_cs_reload/BUILD.bazel index a43914ba5d..0327bf788c 100644 --- a/acceptance/topo_cs_reload/BUILD.bazel +++ b/acceptance/topo_cs_reload/BUILD.bazel @@ -14,7 +14,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,17 +35,6 @@ go_test( ], ) -container_image( - name = "dispatcher", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - files = ["testdata/disp.toml"], -) - container_image( name = "control", base = "//docker:control", diff --git a/acceptance/topo_cs_reload/docker-compose.yml b/acceptance/topo_cs_reload/docker-compose.yml index ba010d1f7e..86d8f1cbb5 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: bazel/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: bazel/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 -version: '2.4' -volumes: - vol_topo_cs_reload_disp: null + networks: + bridge1: + ipv4_address: 242.253.100.2 +version: '2.4' \ No newline at end of file diff --git a/acceptance/topo_cs_reload/reload_test.go b/acceptance/topo_cs_reload/reload_test.go index 53f1742d91..c663959238 100644 --- a/acceptance/topo_cs_reload/reload_test.go +++ b/acceptance/topo_cs_reload/reload_test.go @@ -104,7 +104,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") s.mustExec(t, "docker", "image", "load", "-i", "control.tar") // now start the docker containers s.mustExec(t, "docker", "compose", "-f", "docker-compose.yml", "up", "-d") @@ -123,7 +122,6 @@ func (s testState) teardownTest(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 fdc6d6be6a..b7a52001b9 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, + "endhost_port_range": "1024-65535", "attributes": [ "core" ], diff --git a/acceptance/topo_daemon_reload/BUILD.bazel b/acceptance/topo_daemon_reload/BUILD.bazel index fe25b5841a..516d79224c 100644 --- a/acceptance/topo_daemon_reload/BUILD.bazel +++ b/acceptance/topo_daemon_reload/BUILD.bazel @@ -7,7 +7,6 @@ go_test( data = [ "testdata/topology_reload.json", ":daemon.tar", - ":dispatcher.tar", ":docker-compose.yml", "//acceptance/topo_common:invalid_reloads", "//acceptance/topo_common:topology", @@ -23,17 +22,6 @@ go_test( ], ) -container_image( - name = "dispatcher", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - files = ["testdata/disp.toml"], -) - container_image( name = "daemon", base = "//docker:daemon", diff --git a/acceptance/topo_daemon_reload/docker-compose.yml b/acceptance/topo_daemon_reload/docker-compose.yml index 2335a92fab..3c7c001ce1 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: bazel/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: bazel/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 + vol_topo_daemon_reload_certs: null \ No newline at end of file diff --git a/acceptance/topo_daemon_reload/reload_test.go b/acceptance/topo_daemon_reload/reload_test.go index 56802c18b8..ebcacc83c0 100644 --- a/acceptance/topo_daemon_reload/reload_test.go +++ b/acceptance/topo_daemon_reload/reload_test.go @@ -68,11 +68,10 @@ 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") mustExec(t, "docker", "image", "load", "-i", "daemon.tar") // now start the docker containers - mustExec(t, "docker", "compose", "-f", "docker-compose.yml", - "up", "-d", "topo_daemon_reload_dispatcher", "topo_daemon_reload_daemon") + mustExec(t, "docker", "compose", "-f", "docker-compose.yml", "up", + "-d", "topo_daemon_reload_daemon") // wait a bit to make sure the containers are ready. time.Sleep(time.Second / 2) t.Log("Test setup done") @@ -88,8 +87,7 @@ func teardownTest(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 c37d92aa11..15ebfd7c2f 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, + "endhost_port_range": "1024-65535", "attributes": [ "core" ], diff --git a/acceptance/trc_update/test.py b/acceptance/trc_update/test.py index 2edf26a0bb..28fbb18f23 100755 --- a/acceptance/trc_update/test.py +++ b/acceptance/trc_update/test.py @@ -84,7 +84,7 @@ def _run(self): for cs in cs_services: self.dc.start_container(cs) - time.sleep(5) + time.sleep(20) logger.info('==> Check connectivity') end2end("-d", "-outDir", artifacts) diff --git a/control/beaconing/testdata/topology-core.json b/control/beaconing/testdata/topology-core.json index 1b37ce2bbb..c87b30988f 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, + "endhost_port_range": "1024-65535", "attributes": [ "core" ], diff --git a/control/beaconing/testdata/topology.json b/control/beaconing/testdata/topology.json index ac258730a2..e8a8adf82b 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, + "endhost_port_range": "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 d5cd9a3590..4bfc7ac2af 100644 --- a/control/cmd/control/main.go +++ b/control/cmd/control/main.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "fmt" + "net" "net/http" _ "net/http/pprof" "path/filepath" @@ -202,10 +203,8 @@ 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 @@ -227,6 +226,7 @@ func realMain(ctx context.Context) error { SCIONNetworkMetrics: metrics.SCIONNetworkMetrics, SCIONPacketConnMetrics: metrics.SCIONPacketConnMetrics, MTU: topo.MTU(), + CPInfoProvider: cpInfoProvider{topo: topo}, } quicStack, err := nc.QUICStack() if err != nil { @@ -572,16 +572,24 @@ func realMain(ctx context.Context) error { healthpb.RegisterHealthServer(tcpServer, dsHealth) hpCfg := cs.HiddenPathConfigurator{ - LocalIA: topo.IA(), - Verifier: verifier, - Signer: signer, - PathDB: pathDB, - Dialer: dialer, - FetcherConfig: fetcherCfg, - IntraASTCPServer: tcpServer, - InterASQUICServer: quicServer, - } - hpWriterCfg, err := hpCfg.Setup(globalCfg.PS.HiddenPathsCfg) + LocalIA: topo.IA(), + Verifier: verifier, + Signer: signer, + PathDB: pathDB, + Dialer: dialer, + FetcherConfig: fetcherCfg, + IntraASTCPServer: tcpServer, + } + + // (TODO)JordiSubira: Revisit after rebasing and move out to separate PR if applicable. + // (XXX)JordiSubira: We should revisit how we want to handle HP service, + // right now it only seems to be used within the CS. So perhaps we should treat + // it as a part of the CS (same as BS, CertServ, DRKey, etc). For the moment, we + // create a different grpc.Server endpoint that will be located behind a different + // quic socket using the IP:port in the topology.json file. This is required + // because the client side, uses the DS to discover the address of the remote + // HP server, thus it should use whatever is written in the topology file. + hpInterASServer, hpWriterCfg, err := hpCfg.Setup(globalCfg.PS.HiddenPathsCfg) if err != nil { return err } @@ -683,6 +691,29 @@ func realMain(ctx context.Context) error { return nil }) cleanup.Add(func() error { tcpServer.GracefulStop(); return nil }) + if hpInterASServer != nil { + a, err := topo.HiddenSegmentRegistrationAddresses() + if err != nil { + return err + } + if len(a) == 0 { + return serrors.New("Hidden Path registration address expected and not found") + } + // XXX(JordiSubira): Just take the first address, we only use topology.json with + // information for one unique AS. + hpListener, err := nc.OpenListener(a[0].String()) + if err != nil { + return serrors.WrapStr("opening listener for Hidden Path", err) + } + g.Go(func() error { + defer log.HandlePanic() + if err := hpInterASServer.Serve(hpListener); err != nil { + return serrors.WrapStr("serving Hidden Path API", err) + } + return nil + }) + cleanup.Add(func() error { hpInterASServer.GracefulStop(); return nil }) + } if globalCfg.API.Addr != "" { r := chi.NewRouter() @@ -928,6 +959,27 @@ func (h *healther) GetCAHealth(ctx context.Context) (api.CAHealthStatus, bool) { return api.Unavailable, false } +type cpInfoProvider struct { + topo *topology.Loader +} + +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]*net.UDPAddr, error) { + ifMap := c.topo.InterfaceInfoMap() + ifsToUDP := make(map[uint16]*net.UDPAddr, 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/control/hiddenpaths.go b/control/hiddenpaths.go index 299d265871..eeee20711e 100644 --- a/control/hiddenpaths.go +++ b/control/hiddenpaths.go @@ -31,31 +31,34 @@ import ( // HiddenPathConfigurator can be used to configure the hidden path servers. type HiddenPathConfigurator struct { - LocalIA addr.IA - Verifier infra.Verifier - Signer hpgrpc.Signer - PathDB pathdb.DB - Dialer libgrpc.Dialer - FetcherConfig segreq.FetcherConfig - IntraASTCPServer *grpc.Server - InterASQUICServer *grpc.Server + LocalIA addr.IA + Verifier infra.Verifier + Signer hpgrpc.Signer + PathDB pathdb.DB + Rewriter libgrpc.AddressRewriter + Dialer libgrpc.Dialer + FetcherConfig segreq.FetcherConfig + IntraASTCPServer *grpc.Server } // Setup sets up the hidden paths servers using the configuration at the given // location. An empty location will not enable any hidden path behavior. It // returns the configuration for the hidden segment writer. The return value can // be nil if this AS isn't a writer. -func (c HiddenPathConfigurator) Setup(location string) (*HiddenPathRegistrationCfg, error) { +func (c HiddenPathConfigurator) Setup( + location string, +) (*grpc.Server, *HiddenPathRegistrationCfg, error) { + if location == "" { - return nil, nil + return nil, nil, nil } groups, regPolicy, err := hiddenpath.LoadConfiguration(location) if err != nil { - return nil, err + return nil, nil, err } roles := groups.Roles(c.LocalIA) if roles.None() { - return nil, nil + return nil, nil, nil } log.Info("Starting hidden path forward server") hspb.RegisterHiddenSegmentLookupServiceServer(c.IntraASTCPServer, &hpgrpc.SegmentServer{ @@ -67,26 +70,33 @@ func (c HiddenPathConfigurator) Setup(location string) (*HiddenPathRegistrationC Dialer: c.Dialer, Signer: c.Signer, }, - Resolver: hiddenpath.LookupResolver{ + HPResolver: hiddenpath.LookupResolver{ Router: segreq.NewRouter(c.FetcherConfig), Discoverer: &hpgrpc.Discoverer{ Dialer: c.Dialer, }, }, + CSResolver: hiddenpath.CSResolver{ + Router: segreq.NewRouter(c.FetcherConfig), + Rewriter: c.Rewriter, + }, Verifier: hiddenpath.VerifierAdapter{ Verifier: c.Verifier, }, }, }) + var interASServer *grpc.Server if roles.Registry { + interASServer = grpc.NewServer( + libgrpc.UnaryServerInterceptor(), + ) log.Info("Starting hidden path authoritative and registration server") - hspb.RegisterAuthoritativeHiddenSegmentLookupServiceServer(c.InterASQUICServer, + hspb.RegisterAuthoritativeHiddenSegmentLookupServiceServer(interASServer, &hpgrpc.AuthoritativeSegmentServer{ Lookup: c.localAuthServer(groups), Verifier: c.Verifier, - }, - ) - hspb.RegisterHiddenSegmentRegistrationServiceServer(c.InterASQUICServer, + }) + hspb.RegisterHiddenSegmentRegistrationServiceServer(interASServer, &hpgrpc.RegistrationServer{ Registry: hiddenpath.RegistryServer{ Groups: groups, @@ -103,21 +113,22 @@ func (c HiddenPathConfigurator) Setup(location string) (*HiddenPathRegistrationC ) } if !roles.Writer { - return nil, nil + return interASServer, nil, nil } log.Info("Using hidden path beacon writer") - return &HiddenPathRegistrationCfg{ - Policy: regPolicy, - Router: segreq.NewRouter(c.FetcherConfig), - Discoverer: &hpgrpc.Discoverer{ - Dialer: c.Dialer, - }, - RPC: &hpgrpc.Registerer{ - Dialer: c.Dialer, - RegularRegistration: beaconinggrpc.Registrar{Dialer: c.Dialer}, - Signer: c.Signer, - }, - }, nil + return interASServer, + &HiddenPathRegistrationCfg{ + Policy: regPolicy, + Router: segreq.NewRouter(c.FetcherConfig), + Discoverer: &hpgrpc.Discoverer{ + Dialer: c.Dialer, + }, + RPC: &hpgrpc.Registerer{ + Dialer: c.Dialer, + RegularRegistration: beaconinggrpc.Registrar{Dialer: c.Dialer}, + Signer: c.Signer, + }, + }, nil } func (c HiddenPathConfigurator) localAuthServer(groups hiddenpath.Groups) hiddenpath.Lookuper { 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/grpc.go b/daemon/internal/servers/grpc.go index dd2cb43bc7..422943d649 100644 --- a/daemon/internal/servers/grpc.go +++ b/daemon/internal/servers/grpc.go @@ -49,6 +49,7 @@ type Topology interface { InterfaceIDs() []uint16 UnderlayNextHop(uint16) *net.UDPAddr ControlServiceAddresses() []*net.UDPAddr + PortRange() (uint16, uint16) } // DaemonServer handles gRPC requests to the SCION daemon. @@ -242,15 +243,21 @@ func (s *DaemonServer) as(ctx context.Context, req *sdpb.ASRequest) (*sdpb.ASRes if reqIA.Equal(s.IA) { mtu = uint32(s.MTU) } + var startPort, endPort uint16 + if reqIA.Equal(s.IA) { + startPort, endPort = s.Topology.PortRange() + } core, err := s.ASInspector.HasAttributes(ctx, reqIA, trust.Core) if err != nil { log.FromCtx(ctx).Error("Inspecting ISD-AS", "err", err, "isd_as", reqIA) return nil, serrors.WrapStr("inspecting ISD-AS", err, "isd_as", reqIA) } reply := &sdpb.ASResponse{ - IsdAs: uint64(reqIA), - Core: core, - Mtu: mtu, + IsdAs: uint64(reqIA), + Core: core, + Mtu: mtu, + EndhostStartPort: uint32(startPort), + EndhostEndPort: uint32(endPort), } return reply, nil } 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..e4fe349d16 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,69 @@ 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", + "@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", + "@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", + "@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", + "@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 +76,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..47be4174d8 100644 --- a/dispatcher/cmd/dispatcher/main.go +++ b/dispatcher/cmd/dispatcher/main.go @@ -13,6 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build linux || darwin +// +build linux darwin + package main import ( @@ -22,18 +25,18 @@ import ( "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" @@ -53,10 +56,6 @@ 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 @@ -64,9 +63,7 @@ func realMain(ctx context.Context) error { g.Go(func() error { defer log.HandlePanic() return RunDispatcher( - globalCfg.Dispatcher.DeleteSocket, - globalCfg.Dispatcher.ApplicationSocket, - os.FileMode(globalCfg.Dispatcher.SocketFileMode), + globalCfg.Dispatcher.ParsedServiceAddresses, globalCfg.Dispatcher.UnderlayPort, ) }) @@ -116,12 +113,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 +129,13 @@ func realMain(ctx context.Context) error { } } -func RunDispatcher(deleteSocketFlag bool, applicationSocket string, socketFileMode os.FileMode, - underlayPort int) 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 { +func RunDispatcher(svcAddrs map[addr.Addr]netip.AddrPort, underlayPort int) error { + localAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", underlayPort)) + if err != nil { return err } - return nil + log.Debug("Dispatcher starting", "localAddr", localAddr) + return dispatcher.ListenAndServe(svcAddrs, localAddr) } 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..799080c17a 100644 --- a/dispatcher/config/BUILD.bazel +++ b/dispatcher/config/BUILD.bazel @@ -9,10 +9,9 @@ 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", @@ -26,7 +25,6 @@ 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", diff --git a/dispatcher/config/config.go b/dispatcher/config/config.go index 1fad7d9b09..562c08e21a 100644 --- a/dispatcher/config/config.go +++ b/dispatcher/config/config.go @@ -19,11 +19,12 @@ package config import ( "fmt" "io" + "net/netip" + "strings" + "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" @@ -40,46 +41,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 +74,62 @@ 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 { + config.NoDefaulter + // ID is the SCION element ID of the shim dispatcher. + ID string `toml:"id,omitempty"` + ServiceAddresses map[string]string `toml:"service_addresses,omitempty"` + ParsedServiceAddresses map[addr.Addr]netip.AddrPort + // UnderlayPort is the native port opened by the dispatcher (default 30041) + UnderlayPort int `toml:"underlay_port,omitempty"` +} + +func (cfg *Dispatcher) Validate() error { + if cfg.UnderlayPort == 0 { + cfg.UnderlayPort = topology.EndhostPort + } + if cfg.ID == "" { + return serrors.New("id must be set") + } + + // Process ServiceAddresses + cfg.ParsedServiceAddresses = make(map[addr.Addr]netip.AddrPort, len(cfg.ServiceAddresses)) + for iaSvc, addr := range cfg.ServiceAddresses { + parsedIASvc, err := parseIASvc(iaSvc) + if err != nil { + return serrors.WrapStr("parsing IA,SVC", err) + } + parsedAddr, err := netip.ParseAddrPort(addr) + if err != nil { + return serrors.WrapStr("parsing address", err) + } + cfg.ParsedServiceAddresses[parsedIASvc] = parsedAddr + } + 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 parseIASvc(str string) (addr.Addr, error) { + words := strings.Split(str, ",") + if len(words) != 2 { + return addr.Addr{}, serrors.New("Host addr doesn't match format \"ia, svc\"", "input", str) + } + ia, err := addr.ParseIA(words[0]) + if err != nil { + return addr.Addr{}, serrors.WrapStr("parsing IA in Host addr", err) + } + svc, err := addr.ParseSVC(words[1]) + if err != nil { + return addr.Addr{}, serrors.WrapStr("parsing SVC in Host addr", err) + } + return addr.Addr{IA: ia, Host: addr.HostSVC(svc)}, nil +} diff --git a/dispatcher/config/config_test.go b/dispatcher/config/config_test.go index 07338d9ab2..a3391bfe79 100644 --- a/dispatcher/config/config_test.go +++ b/dispatcher/config/config_test.go @@ -23,7 +23,6 @@ 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" @@ -44,7 +43,6 @@ func InitTestConfig(cfg *Config) { apitest.InitConfig(&cfg.API) envtest.InitTest(nil, &cfg.Metrics, nil, nil) logtest.InitTestLogging(&cfg.Logging) - cfg.Dispatcher.DeleteSocket = true } 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.Len(t, cfg.Dispatcher.ParsedServiceAddresses, 0) } diff --git a/dispatcher/config/sample.go b/dispatcher/config/sample.go index e105927c6e..4a2d2c2a26 100644 --- a/dispatcher/config/sample.go +++ b/dispatcher/config/sample.go @@ -21,15 +21,6 @@ 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" - -# 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 ` diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 86d15a09d8..caf2f06b8e 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,504 @@ 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 + 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) +// NewServer creates new instance of Server. +func NewServer(svcAddrs map[addr.Addr]netip.AddrPort, conn *net.UDPConn) *Server { + server := Server{ + 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, + }, + } + parser := gopacket.NewDecodingLayerParser( + slayers.LayerTypeSCION, + &server.scionLayer, + &server.hbh, + &server.e2e, + &server.udpLayer, + &server.scmpLayer, + ) + parser.IgnoreUnsupported = true + server.parser = parser + server.conn, server.cmParser = setIPPktInfo(conn) + 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 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 (s *Server) Serve() error { + for { + n, nn, _, prevHop, err := s.conn.ReadMsgUDPAddrPort(s.buf, s.oobuf) if err != nil { - return nil, err + log.Error("Reading message", "err", err) + continue } - } - if ipv6Conn == nil { - var err error - ipv6Conn, err = openConn("udp6", address) + + 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 + } + + outBuf, nextHopAddr, err := s.processMsgNextHop(s.buf[:n], underlay, prevHop) if err != nil { - ipv4Conn.Close() - return nil, err + return err + } + if !nextHopAddr.IsValid() { + // some error processing the incoming packet; + // we discard the packet and keep serving. + continue } - } - return &Server{ - routingTable: NewIATable(32768, 65535), - ipv4Conn: ipv4Conn, - ipv6Conn: ipv6Conn, - }, nil -} -// Serve starts reading packets from network and dispatching them to different connections. -// 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, + m, err := s.conn.WriteToUDPAddrPort(outBuf, nextHopAddr) + if err != nil { + log.Error("writing packet out", "err", err) + continue } - errChan <- netToRingDataplane.Run() - }() - go func() { - defer log.HandlePanic() - netToRingDataplane := &NetToRingDataplane{ - UnderlayConn: as.ipv6Conn, - RoutingTable: as.routingTable, + if m != len(outBuf) { + log.Error("writing packet out", "message len", len(outBuf), "written bytes", n) } - 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) { - - tableEntry := newTableEntry() - ref, err := as.routingTable.Register(ia, address, nil, svc, tableEntry) +// processMsgNextHop processes the message arriving to the shim dispatcher and returns +// a byte array corresponding to the packet that has to be forwarded. +// The input byte array *buf* is the raw incoming packet; the *underlay* address MUST NOT +// be nil and corresponds to the IP address in the encapsulation UDP/IP header; *prevHop* +// address is the address from the previous SCION hop in the local network. +// The intended nextHop address, 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 due to +// incorrect address validation the returned buffer and address will be nil. +// The caller must check both values consistently. +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 decode 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() -} + // 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. + var dstAddrPort netip.AddrPort + 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 + } + } -// 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 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()) -func (ac *Conn) WriteTo(p []byte, addr net.Addr) (int, error) { - panic("not implemented") + 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 + } + + 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) + } + 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) } - n = pkt.CopyTo(p) - addr = pkt.UnderlayRemote - pkt.Free() - return + 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 UDP/IP wrapper. +// It returns nil, if the control message information is not present or cannot be parsed. +// This is useful for checking that this address corresponds to the address of the encapsulated +// 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(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(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) -} - -func (o *underlayConnWrapper) Close() error { - return o.Conn.Close() -} - -func (o *underlayConnWrapper) LocalAddr() net.Addr { - return o.Conn.LocalAddr() + return netip.AddrPortFrom(a, port), nil } -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..06ee155c07 --- /dev/null +++ b/dispatcher/dispatcher_test.go @@ -0,0 +1,344 @@ +// 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 + ClientAddr *net.UDPAddr + DispAddr *net.UDPAddr + Pkt *snet.Packet + ExpectedValue bool +} + +func testRunTestCase(t *testing.T, tc testCase) { + serverConn, err := net.ListenUDP(tc.DispAddr.Network(), tc.DispAddr) + require.NoError(t, err) + defer serverConn.Close() + setIPPktInfo(serverConn) + emptyTopo := make(map[addr.Addr]netip.AddrPort) + server := NewServer(emptyTopo, serverConn) + + clientConn, err := net.DialUDP("udp", tc.ClientAddr, tc.DispAddr) + 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) + 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 := xtest.MustParseUDPAddr(t, "127.0.0.1:0") + dispIPv4Addr := xtest.MustParseUDPAddr(t, "127.0.0.1:40032") + clientIPv6Addr := xtest.MustParseUDPAddr(t, "[::1]:0") + dispIPv6Addr := xtest.MustParseUDPAddr(t, "[::1]:40032") + mappedDispIPv4Addr := &net.UDPAddr{ + IP: dispIPv4Addr.IP.To16(), + Port: 40032, + } + testCases := []testCase{ + { + Name: "valid UDP/IPv4", + ClientAddr: clientAddr, + DispAddr: dispIPv4Addr, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(clientAddr.AddrPort().Addr()), + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(dispIPv4Addr.AddrPort().Addr()), + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid UDP/IPv4", + ClientAddr: clientAddr, + DispAddr: dispIPv4Addr, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(clientAddr.AddrPort().Addr()), + }, + 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", + ClientAddr: clientAddr, + DispAddr: dispIPv4Addr, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(clientAddr.AddrPort().Addr()), + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(dispIPv4Addr.AddrPort().Addr()), + }, + Payload: snet.SCMPDestinationUnreachable{ + Payload: MustPack(snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(dispIPv4Addr.AddrPort().Addr()), + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(clientAddr.AddrPort().Addr()), + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid SCMP/IPv4", + ClientAddr: clientAddr, + DispAddr: dispIPv4Addr, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(clientAddr.AddrPort().Addr()), + }, + 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: addr.HostIP(dispIPv4Addr.AddrPort().Addr()), + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(clientAddr.AddrPort().Addr()), + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + { + Name: "valid UDP/IPv6", + ClientAddr: clientIPv6Addr, + DispAddr: dispIPv6Addr, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(clientIPv6Addr.AddrPort().Addr()), + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(dispIPv6Addr.AddrPort().Addr()), + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid UDP/IPv6", + ClientAddr: clientIPv6Addr, + DispAddr: dispIPv6Addr, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(clientAddr.AddrPort().Addr()), + }, + 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", + ClientAddr: clientIPv6Addr, + DispAddr: dispIPv6Addr, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(clientIPv6Addr.AddrPort().Addr()), + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(dispIPv6Addr.AddrPort().Addr()), + }, + Payload: snet.SCMPDestinationUnreachable{ + Payload: MustPack(snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(dispIPv6Addr.AddrPort().Addr()), + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(clientIPv6Addr.AddrPort().Addr()), + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid SCMP/IPv6", + ClientAddr: clientIPv6Addr, + DispAddr: dispIPv6Addr, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(clientIPv6Addr.AddrPort().Addr()), + }, + 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: addr.HostIP(dispIPv6Addr.AddrPort().Addr()), + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(clientIPv6Addr.AddrPort().Addr()), + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + { + Name: "IPv4-mapped-IPv6 to IPv4", + ClientAddr: clientAddr, + DispAddr: dispIPv4Addr, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: addr.HostIP(clientAddr.AddrPort().Addr()), + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(mappedDispIPv4Addr.AddrPort().Addr()), + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + } + 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 737d8bfcd4..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, ShouldNotEqual, 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, ShouldNotEqual, 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/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..4456f9ad2d 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, + "endhost_port_range": "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..853d976cc4 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, + CPInfoProvider: g.Daemon, }, PathUpdateInterval: PathUpdateInterval(ctx), PathFetchTimeout: 0, // using default for now @@ -441,12 +409,9 @@ 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. + LocalIA: localIA, + CPInfoProvider: g.Daemon, + Connector: &snet.DefaultConnector{ SCMPHandler: snet.SCMPPropagationStopper{ Handler: snet.DefaultSCMPHandler{ RevocationHandler: revocationHandler, @@ -454,7 +419,8 @@ func (g *Gateway) Run(ctx context.Context) error { }, Log: log.FromCtx(ctx).Debug, }, - SCIONPacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Metrics: g.Metrics.SCIONPacketConnMetrics, + CPInfoProvider: g.Daemon, }, Metrics: g.Metrics.SCIONNetworkMetrics, } @@ -465,7 +431,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,16 +474,15 @@ 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 + LocalIA: localIA, + CPInfoProvider: g.Daemon, + Connector: &snet.DefaultConnector{ SCMPHandler: snet.DefaultSCMPHandler{ RevocationHandler: revocationHandler, SCMPErrors: g.Metrics.SCMPErrors, }, - SCIONPacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Metrics: g.Metrics.SCIONPacketConnMetrics, + CPInfoProvider: g.Daemon, }, Metrics: g.Metrics.SCIONNetworkMetrics, } @@ -544,8 +508,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, + Connector: scionNetwork.Connector, + LocalIP: g.ServiceDiscoveryClientIP, }, SVCResolutionFraction: 1.337, }, @@ -565,7 +529,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 +576,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 +774,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..14dbaf33a9 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 + // CPInfoProvider is the helper class to get control-plane information for the + // local AS. + CPInfoProvider snet.CPInfoProvider // 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) } + nc, err := (&snet.DefaultConnector{ + SCMPHandler: scmpHandler{ + wrappedHandler: snet.DefaultSCMPHandler{ + RevocationHandler: f.RevocationHandler, + SCMPErrors: f.SCMPErrors, + }, + pkts: pktChan, + }, + Metrics: f.SCIONPacketConnMetrics, + CPInfoProvider: f.CPInfoProvider, + }).OpenUDP(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: nc, + id: uint16(nc.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 c503aae7eb..fea1a5fa3f 100644 --- a/gateway/pathhealth/remotewatcher.go +++ b/gateway/pathhealth/remotewatcher.go @@ -17,7 +17,6 @@ package pathhealth import ( "context" "fmt" - "math/rand" "sync" "time" @@ -51,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. @@ -85,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, @@ -108,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 @@ -163,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))) } @@ -222,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) @@ -241,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) @@ -255,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 := uint16(rand.Uint32()) - 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 d9ee896b1a..3351657c59 100644 --- a/nogo.json +++ b/nogo.json @@ -45,7 +45,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": { @@ -97,7 +98,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/apitypes.go b/pkg/daemon/apitypes.go index 4b4564bef8..ebc46ec744 100644 --- a/pkg/daemon/apitypes.go +++ b/pkg/daemon/apitypes.go @@ -33,8 +33,10 @@ type PathReqFlags struct { // ASInfo provides information about the local AS. type ASInfo struct { - IA addr.IA - MTU uint16 + IA addr.IA + MTU uint16 + EndhostStartPort uint16 + EndhostEndPort uint16 } type Querier struct { diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 24d0b6b9f8..fb4141b5fc 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -24,7 +24,6 @@ import ( "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" @@ -64,15 +63,16 @@ func NewService(name string) Service { type Connector interface { // LocalIA requests from the daemon the local ISD-AS number. 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]*net.UDPAddr, 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..479293126c 100644 --- a/pkg/daemon/grpc.go +++ b/pkg/daemon/grpc.go @@ -74,6 +74,34 @@ func (c grpcConn) LocalIA(ctx context.Context) (addr.IA, error) { return ia, nil } +func (c grpcConn) PortRange(ctx context.Context) (uint16, uint16, error) { + asInfo, err := c.ASInfo(ctx, 0) + if err != nil { + return 0, 0, err + } + return asInfo.EndhostStartPort, asInfo.EndhostEndPort, nil +} + +func (c grpcConn) Interfaces(ctx context.Context) (map[uint16]*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[uint16]*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[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) { @@ -102,33 +130,13 @@ func (c grpcConn) ASInfo(ctx context.Context, ia addr.IA) (ASInfo, error) { } c.metrics.incAS(nil) return ASInfo{ - IA: addr.IA(response.IsdAs), - MTU: uint16(response.Mtu), + IA: addr.IA(response.IsdAs), + MTU: uint16(response.Mtu), + EndhostStartPort: uint16(response.EndhostStartPort), + EndhostEndPort: uint16(response.EndhostEndPort), }, 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..b3d460783a 100644 --- a/pkg/daemon/mock_daemon/mock.go +++ b/pkg/daemon/mock_daemon/mock.go @@ -13,7 +13,6 @@ import ( 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]*net.UDPAddr, 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]*net.UDPAddr) 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/BUILD.bazel b/pkg/experimental/hiddenpath/BUILD.bazel index 3962a4665e..909f9853ea 100644 --- a/pkg/experimental/hiddenpath/BUILD.bazel +++ b/pkg/experimental/hiddenpath/BUILD.bazel @@ -19,6 +19,7 @@ go_library( "//control/beaconing:go_default_library", "//control/ifstate:go_default_library", "//pkg/addr:go_default_library", + "//pkg/grpc:go_default_library", "//pkg/log:go_default_library", "//pkg/metrics:go_default_library", "//pkg/private/prom:go_default_library", diff --git a/pkg/experimental/hiddenpath/discovery.go b/pkg/experimental/hiddenpath/discovery.go index f336a152c0..fe635a5433 100644 --- a/pkg/experimental/hiddenpath/discovery.go +++ b/pkg/experimental/hiddenpath/discovery.go @@ -19,6 +19,7 @@ import ( "net" "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/grpc" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/path" @@ -61,6 +62,32 @@ func (r RegistrationResolver) Resolve(ctx context.Context, ia addr.IA) (net.Addr }) } +// CSResolver resolves the address of a Control Service +// server in an IA. This is necessary to get the needed +// certificates from the Registry to verify the segments. +type CSResolver struct { + Router snet.Router + Rewriter grpc.AddressRewriter +} + +// Resolve resolves the CS server in the remote IA. +func (r CSResolver) Resolve(ctx context.Context, ia addr.IA) (net.Addr, error) { + // TODO(JordiSubira): Put path failover mechanism in-place + path, err := r.Router.Route(ctx, ia) + if err != nil { + return nil, serrors.WrapStr("looking up path", err) + } + if path == nil { + return nil, serrors.WrapStr("no path found to remote", err) + } + return &snet.SVCAddr{ + IA: ia, + NextHop: path.UnderlayNextHop(), + Path: path.Dataplane(), + SVC: addr.SvcCS, + }, nil +} + // LookupResolver resolves the address of a hidden segment lookup // server in an IA. type LookupResolver struct { diff --git a/pkg/experimental/hiddenpath/forwarder.go b/pkg/experimental/hiddenpath/forwarder.go index b7cfaf300c..884b525e85 100644 --- a/pkg/experimental/hiddenpath/forwarder.go +++ b/pkg/experimental/hiddenpath/forwarder.go @@ -46,12 +46,13 @@ type Verifier interface { // For each group id of the request, it requests the segments at the the // respective autoritative registry. type ForwardServer struct { - Groups map[GroupID]*Group - LocalAuth Lookuper - LocalIA addr.IA - RPC RPC - Resolver AddressResolver - Verifier Verifier + Groups map[GroupID]*Group + LocalAuth Lookuper + LocalIA addr.IA + RPC RPC + HPResolver AddressResolver + CSResolver AddressResolver + Verifier Verifier } // Segments serves segments for the given request. It finds per group ID @@ -104,7 +105,7 @@ func (s ForwardServer) Segments(ctx context.Context, replies <- segsOrErr{segs: reply, err: err} return } - a, err := s.Resolver.Resolve(ctx, r) + a, err := s.HPResolver.Resolve(ctx, r) if err != nil { replies <- segsOrErr{err: err} return @@ -114,9 +115,14 @@ func (s ForwardServer) Segments(ctx context.Context, replies <- segsOrErr{err: err} return } + a, err = s.CSResolver.Resolve(ctx, r) + if err != nil { + replies <- segsOrErr{err: err} + return + } if err := s.Verifier.Verify(ctx, reply, a); err != nil { replies <- segsOrErr{ - err: serrors.New("can not verify segments", "crypto-source", r, + err: serrors.WrapStr("verifying segment", err, "crypto-source", r, "server", a), } return diff --git a/pkg/experimental/hiddenpath/forwarder_test.go b/pkg/experimental/hiddenpath/forwarder_test.go index 6d5d54a287..f68aaca26b 100644 --- a/pkg/experimental/hiddenpath/forwarder_test.go +++ b/pkg/experimental/hiddenpath/forwarder_test.go @@ -115,12 +115,13 @@ func TestForwardServerSegments(t *testing.T) { AnyTimes() server := hiddenpath.ForwardServer{ - Groups: tc.groups(), - RPC: tc.rpc(ctrl), - LocalAuth: tc.lookuper(ctrl), - LocalIA: local, - Verifier: tc.verifier(ctrl), - Resolver: resolver, + Groups: tc.groups(), + RPC: tc.rpc(ctrl), + LocalAuth: tc.lookuper(ctrl), + LocalIA: local, + Verifier: tc.verifier(ctrl), + HPResolver: resolver, + CSResolver: resolver, } got, err := server.Segments(context.Background(), tc.request) tc.assertErr(t, err) diff --git a/pkg/experimental/hiddenpath/testdata/topology.json b/pkg/experimental/hiddenpath/testdata/topology.json index 0e9e98d9c4..6ee0982216 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, + "endhost_port_range": "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..b4549c76e7 100644 --- a/pkg/proto/daemon/daemon.pb.go +++ b/pkg/proto/daemon/daemon.pb.go @@ -557,9 +557,11 @@ type ASResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - IsdAs uint64 `protobuf:"varint,1,opt,name=isd_as,json=isdAs,proto3" json:"isd_as,omitempty"` - Core bool `protobuf:"varint,2,opt,name=core,proto3" json:"core,omitempty"` - Mtu uint32 `protobuf:"varint,3,opt,name=mtu,proto3" json:"mtu,omitempty"` + IsdAs uint64 `protobuf:"varint,1,opt,name=isd_as,json=isdAs,proto3" json:"isd_as,omitempty"` + Core bool `protobuf:"varint,2,opt,name=core,proto3" json:"core,omitempty"` + Mtu uint32 `protobuf:"varint,3,opt,name=mtu,proto3" json:"mtu,omitempty"` + EndhostStartPort uint32 `protobuf:"varint,4,opt,name=endhost_start_port,json=endhostStartPort,proto3" json:"endhost_start_port,omitempty"` + EndhostEndPort uint32 `protobuf:"varint,5,opt,name=endhost_end_port,json=endhostEndPort,proto3" json:"endhost_end_port,omitempty"` } func (x *ASResponse) Reset() { @@ -615,6 +617,20 @@ func (x *ASResponse) GetMtu() uint32 { return 0 } +func (x *ASResponse) GetEndhostStartPort() uint32 { + if x != nil { + return x.EndhostStartPort + } + return 0 +} + +func (x *ASResponse) GetEndhostEndPort() uint32 { + if x != nil { + return x.EndhostEndPort + } + return 0 +} + type InterfacesRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1575,184 +1591,189 @@ var file_proto_daemon_v1_daemon_proto_rawDesc = []byte{ 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, + 0xa1, 0x01, 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, 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, - 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, + 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, 0x12, 0x2c, 0x0a, 0x12, 0x65, + 0x6e, 0x64, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x70, 0x6f, 0x72, + 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x65, 0x6e, 0x64, 0x68, 0x6f, 0x73, 0x74, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x6e, 0x64, + 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x65, 0x6e, 0x64, 0x68, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x64, 0x50, + 0x6f, 0x72, 0x74, 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, 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, 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, + 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, 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, 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, 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, + 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, 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, 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, + 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 ( diff --git a/pkg/snet/BUILD.bazel b/pkg/snet/BUILD.bazel index 66f0c6f674..074b9ae393 100644 --- a/pkg/snet/BUILD.bazel +++ b/pkg/snet/BUILD.bazel @@ -5,7 +5,6 @@ go_library( srcs = [ "base.go", "conn.go", - "dispatcher.go", "interface.go", "packet.go", "packet_conn.go", @@ -13,6 +12,7 @@ go_library( "reader.go", "reply_pather.go", "router.go", + "scmp.go", "snet.go", "svcaddr.go", "udpaddr.go", @@ -30,8 +30,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 index a06f956f0c..32758cee27 100644 --- a/pkg/snet/base.go +++ b/pkg/snet/base.go @@ -17,8 +17,6 @@ package snet import ( "net" - - "github.com/scionproto/scion/pkg/addr" ) type scionConnBase struct { @@ -26,9 +24,6 @@ type scionConnBase struct { listen *UDPAddr remote *UDPAddr - // svc address - svc addr.SVC - // Reference to SCION networking context scionNet *SCIONNetwork } @@ -40,7 +35,3 @@ func (c *scionConnBase) LocalAddr() net.Addr { 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..a6a5720b77 100644 --- a/pkg/snet/conn.go +++ b/pkg/snet/conn.go @@ -47,15 +47,24 @@ type Conn struct { scionConnReader } -func newConn(base scionConnBase, conn PacketConn, replyPather ReplyPather) *Conn { +func newConn( + base scionConnBase, + conn PacketConn, + replyPather ReplyPather, + endhostStartPort uint16, + endhostEndPort uint16, +) *Conn { + c := &Conn{ conn: conn, scionConnBase: base, } c.scionConnWriter = scionConnWriter{ - base: &c.scionConnBase, - conn: conn, - buffer: make([]byte, common.SupportedMTU), + base: &c.scionConnBase, + conn: conn, + buffer: make([]byte, common.SupportedMTU), + endhostStartPort: endhostStartPort, + endhostEndPort: endhostEndPort, } c.scionConnReader = scionConnReader{ base: &c.scionConnBase, diff --git a/pkg/snet/interface.go b/pkg/snet/interface.go index 343ec1a442..6fa70eb03d 100644 --- a/pkg/snet/interface.go +++ b/pkg/snet/interface.go @@ -18,13 +18,9 @@ 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) + 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..5155e9c61e 100644 --- a/pkg/snet/mock_snet/BUILD.bazel +++ b/pkg/snet/mock_snet/BUILD.bazel @@ -5,7 +5,7 @@ gomock( name = "go_default_mock", out = "mock.go", interfaces = [ - "PacketDispatcherService", + "Connector", "Network", "PacketConn", "Path", diff --git a/pkg/snet/mock_snet/mock.go b/pkg/snet/mock_snet/mock.go index 181ac9a929..622272755e 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: Connector,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,43 +17,42 @@ import ( snet "github.com/scionproto/scion/pkg/snet" ) -// MockPacketDispatcherService is a mock of PacketDispatcherService interface. -type MockPacketDispatcherService struct { +// MockConnector is a mock of Connector interface. +type MockConnector struct { ctrl *gomock.Controller - recorder *MockPacketDispatcherServiceMockRecorder + recorder *MockConnectorMockRecorder } -// MockPacketDispatcherServiceMockRecorder is the mock recorder for MockPacketDispatcherService. -type MockPacketDispatcherServiceMockRecorder struct { - mock *MockPacketDispatcherService +// MockConnectorMockRecorder is the mock recorder for MockConnector. +type MockConnectorMockRecorder struct { + mock *MockConnector } -// NewMockPacketDispatcherService creates a new mock instance. -func NewMockPacketDispatcherService(ctrl *gomock.Controller) *MockPacketDispatcherService { - mock := &MockPacketDispatcherService{ctrl: ctrl} - mock.recorder = &MockPacketDispatcherServiceMockRecorder{mock} +// NewMockConnector creates a new mock instance. +func NewMockConnector(ctrl *gomock.Controller) *MockConnector { + mock := &MockConnector{ctrl: ctrl} + mock.recorder = &MockConnectorMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockPacketDispatcherService) EXPECT() *MockPacketDispatcherServiceMockRecorder { +func (m *MockConnector) EXPECT() *MockConnectorMockRecorder { 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) { +// OpenUDP mocks base method. +func (m *MockConnector) OpenUDP(arg0 context.Context, arg1 *net.UDPAddr) (snet.PacketConn, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Register", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "OpenUDP", arg0, arg1) ret0, _ := ret[0].(snet.PacketConn) - ret1, _ := ret[1].(uint16) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 + ret1, _ := ret[1].(error) + return ret0, ret1 } -// Register indicates an expected call of Register. -func (mr *MockPacketDispatcherServiceMockRecorder) Register(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +// OpenUDP indicates an expected call of OpenUDP. +func (mr *MockConnectorMockRecorder) OpenUDP(arg0, arg1 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) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenUDP", reflect.TypeOf((*MockConnector)(nil).OpenUDP), arg0, arg1) } // MockNetwork is a mock of Network interface. @@ -79,33 +79,33 @@ 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) } // MockPacketConn is a mock of PacketConn interface. @@ -145,6 +145,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 +215,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..e49e152b1b 100644 --- a/pkg/snet/packet_conn.go +++ b/pkg/snet/packet_conn.go @@ -16,6 +16,7 @@ package snet import ( "net" + "syscall" "time" "github.com/scionproto/scion/pkg/addr" @@ -23,6 +24,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 +39,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 +106,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 + getLastHopAddr func(id uint16) (*net.UDPAddr, error) +} + +func (c *SCIONPacketConn) SetReadBuffer(bytes int) error { + return c.Conn.SetReadBuffer(bytes) } func (c *SCIONPacketConn) SetDeadline(d time.Time) error { @@ -139,6 +152,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 +163,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 +188,139 @@ 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("Reliable socket read error", 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) { + // If packet comes from shim get ingress interface internal 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 sends 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. +func (c *SCIONPacketConn) isShimDispatcher(udpAddr *net.UDPAddr) bool { + localAddr := c.LocalAddr().(*net.UDPAddr) + if udpAddr.IP.Equal(localAddr.IP) && 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("Unexpected path", "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()) + } + var port int + switch p := p.PacketInfo.Payload.(type) { + case UDPPayload: + port = int(p.SrcPort) + case SCMPPayload: + port = underlay.EndhostPort + default: + // we fallback to the endhost port also for unknown payloads + port = underlay.EndhostPort + } + return &net.UDPAddr{ + IP: p.Source.Host.IP().AsSlice(), + Port: port, + }, nil + case onehop.PathType: + var path onehop.Path + err := path.DecodeFromBytes(rpath.Raw) + if err != nil { + return nil, err + } + ifid := path.SecondHop.ConsIngress + if !path.Info.ConsDir { + ifid = path.SecondHop.ConsEgress + } + return c.getLastHopAddr(ifid) + case epic.PathType: + var path epic.Path + err := path.DecodeFromBytes(rpath.Raw) + if 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.getLastHopAddr(ifid) + case scion.PathType: + var path scion.Raw + err := path.DecodeFromBytes(rpath.Raw) + if 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.getLastHopAddr(ifid) + default: + return nil, serrors.New("Unknown 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. 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..1a9602d855 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. // diff --git a/pkg/snet/snet.go b/pkg/snet/snet.go index c41c269e8c..8e16697ad9 100644 --- a/pkg/snet/snet.go +++ b/pkg/snet/snet.go @@ -20,31 +20,27 @@ // 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" + "syscall" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" @@ -52,6 +48,87 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" ) +// CPInfoProvider provides local-IA control-plane information +type CPInfoProvider interface { + PortRange(ctx context.Context) (uint16, uint16, error) + Interfaces(ctx context.Context) (map[uint16]*net.UDPAddr, error) +} + +type Connector interface { + // OpenUDP 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. + OpenUDP(ctx context.Context, address *net.UDPAddr) (PacketConn, error) +} + +type DefaultConnector struct { + SCMPHandler SCMPHandler + Metrics SCIONPacketConnMetrics + CPInfoProvider CPInfoProvider +} + +func (d *DefaultConnector) OpenUDP(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 permitted") + } + start, end, err := d.CPInfoProvider.PortRange(ctx) + if err != nil { + return nil, err + } + ifAddrs, err := d.CPInfoProvider.Interfaces(ctx) + if err != nil { + return nil, err + } + if addr.Port == 0 { + pconn, err = listenUDPRange(addr, start, end) + } else { + // XXX(JordiSubira): We check that given port is within SCION/UDP + // port range for the endhost. + if addr.Port < int(start) || addr.Port > int(end) { + return nil, serrors.New("Provided port is outside the SCION/UDP range", + "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: d.SCMPHandler, + Metrics: d.Metrics, + getLastHopAddr: func(id uint16) (*net.UDPAddr, error) { + addr, ok := ifAddrs[id] + if !ok { + return nil, serrors.New("Interface number not found", "if", id) + } + return addr, nil + }, + }, nil +} + +func listenUDPRange(addr *net.UDPAddr, start, end uint16) (*net.UDPConn, error) { + // XXX(JordiSubira): For now, we simply iterate on the complete SCION/UDP + // range, taking the first unused port. + for port := start; port < end; port++ { + pconn, err := net.ListenUDP(addr.Network(), &net.UDPAddr{ + IP: addr.IP, + Port: int(port), + }) + if err != nil { + if !errors.Is(err, syscall.EADDRINUSE) { + return nil, err + } + continue + } + return pconn, nil + } + return nil, serrors.WrapStr("binding to port range", syscall.EADDRINUSE) +} + var _ Network = (*SCIONNetwork)(nil) type SCIONNetworkMetrics struct { @@ -63,8 +140,9 @@ type SCIONNetworkMetrics struct { // SCIONNetwork is the SCION networking context. type SCIONNetwork struct { - LocalIA addr.IA - Dispatcher PacketDispatcherService + LocalIA addr.IA + CPInfoProvider CPInfoProvider + Connector Connector // 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. @@ -73,22 +151,22 @@ type SCIONNetwork struct { Metrics SCIONNetworkMetrics } -// 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) { metrics.CounterInc(n.Metrics.Dials) if remote == nil { return nil, serrors.New("Unable to dial to nil remote") } - conn, err := n.Listen(ctx, network, listen, svc) + conn, err := n.Listen(ctx, network, listen) if err != nil { return nil, err } @@ -96,15 +174,19 @@ func (n *SCIONNetwork) Dial(ctx context.Context, network string, listen *net.UDP return conn, nil } -// 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". +// Parameter network must be "udp". If listen is unspecified address a suitable address +// will be chosen independently per packet. For finer-grained control, bind to a specific +// anycast address only. // // 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) @@ -112,43 +194,28 @@ func (n *SCIONNetwork) Listen(ctx context.Context, network string, listen *net.U 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") + packetConn, err := n.Connector.OpenUDP(ctx, listen) + if err != nil { + return nil, err } + + log.FromCtx(ctx).Debug("UDP socket openned on", "addr", packetConn.LocalAddr()) + conn := scionConnBase{ scionNet: n, - svc: svc, listen: &UDPAddr{ IA: n.LocalIA, - Host: CopyUDPAddr(listen), + Host: packetConn.LocalAddr().(*net.UDPAddr), }, } - packetConn, port, err := n.Dispatcher.Register(ctx, n.LocalIA, listen, svc) - 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) replyPather := n.ReplyPather if replyPather == nil { replyPather = DefaultReplyPather{} } - - return newConn(conn, packetConn, replyPather), nil + start, end, err := n.CPInfoProvider.PortRange(ctx) + if err != nil { + return nil, err + } + return newConn(conn, packetConn, replyPather, start, end), nil } diff --git a/pkg/snet/writer.go b/pkg/snet/writer.go index 70609f1023..ef3d462ab1 100644 --- a/pkg/snet/writer.go +++ b/pkg/snet/writer.go @@ -24,12 +24,14 @@ 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 + base *scionConnBase + conn PacketConn + endhostStartPort uint16 + endhostEndPort uint16 mtx sync.Mutex buffer []byte @@ -56,9 +58,13 @@ func (c *scionConnWriter) WriteTo(b []byte, raddr net.Addr) (int, error) { port, path = a.Host.Port, a.Path nextHop = a.NextHop if nextHop == nil && c.base.scionNet.LocalIA.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, } @@ -110,3 +116,7 @@ func (c *scionConnWriter) Write(b []byte) (int, error) { func (c *scionConnWriter) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } + +func (c *scionConnWriter) isWithinRange(port int) bool { + return port >= int(c.endhostStartPort) && port <= int(c.endhostEndPort) +} 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..60d26f8bb3 100644 --- a/private/app/appnet/infraenv.go +++ b/private/app/appnet/infraenv.go @@ -39,8 +39,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" @@ -64,10 +62,6 @@ 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 QUIC @@ -81,10 +75,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 + // CPInfoProvider is the helper class to get control-plane information for the + // local AS. + CPInfoProvider snet.CPInfoProvider } // QUICStack contains everything to run a QUIC based RPC stack. @@ -105,7 +102,7 @@ func (nc *NetworkConfig) TCPStack() (net.Listener, error) { func (nc *NetworkConfig) QUICStack() (*QUICStack, error) { if nc.QUIC.Address == "" { - nc.QUIC.Address = nc.Public.String() + nc.QUIC.Address = net.JoinHostPort(nc.Public.IP.String(), "0") } client, server, err := nc.initQUICSockets() @@ -215,29 +212,63 @@ 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. +// 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( - connFactory snet.PacketDispatcherService) *AddressRewriter { + connector snet.Connector) *AddressRewriter { - if connFactory == nil { - connFactory = &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), - SCMPHandler: nc.SCMPHandler, + if connector == nil { + connector = &snet.DefaultConnector{ + SCMPHandler: nc.SCMPHandler, + Metrics: nc.SCIONPacketConnMetrics, + CPInfoProvider: nc.CPInfoProvider, } } 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, + Connector: connector, + LocalIP: nc.Public.IP, }, SVCResolutionFraction: 1.337, } } +func (nc *NetworkConfig) OpenListener(a string) (*squic.ConnListener, error) { + scionNet := &snet.SCIONNetwork{ + LocalIA: nc.IA, + Connector: &snet.DefaultConnector{ + // 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{}, + Metrics: nc.SCIONPacketConnMetrics, + CPInfoProvider: nc.CPInfoProvider, + }, + Metrics: nc.SCIONNetworkMetrics, + CPInfoProvider: nc.CPInfoProvider, + } + udpAddr, err := net.ResolveUDPAddr("udp", a) + if err != nil { + return nil, serrors.WrapStr("parsing server QUIC address", err) + } + server, err := scionNet.Listen(context.Background(), "udp", udpAddr) + if err != nil { + return nil, serrors.WrapStr("creating server connection", err) + } + serverTLSConfig, err := GenerateTLSConfig() + if err != nil { + return nil, err + } + listener, err := quic.Listen(server, serverTLSConfig, nil) + if err != nil { + return nil, err + } + return squic.NewConnListener(listener), nil +} + // 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. @@ -253,34 +284,27 @@ func (nc *NetworkConfig) initSvcRedirect(quicAddress string) (func(), error) { return 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, + LocalIA: nc.IA, + CPInfoProvider: nc.CPInfoProvider, + Connector: &svc.ResolverPacketConnector{ + Connector: &snet.DefaultConnector{ + SCMPHandler: ignoreSCMP{}, + Metrics: nc.SCIONPacketConnMetrics, + CPInfoProvider: nc.CPInfoProvider, + }, + LocalIA: nc.IA, + Handler: &svc.BaseHandler{ + Message: svcResolutionReply, + }, + SVC: addr.SvcWildcard, + }, + Metrics: nc.SCIONNetworkMetrics, } - // 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) + conn, err := network.Listen(context.Background(), "udp", nc.Public) if err != nil { - log.Info("Listen failed", "err", err) - return nil, serrors.WrapStr("listening on SCION", err, "addr", srAddr) + return nil, serrors.WrapStr("listening on SCION", err, "addr", conn.LocalAddr()) } ctx, cancel := context.WithCancel(context.Background()) @@ -310,20 +334,17 @@ func (nc *NetworkConfig) initSvcRedirect(quicAddress string) (func(), error) { } 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, + LocalIA: nc.IA, + CPInfoProvider: nc.CPInfoProvider, + Connector: &snet.DefaultConnector{ // 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, + SCMPHandler: ignoreSCMP{}, + Metrics: nc.SCIONPacketConnMetrics, + CPInfoProvider: nc.CPInfoProvider, }, Metrics: nc.SCIONNetworkMetrics, } @@ -331,31 +352,31 @@ func (nc *NetworkConfig) initQUICSockets() (net.PacketConn, net.PacketConn, erro 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 := serverNet.Listen(context.Background(), "udp", serverAddr) if err != nil { return nil, nil, serrors.WrapStr("creating server connection", err) } clientNet := &snet.SCIONNetwork{ - LocalIA: nc.IA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - Dispatcher: dispatcherService, + LocalIA: nc.IA, + CPInfoProvider: nc.CPInfoProvider, + Connector: &snet.DefaultConnector{ // Discard all SCMP propagation, to avoid read errors on the QUIC // client. SCMPHandler: snet.SCMPPropagationStopper{ Handler: nc.SCMPHandler, Log: log.Debug, }, - SCIONPacketConnMetrics: nc.SCIONPacketConnMetrics, + Metrics: nc.SCIONPacketConnMetrics, + CPInfoProvider: nc.CPInfoProvider, }, Metrics: nc.SCIONNetworkMetrics, } - // Let the dispatcher decide on the port for the client connection. clientAddr := &net.UDPAddr{ IP: serverAddr.IP, Zone: serverAddr.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 6b8bffacbc..b19ba73427 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: uint16(rand.Uint32()), SCIONPacketConnMetrics: cfg.SCIONPacketConnMetrics, - Dispatcher: cfg.Dispatcher, + CPInfoProvider: 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..b49ee4a845 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 + // CPInfoProvider is the helper class to get control-plane information for the + // local AS. + CPInfoProvider snet.CPInfoProvider } 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 connector + connector := &snet.DefaultConnector{ + SCMPHandler: &scmpHandler{}, + Metrics: p.SCIONPacketConnMetrics, + CPInfoProvider: p.CPInfoProvider, } // 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 := connector.OpenUDP(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/logging.go b/private/env/logging.go index 9c2c675699..8287339711 100644 --- a/private/env/logging.go +++ b/private/env/logging.go @@ -17,6 +17,7 @@ package env import ( "fmt" "os" + "runtime" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" @@ -31,10 +32,18 @@ var ( // LogAppStarted should be called by applications as soon as logging is // initialized. func LogAppStarted(svcType, elemID string) error { - inDocker, err := RunsInDocker() - if err != nil { - return serrors.WrapStr("Unable to determine if running in docker", err) + // XXX(JordiSubira): Right now RunsInDocker() only is Linux-compatible. + // If we are going to run apps in docker also in macOS (and potentially in Windows) + // we should make the method compatible. + inDocker := false + if runtime.GOOS == "linux" { + var err error + inDocker, err = RunsInDocker() + if err != nil { + return serrors.WrapStr("Unable to determine if running in docker", err) + } } + info := fmt.Sprintf("=====================> Service started %s %s\n"+ "%s %s\n %s\n %s\n %s\n", svcType, 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/service/statuspages.go b/private/service/statuspages.go index 12526494fe..b050f14e99 100644 --- a/private/service/statuspages.go +++ b/private/service/statuspages.go @@ -20,6 +20,7 @@ import ( "html/template" "net/http" "os" + "runtime" "sort" "strings" @@ -146,9 +147,16 @@ func NewConfigStatusPage(config interface{}) StatusPage { func NewInfoStatusPage() StatusPage { handler := func(w http.ResponseWriter, r *http.Request) { info := env.VersionInfo() - inDocker, err := env.RunsInDocker() - if err == nil { - info += fmt.Sprintf(" In docker: %v\n", inDocker) + // XXX(JordiSubira): Right now RunsInDocker() only is Linux-compatible. + // If we are going to run apps in docker also in macOS (and potentially in Windows) + // we should make the method compatible. + inDocker := false + if runtime.GOOS == "linux" { + var err error + inDocker, err = env.RunsInDocker() + if err != nil { + info += fmt.Sprintf(" In docker: %v\n", inDocker) + } } info += fmt.Sprintf(" pid: %d\n", os.Getpid()) info += fmt.Sprintf(" euid/egid: %d %d\n", os.Geteuid(), os.Getegid()) diff --git a/private/svc/resolver.go b/private/svc/resolver.go index fb01fb173f..5f56d8e5f1 100644 --- a/private/svc/resolver.go +++ b/private/svc/resolver.go @@ -58,9 +58,8 @@ func init() { // Resolver performs SVC address resolution. type Resolver struct { // LocalIA is the local AS. - LocalIA addr.IA - // ConnFactory is used to open ports for SVC resolution messages. - ConnFactory snet.PacketDispatcherService + LocalIA addr.IA + Connector snet.Connector // 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.Connector.OpenUDP(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..ec12367ff5 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")) + mockConnector := mock_snet.NewMockConnector(ctrl) + mockConnector.EXPECT().OpenUDP(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"), + Connector: mockConnector, } 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) + mockConnector := mock_snet.NewMockConnector(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}) + mockConnector.EXPECT().OpenUDP(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, + Connector: mockConnector, LocalIP: net.IP{192, 0, 2, 1}, RoundTripper: mockRoundTripper, } diff --git a/private/svc/svc.go b/private/svc/svc.go index e7451f25c1..ab563f1ccf 100644 --- a/private/svc/svc.go +++ b/private/svc/svc.go @@ -40,25 +40,7 @@ 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 +// ResolverPacketConnector is a Connector 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. @@ -67,45 +49,53 @@ var _ snet.PacketDispatcherService = (*ResolverPacketDispatcher)(nil) // 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 +type ResolverPacketConnector struct { + // Connector opens a PacketConn to receive and send packets. + Connector snet.Connector + // LocalIA contains the address from which packets should be sent. + LocalIA addr.IA + // Handler handles packets for SVC destinations. + Handler RequestHandler + SVC addr.SVC } -func (d *ResolverPacketDispatcher) Register(ctx context.Context, ia addr.IA, - registration *net.UDPAddr, svc addr.SVC) (snet.PacketConn, uint16, error) { +func (c *ResolverPacketConnector) OpenUDP( + ctx context.Context, + u *net.UDPAddr, +) (snet.PacketConn, 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) + pconn, err := c.Connector.OpenUDP(ctx, u) if err != nil { - return nil, 0, err + return nil, err } - packetConn := &resolverPacketConn{ - PacketConn: c, - source: snet.SCIONAddress{ - IA: ia, - Host: addr.HostIP(registrationIP), - }, - handler: d.handler, + ip, ok := netip.AddrFromSlice(u.IP) + if !ok { + return nil, serrors.New("Error extracting IP addr", "UDP addr", u.String()) } - return packetConn, port, err + return &ResolverPacketConn{ + PacketConn: pconn, + Source: snet.SCIONAddress{ + IA: c.LocalIA, + Host: addr.HostIP(ip), + }, + Handler: c.Handler, + SVC: c.SVC, + }, nil } -// 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 + SVC addr.SVC } -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 @@ -123,17 +113,22 @@ func (c *resolverPacketConn) ReadFrom(pkt *snet.Packet, ov *net.UDPAddr) error { return nil } + // Check whether dst SVC matcher configured SVC + if c.SVC != addr.SvcWildcard && c.SVC != svc { + return serrors.WrapStr("SVC destination doesn't match SVC handler", ErrHandler) + } + // 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) case Forward: diff --git a/private/svc/svc_test.go b/private/svc/svc_test.go index f6aed72334..6d893bef3c 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,26 +36,25 @@ import ( func TestSVCResolutionServer(t *testing.T) { testCases := map[string]struct { - DispService func(ctrl *gomock.Controller) snet.PacketDispatcherService + Connector func(ctrl *gomock.Controller) snet.Connector ReqHandler func(ctrl *gomock.Controller) svc.RequestHandler - ErrRegister assert.ErrorAssertionFunc + SVC addr.SVC + 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": { + Connector: func(ctrl *gomock.Controller) snet.Connector { + c := mock_snet.NewMockConnector(ctrl) + c.EXPECT().OpenUDP(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 { + Connector: func(ctrl *gomock.Controller) snet.Connector { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { @@ -65,22 +65,46 @@ func TestSVCResolutionServer(t *testing.T) { }, ) - 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.NewMockConnector(ctrl) + c.EXPECT().OpenUDP(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.Error, errors.New("err")).AnyTimes() return h }, - ErrRegister: assert.NoError, + SVC: addr.SvcCS, + ErrOpen: assert.NoError, + ErrConnRead: assert.Error, + }, + "Handler fails with error cause by SVC mismatch": { + Connector: func(ctrl *gomock.Controller) snet.Connector { + 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), + } + return nil + }, + ) + + c := mock_snet.NewMockConnector(ctrl) + c.EXPECT().OpenUDP(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.Error, errors.New("err")).AnyTimes() + return h + }, + SVC: addr.SvcDS, + ErrOpen: assert.NoError, ErrConnRead: assert.Error, }, "If handler returns forward, caller sees data": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + Connector: func(ctrl *gomock.Controller) snet.Connector { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { @@ -91,22 +115,46 @@ func TestSVCResolutionServer(t *testing.T) { }, ) - 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.NewMockConnector(ctrl) + c.EXPECT().OpenUDP(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() return h }, - ErrRegister: assert.NoError, + SVC: addr.SvcCS, + ErrOpen: assert.NoError, + ErrConnRead: assert.NoError, + }, + "Handler returns forward with SVCWildcard": { + Connector: func(ctrl *gomock.Controller) snet.Connector { + 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), + } + return nil + }, + ) + + c := mock_snet.NewMockConnector(ctrl) + c.EXPECT().OpenUDP(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() + return h + }, + SVC: addr.SvcWildcard, + ErrOpen: assert.NoError, ErrConnRead: assert.NoError, }, "return from conn with no error next internal read yields data": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + Connector: func(ctrl *gomock.Controller) snet.Connector { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { @@ -117,22 +165,20 @@ func TestSVCResolutionServer(t *testing.T) { }, ) - 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.NewMockConnector(ctrl) + c.EXPECT().OpenUDP(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 }, - 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 { + Connector: func(ctrl *gomock.Controller) snet.Connector { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { @@ -140,22 +186,20 @@ func TestSVCResolutionServer(t *testing.T) { }, ) - 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.NewMockConnector(ctrl) + c.EXPECT().OpenUDP(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 }, - ErrRegister: assert.NoError, + ErrOpen: assert.NoError, ErrConnRead: assert.Error, }, "Multicast SVC packets get delivered to caller": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + Connector: func(ctrl *gomock.Controller) snet.Connector { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { @@ -166,18 +210,17 @@ func TestSVCResolutionServer(t *testing.T) { }, ) - 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.NewMockConnector(ctrl) + c.EXPECT().OpenUDP(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 }, - ErrRegister: assert.NoError, + SVC: addr.SvcWildcard, + ErrOpen: assert.NoError, ErrConnRead: assert.NoError, }, } @@ -189,19 +232,23 @@ 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) + connector := &svc.ResolverPacketConnector{ + Connector: tc.Connector(ctrl), + Handler: tc.ReqHandler(ctrl), + SVC: tc.SVC, + } + + conn, err := connector.OpenUDP( + context.Background(), + &net.UDPAddr{IP: xtest.MustParseIP(t, "127.0.0.1")}, + ) - tc.ErrRegister(t, err) + tc.ErrOpen(t, err) if err != nil { assert.Nil(t, conn) - assert.Zero(t, port) return } else { assert.NotNil(t, conn) - assert.Equal(t, port, uint16(1337)) } err = conn.ReadFrom(&snet.Packet{}, &net.UDPAddr{}) tc.ErrConnRead(t, err) diff --git a/private/topology/interface.go b/private/topology/interface.go index f6440c1770..1671fcf866 100644 --- a/private/topology/interface.go +++ b/private/topology/interface.go @@ -40,6 +40,10 @@ 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 applicaction using the underlay 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. @@ -156,6 +160,10 @@ func (t *topologyS) InterfaceIDs() []common.IFIDType { return intfs } +func (t *topologyS) PortRange() (uint16, uint16) { + return t.Topology.EndhostStartPort, t.Topology.EndhostEndPort +} + 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 d07de43a01..d1ab413c54 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:"endhost_port_range"` // 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 e066327497..38b66933b0 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 479ef6f8f5..04bf147bf8 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, + "endhost_port_range": "1024-65535", "attributes": [ "authoritative", "core", diff --git a/private/topology/json/testdata/topology.json b/private/topology/json/testdata/topology.json index 562d4ad08c..9747cb4ee7 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, + "endhost_port_range": "1024-65535", "attributes": [ "core" ], diff --git a/private/topology/mock_topology/mock.go b/private/topology/mock_topology/mock.go index 2c33bb48b2..38f3ed863c 100644 --- a/private/topology/mock_topology/mock.go +++ b/private/topology/mock_topology/mock.go @@ -196,6 +196,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 6aa20ac43d..42999f80d1 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, + "endhost_port_range": "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 3f5505a8b3..5f9e0ee745 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, + "endhost_port_range": "1024-65535", "attributes": [ "core" ], diff --git a/private/topology/topology.go b/private/topology/topology.go index d2d3f5856e..438884110a 100644 --- a/private/topology/topology.go +++ b/private/topology/topology.go @@ -22,6 +22,8 @@ import ( "net" "os" "sort" + "strconv" + "strings" "time" "github.com/scionproto/scion/pkg/addr" @@ -31,8 +33,10 @@ import ( "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") @@ -58,10 +62,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 + EndhostStartPort uint16 + EndhostEndPort uint16 BR map[string]BRInfo BRNames []string @@ -201,6 +207,11 @@ func (t *RWTopology) populateMeta(raw *jsontopo.Topology) error { } t.MTU = raw.MTU + t.EndhostStartPort, t.EndhostEndPort, err = validatePortRange(raw.EndhostPortRange) + if err != nil { + return err + } + isCore := false for _, attr := range raw.Attributes { if attr == jsontopo.AttrCore { @@ -212,6 +223,30 @@ func (t *RWTopology) populateMeta(raw *jsontopo.Topology) error { return nil } +func validatePortRange(portRange string) (uint16, uint16, error) { + ports := strings.Split(portRange, "-") + if len(ports) != 2 { + return 0, 0, serrors.New("invalid format: expected startPort-endPort", "got", portRange) + } + startPort, errStart := strconv.Atoi(ports[0]) + endPort, errEnd := strconv.Atoi(ports[1]) + if errStart != nil || errEnd != nil { + return 0, 0, serrors.New("invalid port numbers", "got", portRange) + } + + if startPort < 1 || startPort > (1<<16-1) { + return 0, 0, serrors.New("Invalid value for start port", "start port", startPort) + } + if endPort < 1 || endPort > (1<<16-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 == "" { @@ -378,10 +413,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, + EndhostStartPort: t.EndhostStartPort, + EndhostEndPort: t.EndhostEndPort, BR: copyBRMap(t.BR), BRNames: append(t.BRNames[:0:0], t.BRNames...), 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/private/underlay/conn/BUILD.bazel b/private/underlay/conn/BUILD.bazel index 3c0395a865..7236035079 100644 --- a/private/underlay/conn/BUILD.bazel +++ b/private/underlay/conn/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools/lint:go.bzl", "go_library") +load("//tools/lint:go.bzl", "go_library", "go_test") go_library( name = "go_default_library", @@ -23,3 +23,14 @@ go_library( "//conditions:default": [], }), ) + +go_test( + name = "go_default_test", + srcs = ["conn_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/private/xtest:go_default_library", + "@com_github_stretchr_testify//assert:go_default_library", + "@com_github_stretchr_testify//require:go_default_library", + ], +) diff --git a/private/underlay/conn/conn.go b/private/underlay/conn/conn.go index c56c30225f..bfec94e4c9 100644 --- a/private/underlay/conn/conn.go +++ b/private/underlay/conn/conn.go @@ -164,9 +164,6 @@ type connUDPBase struct { func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cfg *Config) error { var c *net.UDPConn var err error - if laddr == nil { - return serrors.New("listen address must be specified") - } if raddr == nil { if c, err = net.ListenUDP(network, laddr); err != nil { return serrors.WrapStr("Error listening on socket", err, @@ -248,7 +245,7 @@ func (cc *connUDPBase) initConnUDP(network string, laddr, raddr *net.UDPAddr, cf } cc.conn = c - cc.Listen = laddr + cc.Listen = c.LocalAddr().(*net.UDPAddr) cc.Remote = raddr return nil } diff --git a/private/underlay/conn/conn_test.go b/private/underlay/conn/conn_test.go new file mode 100644 index 0000000000..af9a0b4d43 --- /dev/null +++ b/private/underlay/conn/conn_test.go @@ -0,0 +1,79 @@ +// 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 conn + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/scionproto/scion/pkg/private/xtest" +) + +func TestNew(t *testing.T) { + testCases := map[string]struct { + addr *net.UDPAddr + }{ + "undefined_addr": { + addr: xtest.MustParseUDPAddr(t, "0.0.0.0:0"), + }, + "undefined_port": { + addr: xtest.MustParseUDPAddr(t, "127.0.0.1:0"), + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + sc, err := New(tc.addr, nil, &Config{ + SendBufferSize: 0, + ReceiveBufferSize: 0, + }) + require.NoError(t, err) + defer sc.Close() + lAddr := sc.LocalAddr() + + if tc.addr != nil && !tc.addr.IP.IsUnspecified() { + assert.Equal(t, tc.addr.IP, lAddr.IP) + } + + // Client + cc, err := New(nil, lAddr, &Config{ + SendBufferSize: 0, + ReceiveBufferSize: 0, + }) + require.NoError(t, err) + defer cc.Close() + + exchangeMessages := func(sc Conn, cc Conn) { + msg := []byte("hello") + + go func() { + _, err := cc.Write(msg) + require.NoError(t, err) + }() + + buf := make([]byte, 100) + n, cAddr, err := sc.ReadFrom(buf) + require.NoError(t, err) + assert.Equal(t, msg, buf[:n]) + assert.Equal(t, cc.LocalAddr(), cAddr) + } + + exchangeMessages(sc, cc) + }) + } + +} diff --git a/proto/daemon/v1/daemon.proto b/proto/daemon/v1/daemon.proto index 3fb8632000..5ae650e7d3 100644 --- a/proto/daemon/v1/daemon.proto +++ b/proto/daemon/v1/daemon.proto @@ -26,6 +26,7 @@ service DaemonService { // Return a set of paths to the requested destination. rpc Paths(PathsRequest) returns (PathsResponse) {} // Return information about an AS. + // TODO(JordiSubira): Add PortRange() endpoint instead of reusing AS rpc AS(ASRequest) returns (ASResponse) {} // Return the underlay addresses associated with // the specified interfaces. @@ -153,6 +154,10 @@ message ASResponse { bool core = 2; // The maximum transmission unit (MTU) in the local AS. uint32 mtu = 3; + // The lowest port in the SCION/UDP endhost port range. + uint32 endhost_start_port = 4; + // The highest port in the SCION/UDP endhost port range. + uint32 endhost_end_port = 5; } message InterfacesRequest { } diff --git a/router/cmd/router/main.go b/router/cmd/router/main.go index f458ab2547..4b48671f9a 100644 --- a/router/cmd/router/main.go +++ b/router/cmd/router/main.go @@ -71,6 +71,14 @@ func realMain(ctx context.Context) error { if err := iaCtx.Configure(); err != nil { return serrors.WrapStr("configuring dataplane", err) } + startPort, endPort := controlConfig.Topo.PortRange() + if globalCfg.Router.EndhostStartPort != nil && + globalCfg.Router.EndhostEndPort != nil { + startPort = uint16(*globalCfg.Router.EndhostStartPort) + endPort = uint16(*globalCfg.Router.EndhostEndPort) + } + dp.DataPlane.SetPortRange(startPort, endPort) + log.Debug("Endhost port range configuration", "startPort", startPort, "endPort", endPort) statusPages := service.StatusPages{ "info": service.NewInfoStatusPage(), "config": service.NewConfigStatusPage(globalCfg), diff --git a/router/config/config.go b/router/config/config.go index c4d78bc684..186b4c8728 100644 --- a/router/config/config.go +++ b/router/config/config.go @@ -40,11 +40,13 @@ type Config struct { } type RouterConfig struct { - ReceiveBufferSize int `toml:"receive_buffer_size,omitempty"` - SendBufferSize int `toml:"send_buffer_size,omitempty"` - NumProcessors int `toml:"num_processors,omitempty"` - NumSlowPathProcessors int `toml:"num_slow_processors,omitempty"` - BatchSize int `toml:"batch_size,omitempty"` + ReceiveBufferSize int `toml:"receive_buffer_size,omitempty"` + SendBufferSize int `toml:"send_buffer_size,omitempty"` + NumProcessors int `toml:"num_processors,omitempty"` + NumSlowPathProcessors int `toml:"num_slow_processors,omitempty"` + BatchSize int `toml:"batch_size,omitempty"` + EndhostStartPort *int `toml:"endhost_start_port,omitempty"` + EndhostEndPort *int `toml:"endhost_end_port,omitempty"` } func (cfg *RouterConfig) ConfigName() string { @@ -67,7 +69,27 @@ func (cfg *RouterConfig) Validate() error { if cfg.NumSlowPathProcessors < 1 { return serrors.New("Provided router config is invalid. NumSlowPathProcessors < 1") } - + if cfg.EndhostStartPort != nil { + if cfg.EndhostEndPort == nil { + return serrors.New("Provided router config is invalid. " + + "EndHostEndPort is nil; EndHostStartPort isn't") + } + if *cfg.EndhostStartPort < 0 { + return serrors.New("Provided router config is invalid. EndHostStartPort < 0") + } + if *cfg.EndhostEndPort >= (1 << 16) { + return serrors.New("Provided router config is invalid. EndHostEndPort > 2**16 -1") + } + if *cfg.EndhostStartPort > *cfg.EndhostEndPort { + return serrors.New("Provided router config is invalid. " + + "EndHostStartPort > EndhostEndPort") + } + } else { + if cfg.EndhostEndPort != 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 cd7e5c04e2..7ad0420778 100644 --- a/router/connector.go +++ b/router/connector.go @@ -22,7 +22,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/control" ) @@ -145,25 +144,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. diff --git a/router/control/conf.go b/router/control/conf.go index d5cf15a676..20898c8e8a 100644 --- a/router/control/conf.go +++ b/router/control/conf.go @@ -34,8 +34,8 @@ type Dataplane interface { CreateIACtx(ia addr.IA) error AddInternalInterface(ia addr.IA, local net.UDPAddr) 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 } @@ -125,6 +125,7 @@ func ConfigDataplane(dp Dataplane, cfg *Config) error { return err } } + // Add internal interfaces if cfg.BR != nil { if cfg.BR.InternalAddr != nil { @@ -220,7 +221,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 @@ -230,7 +231,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 6485c148b2..494c650f36 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, + "endhost_port_range": "1024-65535", "attributes": [ "core" ], diff --git a/router/dataplane.go b/router/dataplane.go index 2a1e06e3d6..cdafbf3053 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -20,6 +20,7 @@ import ( "context" "crypto/rand" "crypto/subtle" + "encoding/binary" "errors" "fmt" "hash" @@ -111,6 +112,8 @@ type DataPlane struct { running bool Metrics *Metrics forwardingMetrics map[uint16]interfaceMetrics + endhostStartPort uint16 + endhostEndPort uint16 ExperimentalSCMPAuthentication bool @@ -202,6 +205,11 @@ func (d *DataPlane) SetKey(key []byte) error { return nil } +func (d *DataPlane) SetPortRange(start, end uint16) { + d.endhostStartPort = start + d.endhostEndPort = 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 @@ -1569,7 +1577,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") @@ -1965,14 +1973,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. @@ -1988,14 +2000,155 @@ func (d *DataPlane) resolveLocalDst(s slayers.SCION) (*net.UDPAddr, error) { } 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(fmt.Sprintf("SCION/UDP header len too small: %d", + len(lastLayer.LayerPayload()))) + } + port := binary.BigEndian.Uint16(lastLayer.LayerPayload()[2:]) + if port < d.endhostStartPort || port > d.endhostEndPort { + 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.endhostStartPort || port > d.endhostEndPort { + port = topology.EndhostPort + } + return &net.UDPAddr{IP: dst, Port: int(port)}, nil + default: + log.Debug(fmt.Sprintf("Port rewriting not supported for protcol number %v", l4Type)) + return &net.UDPAddr{IP: dst, Port: topology.EndhostPort}, nil + } +} + +func getDstPortSCMP(scmp *slayers.SCMP) (uint16, error) { + 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 1b51607f47..58013113ee 100644 --- a/router/dataplane_internal_test.go +++ b/router/dataplane_internal_test.go @@ -42,7 +42,11 @@ import ( "github.com/scionproto/scion/router/mock_router" ) -var testKey = []byte("testkey_xxxxxxxx") +var ( + testKey = []byte("testkey_xxxxxxxx") + EndhostStartPort = 1024 + endhostEndPort = 1<<16 - 1 +) // TestReceiver sets up a mocked batchConn, starts the receiver that reads from // this batchConn and forwards it to the processing routines channels. We verify @@ -440,8 +444,9 @@ 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, + uint16(EndhostStartPort), uint16(endhostEndPort), + testKey) }, mockMsg: func() []byte { spkt := prepBaseMsg(t, payload, 0) @@ -465,7 +470,9 @@ 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, + uint16(EndhostStartPort), uint16(endhostEndPort), + testKey) }, mockMsg: func() []byte { spkt := prepBaseMsg(t, payload, 0) @@ -489,7 +496,9 @@ 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, + uint16(EndhostStartPort), uint16(endhostEndPort), + testKey) }, mockMsg: func() []byte { spkt := prepBaseMsg(t, payload, 0) diff --git a/router/dataplane_test.go b/router/dataplane_test.go index baf163fb53..f99a7abbda 100644 --- a/router/dataplane_test.go +++ b/router/dataplane_test.go @@ -50,7 +50,13 @@ import ( "github.com/scionproto/scion/router/mock_router" ) -var metrics = router.GetMetrics() +var ( + metrics = router.GetMetrics() + srcUDPPort = 50001 + dstUDPPort = 50002 + EndhostStartPort = 1024 + endhostEndPort = 1<<16 - 1 +) func TestDataPlaneAddInternalInterface(t *testing.T) { internalIP := net.ParseIP("198.51.100.1") @@ -595,7 +601,8 @@ func TestProcessPkt(t *testing.T) { return router.NewDP(fakeExternalInterfaces, nil, mock_router.NewMockBatchConn(ctrl), fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -611,7 +618,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 @@ -631,7 +638,8 @@ func TestProcessPkt(t *testing.T) { }, nil, fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -670,7 +678,8 @@ func TestProcessPkt(t *testing.T) { }, nil, fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -707,7 +716,8 @@ func TestProcessPkt(t *testing.T) { 1: topology.Child, }, nil, fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -746,7 +756,8 @@ func TestProcessPkt(t *testing.T) { }, nil, fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { // Story: the packet just left segment 0 which ends at @@ -820,7 +831,8 @@ func TestProcessPkt(t *testing.T) { }, nil, fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { // Story: the packet lands on the last (peering) hop of @@ -902,7 +914,8 @@ func TestProcessPkt(t *testing.T) { }, nil, fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { // Story: the packet just left hop 1 (the first hop @@ -980,7 +993,8 @@ func TestProcessPkt(t *testing.T) { }, nil, fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { // Story: the packet lands on the second (non-peering) hop of @@ -1071,7 +1085,8 @@ func TestProcessPkt(t *testing.T) { mock_router.NewMockBatchConn(ctrl), map[uint16]*net.UDPAddr{ uint16(3): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, - }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -1105,7 +1120,8 @@ func TestProcessPkt(t *testing.T) { mock_router.NewMockBatchConn(ctrl), map[uint16]*net.UDPAddr{ uint16(3): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, - }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, _ := prepBaseMsg(now) @@ -1157,11 +1173,12 @@ func TestProcessPkt(t *testing.T) { addr.SvcCS: { &net.UDPAddr{ IP: net.ParseIP("10.0.200.200").To4(), - Port: topology.EndhostPort, + Port: dstUDPPort, }, }, }, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -1177,7 +1194,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 @@ -1195,13 +1212,13 @@ 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"), map[uint16]addr.IA{ uint16(1): xtest.MustParseIA("1-ff00:0:111"), - }, key) + }, uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, _ := prepBaseMsg(now) @@ -1240,7 +1257,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 @@ -1258,7 +1275,7 @@ func TestProcessPkt(t *testing.T) { xtest.MustParseIA("1-ff00:0:110"), map[uint16]addr.IA{ uint16(1): xtest.MustParseIA("1-ff00:0:111"), - }, key) + }, uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, _ := prepBaseMsg(now) @@ -1300,10 +1317,11 @@ 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) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, _ := prepBaseMsg(now) @@ -1361,7 +1379,7 @@ func TestProcessPkt(t *testing.T) { xtest.MustParseIA("1-ff00:0:110"), map[uint16]addr.IA{ uint16(2): xtest.MustParseIA("1-ff00:0:111"), - }, key) + }, uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, _ := prepBaseMsg(now) @@ -1401,7 +1419,8 @@ func TestProcessPkt(t *testing.T) { return router.NewDP(fakeExternalInterfaces, nil, mock_router.NewMockBatchConn(ctrl), fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, epicpath, dpath := prepEpicMsg(t, @@ -1419,7 +1438,8 @@ func TestProcessPkt(t *testing.T) { return router.NewDP(fakeExternalInterfaces, nil, mock_router.NewMockBatchConn(ctrl), fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, epicpath, dpath := prepEpicMsg(t, @@ -1438,7 +1458,8 @@ func TestProcessPkt(t *testing.T) { return router.NewDP(fakeExternalInterfaces, nil, mock_router.NewMockBatchConn(ctrl), fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, epicpath, dpath := prepEpicMsg(t, @@ -1459,7 +1480,8 @@ func TestProcessPkt(t *testing.T) { return router.NewDP(fakeExternalInterfaces, nil, mock_router.NewMockBatchConn(ctrl), fakeInternalNextHops, nil, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, + uint16(EndhostStartPort), uint16(endhostEndPort), key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, epicpath, dpath := prepEpicMsg(t, @@ -1506,9 +1528,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) @@ -1529,7 +1555,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{ @@ -1607,7 +1633,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..c2d8fcbfd4 100644 --- a/router/export_test.go +++ b/router/export_test.go @@ -47,6 +47,8 @@ func NewDP( svc map[addr.SVC][]*net.UDPAddr, local addr.IA, neighbors map[uint16]addr.IA, + endhostStartPort uint16, + endhostEndPort uint16, key []byte) *DataPlane { dp := &DataPlane{ @@ -55,6 +57,8 @@ func NewDP( linkTypes: linkTypes, neighborIAs: neighbors, internalNextHops: internalNextHops, + endhostStartPort: endhostStartPort, + endhostEndPort: endhostEndPort, svc: &services{m: svc}, internal: internal, internalIP: netip.MustParseAddr("198.51.100.1"), 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..bf6fedd5be 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,9 +737,10 @@ func (r *renewer) requestRemote( } sn := &snet.SCIONNetwork{ - LocalIA: local.IA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(r.Disatcher), + LocalIA: local.IA, + CPInfoProvider: r.Daemon, + Connector: &snet.DefaultConnector{ + CPInfoProvider: r.Daemon, SCMPHandler: snet.SCMPPropagationStopper{ Handler: snet.DefaultSCMPHandler{ RevocationHandler: daemon.RevHandler{Connector: r.Daemon}, @@ -754,7 +750,7 @@ func (r *renewer) requestRemote( }, } - 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 +763,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, + Connector: sn.Connector, + 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..e15f327c74 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,13 +261,13 @@ On other errors, ping will exit with code 2. } stats, err := ping.Run(ctx, ping.Config{ - Dispatcher: reliable.NewDispatcher(dispatcher), - Attempts: count, - Interval: flags.interval, - Timeout: flags.timeout, - Local: local, - Remote: remote, - PayloadSize: pldSize, + CPInfoProvider: sd, + Attempts: count, + Interval: flags.interval, + Timeout: flags.timeout, + Local: local, + Remote: remote, + PayloadSize: pldSize, ErrHandler: func(err error) { fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) }, 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..dd6cf3f6e5 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,14 +181,14 @@ On other errors, traceroute will exit with code 2. var stats traceroute.Stats var updates []traceroute.Update cfg := traceroute.Config{ - Dispatcher: reliable.NewDispatcher(dispatcher), - Remote: remote, - MTU: path.Metadata().MTU, - Local: local, - PathEntry: path, - Timeout: flags.timeout, - ProbesPerHop: 3, - ErrHandler: func(err error) { fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) }, + CPInfoProvider: sd, + Remote: remote, + MTU: path.Metadata().MTU, + Local: local, + PathEntry: path, + Timeout: flags.timeout, + ProbesPerHop: 3, + ErrHandler: func(err error) { fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) }, UpdateHandler: func(u traceroute.Update) { updates = append(updates, u) printf("%d %s %s\n", u.Index, fmtRemote(u.Remote, u.Interface), 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 4efea528d5..1476fde84e 100644 --- a/scion/ping/ping.go +++ b/scion/ping/ping.go @@ -18,17 +18,14 @@ package ping import ( "context" "encoding/binary" - "math/rand" "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/serrors" "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/topology/underlay" ) @@ -75,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 + + // CPInfoProvider is the helper class to get control-plane information for the + // local AS. + CPInfoProvider snet.CPInfoProvider // Attempts is the number of pings to send. Attempts uint16 @@ -103,23 +103,20 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { return Stats{}, serrors.New("interval below millisecond") } - id := rand.Uint64() replies := make(chan reply, 10) - - svc := snet.DefaultPacketDispatcherService{ - Dispatcher: cfg.Dispatcher, + svc := snet.DefaultConnector{ SCMPHandler: scmpHandler{ - id: uint16(id), replies: replies, }, + CPInfoProvider: cfg.CPInfoProvider, } - conn, port, err := svc.Register(ctx, cfg.Local.IA, cfg.Local.Host, addr.SvcNone) + conn, err := svc.OpenUDP(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 need to have at least 8 bytes to store the request time in the // payload. @@ -132,7 +129,7 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { timeout: cfg.Timeout, pldSize: cfg.PayloadSize, pld: make([]byte, cfg.PayloadSize), - id: id, + id: uint64(local.Host.Port), conn: conn, local: local, replies: replies, @@ -305,7 +302,6 @@ type reply struct { } type scmpHandler struct { - id uint16 replies chan<- reply } @@ -339,9 +335,5 @@ func (h scmpHandler) handle(pkt *snet.Packet) (snet.SCMPEchoReply, error) { ) } r := pkt.Payload.(snet.SCMPEchoReply) - if r.Identifier != h.id { - return snet.SCMPEchoReply{}, serrors.New("wrong SCMP ID", - "expected", h.id, "actual", r.Identifier) - } return r, nil } 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 37e328fd31..787b4d9f0b 100644 --- a/scion/showpaths/showpaths.go +++ b/scion/showpaths/showpaths.go @@ -19,7 +19,6 @@ import ( "fmt" "io" "math" - "math/rand" "net/netip" "strconv" "strings" @@ -353,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: uint16(rand.Uint32()), - Dispatcher: cfg.Dispatcher, + DstIA: dst, + LocalIA: localIA, + LocalIP: cfg.Local, + CPInfoProvider: 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 7ac6a6cb1a..4fc724b697 100644 --- a/scion/traceroute/traceroute.go +++ b/scion/traceroute/traceroute.go @@ -17,7 +17,6 @@ package traceroute import ( "context" - "math/rand" "net" "net/netip" "time" @@ -29,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. @@ -57,14 +55,14 @@ type Stats struct { // Config configures the traceroute run. type Config struct { - Dispatcher reliable.Dispatcher - Local *snet.UDPAddr - MTU uint16 - PathEntry snet.Path - PayloadSize uint - Remote *snet.UDPAddr - Timeout time.Duration - EPIC bool + Local *snet.UDPAddr + CPInfoProvider snet.CPInfoProvider + MTU uint16 + PathEntry snet.Path + PayloadSize uint + Remote *snet.UDPAddr + Timeout time.Duration + EPIC bool // ProbesPerHop indicates how many probes should be done per hop. ProbesPerHop int @@ -100,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 := rand.Uint64() replies := make(chan reply, 10) - dispatcher := snet.DefaultPacketDispatcherService{ - Dispatcher: cfg.Dispatcher, - SCMPHandler: scmpHandler{replies: replies}, + connector := &snet.DefaultConnector{ + SCMPHandler: scmpHandler{replies: replies}, + CPInfoProvider: cfg.CPInfoProvider, } - conn, port, err := dispatcher.Register(ctx, cfg.Local.IA, cfg.Local.Host, addr.SvcNone) + conn, err := connector.OpenUDP(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, @@ -121,7 +118,7 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { replies: replies, errHandler: cfg.ErrHandler, updateHandler: cfg.UpdateHandler, - id: uint16(id), + id: uint16(conn.LocalAddr().(*net.UDPAddr).Port), path: cfg.PathEntry, epic: cfg.EPIC, } diff --git a/tools/braccept/BUILD.bazel b/tools/braccept/BUILD.bazel index 972054c646..1cd82cfb95 100644 --- a/tools/braccept/BUILD.bazel +++ b/tools/braccept/BUILD.bazel @@ -7,10 +7,12 @@ go_library( importpath = "github.com/scionproto/scion/tools/braccept", visibility = ["//visibility:private"], deps = [ + "//pkg/addr:go_default_library", "//pkg/log:go_default_library", "//pkg/scrypto:go_default_library", "//pkg/slayers:go_default_library", "//private/keyconf:go_default_library", + "//private/topology:go_default_library", "//tools/braccept/cases:go_default_library", "//tools/braccept/runner:go_default_library", "@com_github_google_gopacket//layers:go_default_library", diff --git a/tools/braccept/cases/child_to_internal.go b/tools/braccept/cases/child_to_internal.go index 88d5fcdcb9..901fa3856e 100644 --- a/tools/braccept/cases/child_to_internal.go +++ b/tools/braccept/cases/child_to_internal.go @@ -33,7 +33,12 @@ 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, + endHostPort int, +) runner.Case { + options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -106,7 +111,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 +132,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 +153,12 @@ 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, + endHostPort int, +) runner.Case { + options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -213,7 +222,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 +243,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..f8ce086b17 100644 --- a/tools/braccept/cases/onehop.go +++ b/tools/braccept/cases/onehop.go @@ -34,7 +34,12 @@ 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, + endHostPort int, +) runner.Case { + options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -88,7 +93,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 +112,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..ae167ff013 100644 --- a/tools/braccept/cases/parent_to_internal.go +++ b/tools/braccept/cases/parent_to_internal.go @@ -33,7 +33,12 @@ 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, + endhostPort int, +) runner.Case { + options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -101,7 +106,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 +125,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 +145,12 @@ 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, + endHostPort int, +) runner.Case { + options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -215,7 +225,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 +244,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..1fb3619cb3 100644 --- a/tools/braccept/cases/svc.go +++ b/tools/braccept/cases/svc.go @@ -33,7 +33,7 @@ import ( ) // SVC tests resolution of SVC addresses. -func SVC(artifactsDir string, mac hash.Hash) runner.Case { +func SVC(artifactsDir string, mac hash.Hash, svcResolverPort int) runner.Case { options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -103,7 +103,7 @@ func SVC(artifactsDir string, mac hash.Hash) runner.Case { } scionudp := &slayers.UDP{} scionudp.SrcPort = 2345 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(svcResolverPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -125,8 +125,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(svcResolverPort) 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 e36d9de30d..254e42408b 100644 --- a/tools/braccept/main.go +++ b/tools/braccept/main.go @@ -18,23 +18,27 @@ import ( "flag" "fmt" "hash" + "net" "os" "path/filepath" "github.com/google/gopacket/layers" + "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/scrypto" "github.com/scionproto/scion/pkg/slayers" "github.com/scionproto/scion/private/keyconf" + "github.com/scionproto/scion/private/topology" "github.com/scionproto/scion/tools/braccept/cases" "github.com/scionproto/scion/tools/braccept/runner" ) var ( - bfd = flag.Bool("bfd", false, "Run BFD tests instead of the common ones") - logConsole = flag.String("log.console", "debug", "Console logging level: debug|info|error") - dir = flag.String("artifacts", "", "Artifacts directory") + bfd = flag.Bool("bfd", false, "Run BFD tests instead of the common ones") + logConsole = flag.String("log.console", "debug", "Console logging level: debug|info|error") + dir = flag.String("artifacts", "", "Artifacts directory") + endHostPort = 21000 ) func main() { @@ -68,6 +72,12 @@ func realMain() int { return 1 } + csAddr, err := loadCSPort(artifactsDir) + if err != nil { + fmt.Fprintf(os.Stderr, "Loading topo failed: %v\n", err) + return 1 + } + rc, err := runner.NewRunConfig() if err != nil { fmt.Fprintf(os.Stderr, "Loading devices failed: %v\n", err) @@ -80,12 +90,12 @@ func realMain() int { multi := []runner.Case{ cases.ParentToChild(artifactsDir, hfMAC), - cases.ParentToInternalHost(artifactsDir, hfMAC), - cases.ParentToInternalHostMultiSegment(artifactsDir, hfMAC), + cases.ParentToInternalHost(artifactsDir, hfMAC, endHostPort), + cases.ParentToInternalHostMultiSegment(artifactsDir, hfMAC, endHostPort), cases.ChildToParent(artifactsDir, hfMAC), cases.ChildToChildXover(artifactsDir, hfMAC), - cases.ChildToInternalHost(artifactsDir, hfMAC), - cases.ChildToInternalHostShortcut(artifactsDir, hfMAC), + cases.ChildToInternalHost(artifactsDir, hfMAC, endHostPort), + cases.ChildToInternalHostShortcut(artifactsDir, hfMAC, endHostPort), cases.ChildToInternalParent(artifactsDir, hfMAC), cases.InternalHostToChild(artifactsDir, hfMAC), cases.InternalParentToChild(artifactsDir, hfMAC), @@ -123,9 +133,9 @@ func realMain() int { cases.SCMPInvalidSrcIAChildToParent(artifactsDir, hfMAC), cases.SCMPInvalidDstIAChildToParent(artifactsDir, hfMAC), cases.NoSCMPReplyForSCMPError(artifactsDir, hfMAC), - cases.IncomingOneHop(artifactsDir, hfMAC), + cases.IncomingOneHop(artifactsDir, hfMAC, endHostPort), cases.OutgoingOneHop(artifactsDir, hfMAC), - cases.SVC(artifactsDir, hfMAC), + cases.SVC(artifactsDir, hfMAC, csAddr.Port), cases.JumboPacket(artifactsDir, hfMAC), cases.ChildToPeer(artifactsDir, hfMAC), cases.PeerToChild(artifactsDir, hfMAC), @@ -163,10 +173,23 @@ func loadKey(artifactsDir string) (hash.Hash, error) { return macGen(), nil } +func loadCSPort(artifactsDir string) (*net.UDPAddr, error) { + topoPath := filepath.Join(artifactsDir, "conf", "topology.json") + topo, err := topology.FromJSONFile(topoPath) + if err != nil { + return nil, err + } + csAddr := topo.PublicAddress(addr.SvcCS, "csA") + if csAddr == nil { + return topo.Anycast(addr.SvcCS) + } + return csAddr, nil +} + // 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..3b1e2c7991 100644 --- a/tools/end2end/BUILD.bazel +++ b/tools/end2end/BUILD.bazel @@ -14,10 +14,9 @@ go_library( "//pkg/private/serrors:go_default_library", "//pkg/private/util:go_default_library", "//pkg/snet:go_default_library", + "//pkg/snet/addrutil: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..2d4d629b5a 100644 --- a/tools/end2end/main.go +++ b/tools/end2end/main.go @@ -43,10 +43,9 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/private/util" "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/addrutil" "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,26 +136,26 @@ func (s server) run() { sdConn := integration.SDConn() defer sdConn.Close() - connFactory := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), + connector := &snet.DefaultConnector{ SCMPHandler: snet.DefaultSCMPHandler{ RevocationHandler: daemon.RevHandler{Connector: sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + Metrics: scionPacketConnMetrics, + CPInfoProvider: sdConn, } - conn, port, err := connFactory.Register(context.Background(), integration.Local.IA, - integration.Local.Host, addr.SvcNone) + conn, err := connector.OpenUDP(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 { @@ -214,7 +213,7 @@ func (s server) handlePing(conn snet.PacketConn) error { "data", pld, )) } - log.Info(fmt.Sprintf("Ping received from %s, sending pong.", p.Source)) + log.Info(fmt.Sprintf("Ping received from %s:%d, sending pong.", p.Source, udp.SrcPort)) raw, err := json.Marshal(Pong{ Client: p.Source.IA, Server: integration.Local.IA, @@ -251,9 +250,9 @@ func (s server) handlePing(conn snet.PacketConn) error { } type client struct { - conn snet.PacketConn - port uint16 - sdConn daemon.Connector + conn snet.PacketConn + sdConn daemon.Connector + errorPaths map[snet.PathFingerprint]struct{} } @@ -262,25 +261,26 @@ 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() + + connector := &snet.DefaultConnector{ SCMPHandler: snet.DefaultSCMPHandler{ - RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()}, + RevocationHandler: daemon.RevHandler{Connector: c.sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + Metrics: scionPacketConnMetrics, + CPInfoProvider: 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 = connector.OpenUDP(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)) - c.sdConn = integration.SDConn() - defer c.sdConn.Close() + fmt.Sprintf("%v,[%v]:%d", integration.Local.IA, integration.Local.Host.IP, port)) c.errorPaths = make(map[snet.PathFingerprint]struct{}) return integration.AttemptRepeatedly("End2End", c.attemptRequest) } @@ -340,7 +340,7 @@ func (c *client) ping(ctx context.Context, n int, path snet.Path) error { if remote.NextHop == nil { remote.NextHop = &net.UDPAddr{ IP: remote.Host.IP, - Port: topology.EndhostPort, + Port: remote.Host.Port, } } @@ -352,6 +352,16 @@ func (c *client) ping(ctx context.Context, n int, path snet.Path) error { if !ok { return serrors.New("invalid local host IP", "ip", integration.Local.Host.IP) } + if localHostIP.Unmap().IsUnspecified() { + resolvedLocal, err := addrutil.ResolveLocal(remote.Host.IP) + if err != nil { + return err + } + localHostIP, ok = netip.AddrFromSlice(resolvedLocal) + if !ok { + return serrors.New("invalid resolved local addr", "ip", resolvedLocal) + } + } pkt := &snet.Packet{ PacketInfo: snet.PacketInfo{ Destination: snet.SCIONAddress{ @@ -364,7 +374,7 @@ func (c *client) ping(ctx context.Context, n int, path snet.Path) error { }, Path: remote.Path, Payload: snet.UDPPayload{ - SrcPort: c.port, + SrcPort: uint16(c.conn.LocalAddr().(*net.UDPAddr).Port), DstPort: uint16(remote.Host.Port), Payload: rawPing, }, 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..84837161e2 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(""), + connector := &snet.DefaultConnector{ SCMPHandler: snet.DefaultSCMPHandler{ RevocationHandler: daemon.RevHandler{Connector: sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + Metrics: scionPacketConnMetrics, + CPInfoProvider: sdConn, } - - conn, port, err := connFactory.Register(context.Background(), integration.Local.IA, - integration.Local.Host, addr.SvcNone) + conn, err := connector.OpenUDP(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(""), + connector := &snet.DefaultConnector{ SCMPHandler: snet.DefaultSCMPHandler{ - RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()}, + RevocationHandler: daemon.RevHandler{Connector: c.sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + Metrics: scionPacketConnMetrics, + CPInfoProvider: 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 = connector.OpenUDP(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/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..d13a5b383b 100644 --- a/tools/topology/config.py +++ b/tools/topology/config.py @@ -31,6 +31,7 @@ DEFAULT_MTU, DEFAULT6_NETWORK, NETWORKS_FILE, + DEFAULT_ENDHOST_PORT_RANGE, ) 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.endhost_port_range = defaults.get("endhost_port_range", DEFAULT_ENDHOST_PORT_RANGE) 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.endhost_port_range) 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..86b0f080f3 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_ENDHOST_PORT_RANGE = "1024-65535" + #: 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 6b49df2e4c..54388d9268 100644 --- a/tools/topology/docker.py +++ b/tools/topology/docker.py @@ -183,8 +183,8 @@ def _control_service_conf(self, topo_id, topo, base): self.user, 'volumes': [ self._cache_vol(), + self._certs_vol(), '%s:/etc/scion:ro' % base, - self._disp_vol(k), ], 'command': ['--config', '/etc/scion/%s.toml' % k] } @@ -198,14 +198,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 @@ -217,7 +213,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'] = [ @@ -225,8 +220,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) @@ -244,7 +237,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 ], @@ -257,8 +249,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..723201ec67 100644 --- a/tools/topology/docker_utils.py +++ b/tools/topology/docker_utils.py @@ -49,49 +49,41 @@ 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], + 'container_name': 'tester_%s' % topo_id.file_fmt(), '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..ba8cb8d8b0 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,7 +194,8 @@ 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, }, @@ -209,6 +208,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..1eba6ac1c6 100644 --- a/tools/topology/net.py +++ b/tools/topology/net.py @@ -176,6 +176,8 @@ def _exclude_net(self, alloc, net): class PortGenerator(object): + # TODO(JordiSubira): It probably makes more sense to also feed the port + # range for CP services, so that it is within endhost_port_range def __init__(self): self.iter = iter(range(31000, 35000)) self._ports = defaultdict(lambda: next(self.iter)) diff --git a/tools/topology/sig.py b/tools/topology/sig.py index f307a76673..55e9b2b380 100644 --- a/tools/topology/sig.py +++ b/tools/topology/sig.py @@ -71,16 +71,10 @@ def _dispatcher_conf(self, topo_id, base): entry = { 'image': '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() @@ -127,7 +119,6 @@ def _sig_dc_conf(self, topo_id, base): # but on the RHEL machines in in CI it simply doesn't. Needs to be investigated & fixed. # 'user': self.user, 'volumes': [ - self._disp_vol(topo_id), '/dev/net/tun:/dev/net/tun', '%s:/etc/scion' % base, ], @@ -188,6 +179,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 298605bdc8..3a64f181b8 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, + endhost_port_range: 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.endhost_port_range = endhost_port_range self.port_gen = PortGenerator() @@ -242,6 +244,8 @@ def _generate_as_topo(self, topo_id, as_conf): 'attributes': attributes, 'isd_as': str(topo_id), 'mtu': mtu, + 'test_dispatcher': as_conf.get('test_dispatcher', True), + 'endhost_port_range': as_conf.get('endhost_port_range', self.args.endhost_port_range), } for i in SCION_SERVICE_NAMES: self.topo_dicts[topo_id][i] = {}