From 6b820ef10c7f6457b3ea8de06d5b13ffab4f3202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Subir=C3=A0=20Nieto?= Date: Tue, 21 Mar 2023 13:45:22 +0100 Subject: [PATCH] change last-mile router port forwarding removing dispatcher from snet and infra libraries intermediate commit remove dispatcher fix dataplane port parsing adapt end2end braccept UTs integration no probe add port range fix port range remove ref to dispatcher & reliable socket fix epic failing test remove dispatcher and reliable: - still integration test failing due to lack of support for SCMP handling and more comments, leftovers, small fixes more minor after rebasing pass add stateless dispatcher intermediate commit - Still things missing, e.g., update topology to include stateless dispatcher update topology with reduced dispatcher config add error handling and debug verbose to endHost resolution in BR add reduced forwarding dispatcher integration and utils integration tests lint + chown container fix docker check, only for linux dev lint modify HP and tests lint fix broken rebase lint pass change dispatcher configuration bugfix: QUIC address for client with :0 port slayers: unmap IPv4-mapped IPv6 addresses (#4377) The Go standard library can produce IPv4-mapped IPv6 addresses when resolving IP addresses. These IP addresses need to be unmapped before putting them on the wire. Before this patch, we could observe the following with tshark: Len=1304 SCION 1-ff00:0:110,[::ffff:172.20.2.2] -> 1-ff00:0:111,[::ffff:172.20.3.2] UDP 32769 -> 32768 1208 The regression was introduced in #4346, which removed the unmapping behavior in slayers.PackAddr. This patch restores the behavior to ensure only unmapped IPv4 addresses make it on the wire. Handling the unmapping in the code that generates the addresses and only checking this in slayers would seem ideal, but these calls are often very far away from the place that would then trigger an error. Thus handling this in slayers seems like a compromise that saves us and the users of the slayers package a lot of trouble. add packet reflection safeguard in shim add compatible IP_PKTINFO code for windows fix validateNextHopAddr fix isSCMPInfo add endhost port range configuration port range comment udpportRange add fixme allow unspecified address fon SCIONNetwork.Listen retriving nextHop from path and local Interface information add dispatching logic for SCMP at BR remove dispatcher shim support for Windows remove br dispatcher configuration from integration tests add test for old and new br configuration with(out) shim dispatcher comments and minor fixes remove utils_chown container remove docker utils from script pass pass pass refactor topology endhost_port_range comment for router fix dispatcherless docker and integration tests ignore SCMP errors messages on initSvcRedirect() adapt HP test adapt integration tests error string HP control/main dispatcher pass return addresses in helper function by value --- .golangcilint.yml | 5 - acceptance/hidden_paths/test.py | 35 +- acceptance/old_new_br/BUILD.bazel | 9 + acceptance/old_new_br/test.py | 56 + acceptance/old_new_br/testdata/BUILD.bazel | 3 + acceptance/old_new_br/testdata/topology.topo | 15 + .../router_benchmark/conf/topology.json | 1 + acceptance/router_multi/conf/topology.json | 1 + acceptance/sig_short_exp_time/BUILD.bazel | 24 - .../sig_short_exp_time/docker-compose.yml | 24 +- acceptance/sig_short_exp_time/test | 2 +- .../1-ff00_0_110/dispatcher/disp.toml | 2 +- .../1-ff00_0_111/dispatcher/disp.toml | 2 +- acceptance/topo_common/topology.json | 1 + acceptance/topo_cs_reload/BUILD.bazel | 12 - acceptance/topo_cs_reload/docker-compose.yml | 18 +- acceptance/topo_cs_reload/reload_test.go | 2 - acceptance/topo_cs_reload/testdata/cs.toml | 1 - acceptance/topo_cs_reload/testdata/disp.toml | 5 - acceptance/topo_cs_reload/testdata/sd.toml | 1 - .../testdata/topology_reload.json | 1 + acceptance/topo_daemon_reload/BUILD.bazel | 12 - .../topo_daemon_reload/docker-compose.yml | 16 +- acceptance/topo_daemon_reload/reload_test.go | 8 +- .../topo_daemon_reload/testdata/disp.toml | 5 - .../topo_daemon_reload/testdata/sd.toml | 1 - .../testdata/topology_reload.json | 1 + acceptance/trc_update/test.py | 2 +- control/beaconing/testdata/topology-core.json | 1 + control/beaconing/testdata/topology.json | 1 + control/cmd/control/main.go | 80 +- control/hiddenpaths.go | 71 +- daemon/daemon.go | 7 +- daemon/internal/servers/grpc.go | 13 +- dispatcher/BUILD.bazel | 23 +- dispatcher/cmd/dispatcher/BUILD.bazel | 95 +- dispatcher/cmd/dispatcher/main.go | 52 +- dispatcher/cmd/dispatcher/main_test.go | 487 --------- dispatcher/config/BUILD.bazel | 4 +- dispatcher/config/config.go | 104 +- dispatcher/config/config_test.go | 6 +- dispatcher/config/sample.go | 9 - dispatcher/dispatcher.go | 634 ++++++++---- dispatcher/dispatcher_test.go | 344 +++++++ dispatcher/internal/metrics/BUILD.bazel | 12 - dispatcher/internal/metrics/metrics.go | 225 ---- dispatcher/internal/registration/BUILD.bazel | 43 - .../internal/registration/bench_test.go | 109 -- dispatcher/internal/registration/errors.go | 29 - .../internal/registration/generators_test.go | 59 -- dispatcher/internal/registration/iatable.go | 213 ---- .../internal/registration/iatable_test.go | 172 ---- dispatcher/internal/registration/portlist.go | 87 -- .../internal/registration/portlist_test.go | 67 -- .../internal/registration/scmp_table.go | 50 - .../internal/registration/scmp_table_test.go | 67 -- dispatcher/internal/registration/svctable.go | 267 ----- .../internal/registration/svctable_test.go | 473 --------- dispatcher/internal/registration/table.go | 142 --- .../internal/registration/table_test.go | 228 ----- dispatcher/internal/registration/udptable.go | 215 ---- .../internal/registration/udptable_test.go | 352 ------- dispatcher/internal/respool/BUILD.bazel | 34 - dispatcher/internal/respool/buffer.go | 42 - dispatcher/internal/respool/packet.go | 184 ---- dispatcher/internal/respool/packet_test.go | 137 --- dispatcher/network/BUILD.bazel | 20 - dispatcher/network/app_socket.go | 223 ---- dispatcher/network/dispatcher.go | 64 -- dispatcher/table.go | 71 -- dispatcher/underlay.go | 409 -------- dispatcher/underlay_test.go | 957 ------------------ dist/conffiles/daemon.toml | 1 - dist/conffiles/dispatcher.toml | 6 +- dist/systemd/scion-dispatcher.service | 1 - dist/test/deb_test.sh | 1 + gateway/BUILD.bazel | 2 - gateway/cmd/gateway/BUILD.bazel | 1 - gateway/cmd/gateway/main.go | 2 - gateway/control/grpc/BUILD.bazel | 1 - gateway/control/grpc/probeserver.go | 4 - gateway/dataplane/BUILD.bazel | 1 - gateway/dataplane/ingressserver.go | 4 - gateway/gateway.go | 80 +- gateway/pathhealth/pathwatcher.go | 54 +- gateway/pathhealth/remotewatcher.go | 30 +- nogo.json | 10 +- pkg/daemon/apitypes.go | 6 +- pkg/daemon/daemon.go | 10 +- pkg/daemon/grpc.go | 56 +- pkg/daemon/mock_daemon/BUILD.bazel | 1 - pkg/daemon/mock_daemon/mock.go | 31 +- pkg/experimental/hiddenpath/BUILD.bazel | 1 + pkg/experimental/hiddenpath/discovery.go | 27 + pkg/experimental/hiddenpath/forwarder.go | 22 +- pkg/experimental/hiddenpath/forwarder_test.go | 13 +- .../hiddenpath/testdata/topology.json | 1 + pkg/proto/daemon/daemon.pb.go | 373 +++---- pkg/snet/BUILD.bazel | 7 +- pkg/snet/base.go | 9 - pkg/snet/conn.go | 17 +- pkg/snet/interface.go | 8 +- pkg/snet/metrics/metrics.go | 6 +- pkg/snet/mock_snet/BUILD.bazel | 2 +- pkg/snet/mock_snet/mock.go | 87 +- pkg/snet/packet_conn.go | 161 ++- pkg/snet/{dispatcher.go => scmp.go} | 41 +- pkg/snet/snet.go | 163 ++- pkg/snet/writer.go | 18 +- pkg/sock/reliable/BUILD.bazel | 41 - pkg/sock/reliable/errors.go | 54 - pkg/sock/reliable/errors_test.go | 75 -- pkg/sock/reliable/frame.go | 146 --- pkg/sock/reliable/frame_test.go | 193 ---- .../reliable/internal/metrics/BUILD.bazel | 21 - pkg/sock/reliable/internal/metrics/metrics.go | 113 --- .../reliable/internal/metrics/metrics_test.go | 28 - pkg/sock/reliable/mock_reliable/BUILD.bazel | 21 - pkg/sock/reliable/mock_reliable/mock.go | 53 - pkg/sock/reliable/packetizer.go | 117 --- pkg/sock/reliable/packetizer_test.go | 79 -- pkg/sock/reliable/reconnect/BUILD.bazel | 48 - pkg/sock/reliable/reconnect/conn.go | 280 ----- pkg/sock/reliable/reconnect/conn_io_test.go | 306 ------ pkg/sock/reliable/reconnect/doc.go | 17 - pkg/sock/reliable/reconnect/errors.go | 25 - .../reconnect/internal/metrics/BUILD.bazel | 12 - .../reconnect/internal/metrics/metrics.go | 55 - pkg/sock/reliable/reconnect/io.go | 72 -- pkg/sock/reliable/reconnect/main_test.go | 80 -- .../reconnect/mock_reconnect/BUILD.bazel | 21 - .../reliable/reconnect/mock_reconnect/mock.go | 115 --- pkg/sock/reliable/reconnect/network.go | 92 -- pkg/sock/reliable/reconnect/network_test.go | 145 --- pkg/sock/reliable/reconnect/reconnecter.go | 140 --- .../reliable/reconnect/reconnecter_test.go | 98 -- pkg/sock/reliable/reconnect/util.go | 76 -- pkg/sock/reliable/reconnect/util_test.go | 31 - pkg/sock/reliable/registration.go | 220 ---- pkg/sock/reliable/registration_test.go | 279 ----- pkg/sock/reliable/reliable.go | 380 ------- pkg/sock/reliable/util.go | 76 -- private/app/appnet/BUILD.bazel | 2 - private/app/appnet/infraenv.go | 137 +-- private/app/env/env.go | 2 - private/app/env/env_test.go | 21 +- private/app/flag/BUILD.bazel | 2 - private/app/flag/env.go | 39 +- private/app/flag/env_test.go | 73 +- private/app/path/path.go | 8 +- private/app/path/pathprobe/BUILD.bazel | 1 - private/app/path/pathprobe/paths.go | 26 +- private/env/env.go | 3 - private/env/envtest/config.go | 6 +- private/env/logging.go | 15 +- private/env/sample.go | 3 - private/service/statuspages.go | 14 +- private/svc/resolver.go | 9 +- private/svc/resolver_test.go | 26 +- private/svc/svc.go | 89 +- private/svc/svc_test.go | 151 ++- private/topology/interface.go | 8 + private/topology/json/json.go | 9 +- private/topology/json/json_test.go | 11 +- .../testdata/topology-deprecated-attrs.json | 1 + private/topology/json/testdata/topology.json | 1 + private/topology/mock_topology/mock.go | 15 + private/topology/reload.go | 7 + private/topology/testdata/basic.json | 1 + private/topology/testdata/core.json | 1 + private/topology/topology.go | 57 +- private/topology/underlay/defs.go | 2 +- private/underlay/conn/BUILD.bazel | 13 +- private/underlay/conn/conn.go | 5 +- private/underlay/conn/conn_test.go | 79 ++ proto/daemon/v1/daemon.proto | 5 + router/cmd/router/main.go | 8 + router/config/config.go | 34 +- router/connector.go | 13 +- router/control/conf.go | 9 +- router/control/testdata/topology.json | 1 + router/dataplane.go | 165 ++- router/dataplane_internal_test.go | 19 +- router/dataplane_test.go | 84 +- router/export_test.go | 4 + scion-pki/certs/BUILD.bazel | 1 - scion-pki/certs/renew.go | 34 +- scion.sh | 15 - scion/cmd/scion/BUILD.bazel | 1 - scion/cmd/scion/ping.go | 22 +- scion/cmd/scion/showpaths.go | 2 - scion/cmd/scion/traceroute.go | 19 +- scion/ping/BUILD.bazel | 1 - scion/ping/ping.go | 30 +- scion/showpaths/config.go | 3 - scion/showpaths/showpaths.go | 10 +- scion/traceroute/BUILD.bazel | 1 - scion/traceroute/traceroute.go | 31 +- tools/braccept/BUILD.bazel | 2 + tools/braccept/cases/child_to_internal.go | 24 +- tools/braccept/cases/onehop.go | 11 +- tools/braccept/cases/parent_to_internal.go | 22 +- tools/braccept/cases/svc.go | 7 +- tools/braccept/main.go | 43 +- tools/end2end/BUILD.bazel | 3 +- tools/end2end/main.go | 58 +- tools/end2end_integration/main.go | 2 +- tools/end2endblast/BUILD.bazel | 1 - tools/end2endblast/main.go | 31 +- tools/integration/integration.go | 8 +- tools/scion_integration/main.go | 2 +- tools/topology/config.py | 5 +- tools/topology/defines.py | 2 + tools/topology/docker.py | 19 +- tools/topology/docker_utils.py | 28 +- tools/topology/go.py | 29 +- tools/topology/net.py | 2 + tools/topology/sig.py | 12 - tools/topology/topo.py | 6 +- 219 files changed, 2938 insertions(+), 10642 deletions(-) create mode 100644 acceptance/old_new_br/BUILD.bazel create mode 100755 acceptance/old_new_br/test.py create mode 100644 acceptance/old_new_br/testdata/BUILD.bazel create mode 100644 acceptance/old_new_br/testdata/topology.topo delete mode 100644 acceptance/topo_cs_reload/testdata/disp.toml delete mode 100644 acceptance/topo_daemon_reload/testdata/disp.toml delete mode 100644 dispatcher/cmd/dispatcher/main_test.go create mode 100644 dispatcher/dispatcher_test.go delete mode 100644 dispatcher/internal/metrics/BUILD.bazel delete mode 100644 dispatcher/internal/metrics/metrics.go delete mode 100644 dispatcher/internal/registration/BUILD.bazel delete mode 100644 dispatcher/internal/registration/bench_test.go delete mode 100644 dispatcher/internal/registration/errors.go delete mode 100644 dispatcher/internal/registration/generators_test.go delete mode 100644 dispatcher/internal/registration/iatable.go delete mode 100644 dispatcher/internal/registration/iatable_test.go delete mode 100644 dispatcher/internal/registration/portlist.go delete mode 100644 dispatcher/internal/registration/portlist_test.go delete mode 100644 dispatcher/internal/registration/scmp_table.go delete mode 100644 dispatcher/internal/registration/scmp_table_test.go delete mode 100644 dispatcher/internal/registration/svctable.go delete mode 100644 dispatcher/internal/registration/svctable_test.go delete mode 100644 dispatcher/internal/registration/table.go delete mode 100644 dispatcher/internal/registration/table_test.go delete mode 100644 dispatcher/internal/registration/udptable.go delete mode 100644 dispatcher/internal/registration/udptable_test.go delete mode 100644 dispatcher/internal/respool/BUILD.bazel delete mode 100644 dispatcher/internal/respool/buffer.go delete mode 100644 dispatcher/internal/respool/packet.go delete mode 100644 dispatcher/internal/respool/packet_test.go delete mode 100644 dispatcher/network/BUILD.bazel delete mode 100644 dispatcher/network/app_socket.go delete mode 100644 dispatcher/network/dispatcher.go delete mode 100644 dispatcher/table.go delete mode 100644 dispatcher/underlay.go delete mode 100644 dispatcher/underlay_test.go rename pkg/snet/{dispatcher.go => scmp.go} (75%) delete mode 100644 pkg/sock/reliable/BUILD.bazel delete mode 100644 pkg/sock/reliable/errors.go delete mode 100644 pkg/sock/reliable/errors_test.go delete mode 100644 pkg/sock/reliable/frame.go delete mode 100644 pkg/sock/reliable/frame_test.go delete mode 100644 pkg/sock/reliable/internal/metrics/BUILD.bazel delete mode 100644 pkg/sock/reliable/internal/metrics/metrics.go delete mode 100644 pkg/sock/reliable/internal/metrics/metrics_test.go delete mode 100644 pkg/sock/reliable/mock_reliable/BUILD.bazel delete mode 100644 pkg/sock/reliable/mock_reliable/mock.go delete mode 100644 pkg/sock/reliable/packetizer.go delete mode 100644 pkg/sock/reliable/packetizer_test.go delete mode 100644 pkg/sock/reliable/reconnect/BUILD.bazel delete mode 100644 pkg/sock/reliable/reconnect/conn.go delete mode 100644 pkg/sock/reliable/reconnect/conn_io_test.go delete mode 100644 pkg/sock/reliable/reconnect/doc.go delete mode 100644 pkg/sock/reliable/reconnect/errors.go delete mode 100644 pkg/sock/reliable/reconnect/internal/metrics/BUILD.bazel delete mode 100644 pkg/sock/reliable/reconnect/internal/metrics/metrics.go delete mode 100644 pkg/sock/reliable/reconnect/io.go delete mode 100644 pkg/sock/reliable/reconnect/main_test.go delete mode 100644 pkg/sock/reliable/reconnect/mock_reconnect/BUILD.bazel delete mode 100644 pkg/sock/reliable/reconnect/mock_reconnect/mock.go delete mode 100644 pkg/sock/reliable/reconnect/network.go delete mode 100644 pkg/sock/reliable/reconnect/network_test.go delete mode 100644 pkg/sock/reliable/reconnect/reconnecter.go delete mode 100644 pkg/sock/reliable/reconnect/reconnecter_test.go delete mode 100644 pkg/sock/reliable/reconnect/util.go delete mode 100644 pkg/sock/reliable/reconnect/util_test.go delete mode 100644 pkg/sock/reliable/registration.go delete mode 100644 pkg/sock/reliable/registration_test.go delete mode 100644 pkg/sock/reliable/reliable.go delete mode 100644 pkg/sock/reliable/util.go create mode 100644 private/underlay/conn/conn_test.go 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] = {}