From b20fb4d866cc128eac40eb530ee6d39ac4b33cde 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] dispatcher removal 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 fix rebase upgrade dispatcher shim config to toml v2 add PortRange() RPC in daemon Revert "modify HP and tests" This reverts commit 1c82e9c363b9d75554b0f4a55aaeec11e9756af9. remove leftover CSResolver leftover in HP discovery open a single underlay socket for both the QUIC server and the SVC redirector revert acceptance/hiden_paths test await connectivity in old_br acceptance test pass pass pass pass pass + lint pass changes to snet API + refactor pass + allow for using snet outside the defined port range changes in isShimDispatcher() add destination safeguard to snet.scionConnReader.read() add TODOs lint change dispatched_ports name in topo add dispatched_ports all|ALL option range for services in topology PortGenerator dynamic ports refactoring add isDispatcher flag fix clientNet SCMPHandler add default value for shim underlay addr fix dispatcher port + cleaning isShimDispatcher add dstPort check reader remove leftover + TODO revert destination type in ResolverPacketConn replace UnderlayAddr comment comments + TODOs + refactoring add options pattern NewCookedConn improve error message pass fix rebase rename dispatcher flag mocks pass update sig_short_exp_time docker file fix dialer constructor fix docker image references for sig adapt end2end test to use Dial/Listen API remove debug logs add comment for snet.Dial typo --- .golangcilint.yml | 5 - .../app_vs_endhost_br_dispatch/BUILD.bazel | 9 + acceptance/app_vs_endhost_br_dispatch/test.py | 53 + .../testdata/BUILD.bazel | 3 + .../testdata/topology.topo | 15 + acceptance/common/docker.py | 3 +- .../router_benchmark/conf/topology.json | 1 + acceptance/router_multi/conf/topology.json | 1 + acceptance/sig_short_exp_time/BUILD.bazel | 1 - .../sig_short_exp_time/docker-compose.yml | 28 +- acceptance/sig_short_exp_time/test | 2 +- acceptance/topo_common/topology.json | 1 + acceptance/topo_cs_reload/BUILD.bazel | 27 - acceptance/topo_cs_reload/docker-compose.yml | 16 +- acceptance/topo_cs_reload/reload_test.go | 5 - 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 | 27 - .../topo_daemon_reload/docker-compose.yml | 14 +- acceptance/topo_daemon_reload/reload_test.go | 9 +- .../topo_daemon_reload/testdata/disp.toml | 5 - .../topo_daemon_reload/testdata/sd.toml | 1 - .../testdata/topology_reload.json | 1 + acceptance/trc_update/test.py | 1 + buf.yaml | 2 + control/beaconing/testdata/topology-core.json | 1 + control/beaconing/testdata/topology.json | 1 + control/cmd/control/main.go | 42 +- daemon/daemon.go | 7 +- daemon/internal/servers/BUILD.bazel | 1 + daemon/internal/servers/grpc.go | 15 + dispatcher/BUILD.bazel | 23 +- dispatcher/cmd/dispatcher/BUILD.bazel | 99 +- dispatcher/cmd/dispatcher/main.go | 65 +- dispatcher/cmd/dispatcher/main_test.go | 487 --------- dispatcher/config/BUILD.bazel | 6 +- dispatcher/config/config.go | 92 +- dispatcher/config/config_test.go | 10 +- dispatcher/config/sample.go | 24 +- dispatcher/dispatcher.go | 670 ++++++++---- dispatcher/dispatcher_test.go | 390 +++++++ 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/openwrt/test_configs/control.toml | 1 - dist/openwrt/test_configs/topology.json | 1 + 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 | 98 +- gateway/pathhealth/pathwatcher.go | 54 +- gateway/pathhealth/remotewatcher.go | 29 +- nogo.json | 10 +- pkg/daemon/BUILD.bazel | 1 + pkg/daemon/daemon.go | 13 +- pkg/daemon/grpc.go | 53 +- pkg/daemon/mock_daemon/BUILD.bazel | 1 - pkg/daemon/mock_daemon/mock.go | 33 +- .../hiddenpath/testdata/topology.json | 1 + pkg/proto/daemon/daemon.pb.go | 710 +++++++------ pkg/snet/BUILD.bazel | 8 +- pkg/snet/base.go | 46 - pkg/snet/conn.go | 109 +- pkg/snet/interface.go | 9 +- pkg/snet/metrics/metrics.go | 6 +- pkg/snet/mock_snet/BUILD.bazel | 1 - pkg/snet/mock_snet/mock.go | 102 +- pkg/snet/packet_conn.go | 178 +++- pkg/snet/reader.go | 28 +- pkg/snet/{dispatcher.go => scmp.go} | 42 +- pkg/snet/snet.go | 180 ++-- pkg/snet/writer.go | 31 +- 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 | 188 +--- 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/sample.go | 3 - private/svc/resolver.go | 7 +- private/svc/resolver_test.go | 26 +- private/svc/svc.go | 87 +- private/svc/svc_test.go | 179 ++-- private/topology/interface.go | 7 + 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 | 65 +- private/topology/underlay/defs.go | 2 +- proto/daemon/v1/BUILD.bazel | 1 + proto/daemon/v1/daemon.proto | 10 + router/cmd/router/main.go | 9 +- router/config/config.go | 28 +- router/connector.go | 34 +- router/control/conf.go | 12 +- router/control/testdata/topology.json | 1 + router/dataplane.go | 205 +++- router/dataplane_internal_test.go | 7 +- router/dataplane_test.go | 28 +- router/export_test.go | 25 +- scion-pki/certs/BUILD.bazel | 1 - scion-pki/certs/renew.go | 40 +- scion.sh | 15 - scion/cmd/scion/BUILD.bazel | 1 - scion/cmd/scion/ping.go | 10 +- scion/cmd/scion/showpaths.go | 2 - scion/cmd/scion/traceroute.go | 5 +- scion/ping/BUILD.bazel | 1 - scion/ping/ping.go | 40 +- scion/showpaths/config.go | 3 - scion/showpaths/showpaths.go | 9 +- scion/traceroute/BUILD.bazel | 1 - scion/traceroute/traceroute.go | 14 +- tools/braccept/cases/child_to_internal.go | 22 +- tools/braccept/cases/onehop.go | 10 +- tools/braccept/cases/parent_to_internal.go | 20 +- tools/braccept/cases/svc.go | 6 +- tools/braccept/main.go | 2 +- tools/end2end/BUILD.bazel | 2 - tools/end2end/main.go | 188 ++-- tools/end2end_integration/main.go | 2 +- tools/end2endblast/BUILD.bazel | 1 - tools/end2endblast/main.go | 31 +- tools/integration/integration.go | 8 +- tools/integration/integrationlib/common.go | 2 + tools/scion_integration/main.go | 2 +- tools/topology/config.py | 5 +- tools/topology/defines.py | 2 + tools/topology/docker.py | 18 +- tools/topology/docker_utils.py | 27 +- tools/topology/go.py | 30 +- tools/topology/net.py | 5 +- tools/topology/sig.py | 12 - tools/topology/topo.py | 14 +- 213 files changed, 3067 insertions(+), 11037 deletions(-) create mode 100644 acceptance/app_vs_endhost_br_dispatch/BUILD.bazel create mode 100755 acceptance/app_vs_endhost_br_dispatch/test.py create mode 100644 acceptance/app_vs_endhost_br_dispatch/testdata/BUILD.bazel create mode 100644 acceptance/app_vs_endhost_br_dispatch/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 delete mode 100644 pkg/snet/base.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 diff --git a/.golangcilint.yml b/.golangcilint.yml index 32b2a7533f..ba3e6dff79 100644 --- a/.golangcilint.yml +++ b/.golangcilint.yml @@ -63,8 +63,3 @@ issues: - path: pkg/scrypto/cms linters: [goheader] - # Exceptions to errcheck for some old-ish convey tests. - - linters: [errcheck] - path: "^pkg/sock/reliable/reconnect/conn_io_test.go$|\ - ^pkg/sock/reliable/reconnect/network_test.go$|\ - ^pkg/sock/reliable/reconnect/reconnecter_test.go$" diff --git a/acceptance/app_vs_endhost_br_dispatch/BUILD.bazel b/acceptance/app_vs_endhost_br_dispatch/BUILD.bazel new file mode 100644 index 0000000000..d3554771cd --- /dev/null +++ b/acceptance/app_vs_endhost_br_dispatch/BUILD.bazel @@ -0,0 +1,9 @@ +load("//acceptance/common:topogen.bzl", "topogen_test") + +topogen_test( + name = "test", + src = "test.py", + args = ["--executable=end2end_integration:$(location //tools/end2end_integration)"], + data = ["//tools/end2end_integration"], + topo = "//acceptance/app_vs_endhost_br_dispatch/testdata:topology.topo", +) diff --git a/acceptance/app_vs_endhost_br_dispatch/test.py b/acceptance/app_vs_endhost_br_dispatch/test.py new file mode 100755 index 0000000000..602e86f7ae --- /dev/null +++ b/acceptance/app_vs_endhost_br_dispatch/test.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +# Copyright 2023 ETH Zurich + +from acceptance.common import base +from acceptance.common import scion + + +class Test(base.TestTopogen): + """ + Constructs a simple test topology with one core, two leaf ASes. + Each of them will run a different mix between BR that will replicate + the legacy endhost-port-dispatch behaviour (i.e., they will send + traffic to its own AS to the endhost default port) and + application-port-dispatch routers (i.e., they will rewrite the underlay + UDP/IP destination port with the UDP/SCION port). + + AS 1-ff00:0:1 is core. + AS 1-ff00:0:2, 1-ff00:0:3 are leaves. + + We use the shortnames AS1, AS2, etc. for the ASes above. + + AS1 contains a BR with the port rewriting configuration to the default + range. It also includes a shim dispatcher. + AS2 contains a BR with a configuration that imitates the old + behaviour, i.e., sending all traffic to default endhost port 30041. + It also includes a shim dispatcher. + AS3 contains a BR with the port rewriting configuration to the default + range. It does not include the shim dispatcher. + """ + + def setup_prepare(self): + super().setup_prepare() + + br_as_2_id = "br1-ff00_0_2-1" + + br_as_2_file = self.artifacts / "gen" / "ASff00_0_2" \ + / ("%s.toml" % br_as_2_id) + scion.update_toml({"router.dispatched_port_start": 0, + "router.dispatched_port_end": 0}, + [br_as_2_file]) + + def setup_start(self): + super().setup_start() + self.await_connectivity() + + def _run(self): + ping_test = self.get_executable("end2end_integration") + ping_test["-d", "-outDir", self.artifacts].run_fg() + + +if __name__ == "__main__": + base.main(Test) diff --git a/acceptance/app_vs_endhost_br_dispatch/testdata/BUILD.bazel b/acceptance/app_vs_endhost_br_dispatch/testdata/BUILD.bazel new file mode 100644 index 0000000000..d803c4eee5 --- /dev/null +++ b/acceptance/app_vs_endhost_br_dispatch/testdata/BUILD.bazel @@ -0,0 +1,3 @@ +exports_files([ + "topology.topo", +]) diff --git a/acceptance/app_vs_endhost_br_dispatch/testdata/topology.topo b/acceptance/app_vs_endhost_br_dispatch/testdata/topology.topo new file mode 100644 index 0000000000..68b249b07e --- /dev/null +++ b/acceptance/app_vs_endhost_br_dispatch/testdata/topology.topo @@ -0,0 +1,15 @@ +--- # Test Topology +ASes: + "1-ff00:0:1": + core: true + voting: true + authoritative: true + issuing: true + "1-ff00:0:2": + cert_issuer: 1-ff00:0:1 + "1-ff00:0:3": + cert_issuer: 1-ff00:0:1 + test_dispatcher: False +links: + - {a: "1-ff00:0:1#2", b: "1-ff00:0:2#1", linkAtoB: CHILD} + - {a: "1-ff00:0:1#3", b: "1-ff00:0:3#1", linkAtoB: CHILD} diff --git a/acceptance/common/docker.py b/acceptance/common/docker.py index c6f56fb2c8..0605214dae 100644 --- a/acceptance/common/docker.py +++ b/acceptance/common/docker.py @@ -61,8 +61,9 @@ def collect_logs(self, out_dir: str = "logs/docker"): for svc in self("config", "--services").splitlines(): # Collect logs. dst_f = out_p / "%s.log" % svc + print(svc) with open(dst_f, "w") as log_file: - cmd.docker.run(args=("logs", svc), stdout=log_file, + cmd.docker.run(args=("logs", "scion-"+svc+"-1"), stdout=log_file, stderr=subprocess.STDOUT, retcode=None) # Collect coredupms. coredump_f = out_p / "%s.coredump" % svc diff --git a/acceptance/router_benchmark/conf/topology.json b/acceptance/router_benchmark/conf/topology.json index 7e53f64275..6e48ac2cf4 100644 --- a/acceptance/router_benchmark/conf/topology.json +++ b/acceptance/router_benchmark/conf/topology.json @@ -4,6 +4,7 @@ ], "isd_as": "1-ff00:0:1", "mtu": 1400, + "dispatched_ports": "1024-65535", "border_routers": { "br1a": { "internal_addr": "10.123.10.1:30042", diff --git a/acceptance/router_multi/conf/topology.json b/acceptance/router_multi/conf/topology.json index 797f489c2a..c8b137b2c2 100644 --- a/acceptance/router_multi/conf/topology.json +++ b/acceptance/router_multi/conf/topology.json @@ -2,6 +2,7 @@ "isd_as": "1-ff00:0:1", "mtu": 1472, "attributes": [], + "dispatched_ports": "1024-65535", "border_routers": { "brA": { "internal_addr": "192.168.0.11:30001", diff --git a/acceptance/sig_short_exp_time/BUILD.bazel b/acceptance/sig_short_exp_time/BUILD.bazel index 5359d34582..8fa03036d9 100644 --- a/acceptance/sig_short_exp_time/BUILD.bazel +++ b/acceptance/sig_short_exp_time/BUILD.bazel @@ -4,7 +4,6 @@ sh_test( srcs = ["test"], data = [ "docker-compose.yml", - "//docker:dispatcher.tarball", "//docker:gateway.tarball", "//tools/udpproxy:udpproxy.tarball", ] + glob(["testdata/**"]), diff --git a/acceptance/sig_short_exp_time/docker-compose.yml b/acceptance/sig_short_exp_time/docker-compose.yml index d5a6d1869b..3286b77531 100644 --- a/acceptance/sig_short_exp_time/docker-compose.yml +++ b/acceptance/sig_short_exp_time/docker-compose.yml @@ -47,7 +47,6 @@ services: bridge1: ipv4_address: 242.254.100.2 volumes: - - vol_scion_disp_sig1-ff00_0_110:/run/shm/dispatcher:rw - ./testdata/1-ff00_0_110/dispatcher:/etc/scion/ command: [ "--config", "/etc/scion/disp.toml" ] dispatcher2: @@ -56,45 +55,46 @@ services: bridge2: ipv4_address: 242.254.200.2 volumes: - - vol_scion_disp_sig1-ff00_0_111:/run/shm/dispatcher:rw - ./testdata/1-ff00_0_111/dispatcher:/etc/scion/ command: [ "--config", "/etc/scion/disp.toml" ] sig1: cap_add: - NET_ADMIN - depends_on: - - dispatcher1 + container_name: sig1 image: scion/gateway:latest - network_mode: service:dispatcher1 + networks: + bridge1: + ipv4_address: 242.254.100.2 privileged: true volumes: - - vol_scion_disp_sig1-ff00_0_110:/run/shm/dispatcher:rw - /dev/net/tun:/dev/net/tun - ./testdata/1-ff00_0_110/sig:/etc/scion/ command: [ "--config", "/etc/scion/sig.toml" ] sig2: cap_add: - NET_ADMIN - depends_on: - - dispatcher2 + container_name: sig2 image: scion/gateway:latest - network_mode: service:dispatcher2 + networks: + bridge2: + ipv4_address: 242.254.200.2 privileged: true volumes: - - vol_scion_disp_sig1-ff00_0_111:/run/shm/dispatcher:rw - /dev/net/tun:/dev/net/tun - ./testdata/1-ff00_0_111/sig:/etc/scion/ command: [ "--config", "/etc/scion/sig.toml" ] tester1: image: alpine - network_mode: service:dispatcher1 + networks: + bridge1: + ipv4_address: 242.254.100.10 privileged: true tester2: image: alpine - network_mode: service:dispatcher2 + networks: + bridge2: + ipv4_address: 242.254.200.10 privileged: true version: '2.4' volumes: vol_logs: null - vol_scion_disp_sig1-ff00_0_110: null - vol_scion_disp_sig1-ff00_0_111: null diff --git a/acceptance/sig_short_exp_time/test b/acceptance/sig_short_exp_time/test index f258743c30..c19f3d2385 100755 --- a/acceptance/sig_short_exp_time/test +++ b/acceptance/sig_short_exp_time/test @@ -44,7 +44,7 @@ # | | # | +---------------------------------------------+ | # +---+ pathb +---- -# | 242.254.100.3:50000 <-> 242.254.200.4:50000 | +# | 242.254.100.4:50000 <-> 242.254.200.4:50000 | # +---------------------------------------------+ run_test() {(set -e diff --git a/acceptance/topo_common/topology.json b/acceptance/topo_common/topology.json index d3bfefb53b..cd5eaeafb8 100644 --- a/acceptance/topo_common/topology.json +++ b/acceptance/topo_common/topology.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:110", "mtu": 1400, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/acceptance/topo_cs_reload/BUILD.bazel b/acceptance/topo_cs_reload/BUILD.bazel index 53d8eb89e1..411df00ee9 100644 --- a/acceptance/topo_cs_reload/BUILD.bazel +++ b/acceptance/topo_cs_reload/BUILD.bazel @@ -15,7 +15,6 @@ go_test( "docker-compose.yml", "testdata/topology_reload.json", ":control.tar", - ":dispatcher.tar", ":invalid_changed_ip", ":invalid_changed_port", ":testdata/gen_crypto.sh", @@ -36,32 +35,6 @@ go_test( ], ) -# dispatcher container -oci_tarball( - name = "dispatcher.tar", - format = "docker", - image = ":dispatcher_image", - repo_tags = ["scion/" + package_name() + ":dispatcher"], -) - -oci_image( - name = "dispatcher_image", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - tars = [ - ":dispatcher_data", - ], -) - -pkg_tar( - name = "dispatcher_data", - srcs = ["testdata/disp.toml"], -) - # control container oci_tarball( name = "control.tar", diff --git a/acceptance/topo_cs_reload/docker-compose.yml b/acceptance/topo_cs_reload/docker-compose.yml index 945da70a23..4b99195829 100644 --- a/acceptance/topo_cs_reload/docker-compose.yml +++ b/acceptance/topo_cs_reload/docker-compose.yml @@ -7,23 +7,13 @@ networks: config: - subnet: 242.253.100.0/24 services: - topo_cs_reload_dispatcher: - image: scion/acceptance/topo_cs_reload:dispatcher - networks: - bridge1: - ipv4_address: 242.253.100.2 - volumes: - - vol_topo_cs_reload_disp:/run/shm/dispatcher:rw topo_cs_reload_control_srv: image: scion/acceptance/topo_cs_reload:control - depends_on: - - topo_cs_reload_dispatcher volumes: - - vol_topo_cs_reload_disp:/run/shm/dispatcher:ro - "${TOPO_CS_RELOAD_CONFIG_DIR}/certs:/certs:ro" - "${TOPO_CS_RELOAD_CONFIG_DIR}/keys:/keys:ro" - "${TOPO_CS_RELOAD_CONFIG_DIR}/crypto:/crypto:ro" - network_mode: service:topo_cs_reload_dispatcher + networks: + bridge1: + ipv4_address: 242.253.100.2 version: '2.4' -volumes: - vol_topo_cs_reload_disp: null diff --git a/acceptance/topo_cs_reload/reload_test.go b/acceptance/topo_cs_reload/reload_test.go index 1d361f89a3..eba08eb18c 100644 --- a/acceptance/topo_cs_reload/reload_test.go +++ b/acceptance/topo_cs_reload/reload_test.go @@ -100,10 +100,6 @@ func setupTest(t *testing.T) testState { s.mustExec(t, "tar", "-xf", "crypto.tar", "-C", tmpDir) // first load the docker images from bazel into the docker deamon, the // tars are in the same folder as this test runs in bazel. - s.mustExec(t, "docker", "image", "load", "-i", "dispatcher.tar/tarball.tar") - t.Cleanup(func() { - s.mustExec(t, "docker", "image", "rm", "scion/acceptance/topo_cs_reload:dispatcher") - }) s.mustExec(t, "docker", "image", "load", "-i", "control.tar/tarball.tar") t.Cleanup(func() { s.mustExec(t, "docker", "image", "rm", "scion/acceptance/topo_cs_reload:control") @@ -126,7 +122,6 @@ func (s testState) collectLogs(t *testing.T) { require.NoError(t, os.MkdirAll(fmt.Sprintf("%s/logs", outdir), os.ModePerm|os.ModeDir)) // collect logs for service, file := range map[string]string{ - "topo_cs_reload_dispatcher": "disp.log", "topo_cs_reload_control_srv": "control.log", } { cmd := exec.Command("docker", "compose", diff --git a/acceptance/topo_cs_reload/testdata/cs.toml b/acceptance/topo_cs_reload/testdata/cs.toml index d663dc7120..96cc0e380e 100644 --- a/acceptance/topo_cs_reload/testdata/cs.toml +++ b/acceptance/topo_cs_reload/testdata/cs.toml @@ -1,5 +1,4 @@ [general] -reconnect_to_dispatcher = true config_dir = "/" id = "cs1-ff00_0_110-1" diff --git a/acceptance/topo_cs_reload/testdata/disp.toml b/acceptance/topo_cs_reload/testdata/disp.toml deleted file mode 100644 index f02dc620a8..0000000000 --- a/acceptance/topo_cs_reload/testdata/disp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[dispatcher] -id = "disp_1-ff00_0_110" - -[log.console] -level = "debug" diff --git a/acceptance/topo_cs_reload/testdata/sd.toml b/acceptance/topo_cs_reload/testdata/sd.toml index 4e4dfee564..7cc184a174 100644 --- a/acceptance/topo_cs_reload/testdata/sd.toml +++ b/acceptance/topo_cs_reload/testdata/sd.toml @@ -1,5 +1,4 @@ [general] -reconnect_to_dispatcher = true config_dir = "/" id = "sd1-ff00_0_110" diff --git a/acceptance/topo_cs_reload/testdata/topology_reload.json b/acceptance/topo_cs_reload/testdata/topology_reload.json index 2db6194114..fcdf6312f6 100644 --- a/acceptance/topo_cs_reload/testdata/topology_reload.json +++ b/acceptance/topo_cs_reload/testdata/topology_reload.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:110", "mtu": 1400, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/acceptance/topo_daemon_reload/BUILD.bazel b/acceptance/topo_daemon_reload/BUILD.bazel index d0e7c73a87..71df628af9 100644 --- a/acceptance/topo_daemon_reload/BUILD.bazel +++ b/acceptance/topo_daemon_reload/BUILD.bazel @@ -8,7 +8,6 @@ go_test( data = [ "testdata/topology_reload.json", ":daemon.tar", - ":dispatcher.tar", ":docker-compose.yml", "//acceptance/topo_common:invalid_reloads", "//acceptance/topo_common:topology", @@ -24,32 +23,6 @@ go_test( ], ) -# dispatcher container -oci_tarball( - name = "dispatcher.tar", - format = "docker", - image = ":dispatcher_image", - repo_tags = ["scion/" + package_name() + ":dispatcher"], -) - -oci_image( - name = "dispatcher_image", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - tars = [ - ":dispatcher_data", - ], -) - -pkg_tar( - name = "dispatcher_data", - srcs = ["testdata/disp.toml"], -) - # daemon container oci_tarball( name = "daemon.tar", diff --git a/acceptance/topo_daemon_reload/docker-compose.yml b/acceptance/topo_daemon_reload/docker-compose.yml index 3a9ff0cdcb..ade1eb5f32 100644 --- a/acceptance/topo_daemon_reload/docker-compose.yml +++ b/acceptance/topo_daemon_reload/docker-compose.yml @@ -7,22 +7,14 @@ networks: config: - subnet: 242.254.100.0/24 services: - topo_daemon_reload_dispatcher: - container_name: topo_daemon_reload_dispatcher - image: scion/acceptance/topo_daemon_reload:dispatcher - networks: - bridge1: - ipv4_address: 242.254.100.2 - volumes: - - vol_topo_daemon_reload_disp:/run/shm/dispatcher:rw topo_daemon_reload_daemon: container_name: topo_daemon_reload_daemon image: scion/acceptance/topo_daemon_reload:daemon volumes: - - vol_topo_daemon_reload_disp:/run/shm/dispatcher:ro - vol_topo_daemon_reload_certs:/certs:ro - network_mode: service:topo_daemon_reload_dispatcher + networks: + bridge1: + ipv4_address: 242.254.100.2 version: '2.4' volumes: - vol_topo_daemon_reload_disp: null vol_topo_daemon_reload_certs: null diff --git a/acceptance/topo_daemon_reload/reload_test.go b/acceptance/topo_daemon_reload/reload_test.go index 03f38a980c..171d61eeb9 100644 --- a/acceptance/topo_daemon_reload/reload_test.go +++ b/acceptance/topo_daemon_reload/reload_test.go @@ -68,17 +68,13 @@ func TestSDTopoReload(t *testing.T) { func setupTest(t *testing.T) { // first load the docker images from bazel into the docker deamon, the // tars are in the same folder as this test runs in bazel. - mustExec(t, "docker", "image", "load", "-i", "dispatcher.tar/tarball.tar") - t.Cleanup(func() { - mustExec(t, "docker", "image", "rm", "scion/acceptance/topo_daemon_reload:dispatcher") - }) mustExec(t, "docker", "image", "load", "-i", "daemon.tar/tarball.tar") t.Cleanup(func() { mustExec(t, "docker", "image", "rm", "scion/acceptance/topo_daemon_reload:daemon") }) // now start the docker containers mustExec(t, "docker", "compose", "-f", "docker-compose.yml", - "up", "-d", "topo_daemon_reload_dispatcher", "topo_daemon_reload_daemon") + "up", "-d", "topo_daemon_reload_daemon") t.Cleanup(func() { mustExec(t, "docker", "compose", "-f", "docker-compose.yml", "down", "-v") }) // wait a bit to make sure the containers are ready. time.Sleep(time.Second / 2) @@ -92,8 +88,7 @@ func collectLogs(t *testing.T) { require.NoError(t, os.MkdirAll(fmt.Sprintf("%s/logs", outdir), os.ModePerm|os.ModeDir)) // collect logs for service, file := range map[string]string{ - "topo_daemon_reload_dispatcher": "disp.log", - "topo_daemon_reload_daemon": "daemon.log", + "topo_daemon_reload_daemon": "daemon.log", } { cmd := exec.Command("docker", "compose", "-f", "docker-compose.yml", "logs", "--no-color", diff --git a/acceptance/topo_daemon_reload/testdata/disp.toml b/acceptance/topo_daemon_reload/testdata/disp.toml deleted file mode 100644 index f02dc620a8..0000000000 --- a/acceptance/topo_daemon_reload/testdata/disp.toml +++ /dev/null @@ -1,5 +0,0 @@ -[dispatcher] -id = "disp_1-ff00_0_110" - -[log.console] -level = "debug" diff --git a/acceptance/topo_daemon_reload/testdata/sd.toml b/acceptance/topo_daemon_reload/testdata/sd.toml index f675b0bb3a..7c4a6eeac2 100644 --- a/acceptance/topo_daemon_reload/testdata/sd.toml +++ b/acceptance/topo_daemon_reload/testdata/sd.toml @@ -1,5 +1,4 @@ [general] -reconnect_to_dispatcher = true config_dir = "/" id = "sd1-ff00_0_110" diff --git a/acceptance/topo_daemon_reload/testdata/topology_reload.json b/acceptance/topo_daemon_reload/testdata/topology_reload.json index eeddaf5a2a..8cc03affec 100644 --- a/acceptance/topo_daemon_reload/testdata/topology_reload.json +++ b/acceptance/topo_daemon_reload/testdata/topology_reload.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:110", "mtu": 1400, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/acceptance/trc_update/test.py b/acceptance/trc_update/test.py index 2edf26a0bb..3aa89cdbb9 100755 --- a/acceptance/trc_update/test.py +++ b/acceptance/trc_update/test.py @@ -46,6 +46,7 @@ class Test(base.TestTopogen): 6. Restart control servers and check connectivity again. """ + # TODO: Replace timers with the await_connectivity tool where appropriate. def _run(self): # Give some time for the topology to start. time.sleep(10) diff --git a/buf.yaml b/buf.yaml index a5d3ad80cc..a6b06a30cf 100644 --- a/buf.yaml +++ b/buf.yaml @@ -9,3 +9,5 @@ lint: - COMMENT_ENUM_VALUE - COMMENT_FIELD - COMMENT_RPC + rpc_allow_google_protobuf_empty_requests: true + rpc_allow_google_protobuf_empty_responses: true diff --git a/control/beaconing/testdata/topology-core.json b/control/beaconing/testdata/topology-core.json index 870ae77921..11284ccd2f 100644 --- a/control/beaconing/testdata/topology-core.json +++ b/control/beaconing/testdata/topology-core.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:110", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/control/beaconing/testdata/topology.json b/control/beaconing/testdata/topology.json index 534e51e2bc..3a5f69f35e 100644 --- a/control/beaconing/testdata/topology.json +++ b/control/beaconing/testdata/topology.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:111", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [], "border_routers": { "br1-ff00_0_111-1": { diff --git a/control/cmd/control/main.go b/control/cmd/control/main.go index 1e74e7265f..7b36a7e28d 100644 --- a/control/cmd/control/main.go +++ b/control/cmd/control/main.go @@ -21,6 +21,7 @@ import ( "errors" "net/http" _ "net/http/pprof" + "net/netip" "path/filepath" "strings" "sync" @@ -200,15 +201,9 @@ func realMain(ctx context.Context) error { // FIXME: readability would be improved if we could be consistent with address // representations in NetworkConfig (string or cooked, chose one). nc := infraenv.NetworkConfig{ - IA: topo.IA(), - // Public: (Historical name) The TCP/IP:port address for the control service. - Public: topo.ControlServiceAddress(globalCfg.General.ID), - ReconnectToDispatcher: globalCfg.General.ReconnectToDispatcher, + IA: topo.IA(), + Public: topo.ControlServiceAddress(globalCfg.General.ID), QUIC: infraenv.QUIC{ - // Address: the QUIC/SCION address of this service. If not - // configured, QUICStack() uses the same IP and port as - // for the public address. - Address: globalCfg.QUIC.Address, TLSVerifier: trust.NewTLSCryptoVerifier(trustDB), GetCertificate: cs.NewTLSCertificateLoader( topo.IA(), x509.ExtKeyUsageServerAuth, trustDB, globalCfg.General.ConfigDir, @@ -225,19 +220,19 @@ func realMain(ctx context.Context) error { SCIONNetworkMetrics: metrics.SCIONNetworkMetrics, SCIONPacketConnMetrics: metrics.SCIONPacketConnMetrics, MTU: topo.MTU(), + Topology: cpInfoProvider{topo: topo}, } quicStack, err := nc.QUICStack() if err != nil { return serrors.WrapStr("initializing QUIC stack", err) } - defer quicStack.RedirectCloser() tcpStack, err := nc.TCPStack() if err != nil { return serrors.WrapStr("initializing TCP stack", err) } dialer := &libgrpc.QUICDialer{ Rewriter: &onehop.AddressRewriter{ - Rewriter: nc.AddressRewriter(nil), + Rewriter: nc.AddressRewriter(), MAC: macGen(), }, Dialer: quicStack.InsecureDialer, @@ -631,7 +626,7 @@ func realMain(ctx context.Context) error { drkeyFetcher := drkeygrpc.Fetcher{ Dialer: &libgrpc.QUICDialer{ - Rewriter: nc.AddressRewriter(nil), + Rewriter: nc.AddressRewriter(), Dialer: quicStack.Dialer, }, Router: segreq.NewRouter(fetcherCfg), @@ -940,6 +935,31 @@ func (h *healther) GetCAHealth(ctx context.Context) (api.CAHealthStatus, bool) { return api.Unavailable, false } +type cpInfoProvider struct { + topo *topology.Loader +} + +func (c cpInfoProvider) LocalIA(_ context.Context) (addr.IA, error) { + return c.topo.IA(), nil +} + +func (c cpInfoProvider) PortRange(_ context.Context) (uint16, uint16, error) { + start, end := c.topo.PortRange() + return start, end, nil +} + +func (c cpInfoProvider) Interfaces(_ context.Context) (map[uint16]netip.AddrPort, error) { + ifMap := c.topo.InterfaceInfoMap() + ifsToUDP := make(map[uint16]netip.AddrPort, len(ifMap)) + for i, v := range ifMap { + if i > (1<<16)-1 { + return nil, serrors.New("invalid interface id", "id", i) + } + ifsToUDP[uint16(i)] = v.InternalAddr + } + return ifsToUDP, nil +} + func getCAHealth( ctx context.Context, caClient *caapi.Client, diff --git a/daemon/daemon.go b/daemon/daemon.go index 8fbc106938..30c92918bc 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,8 +119,11 @@ type ServerConfig struct { // NewServer constructs a daemon API server. func NewServer(cfg ServerConfig) *servers.DaemonServer { return &servers.DaemonServer{ - IA: cfg.IA, - MTU: cfg.MTU, + IA: cfg.IA, + MTU: cfg.MTU, + // TODO(JordiSubira): This will be changed in the future to fetch + // the information from the CS instead of feeding the configuration + // file into. Topology: cfg.Topology, Fetcher: cfg.Fetcher, ASInspector: cfg.Engine.Inspector, diff --git a/daemon/internal/servers/BUILD.bazel b/daemon/internal/servers/BUILD.bazel index 1d1534f2c2..9bf6640324 100644 --- a/daemon/internal/servers/BUILD.bazel +++ b/daemon/internal/servers/BUILD.bazel @@ -30,6 +30,7 @@ go_library( "@com_github_golang_protobuf//ptypes/duration", "@com_github_golang_protobuf//ptypes/timestamp", "@com_github_opentracing_opentracing_go//:go_default_library", + "@org_golang_google_protobuf//types/known/emptypb:go_default_library", "@org_golang_x_sync//singleflight:go_default_library", ], ) diff --git a/daemon/internal/servers/grpc.go b/daemon/internal/servers/grpc.go index dd2cb43bc7..c8c8258bcb 100644 --- a/daemon/internal/servers/grpc.go +++ b/daemon/internal/servers/grpc.go @@ -24,6 +24,7 @@ import ( timestamppb "github.com/golang/protobuf/ptypes/timestamp" "github.com/opentracing/opentracing-go" "golang.org/x/sync/singleflight" + "google.golang.org/protobuf/types/known/emptypb" drkey_daemon "github.com/scionproto/scion/daemon/drkey" "github.com/scionproto/scion/daemon/fetcher" @@ -49,6 +50,7 @@ type Topology interface { InterfaceIDs() []uint16 UnderlayNextHop(uint16) *net.UDPAddr ControlServiceAddresses() []*net.UDPAddr + PortRange() (uint16, uint16) } // DaemonServer handles gRPC requests to the SCION daemon. @@ -351,6 +353,19 @@ func (s *DaemonServer) notifyInterfaceDown(ctx context.Context, return &sdpb.NotifyInterfaceDownResponse{}, nil } +// PortRange returns the port range for the dispatched ports. +func (s *DaemonServer) PortRange( + _ context.Context, + _ *emptypb.Empty, +) (*sdpb.PortRangeResponse, error) { + + startPort, endPort := s.Topology.PortRange() + return &sdpb.PortRangeResponse{ + DispatchedPortStart: uint32(startPort), + DispatchedPortEnd: uint32(endPort), + }, nil +} + func (s *DaemonServer) DRKeyASHost( ctx context.Context, req *pb_daemon.DRKeyASHostRequest, diff --git a/dispatcher/BUILD.bazel b/dispatcher/BUILD.bazel index 8538e90bdd..d091d76e49 100644 --- a/dispatcher/BUILD.bazel +++ b/dispatcher/BUILD.bazel @@ -2,17 +2,10 @@ load("//tools/lint:go.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = [ - "dispatcher.go", - "table.go", - "underlay.go", - ], + srcs = ["dispatcher.go"], importpath = "github.com/scionproto/scion/dispatcher", visibility = ["//visibility:public"], deps = [ - "//dispatcher/internal/metrics:go_default_library", - "//dispatcher/internal/registration:go_default_library", - "//dispatcher/internal/respool:go_default_library", "//pkg/addr:go_default_library", "//pkg/log:go_default_library", "//pkg/private/common:go_default_library", @@ -20,25 +13,21 @@ go_library( "//pkg/slayers:go_default_library", "//pkg/slayers/path/epic:go_default_library", "//pkg/slayers/path/scion:go_default_library", - "//private/ringbuf:go_default_library", - "//private/underlay/conn:go_default_library", "@com_github_google_gopacket//:go_default_library", + "@org_golang_x_net//ipv4:go_default_library", + "@org_golang_x_net//ipv6:go_default_library", ], ) go_test( name = "go_default_test", - srcs = ["underlay_test.go"], + srcs = ["dispatcher_test.go"], embed = [":go_default_library"], deps = [ - "//dispatcher/internal/respool:go_default_library", "//pkg/addr:go_default_library", "//pkg/private/xtest:go_default_library", - "//pkg/slayers:go_default_library", - "//pkg/slayers/path:go_default_library", - "//pkg/slayers/path/scion:go_default_library", - "@com_github_golang_mock//gomock:go_default_library", - "@com_github_google_gopacket//:go_default_library", + "//pkg/snet:go_default_library", + "//pkg/snet/path:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", "@com_github_stretchr_testify//require:go_default_library", ], diff --git a/dispatcher/cmd/dispatcher/BUILD.bazel b/dispatcher/cmd/dispatcher/BUILD.bazel index f7a8f876c0..986b88784d 100644 --- a/dispatcher/cmd/dispatcher/BUILD.bazel +++ b/dispatcher/cmd/dispatcher/BUILD.bazel @@ -1,4 +1,4 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") +load("//tools/lint:go.bzl", "go_library") load("//:scion.bzl", "scion_go_binary") go_library( @@ -6,21 +6,73 @@ go_library( srcs = ["main.go"], importpath = "github.com/scionproto/scion/dispatcher/cmd/dispatcher", visibility = ["//visibility:private"], - deps = [ - "//dispatcher/config:go_default_library", - "//dispatcher/mgmtapi:go_default_library", - "//dispatcher/network:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/private/util:go_default_library", - "//pkg/slayers/path:go_default_library", - "//private/app:go_default_library", - "//private/app/launcher:go_default_library", - "//private/service:go_default_library", - "@com_github_go_chi_chi_v5//:go_default_library", - "@com_github_go_chi_cors//:go_default_library", - "@org_golang_x_sync//errgroup:go_default_library", - ], + deps = select({ + "@io_bazel_rules_go//go/platform:android": [ + "//dispatcher:go_default_library", + "//dispatcher/config:go_default_library", + "//dispatcher/mgmtapi:go_default_library", + "//pkg/addr:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/slayers/path:go_default_library", + "//private/app:go_default_library", + "//private/app/launcher:go_default_library", + "//private/service:go_default_library", + "//private/topology/underlay:go_default_library", + "@com_github_go_chi_chi_v5//:go_default_library", + "@com_github_go_chi_cors//:go_default_library", + "@org_golang_x_sync//errgroup:go_default_library", + ], + "@io_bazel_rules_go//go/platform:darwin": [ + "//dispatcher:go_default_library", + "//dispatcher/config:go_default_library", + "//dispatcher/mgmtapi:go_default_library", + "//pkg/addr:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/slayers/path:go_default_library", + "//private/app:go_default_library", + "//private/app/launcher:go_default_library", + "//private/service:go_default_library", + "//private/topology/underlay:go_default_library", + "@com_github_go_chi_chi_v5//:go_default_library", + "@com_github_go_chi_cors//:go_default_library", + "@org_golang_x_sync//errgroup:go_default_library", + ], + "@io_bazel_rules_go//go/platform:ios": [ + "//dispatcher:go_default_library", + "//dispatcher/config:go_default_library", + "//dispatcher/mgmtapi:go_default_library", + "//pkg/addr:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/slayers/path:go_default_library", + "//private/app:go_default_library", + "//private/app/launcher:go_default_library", + "//private/service:go_default_library", + "//private/topology/underlay:go_default_library", + "@com_github_go_chi_chi_v5//:go_default_library", + "@com_github_go_chi_cors//:go_default_library", + "@org_golang_x_sync//errgroup:go_default_library", + ], + "@io_bazel_rules_go//go/platform:linux": [ + "//dispatcher:go_default_library", + "//dispatcher/config:go_default_library", + "//dispatcher/mgmtapi:go_default_library", + "//pkg/addr:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/slayers/path:go_default_library", + "//private/app:go_default_library", + "//private/app/launcher:go_default_library", + "//private/service:go_default_library", + "//private/topology/underlay:go_default_library", + "@com_github_go_chi_chi_v5//:go_default_library", + "@com_github_go_chi_cors//:go_default_library", + "@org_golang_x_sync//errgroup:go_default_library", + ], + "//conditions:default": [], + }), ) scion_go_binary( @@ -28,18 +80,3 @@ scion_go_binary( embed = [":go_default_library"], visibility = ["//visibility:public"], ) - -go_test( - name = "go_default_test", - srcs = ["main_test.go"], - embed = [":go_default_library"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/private/xtest:go_default_library", - "//pkg/snet:go_default_library", - "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - "@com_github_stretchr_testify//require:go_default_library", - ], -) diff --git a/dispatcher/cmd/dispatcher/main.go b/dispatcher/cmd/dispatcher/main.go index ab26beac30..3eaed918e2 100644 --- a/dispatcher/cmd/dispatcher/main.go +++ b/dispatcher/cmd/dispatcher/main.go @@ -13,31 +13,34 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build linux || darwin +// +build linux darwin + package main import ( "context" "errors" - "fmt" "net" "net/http" _ "net/http/pprof" - "os" + "net/netip" "github.com/go-chi/chi/v5" "github.com/go-chi/cors" "golang.org/x/sync/errgroup" + "github.com/scionproto/scion/dispatcher" "github.com/scionproto/scion/dispatcher/config" api "github.com/scionproto/scion/dispatcher/mgmtapi" - "github.com/scionproto/scion/dispatcher/network" + "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/private/util" "github.com/scionproto/scion/pkg/slayers/path" "github.com/scionproto/scion/private/app" "github.com/scionproto/scion/private/app/launcher" "github.com/scionproto/scion/private/service" + "github.com/scionproto/scion/private/topology/underlay" ) var globalCfg config.Config @@ -53,21 +56,19 @@ func main() { } func realMain(ctx context.Context) error { - if err := util.CreateParentDirs(globalCfg.Dispatcher.ApplicationSocket); err != nil { - return serrors.WrapStr("creating directory tree for socket", err) - } - path.StrictDecoding(false) var cleanup app.Cleanup g, errCtx := errgroup.WithContext(ctx) g.Go(func() error { defer log.HandlePanic() - return RunDispatcher( - globalCfg.Dispatcher.DeleteSocket, - globalCfg.Dispatcher.ApplicationSocket, - os.FileMode(globalCfg.Dispatcher.SocketFileMode), - globalCfg.Dispatcher.UnderlayPort, + return runDispatcher( + globalCfg.Dispatcher.LocalUDPForwarding, + globalCfg.Dispatcher.ServiceAddresses, + netip.AddrPortFrom( + globalCfg.Dispatcher.UnderlayAddr, + underlay.EndhostPort, + ), ) }) @@ -116,12 +117,6 @@ func realMain(ctx context.Context) error { return globalCfg.Metrics.ServePrometheus(errCtx) }) - defer func() { - if err := deleteSocket(globalCfg.Dispatcher.ApplicationSocket); err != nil { - log.Error("deleting socket", "err", err) - } - }() - g.Go(func() error { defer log.HandlePanic() <-errCtx.Done() @@ -138,32 +133,14 @@ func realMain(ctx context.Context) error { } } -func RunDispatcher(deleteSocketFlag bool, applicationSocket string, socketFileMode os.FileMode, - underlayPort int) error { +func runDispatcher( + isDispatcher bool, + svcAddrs map[addr.Addr]netip.AddrPort, + underlayAddr netip.AddrPort, +) error { - if deleteSocketFlag { - if err := deleteSocket(globalCfg.Dispatcher.ApplicationSocket); err != nil { - return err - } - } - dispatcher := &network.Dispatcher{ - UnderlaySocket: fmt.Sprintf(":%d", underlayPort), - ApplicationSocket: applicationSocket, - SocketFileMode: socketFileMode, - } - log.Debug("Dispatcher starting", "appSocket", applicationSocket, "underlayPort", underlayPort) - return dispatcher.ListenAndServe() -} - -func deleteSocket(socket string) error { - if _, err := os.Stat(socket); err != nil { - // File does not exist, or we can't read it, nothing to delete - return nil - } - if err := os.Remove(socket); err != nil { - return err - } - return nil + log.Debug("Dispatcher starting", "localAddr", underlayAddr, "dispatcher feature", isDispatcher) + return dispatcher.ListenAndServe(isDispatcher, svcAddrs, net.UDPAddrFromAddrPort(underlayAddr)) } func requiredIPs() ([]net.IP, error) { diff --git a/dispatcher/cmd/dispatcher/main_test.go b/dispatcher/cmd/dispatcher/main_test.go deleted file mode 100644 index 86e3dc710d..0000000000 --- a/dispatcher/cmd/dispatcher/main_test.go +++ /dev/null @@ -1,487 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "context" - "fmt" - "net" - "net/netip" - "os" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" -) - -const ( - defaultTimeout = 2 * time.Second - defaultWaitDuration = 200 * time.Millisecond -) - -type TestSettings struct { - ApplicationSocket string - UnderlayPort int -} - -func InitTestSettings(t *testing.T, dispatcherTestPort int) *TestSettings { - socketName, err := getSocketName("/tmp") - if err != nil { - t.Fatal(err) - } - return &TestSettings{ - ApplicationSocket: socketName, - UnderlayPort: dispatcherTestPort, - } -} - -func getSocketName(dir string) (string, error) { - dir, err := os.MkdirTemp(dir, "dispatcher") - if err != nil { - return "", err - } - return filepath.Join(dir, "server.sock"), nil -} - -type ClientAddress struct { - IA addr.IA - PublicAddress netip.Addr - PublicPort uint16 - ServiceAddress addr.SVC - UnderlayAddress *net.UDPAddr -} - -type TestCase struct { - Name string - ClientAddress *ClientAddress - TestPackets []*snet.Packet - UnderlayAddress *net.UDPAddr - ExpectedPacket *snet.Packet -} - -func genTestCases(dispatcherPort int) []*TestCase { - // Addressing information - var ( - commonIA = xtest.MustParseIA("1-ff00:0:1") - commonPublicL3Address = netip.AddrFrom4([4]byte{127, 0, 0, 1}) - commonUnderlayAddress = &net.UDPAddr{IP: net.IP{127, 0, 0, 1}, Port: dispatcherPort} - clientXAddress = &ClientAddress{ - IA: commonIA, - PublicAddress: commonPublicL3Address, - PublicPort: 8080, - ServiceAddress: addr.SvcNone, - UnderlayAddress: commonUnderlayAddress, - } - clientYAddress = &ClientAddress{ - IA: commonIA, - PublicAddress: commonPublicL3Address, - PublicPort: 8081, - ServiceAddress: addr.SvcCS, - UnderlayAddress: commonUnderlayAddress, - } - ) - - var testCases = []*TestCase{ - { - Name: "UDP/IPv4 packet", - ClientAddress: clientXAddress, - TestPackets: []*snet.Packet{ - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.UDPPayload{ - SrcPort: clientXAddress.PublicPort, - DstPort: clientXAddress.PublicPort, - Payload: []byte{1, 2, 3, 4}, - }, - Path: path.Empty{}, - }, - }, - }, - UnderlayAddress: clientXAddress.UnderlayAddress, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.UDPPayload{ - SrcPort: clientXAddress.PublicPort, - DstPort: clientXAddress.PublicPort, - Payload: []byte{1, 2, 3, 4}, - }, - Path: snet.RawPath{}, - }, - }, - }, - { - Name: "UDP/SVC packet", - ClientAddress: clientYAddress, - TestPackets: []*snet.Packet{ - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostSVC(clientYAddress.ServiceAddress), - }, - Payload: snet.UDPPayload{ - SrcPort: clientYAddress.PublicPort, - DstPort: clientYAddress.PublicPort, - Payload: []byte{5, 6, 7, 8}, - }, - Path: path.Empty{}, - }, - }, - }, - UnderlayAddress: clientXAddress.UnderlayAddress, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostSVC(clientYAddress.ServiceAddress), - }, - Payload: snet.UDPPayload{ - SrcPort: clientYAddress.PublicPort, - DstPort: clientYAddress.PublicPort, - Payload: []byte{5, 6, 7, 8}, - }, - Path: snet.RawPath{}, - }, - }, - }, - { - Name: "SCMP::Error, UDP quote", - ClientAddress: clientXAddress, - UnderlayAddress: clientXAddress.UnderlayAddress, - TestPackets: []*snet.Packet{ - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPDestinationUnreachable{ - Payload: MustPack(snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.UDPPayload{SrcPort: clientXAddress.PublicPort}, - Path: path.Empty{}, - }, - }), - }, - Path: path.Empty{}, - }, - }, - }, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPDestinationUnreachable{ - Payload: MustPack(snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.UDPPayload{SrcPort: clientXAddress.PublicPort}, - Path: path.Empty{}, - }, - }), - }, - Path: snet.RawPath{}, - }, - }, - }, - { - Name: "SCMP::Error, SCMP quote", - ClientAddress: clientXAddress, - UnderlayAddress: clientXAddress.UnderlayAddress, - TestPackets: []*snet.Packet{ - { - // Force a SCMP General ID registration to happen, but route it - // from nowhere so we don't get it back - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: xtest.MustParseIA("1-ff00:0:42"), // middle of nowhere - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, - Path: path.Empty{}, - }, - }, - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPDestinationUnreachable{ - Payload: MustPack(snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, - Path: path.Empty{}, - }, - }), - }, - Path: path.Empty{}, - }, - }, - }, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPDestinationUnreachable{ - Payload: MustPack(snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, - Path: path.Empty{}, - }, - }), - }, - Path: snet.RawPath{}, - }, - }, - }, - { - Name: "SCMP::General::EchoRequest", - ClientAddress: clientXAddress, - UnderlayAddress: clientYAddress.UnderlayAddress, - TestPackets: []*snet.Packet{ - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Payload: snet.SCMPEchoRequest{ - Identifier: 0xdead, - SeqNumber: 0xcafe, - Payload: []byte("hello?"), - }, - Path: path.Empty{}, - }, - }, - }, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPEchoReply{ - Identifier: 0xdead, - SeqNumber: 0xcafe, - Payload: []byte("hello?"), - }, - Path: snet.RawPath{}, - }, - }, - }, - { - Name: "SCMP::General::TraceRouteRequest", - ClientAddress: clientXAddress, - UnderlayAddress: clientYAddress.UnderlayAddress, - TestPackets: []*snet.Packet{ - { - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Payload: snet.SCMPTracerouteRequest{Identifier: 0xdeaf, Sequence: 0xcafd}, - Path: path.Empty{}, - }, - }, - }, - ExpectedPacket: &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Source: snet.SCIONAddress{ - IA: clientYAddress.IA, - Host: addr.HostIP(clientYAddress.PublicAddress), - }, - Destination: snet.SCIONAddress{ - IA: clientXAddress.IA, - Host: addr.HostIP(clientXAddress.PublicAddress), - }, - Payload: snet.SCMPTracerouteReply{Identifier: 0xdeaf, Sequence: 0xcafd}, - Path: snet.RawPath{}, - }, - }, - }, - } - return testCases -} - -func TestDataplaneIntegration(t *testing.T) { - dispatcherTestPort := 40032 - settings := InitTestSettings(t, dispatcherTestPort) - - go func() { - err := RunDispatcher(false, settings.ApplicationSocket, reliable.DefaultDispSocketFileMode, - settings.UnderlayPort) - require.NoError(t, err, "dispatcher error") - }() - time.Sleep(defaultWaitDuration) - - testCases := genTestCases(dispatcherTestPort) - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - RunTestCase(t, tc, settings) - }) - time.Sleep(defaultWaitDuration) - } -} - -func RunTestCase(t *testing.T, tc *TestCase, settings *TestSettings) { - dispatcherService := reliable.NewDispatcher(settings.ApplicationSocket) - ctx, cancelF := context.WithTimeout(context.Background(), defaultTimeout) - defer cancelF() - conn, _, err := dispatcherService.Register( - ctx, - tc.ClientAddress.IA, - &net.UDPAddr{ - IP: tc.ClientAddress.PublicAddress.AsSlice(), - Port: int(tc.ClientAddress.PublicPort), - }, - tc.ClientAddress.ServiceAddress, - ) - require.NoError(t, err, "unable to open socket") - // Always destroy the connection s.t. future tests aren't compromised by a - // fatal in this subtest - defer conn.Close() - - for _, packet := range tc.TestPackets { - require.NoError(t, packet.Serialize()) - fmt.Printf("sending packet: %x\n", packet.Bytes) - _, err = conn.WriteTo(packet.Bytes, tc.UnderlayAddress) - require.NoError(t, err, "unable to write message") - } - - err = conn.SetReadDeadline(time.Now().Add(defaultTimeout)) - require.NoError(t, err, "unable to set read deadline") - - rcvPkt := snet.Packet{} - rcvPkt.Prepare() - n, _, err := conn.ReadFrom(rcvPkt.Bytes) - require.NoError(t, err, "unable to read message") - rcvPkt.Bytes = rcvPkt.Bytes[:n] - - require.NoError(t, rcvPkt.Decode()) - - err = conn.Close() - require.NoError(t, err, "unable to close conn") - - assert.Equal(t, tc.ExpectedPacket.PacketInfo, rcvPkt.PacketInfo) -} - -func MustPack(pkt snet.Packet) []byte { - if err := pkt.Serialize(); err != nil { - panic(err) - } - return pkt.Bytes -} diff --git a/dispatcher/config/BUILD.bazel b/dispatcher/config/BUILD.bazel index 2e55a639c2..4b3cce9ebe 100644 --- a/dispatcher/config/BUILD.bazel +++ b/dispatcher/config/BUILD.bazel @@ -9,14 +9,12 @@ go_library( importpath = "github.com/scionproto/scion/dispatcher/config", visibility = ["//visibility:public"], deps = [ + "//pkg/addr:go_default_library", "//pkg/log:go_default_library", "//pkg/private/serrors:go_default_library", - "//pkg/private/util:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/config:go_default_library", "//private/env:go_default_library", "//private/mgmtapi:go_default_library", - "//private/topology:go_default_library", ], ) @@ -26,10 +24,8 @@ go_test( embed = [":go_default_library"], deps = [ "//pkg/log/logtest:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/env/envtest:go_default_library", "//private/mgmtapi/mgmtapitest:go_default_library", - "//private/topology:go_default_library", "@com_github_pelletier_go_toml_v2//:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", ], diff --git a/dispatcher/config/config.go b/dispatcher/config/config.go index 1fad7d9b09..4d378ad984 100644 --- a/dispatcher/config/config.go +++ b/dispatcher/config/config.go @@ -19,15 +19,14 @@ package config import ( "fmt" "io" + "net/netip" + "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/private/util" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/config" "github.com/scionproto/scion/private/env" api "github.com/scionproto/scion/private/mgmtapi" - "github.com/scionproto/scion/private/topology" ) var _ config.Config = (*Config)(nil) @@ -40,46 +39,6 @@ type Config struct { Dispatcher Dispatcher `toml:"dispatcher,omitempty"` } -// Dispatcher contains the dispatcher specific config. -type Dispatcher struct { - config.NoDefaulter - // ID of the Dispatcher (required) - ID string `toml:"id,omitempty"` - // ApplicationSocket is the local API socket (default /run/shm/dispatcher/default.sock) - ApplicationSocket string `toml:"application_socket,omitempty"` - // Socket file permissions when created; read from octal. (default 0770) - SocketFileMode util.FileMode `toml:"socket_file_mode,omitempty"` - // UnderlayPort is the native port opened by the dispatcher (default 30041) - UnderlayPort int `toml:"underlay_port,omitempty"` - // DeleteSocket specifies whether the dispatcher should delete the - // socket file prior to attempting to create a new one. - DeleteSocket bool `toml:"delete_socket,omitempty"` -} - -func (cfg *Dispatcher) Validate() error { - if cfg.ApplicationSocket == "" { - cfg.ApplicationSocket = reliable.DefaultDispPath - } - if cfg.SocketFileMode == 0 { - cfg.SocketFileMode = reliable.DefaultDispSocketFileMode - } - if cfg.UnderlayPort == 0 { - cfg.UnderlayPort = topology.EndhostPort - } - if cfg.ID == "" { - return serrors.New("id must be set") - } - return nil -} - -func (cfg *Dispatcher) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) { - config.WriteString(dst, fmt.Sprintf(dispSample, idSample)) -} - -func (cfg *Dispatcher) ConfigName() string { - return "dispatcher" -} - func (cfg *Config) InitDefaults() { config.InitAll( &cfg.Features, @@ -113,3 +72,50 @@ func (cfg *Config) Sample(dst io.Writer, path config.Path, _ config.CtxMap) { func (cfg *Config) ConfigName() string { return "dispatcher_config" } + +// Dispatcher contains the dispatcher specific config. +type Dispatcher struct { + // ID is the SCION element ID of the shim dispatcher. + ID string `toml:"id,omitempty"` + // LocalUDPForwarding specifies whether UDP forwarding is enabled for the dispatcher. + // Otherwise, it will only reply to SCMPInfo packets. + LocalUDPForwarding bool `toml:"local_udp_forwarding,omitempty"` + // ServiceAddresses is the map of IA,SVC -> underlay UDP/IP address. + // The map should be configured provided that the shim dispatcher runs colocated to such + // mapped services, e.g., the shim dispatcher runs on the same host, + // where the CS for the local IA runs. + ServiceAddresses map[addr.Addr]netip.AddrPort `toml:"service_addresses,omitempty"` + // UnderlayAddr is the IP address where the shim dispatcher listens on (default ::). + UnderlayAddr netip.Addr `toml:"underlay_addr,omitempty"` +} + +func (cfg *Dispatcher) InitDefaults() { + if cfg.UnderlayAddr == (netip.Addr{}) { + cfg.UnderlayAddr = netip.IPv6Unspecified() + } +} + +func (cfg *Dispatcher) Validate() error { + if !cfg.UnderlayAddr.IsValid() { + return serrors.New("underlay_addr is not set or it is incorrect") + } + if cfg.ID == "" { + return serrors.New("id must be set") + } + + // Process ServiceAddresses + for iaSVC := range cfg.ServiceAddresses { + if iaSVC.Host.Type() != addr.HostTypeSVC { + return serrors.New("parsed address must be SVC", "type", iaSVC.Host.Type().String()) + } + } + return nil +} + +func (cfg *Dispatcher) Sample(dst io.Writer, path config.Path, ctx config.CtxMap) { + config.WriteString(dst, fmt.Sprintf(dispSample, idSample)) +} + +func (cfg *Dispatcher) ConfigName() string { + return "dispatcher" +} diff --git a/dispatcher/config/config_test.go b/dispatcher/config/config_test.go index 07338d9ab2..f24dea5915 100644 --- a/dispatcher/config/config_test.go +++ b/dispatcher/config/config_test.go @@ -23,10 +23,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/scionproto/scion/pkg/log/logtest" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/env/envtest" apitest "github.com/scionproto/scion/private/mgmtapi/mgmtapitest" - "github.com/scionproto/scion/private/topology" ) func TestConfigSample(t *testing.T) { @@ -44,7 +42,7 @@ func InitTestConfig(cfg *Config) { apitest.InitConfig(&cfg.API) envtest.InitTest(nil, &cfg.Metrics, nil, nil) logtest.InitTestLogging(&cfg.Logging) - cfg.Dispatcher.DeleteSocket = true + cfg.Dispatcher.InitDefaults() } func CheckTestConfig(t *testing.T, cfg *Config, id string) { @@ -52,8 +50,6 @@ func CheckTestConfig(t *testing.T, cfg *Config, id string) { envtest.CheckTest(t, nil, &cfg.Metrics, nil, nil, id) logtest.CheckTestLogging(t, &cfg.Logging, id) assert.Equal(t, id, cfg.Dispatcher.ID) - assert.Equal(t, reliable.DefaultDispPath, cfg.Dispatcher.ApplicationSocket) - assert.Equal(t, reliable.DefaultDispSocketFileMode, int(cfg.Dispatcher.SocketFileMode)) - assert.Equal(t, topology.EndhostPort, cfg.Dispatcher.UnderlayPort) - assert.False(t, cfg.Dispatcher.DeleteSocket) + assert.True(t, cfg.Dispatcher.UnderlayAddr.IsValid()) + assert.Len(t, cfg.Dispatcher.ServiceAddresses, 6) } diff --git a/dispatcher/config/sample.go b/dispatcher/config/sample.go index e105927c6e..040cf23250 100644 --- a/dispatcher/config/sample.go +++ b/dispatcher/config/sample.go @@ -21,15 +21,19 @@ const dispSample = ` # ID of the Dispatcher. (required) id = "%s" -# The local API socket. (default /run/shm/dispatcher/default.sock) -application_socket = "/run/shm/dispatcher/default.sock" +# The underlay IP address opened by the dispatcher. (default ::) +# underlay_addr = "::" -# File permissions of the ApplicationSocket socket file, in octal. (default "0770") -socket_file_mode = "0770" - -# The native port opened by the dispatcher. (default 30041) -underlay_port = 30041 - -# Remove the socket file (if it exists) on start. (default false) -delete_socket = false +# ServiceAddresses is the map of IA,SVC -> underlay UDP/IP address. +# The map should be configured provided that the shim dispatcher runs colocated to such +# mapped services, e.g., the shim dispatcher runs on the same host, +# where the CS for the local IA runs. +# For other use cases it can be ignored. +[dispatcher.service_addresses] +"1-ff00:0:110,CS" = "[fd00:f00d:cafe::7f00:14]:31000" +"1-ff00:0:110,DS" = "[fd00:f00d:cafe::7f00:14]:31000" +"1-ff00:0:120,CS" = "127.0.0.68:31008" +"1-ff00:0:120,DS" = "127.0.0.68:31008" +"1-ff00:0:130,CS" = "[fd00:f00d:cafe::7f00:2b]:31016" +"1-ff00:0:130,DS" = "[fd00:f00d:cafe::7f00:2b]:31016" ` diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 86d15a09d8..2bb71e6d89 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -1,4 +1,4 @@ -// Copyright 2020 Anapaya Systems +// Copyright 2023 ETH Zurich // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,272 +15,540 @@ package dispatcher import ( - "context" + "fmt" "net" - "time" + "net/netip" + + "github.com/google/gopacket" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" - "github.com/scionproto/scion/dispatcher/internal/registration" - "github.com/scionproto/scion/dispatcher/internal/respool" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/slayers" - "github.com/scionproto/scion/private/ringbuf" - "github.com/scionproto/scion/private/underlay/conn" + "github.com/scionproto/scion/pkg/slayers/path/epic" + "github.com/scionproto/scion/pkg/slayers/path/scion" ) -const ( - // ReceiveBufferSize is the size of receive buffers used by the dispatcher. - ReceiveBufferSize = 1 << 20 - // SendBufferSize is the size of the send buffers used by the dispatcher. - SendBufferSize = 1 << 20 -) +const ErrUnsupportedL4 common.ErrMsg = "unsupported SCION L4 protocol" -// Server is the main object allowing to create new SCION connections. +// Server is the main object allowing to forward SCION packets coming +// from legacy BR to the final endhost application and to handle SCMP +// info packets destined to this endhost. type Server struct { - // routingTable is used to register new connections. - routingTable *IATable - ipv4Conn net.PacketConn - ipv6Conn net.PacketConn + // isDispatcher indicates whether the shim acts as SCION packet + // dispatcher + isDispatcher bool + conn *net.UDPConn + // topo keeps the topology for the local AS. It can keep multiple ASes + // in case we run several topologies locally, e.g., developer environment. + + // TODO(JordiSubira): This may be taken from daemon for non self-contained + // applications. + ServiceAddresses map[addr.Addr]netip.AddrPort + buf []byte + oobuf []byte + outBuffer gopacket.SerializeBuffer + decoded []gopacket.LayerType + parser *gopacket.DecodingLayerParser + cmParser controlMessageParser + options gopacket.SerializeOptions + + scionLayer slayers.SCION + hbh slayers.HopByHopExtnSkipper + e2e slayers.EndToEndExtn + udpLayer slayers.UDP + scmpLayer slayers.SCMP } -// NewServer creates new instance of Server. Internally, it opens the dispatcher ports -// for both IPv4 and IPv6. Returns error if the ports can't be opened. -func NewServer(address string, ipv4Conn, ipv6Conn net.PacketConn) (*Server, error) { - if ipv4Conn == nil { - var err error - ipv4Conn, err = openConn("udp4", address) - if err != nil { - return nil, err - } +// NewServer creates new instance of Server. +func NewServer( + isDispatcher bool, + svcAddrs map[addr.Addr]netip.AddrPort, + conn *net.UDPConn, +) *Server { + server := Server{ + isDispatcher: isDispatcher, + ServiceAddresses: svcAddrs, + buf: make([]byte, common.SupportedMTU), + oobuf: make([]byte, 1024), + decoded: make([]gopacket.LayerType, 4), + outBuffer: gopacket.NewSerializeBuffer(), + options: gopacket.SerializeOptions{ + ComputeChecksums: true, + FixLengths: true, + }, } - if ipv6Conn == nil { - var err error - ipv6Conn, err = openConn("udp6", address) - if err != nil { - ipv4Conn.Close() - return nil, err - } + parser := gopacket.NewDecodingLayerParser( + slayers.LayerTypeSCION, + &server.scionLayer, + &server.hbh, + &server.e2e, + &server.udpLayer, + &server.scmpLayer, + ) + parser.IgnoreUnsupported = true + server.parser = parser + server.conn = conn + if isDispatcher { + server.conn, server.cmParser = setIPPktInfo(conn) } - return &Server{ - routingTable: NewIATable(32768, 65535), - ipv4Conn: ipv4Conn, - ipv6Conn: ipv6Conn, - }, nil + server.scionLayer.RecyclePaths() + server.udpLayer.SetNetworkLayerForChecksum(&server.scionLayer) + server.scmpLayer.SetNetworkLayerForChecksum(&server.scionLayer) + return &server } -// Serve starts reading packets from network and dispatching them to different connections. +// Serve starts reading packets from network and dispatching them to the end application. +// It also replies to SCMPEchoRequest and SCMPTracerouteRequest. // The function blocks and returns if there's an error or when Close has been called. -func (as *Server) Serve() error { - errChan := make(chan error) - go func() { - defer log.HandlePanic() - netToRingDataplane := &NetToRingDataplane{ - UnderlayConn: as.ipv4Conn, - RoutingTable: as.routingTable, +func (s *Server) Serve() error { + for { + n, nn, _, prevHop, err := s.conn.ReadMsgUDPAddrPort(s.buf, s.oobuf) + if err != nil { + log.Error("Reading message", "err", err) + continue } - errChan <- netToRingDataplane.Run() - }() - go func() { - defer log.HandlePanic() - netToRingDataplane := &NetToRingDataplane{ - UnderlayConn: as.ipv6Conn, - RoutingTable: as.routingTable, + + var underlay netip.Addr + if s.isDispatcher { + underlay = s.parseUnderlayAddr(s.oobuf[:nn]) + if !underlay.IsValid() { + // some error parsing the CM info from the incoming packet; + // we discard the packet and keep serving. + continue + } } - errChan <- netToRingDataplane.Run() - }() - return <-errChan -} -// Register creates a new connection. -func (as *Server) Register(ctx context.Context, ia addr.IA, address *net.UDPAddr, - svc addr.SVC) (net.PacketConn, uint16, error) { + outBuf, nextHopAddr, err := s.processMsgNextHop(s.buf[:n], underlay, prevHop) + if err != nil { + return err + } + if !nextHopAddr.IsValid() { + // some error processing the incoming packet; + // we discard the packet and keep serving. + continue + } + + m, err := s.conn.WriteToUDPAddrPort(outBuf, nextHopAddr) + if err != nil { + log.Error("writing packet out", "err", err) + continue + } + if m != len(outBuf) { + log.Error("writing packet out", "message len", len(outBuf), "written bytes", n) + } + } +} - tableEntry := newTableEntry() - ref, err := as.routingTable.Register(ia, address, nil, svc, tableEntry) +// processMsgNextHop processes the message arriving at the shim dispatcher and returns +// a byte array corresponding to the packet that needs to be forwarded. +// The input byte array `buf` is the raw incoming packet; +// `underlay` corresponds to the IP address on the outer UDP/IP header; +// `prevHop` is the address from the previous SCION hop in the local network. +// The intended nextHop address, i.e., either the end application +// or the next BR (for SCMP informational response), is returned. +// It returns a non-nil error for non-recoverable errors only. +// If the incoming packet couldn't be processed due to a recoverable error or +// incorrect address validation, the returned buffer will be nil and the address +// will be empty. +// The caller must consistently check both values. +func (s *Server) processMsgNextHop( + buf []byte, + underlay netip.Addr, + prevHop netip.AddrPort, +) ([]byte, netip.AddrPort, error) { + + err := s.parser.DecodeLayers(buf, &s.decoded) if err != nil { - return nil, 0, err + log.Error("Decoding layers", "err", err) + return nil, netip.AddrPort{}, nil } - var ovConn net.PacketConn - if address.IP.To4() == nil { - ovConn = as.ipv6Conn - } else { - ovConn = as.ipv4Conn + if len(s.decoded) < 2 { + log.Error("Unexpected packet", "layers decoded", len(s.decoded)) + return nil, netip.AddrPort{}, nil } - conn := &Conn{ - conn: ovConn, - ring: tableEntry.appIngressRing, - regReference: ref, + err = s.outBuffer.Clear() + if err != nil { + return nil, netip.AddrPort{}, err } - return conn, uint16(ref.UDPAddr().Port), nil -} -func (as *Server) Close() { - as.ipv4Conn.Close() - as.ipv6Conn.Close() -} + // If the dispatcher feature flag is disabled we only process SCMPInfo packets. + if !s.isDispatcher { + if s.decoded[len(s.decoded)-1] != slayers.LayerTypeSCMP { + log.Debug("Dispatcher feature is disabled, shim discards non-SCMPInfo packets", + "received", s.decoded[len(s.decoded)-1]) + return nil, netip.AddrPort{}, nil + } + if s.scmpLayer.TypeCode.Type() != slayers.SCMPTypeTracerouteRequest && + s.scmpLayer.TypeCode.Type() != slayers.SCMPTypeEchoRequest { + log.Debug("Dispatcher feature is disabled, shim discards non-SCMPInfo packets", + "received", s.scmpLayer.TypeCode.Type()) + return nil, netip.AddrPort{}, nil + } + } -// Conn represents a connection bound to a specific SCION port/SVC. -type Conn struct { - // conn is used to send packets. - conn net.PacketConn - // ring is used to retrieve incoming packets. - ring *ringbuf.Ring - // regReference is the reference to the registration in the routing table. - regReference registration.RegReference -} + var dstAddrPort netip.AddrPort + // Retrieve DST UDP/SCION addr and compare to underlay address if it applies, + // i.e., all cases expect SCMPInfo request messages, which are to be replied + // by the shim dispatcher itself. + switch s.decoded[len(s.decoded)-1] { + case slayers.LayerTypeSCMP: + // send response to BR + if s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeTracerouteRequest || + s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeEchoRequest { + dstAddrPort = prevHop + } else { // relay to end application + dstAddrPort, err = s.getDstSCMP() + if err != nil { + log.Error("Getting destination for SCMP message", "err", err) + return nil, netip.AddrPort{}, nil + } + if dstAddrPort.Addr().Unmap().Compare(underlay.Unmap()) != 0 { + log.Error("UDP/IP addr destination different from UDP/SCION addr", + "UDP/IP:", underlay.Unmap().String(), + "UDP/SCION:", dstAddrPort.Addr().Unmap().String()) + return nil, netip.AddrPort{}, nil + } + } + case slayers.LayerTypeSCIONUDP: + dstAddrPort, err = s.getDstSCIONUDP() + if err != nil { + log.Error("Getting destination for SCION/UDP message", "err", err) + return nil, netip.AddrPort{}, nil + } + if dstAddrPort.Addr().Unmap().Compare(underlay.Unmap()) != 0 { + log.Error("UDP/IP addr destination different from UDP/SCION addr", + "UDP/IP:", underlay.Unmap().String(), + "UDP/SCION:", dstAddrPort.Addr().Unmap().String()) + return nil, netip.AddrPort{}, nil + } + } + + var outBuf []byte + // generate SCMPInfo response + if s.decoded[len(s.decoded)-1] == slayers.LayerTypeSCMP && + (s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeTracerouteRequest || + s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeEchoRequest) { + err = s.replyToSCMPInfoRequest() + if err != nil { + log.Error("Reversing SCMP information", "err", err) + return nil, netip.AddrPort{}, nil + } + payload := gopacket.Payload(s.scmpLayer.Payload) + err = payload.SerializeTo(s.outBuffer, s.options) + if err != nil { + log.Error("Serializing payload", "err", err) + return nil, netip.AddrPort{}, nil + } + s.outBuffer.PushLayer(payload.LayerType()) + + err = s.scmpLayer.SerializeTo(s.outBuffer, s.options) + if err != nil { + log.Error("Serializing SCMP header", "err", err) + return nil, netip.AddrPort{}, nil + } + s.outBuffer.PushLayer(s.scmpLayer.LayerType()) + + if s.decoded[len(s.decoded)-2] == slayers.LayerTypeEndToEndExtn { + err = s.e2e.SerializeTo(s.outBuffer, s.options) + if err != nil { + log.Error("Serializing e2e extension", "err", err) + return nil, netip.AddrPort{}, nil + } + s.outBuffer.PushLayer(s.e2e.LayerType()) + } + err = s.scionLayer.SerializeTo(s.outBuffer, s.options) + if err != nil { + log.Error("Serializing SCION header", "err", err) + return nil, netip.AddrPort{}, nil + } + s.outBuffer.PushLayer(s.scionLayer.LayerType()) + outBuf = s.outBuffer.Bytes() + } else { //forward incoming byte array + outBuf = buf + } -func (ac *Conn) WriteTo(p []byte, addr net.Addr) (int, error) { - panic("not implemented") + return outBuf, dstAddrPort, nil } -// Write is optimized for the use by ConnHandler (avoids reparsing the packet). -func (ac *Conn) Write(pkt *respool.Packet) (int, error) { - // XXX(roosd): Ignore error since there is nothing we can do about it. - // Currently, the ID space is shared between all applications that register - // with the dispatcher. Likelihood that they overlap is very small. - // If this becomes ever a problem, we can namespace the ID per registered - // application. - _ = registerIfSCMPInfo(ac.regReference, pkt) - return pkt.SendOnConn(ac.conn, pkt.UnderlayRemote) +func (s *Server) replyToSCMPInfoRequest() error { + // Translate request to a reply. + switch s.scmpLayer.NextLayerType() { + case slayers.LayerTypeSCMPEcho: + s.scmpLayer.TypeCode = slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0) + case slayers.LayerTypeSCMPTraceroute: + s.scmpLayer.TypeCode = slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0) + default: + return serrors.New("unsupported SCMP informational message") + } + if err := s.reverseSCION(); err != nil { + return err + } + // XXX(roosd): This does not take HBH and E2E extensions into consideration. + // See: https://github.com/scionproto/scion/issues/4128 + // TODO(JordiSubira): Add support for SPAO-E2E + s.scionLayer.NextHdr = slayers.L4SCMP + return nil } -func (ac *Conn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - pkt := ac.Read() - if pkt == nil { - return 0, nil, serrors.New("Connection closed") +func (s *Server) reverseSCION() error { + // Reverse the SCION packet. + s.scionLayer.DstIA, s.scionLayer.SrcIA = s.scionLayer.SrcIA, s.scionLayer.DstIA + src, err := s.scionLayer.SrcAddr() + if err != nil { + return serrors.WrapStr("parsing source address", err) + } + dst, err := s.scionLayer.DstAddr() + if err != nil { + return serrors.WrapStr("parsing destination address", err) } - n = pkt.CopyTo(p) - addr = pkt.UnderlayRemote - pkt.Free() - return + if err := s.scionLayer.SetSrcAddr(dst); err != nil { + return serrors.WrapStr("setting source address", err) + } + if err := s.scionLayer.SetDstAddr(src); err != nil { + return serrors.WrapStr("setting destination address", err) + } + if s.scionLayer.PathType == epic.PathType { + // Received packet with EPIC path type, hence extract the SCION path + epicPath, ok := s.scionLayer.Path.(*epic.Path) + if !ok { + return serrors.New("path type and path data do not match") + } + s.scionLayer.Path = epicPath.ScionPath + s.scionLayer.PathType = scion.PathType + } + if s.scionLayer.Path, err = s.scionLayer.Path.Reverse(); err != nil { + return serrors.WrapStr("reversing path", err) + } + return nil } -// Read is optimized for the use by ConnHandler (avoids one copy). -func (ac *Conn) Read() *respool.Packet { - entries := make(ringbuf.EntryList, 1) - n, _ := ac.ring.Read(entries, true) - if n < 0 { - // Ring was closed because app shut down its data socket. - return nil +func (s *Server) getDstSCMP() (netip.AddrPort, error) { + // Check if its SCMPEcho or SCMPTraceroute reply + if s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeEchoReply { + var scmpEcho slayers.SCMPEcho + err := scmpEcho.DecodeFromBytes(s.scmpLayer.Payload, gopacket.NilDecodeFeedback) + if err != nil { + return netip.AddrPort{}, err + } + return addrPortFromBytes(s.scionLayer.RawDstAddr, scmpEcho.Identifier) + } + if s.scmpLayer.TypeCode.Type() == slayers.SCMPTypeTracerouteReply { + var scmpTraceroute slayers.SCMPTraceroute + err := scmpTraceroute.DecodeFromBytes(s.scmpLayer.Payload, gopacket.NilDecodeFeedback) + if err != nil { + return netip.AddrPort{}, err + } + return addrPortFromBytes(s.scionLayer.RawDstAddr, scmpTraceroute.Identifier) + } + + // Drop unknown SCMP error messages. + if s.scmpLayer.NextLayerType() == gopacket.LayerTypePayload { + return netip.AddrPort{}, serrors.New("unsupported SCMP error message", + "type", s.scmpLayer.TypeCode.Type()) + } + l, err := decodeSCMP(&s.scmpLayer) + if err != nil { + return netip.AddrPort{}, err + } + if len(l) != 2 { + return netip.AddrPort{}, serrors.New("SCMP error message without payload") + } + gpkt := gopacket.NewPacket(*l[1].(*gopacket.Payload), slayers.LayerTypeSCION, + gopacket.DecodeOptions{ + NoCopy: true, + }, + ) + + // If the offending packet was UDP/SCION, use the source port to deliver. + if udp := gpkt.Layer(slayers.LayerTypeSCIONUDP); udp != nil { + port := udp.(*slayers.UDP).SrcPort + // XXX(roosd): We assume that the zero value means the UDP header is + // truncated. This flags packets of misbehaving senders as truncated, if + // they set the source port to 0. But there is no harm, since those + // packets are destined to be dropped anyway. + if port == 0 { + return netip.AddrPort{}, serrors.New("SCMP error with truncated UDP header") + } + return addrPortFromBytes(s.scionLayer.RawDstAddr, port) + } + + // If the offending packet was SCMP/SCION, and it is an echo or traceroute, + // use the Identifier to deliver. In all other cases, the message is dropped. + if scmp := gpkt.Layer(slayers.LayerTypeSCMP); scmp != nil { + + tc := scmp.(*slayers.SCMP).TypeCode + // SCMP Error messages in response to an SCMP error message are not allowed. + if !tc.InfoMsg() { + return netip.AddrPort{}, + serrors.New("SCMP error message in response to SCMP error message", + "type", tc.Type()) + } + // We only support echo and traceroute requests. + t := tc.Type() + if t != slayers.SCMPTypeEchoRequest && t != slayers.SCMPTypeTracerouteRequest { + return netip.AddrPort{}, serrors.New("unsupported SCMP info message", "type", t) + } + + var port uint16 + // Extract the port from the echo or traceroute ID field. + if echo := gpkt.Layer(slayers.LayerTypeSCMPEcho); echo != nil { + port = echo.(*slayers.SCMPEcho).Identifier + } else if tr := gpkt.Layer(slayers.LayerTypeSCMPTraceroute); tr != nil { + port = tr.(*slayers.SCMPTraceroute).Identifier + } else { + return netip.AddrPort{}, serrors.New("SCMP error with truncated payload") + } + return addrPortFromBytes(s.scionLayer.RawDstAddr, port) } - pkt := entries[0].(*respool.Packet) - return pkt + return netip.AddrPort{}, ErrUnsupportedL4 } -func (ac *Conn) Close() error { - ac.regReference.Free() - ac.ring.Close() - return nil +func (s *Server) getDstSCIONUDP() (netip.AddrPort, error) { + host, err := s.scionLayer.DstAddr() + if err != nil { + return netip.AddrPort{}, err + } + switch host.Type() { + case addr.HostTypeSVC: + hostAddr := addr.Addr{IA: s.scionLayer.DstIA, Host: host} + addrPort, ok := s.ServiceAddresses[hostAddr] + if !ok { + return netip.AddrPort{}, serrors.New("SVC destination not found", + "Host", hostAddr) + } + return addrPort, nil + case addr.HostTypeIP: + return addrPortFromBytes(s.scionLayer.RawDstAddr, s.udpLayer.DstPort) + default: + return netip.AddrPort{}, serrors.New("invalid host type", "type", host.Type().String()) + } } -func (ac *Conn) LocalAddr() net.Addr { - return ac.regReference.UDPAddr() +type controlMessageParser interface { + Destination() net.IP + Parse(b []byte) error + String() string } -func (ac *Conn) SVCAddr() addr.SVC { - return ac.regReference.SVCAddr() +type ipv4ControlMessage struct { + *ipv4.ControlMessage } -func (ac *Conn) SetDeadline(t time.Time) error { - panic("not implemented") +func (m ipv4ControlMessage) Destination() net.IP { + return m.Dst } -func (ac *Conn) SetReadDeadline(t time.Time) error { - panic("not implemented") +type ipv6ControlMessage struct { + *ipv6.ControlMessage } -func (ac *Conn) SetWriteDeadline(t time.Time) error { - panic("not implemented") +func (m ipv6ControlMessage) Destination() net.IP { + return m.Dst } -// openConn opens an underlay socket that tracks additional socket information -// such as packets dropped due to buffer full. -// -// Note that Go-style dual-stacked IPv4/IPv6 connections are not supported. If -// network is udp, it will be treated as udp4. -func openConn(network, address string) (net.PacketConn, error) { - // We cannot allow the Go standard library to open both types of sockets - // because the socket options are specific to only one socket type, so we - // degrade udp to only udp4. - if network == "udp" { - network = "udp4" - } - listeningAddress, err := net.ResolveUDPAddr(network, address) - if err != nil { - return nil, serrors.WrapStr("unable to construct UDP addr", err) - } - if network == "udp4" && listeningAddress.IP == nil { - listeningAddress.IP = net.IPv4zero - } - if network == "udp6" && listeningAddress.IP == nil { - listeningAddress.IP = net.IPv6zero +// parseUnderlayAddr returns the underlay destination address on the outer UDP/IP wrapper. +// It returns an empty address, if the control message information is not present +// or it cannot be parsed. +// This is useful for checking that this address corresponds to the address of the inner +// UDP/SCION header. This refers to the safeguard for traffic reflection as discussed in: +// https://github.com/scionproto/scion/pull/4280#issuecomment-1775177351 +func (s *Server) parseUnderlayAddr(oobuffer []byte) netip.Addr { + if err := s.cmParser.Parse(oobuffer); err != nil { + log.Error("Parsing Control Message Information", "err", err) + return netip.Addr{} } - - c, err := conn.New(listeningAddress, nil, &conn.Config{ - SendBufferSize: SendBufferSize, - ReceiveBufferSize: ReceiveBufferSize, - }) - if err != nil { - return nil, serrors.WrapStr("unable to open conn", err) + if !s.cmParser.Destination().IsUnspecified() { + pktAddr, ok := netip.AddrFromSlice(s.cmParser.Destination()) + if !ok { + log.Error("Getting DST from IP_PKTINFO", "DST", s.cmParser.Destination()) + return netip.Addr{} + } + return pktAddr } - - return &underlayConnWrapper{Conn: c}, nil + log.Error("Destination in IP_PKTINFO is unspecified") + return netip.Addr{} } -// registerIfSCMPInfo registers the ID of the SCMP request if it is an echo or -// traceroute message. -func registerIfSCMPInfo(ref registration.RegReference, pkt *respool.Packet) error { - if pkt.L4 != slayers.LayerTypeSCMP { - return nil - } - t := pkt.SCMP.TypeCode.Type() - if t != slayers.SCMPTypeEchoRequest && t != slayers.SCMPTypeTracerouteRequest { - return nil - } - id, err := extractSCMPIdentifier(&pkt.SCMP) +func ListenAndServe( + isDispatcher bool, + svcAddrs map[addr.Addr]netip.AddrPort, + addr *net.UDPAddr, +) error { + + conn, err := net.ListenUDP(addr.Network(), addr) if err != nil { return err } - // FIXME(roosd): add metrics again. - return ref.RegisterID(uint64(id)) -} + defer conn.Close() + log.Debug(fmt.Sprintf("local address: %s", conn.LocalAddr())) + dispServer := NewServer(isDispatcher, svcAddrs, conn) -// underlayConnWrapper wraps a specialized underlay conn into a net.PacketConn -// implementation. Only *net.UDPAddr addressing is supported. -type underlayConnWrapper struct { - // Conn is the wrapped underlay connection object. - conn.Conn + return dispServer.Serve() } -func (o *underlayConnWrapper) ReadFrom(p []byte) (int, net.Addr, error) { - return o.Conn.ReadFrom(p) +// decodeSCMP decodes the SCMP payload. WARNING: Decoding is done with NoCopy set. +func decodeSCMP(scmp *slayers.SCMP) ([]gopacket.SerializableLayer, error) { + gpkt := gopacket.NewPacket(scmp.Payload, scmp.NextLayerType(), + gopacket.DecodeOptions{NoCopy: true}) + layers := gpkt.Layers() + if len(layers) == 0 || len(layers) > 2 { + return nil, serrors.New("invalid number of SCMP layers", "count", len(layers)) + } + ret := make([]gopacket.SerializableLayer, len(layers)) + for i, l := range layers { + s, ok := l.(gopacket.SerializableLayer) + if !ok { + return nil, serrors.New("invalid SCMP layer, not serializable", "index", i) + } + ret[i] = s + } + return ret, nil } -func (o *underlayConnWrapper) WriteTo(p []byte, a net.Addr) (int, error) { - udpAddr, ok := a.(*net.UDPAddr) +func addrPortFromBytes(addr []byte, port uint16) (netip.AddrPort, error) { + a, ok := netip.AddrFromSlice(addr) if !ok { - return 0, serrors.New("address is not UDP", "addr", a) + return netip.AddrPort{}, serrors.New("unexpected raw address byte slice format") } - return o.Conn.WriteTo(p, udpAddr) + return netip.AddrPortFrom(a, port), nil } -func (o *underlayConnWrapper) Close() error { - return o.Conn.Close() -} - -func (o *underlayConnWrapper) LocalAddr() net.Addr { - return o.Conn.LocalAddr() -} - -func (o *underlayConnWrapper) SetDeadline(t time.Time) error { - return o.Conn.SetDeadline(t) -} +// setIPPktInfo sets the IP_PKTINFO.DST flag to the underlay socket. The IPv4 part +// covers the case for IPv4-only hosts. For hosts supporting dual stack, the IPv6 +// part handles both 6 and 4 (with mapped addresses). +// The argument conn must not be nil. The returned conn will have the flag set, +// and the returned controlMessageParser can be used as a facilitator to +// parse the OOB after reading on the conn. +func setIPPktInfo(conn *net.UDPConn) (*net.UDPConn, controlMessageParser) { + udpAddr, ok := conn.LocalAddr().(*net.UDPAddr) + if !ok { + panic(fmt.Sprintln("Connection address is not UDPAddr", + "conn", conn.LocalAddr().Network())) + } -func (o *underlayConnWrapper) SetReadDeadline(t time.Time) error { - return o.Conn.SetReadDeadline(t) -} + var cm controlMessageParser + if udpAddr.AddrPort().Addr().Unmap().Is4() { + err := ipv4.NewPacketConn(conn).SetControlMessage(ipv4.FlagDst, true) + if err != nil { + panic(fmt.Sprintf("cannot set IP_PKTINFO on socket: %s", err)) + } + cm = ipv4ControlMessage{ + ControlMessage: new(ipv4.ControlMessage), + } + } + if udpAddr.AddrPort().Addr().Unmap().Is6() { + err := ipv6.NewPacketConn(conn).SetControlMessage(ipv6.FlagDst, true) + if err != nil { + panic(fmt.Sprintf("cannot set IP_PKTINFO on socket: %s", err)) + } + cm = ipv6ControlMessage{ + ControlMessage: new(ipv6.ControlMessage), + } + } -func (o *underlayConnWrapper) SetWriteDeadline(t time.Time) error { - return o.Conn.SetWriteDeadline(t) + return conn, cm } diff --git a/dispatcher/dispatcher_test.go b/dispatcher/dispatcher_test.go new file mode 100644 index 0000000000..a3905a2712 --- /dev/null +++ b/dispatcher/dispatcher_test.go @@ -0,0 +1,390 @@ +// Copyright 2023 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dispatcher + +import ( + "net" + "net/netip" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/private/xtest" + "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" +) + +type testCase struct { + Name string + IsDispatcher bool + ClientAddrPort netip.AddrPort + DispAddrPort netip.AddrPort + Pkt *snet.Packet + ExpectedValue bool +} + +func testRunTestCase(t *testing.T, tc testCase) { + serverConn, err := net.ListenUDP("udp", net.UDPAddrFromAddrPort(tc.DispAddrPort)) + require.NoError(t, err) + defer serverConn.Close() + setIPPktInfo(serverConn) + emptyTopo := make(map[addr.Addr]netip.AddrPort) + server := NewServer(tc.IsDispatcher, emptyTopo, serverConn) + + clientConn, err := net.DialUDP( + "udp", + net.UDPAddrFromAddrPort(tc.ClientAddrPort), + net.UDPAddrFromAddrPort(tc.DispAddrPort), + ) + require.NoError(t, err) + defer clientConn.Close() + require.NoError(t, tc.Pkt.Serialize()) + _, err = clientConn.Write(tc.Pkt.Bytes) + require.NoError(t, err) + + buf := make([]byte, 1024) + oobuf := make([]byte, 1024) + n, nn, _, nextHop, err := server.conn.ReadMsgUDPAddrPort(buf, oobuf) + require.NoError(t, err) + var underlayAddr netip.Addr + if tc.IsDispatcher { + underlayAddr = server.parseUnderlayAddr(oobuf[:nn]) + require.NotNil(t, underlayAddr) + } + _, dstAddr, err := server.processMsgNextHop(buf[:n], underlayAddr, nextHop) + assert.NoError(t, err) + assert.Equal(t, tc.ExpectedValue, dstAddr.IsValid()) +} + +func TestValidateAddr(t *testing.T) { + clientAddr := netip.MustParseAddr("127.0.0.1") + clientHost := addr.HostIP(clientAddr) + clientAddrPort := netip.AddrPortFrom(clientAddr, 0) + dispIPv4Addr := netip.MustParseAddr("127.0.0.1") + dispIPv4Host := addr.HostIP(dispIPv4Addr) + dispIPv4AddrPort := netip.AddrPortFrom(dispIPv4Addr, 40032) + clientIPv6Addr := netip.MustParseAddr("::1") + clientIPv6Host := addr.HostIP(clientIPv6Addr) + clientIPv6AddrPort := netip.AddrPortFrom(clientIPv6Addr, 0) + dispIPv6Addr := netip.MustParseAddr("::1") + dispIPv6Host := addr.HostIP(dispIPv6Addr) + dispIPv6AddrPort := netip.AddrPortFrom(dispIPv6Addr, 40032) + + testCases := []testCase{ + { + Name: "valid UDP/IPv4", + IsDispatcher: true, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: dispIPv4Host, + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid UDP/IPv4", + IsDispatcher: true, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.MustParseHost("127.0.0.2"), + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + { + Name: "valid SCMP/IPv4", + IsDispatcher: true, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: dispIPv4Host, + }, + Payload: snet.SCMPDestinationUnreachable{ + Payload: MustPack(snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: dispIPv4Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: clientHost, + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid SCMP/IPv4", + IsDispatcher: true, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.MustParseHost("127.0.0.2"), + }, + Payload: snet.SCMPDestinationUnreachable{ + Payload: MustPack(snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: dispIPv4Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: clientHost, + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + { + Name: "valid UDP/IPv6", + IsDispatcher: true, + ClientAddrPort: clientIPv6AddrPort, + DispAddrPort: dispIPv6AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientIPv6Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: dispIPv6Host, + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid UDP/IPv6", + IsDispatcher: true, + ClientAddrPort: clientIPv6AddrPort, + DispAddrPort: dispIPv6AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.MustParseHost("::2"), + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + { + Name: "valid SCMP/IPv6", + IsDispatcher: true, + ClientAddrPort: clientIPv6AddrPort, + DispAddrPort: dispIPv6AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientIPv6Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: dispIPv6Host, + }, + Payload: snet.SCMPDestinationUnreachable{ + Payload: MustPack(snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: dispIPv6Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: clientIPv6Host, + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "invalid SCMP/IPv6", + IsDispatcher: true, + ClientAddrPort: clientIPv6AddrPort, + DispAddrPort: dispIPv6AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientIPv6Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.MustParseHost("::2"), + }, + Payload: snet.SCMPDestinationUnreachable{ + Payload: MustPack(snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: dispIPv6Host, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: clientIPv6Host, + }, + Payload: snet.SCMPEchoRequest{Identifier: 0xdead}, + Path: path.Empty{}, + }, + }), + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + { + Name: "IPv4-mapped-IPv6 to IPv4", + IsDispatcher: true, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: addr.HostIP(netip.AddrFrom16(dispIPv4Addr.As16())), + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: true, + }, + { + Name: "isn't dispatcher", + IsDispatcher: false, + ClientAddrPort: clientAddrPort, + DispAddrPort: dispIPv4AddrPort, + Pkt: &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Source: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:2"), + Host: clientHost, + }, + Destination: snet.SCIONAddress{ + IA: xtest.MustParseIA("1-ff00:0:1"), + Host: dispIPv4Host, + }, + Payload: snet.UDPPayload{ + SrcPort: 20001, + DstPort: 40001, + }, + Path: path.Empty{}, + }, + }, + ExpectedValue: false, + }, + } + for _, test := range testCases { + t.Run(test.Name, func(t *testing.T) { + testRunTestCase(t, test) + }) + } + +} + +func MustPack(pkt snet.Packet) []byte { + if err := pkt.Serialize(); err != nil { + panic(err) + } + return pkt.Bytes +} diff --git a/dispatcher/internal/metrics/BUILD.bazel b/dispatcher/internal/metrics/BUILD.bazel deleted file mode 100644 index 1acf5d718d..0000000000 --- a/dispatcher/internal/metrics/BUILD.bazel +++ /dev/null @@ -1,12 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["metrics.go"], - importpath = "github.com/scionproto/scion/dispatcher/internal/metrics", - visibility = ["//dispatcher:__subpackages__"], - deps = [ - "//pkg/private/prom:go_default_library", - "@com_github_prometheus_client_golang//prometheus:go_default_library", - ], -) diff --git a/dispatcher/internal/metrics/metrics.go b/dispatcher/internal/metrics/metrics.go deleted file mode 100644 index 37903fc63f..0000000000 --- a/dispatcher/internal/metrics/metrics.go +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "github.com/prometheus/client_golang/prometheus" - - "github.com/scionproto/scion/pkg/private/prom" -) - -// Namespace is the metrics namespace for the dispatcher. -const Namespace = "disp" - -// Packet result labels -const ( - PacketResultParseError = "parse_error" - PacketResultRouteNotFound = "route_not_found" - PacketResultOk = "ok" -) - -var ( - // M exposes all the initialized metrics for this package. - M = newMetrics() -) - -// IncomingPacket contains the labels for incoming packet metrics. -type IncomingPacket struct { - Result string -} - -// Labels returns the list of labels. -func (l IncomingPacket) Labels() []string { - return []string{"incoming_packet_result"} -} - -// Values returns the label values in the order defined by Labels. -func (l IncomingPacket) Values() []string { - return []string{l.Result} -} - -// SVC contains the labels for SVC-related metrics. -type SVC struct { - Type string -} - -// Labels returns the list of labels. -func (l SVC) Labels() []string { - return []string{"type"} -} - -// Values returns the label values in the order defined by Labels. -func (l SVC) Values() []string { - return []string{l.Type} -} - -// SCMP contains the labels for SCMP-related metrics. -type SCMP struct { - Class string - Type string -} - -// Labels returns the list of labels. -func (l SCMP) Labels() []string { - return []string{"class", "type"} -} - -// Values returns the label values in the order defined by Labels. -func (l SCMP) Values() []string { - return []string{"class", "type"} -} - -type metrics struct { - netWriteBytes prometheus.Counter - netWritePkts prometheus.Counter - netWriteErrors prometheus.Counter - netReadBytes prometheus.Counter - netReadPkts *prometheus.CounterVec - netReadParseErrors prometheus.Counter - appWriteBytes prometheus.Counter - appWritePkts prometheus.Counter - appWriteErrors prometheus.Counter - appReadBytes prometheus.Counter - appReadPkts prometheus.Counter - appReadErrors prometheus.Counter - openSockets *prometheus.GaugeVec - appConnErrors prometheus.Counter - scmpReadPkts *prometheus.CounterVec - scmpWritePkts *prometheus.CounterVec - appNotFoundErrors prometheus.Counter - appWriteSVCPkts *prometheus.CounterVec - netReadOverflows prometheus.Counter -} - -func newMetrics() metrics { - return metrics{ - netWriteBytes: prom.NewCounter(Namespace, "", "net_write_bytes_total", - "Total bytes sent on the network."), - netWritePkts: prom.NewCounter(Namespace, "", "net_write_pkts_total", - "Total packets sent on the network."), - netWriteErrors: prom.NewCounter(Namespace, "", "net_write_errors_total", - "Network packet send errors"), - netReadBytes: prom.NewCounter(Namespace, "", "net_read_bytes_total", - "Total bytes received from the network irrespective of packet outcome."), - netReadPkts: prom.NewCounterVecWithLabels(Namespace, "", "net_read_pkts_total", - "Total packets received from the network.", IncomingPacket{}), - netReadParseErrors: prom.NewCounter(Namespace, "", "net_read_parse_errors_total", - "Total network packet parse error"), - appWriteBytes: prom.NewCounter(Namespace, "", "app_write_bytes_total", - "Total bytes sent to applications."), - appWritePkts: prom.NewCounter(Namespace, "", "app_write_pkts_total", - "Total packets sent to applications."), - appWriteErrors: prom.NewCounter(Namespace, "", "app_write_errors_total", - "Send packet to applications errors."), - appReadBytes: prom.NewCounter(Namespace, "", "app_read_bytes_total", - "Total bytes read from applications."), - appReadPkts: prom.NewCounter(Namespace, "", "app_read_pkts_total", - "Total packets read from applications"), - appReadErrors: prom.NewCounter(Namespace, "", "app_read_errors_total", - "Total errors when reading packets from applications."), - openSockets: prom.NewGaugeVecWithLabels(Namespace, "", "app_sockets_open", - "Number of sockets currently opened by applications.", SVC{}), - appConnErrors: prom.NewCounter(Namespace, "", "app_conn_reg_errors_total", - "Application socket registration errors"), - scmpReadPkts: prom.NewCounterVecWithLabels(Namespace, "", "scmp_read_pkts_total", - "Total SCMP packets received from the network.", SCMP{}), - scmpWritePkts: prom.NewCounterVecWithLabels(Namespace, "", "scmp_write_pkts_total", - "Total SCMP packets received from the network.", SCMP{}), - appNotFoundErrors: prom.NewCounter(Namespace, "", "app_not_found_errors_total", - "Number of packets for which the destination application was not found."), - appWriteSVCPkts: prom.NewCounterVecWithLabels(Namespace, "", "app_write_svc_pkts_total", - "Total SVC packets delivered to applications", SVC{}), - netReadOverflows: prom.NewCounter(Namespace, "", "net_read_overflow_pkts_total", - "Total ingress packets that were dropped on the OS socket"), - } -} - -func (m metrics) NetWriteBytes() prometheus.Counter { - return m.netWriteBytes -} - -func (m metrics) NetWritePkts() prometheus.Counter { - return m.netWritePkts -} - -func (m metrics) NetReadBytes() prometheus.Counter { - return m.netReadBytes -} - -func (m metrics) NetReadPkts(labels IncomingPacket) prometheus.Counter { - return m.netReadPkts.WithLabelValues(labels.Values()...) -} - -func (m metrics) NetReadParseErrors() prometheus.Counter { - return m.netReadParseErrors -} - -func (m metrics) AppWriteBytes() prometheus.Counter { - return m.appWriteBytes -} - -func (m metrics) AppWritePkts() prometheus.Counter { - return m.appWritePkts -} - -func (m metrics) AppWriteErrors() prometheus.Counter { - return m.appWriteErrors -} - -func (m metrics) AppReadBytes() prometheus.Counter { - return m.appReadBytes -} - -func (m metrics) AppReadPkts() prometheus.Counter { - return m.appReadPkts -} - -func (m metrics) AppReadErrors() prometheus.Counter { - return m.appReadErrors -} - -func (m metrics) OpenSockets(labels SVC) prometheus.Gauge { - return m.openSockets.WithLabelValues(labels.Values()...) -} - -func (m metrics) AppConnErrors() prometheus.Counter { - return m.appConnErrors -} - -func (m metrics) NetWriteErrors() prometheus.Counter { - return m.netWriteErrors -} - -// SCMPReadPackets returns the metrics counters for SCMP packets read from the network. -func (m metrics) SCMPReadPkts(labels SCMP) prometheus.Counter { - return m.scmpReadPkts.WithLabelValues(labels.Values()...) -} - -// SCMPWritePkts returns the metrics counters for SCMP packets written to the network. -func (m metrics) SCMPWritePkts(labels SCMP) prometheus.Counter { - return m.scmpWritePkts.WithLabelValues(labels.Values()...) -} - -func (m metrics) AppNotFoundErrors() prometheus.Counter { - return m.appNotFoundErrors -} - -func (m metrics) AppWriteSVCPkts(labels SVC) prometheus.Counter { - return m.appWriteSVCPkts.WithLabelValues(labels.Values()...) -} - -func (m metrics) NetReadOverflows() prometheus.Counter { - return m.netReadOverflows -} diff --git a/dispatcher/internal/registration/BUILD.bazel b/dispatcher/internal/registration/BUILD.bazel deleted file mode 100644 index 70dc5a3245..0000000000 --- a/dispatcher/internal/registration/BUILD.bazel +++ /dev/null @@ -1,43 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "errors.go", - "iatable.go", - "portlist.go", - "scmp_table.go", - "svctable.go", - "table.go", - "udptable.go", - ], - importpath = "github.com/scionproto/scion/dispatcher/internal/registration", - visibility = ["//dispatcher:__subpackages__"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/private/common:go_default_library", - "//pkg/private/serrors:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "bench_test.go", - "generators_test.go", - "iatable_test.go", - "portlist_test.go", - "scmp_table_test.go", - "svctable_test.go", - "table_test.go", - "udptable_test.go", - ], - embed = [":go_default_library"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/private/xtest:go_default_library", - "@com_github_smartystreets_goconvey//convey:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - "@com_github_stretchr_testify//require:go_default_library", - ], -) diff --git a/dispatcher/internal/registration/bench_test.go b/dispatcher/internal/registration/bench_test.go deleted file mode 100644 index 177791c6a5..0000000000 --- a/dispatcher/internal/registration/bench_test.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "testing" - - "github.com/scionproto/scion/pkg/addr" -) - -type registerArgs struct { - ia addr.IA - public *net.UDPAddr - bind net.IP - svc addr.SVC - value interface{} -} - -func generateRegisterArgs(n int) []*registerArgs { - var data []*registerArgs - for i := 0; i < n; i++ { - newData := ®isterArgs{ - ia: getRandomIA(), - public: getRandomUDPAddress(), - bind: getRandomIPv4(), - svc: getRandomSVC(), - value: getRandomValue(), - } - data = append(data, newData) - } - return data -} - -func generateLookupPublicArgs(n int) []*net.UDPAddr { - var data []*net.UDPAddr - for i := 0; i < n; i++ { - data = append(data, getRandomUDPAddress()) - } - return data -} - -func BenchmarkRegister(b *testing.B) { - table := NewIATable(minPort, maxPort) - regData := generateRegisterArgs(b.N) - b.ResetTimer() - for n := 0; n < b.N; n++ { - _, _ = table.Register(regData[n].ia, regData[n].public, nil, - addr.SvcNone, regData[n].value) - } -} - -func BenchmarkLookupPublicIPv4(b *testing.B) { - numEntries := 1000 - table := NewIATable(minPort, maxPort) - regData := generateRegisterArgs(numEntries) - for i := 0; i < numEntries; i++ { - _, _ = table.Register(regData[i].ia, regData[i].public, nil, - addr.SvcNone, regData[i].value) - } - lookupData := generateLookupPublicArgs(b.N) - b.ResetTimer() - for n := 0; n < b.N; n++ { - table.LookupPublic(addr.MustIAFrom(1, 1), lookupData[n]) - } -} - -type lookupServiceArgs struct { - svc addr.SVC - bind net.IP -} - -func generateLookupServiceArgs(n int) []*lookupServiceArgs { - var data []*lookupServiceArgs - for i := 0; i < n; i++ { - data = append(data, &lookupServiceArgs{ - svc: getRandomSVC(), - bind: getRandomIPv4(), - }) - } - return data -} - -func BenchmarkLookupServiceIPv4(b *testing.B) { - numEntries := 1000 - table := NewIATable(minPort, maxPort) - regData := generateRegisterArgs(numEntries) - for i := 0; i < numEntries; i++ { - _, _ = table.Register(regData[i].ia, regData[i].public, regData[i].bind, - regData[i].svc, regData[i].value) - } - lookupData := generateLookupServiceArgs(b.N) - b.ResetTimer() - for n := 0; n < b.N; n++ { - table.LookupService(addr.MustIAFrom(1, 1), lookupData[n].svc, lookupData[n].bind) - } -} diff --git a/dispatcher/internal/registration/errors.go b/dispatcher/internal/registration/errors.go deleted file mode 100644 index 2ea443a6b0..0000000000 --- a/dispatcher/internal/registration/errors.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import "github.com/scionproto/scion/pkg/private/common" - -const ( - ErrNoPublicAddress common.ErrMsg = "no public address" - ErrBindWithoutSvc common.ErrMsg = "bind address without svc address" - ErrOverlappingAddress common.ErrMsg = "overlapping address" - ErrNoValue common.ErrMsg = "nil value" - ErrZeroIP common.ErrMsg = "zero address" - ErrZeroPort common.ErrMsg = "zero port" - ErrNilAddress common.ErrMsg = "nil address" - ErrSvcNone common.ErrMsg = "svc none" - ErrNoPorts common.ErrMsg = "no free ports" -) diff --git a/dispatcher/internal/registration/generators_test.go b/dispatcher/internal/registration/generators_test.go deleted file mode 100644 index ce12a84c07..0000000000 --- a/dispatcher/internal/registration/generators_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "math/rand" - "net" - "strconv" - - "github.com/scionproto/scion/pkg/addr" -) - -func getRandomUDPAddress() *net.UDPAddr { - return &net.UDPAddr{ - IP: getRandomIPv4(), - Port: getRandomPort(), - } -} - -func getRandomIPv4() net.IP { - b := byte(rand.Intn(4)) - return net.IP{10, 0, 0, b} -} - -func getRandomPort() int { - return rand.Intn(16) -} - -func getRandomValue() string { - return strconv.Itoa(rand.Intn(1 << 16)) -} - -func getRandomIA() addr.IA { - return addr.MustIAFrom( - addr.ISD(rand.Intn(3)+1), - addr.AS(rand.Intn(3)+1), - ) -} - -func getRandomSVC() addr.SVC { - switch rand.Intn(2) { - case 0: - return addr.SvcNone - default: - return addr.SvcCS - } -} diff --git a/dispatcher/internal/registration/iatable.go b/dispatcher/internal/registration/iatable.go deleted file mode 100644 index b677d09d31..0000000000 --- a/dispatcher/internal/registration/iatable.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "sync" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/common" -) - -const ( - ErrBadISD common.ErrMsg = "0 is not valid ISD" - ErrBadAS common.ErrMsg = "0 is not valid AS" -) - -// Reference tracks an object from a collection. -type Reference interface { - // Free removes the object from its parent collection, cleaning up any allocations. - Free() -} - -type RegReference interface { - Reference - // UDPAddr returns the UDP address associated with this reference - UDPAddr() *net.UDPAddr - // SVCAddr returns the SVC address associated with this reference. If no - // SVC address is associated, it returns SvcNone. - SVCAddr() addr.SVC - // RegisterID attaches an SCMP ID to this reference. The ID is released - // when the reference is freed. Registering another ID does not overwrite - // the previous; instead, multiple IDs get associated with the reference. - // SCMP messages targeted at the ID will get sent to the socket associated - // with the reference. The IA of the id is set to the IA of the reference. - RegisterID(id uint64) error -} - -// IATable manages the UDP/IP port registrations for a SCION Dispatcher. -// -// IATable is safe for concurrent use from multiple goroutines. -type IATable interface { - // Register a new entry for AS ia with the specified public, bind and - // services addresses and associate a value with the entry. Lookup calls - // for matching addresses will return the associated value. - // - // A LookupPublic call will select an entry with a matching public address. - // For IPv4, this is either a perfect match or a 0.0.0.0 entry. For IPv6, - // this is either a perfect match or a :: entry. If the public address to - // register matches an existing entry, an error is returned. Using port 0 - // for the public address will allocate a valid port. - // - // A LookupService call will select an entry with matching bind and service - // addresses. Binds for 0.0.0.0 or :: are not allowed. The port is - // inherited from the public address. To not register for a service, use a - // bind of nil and a svc of none. For more information about SVC behavior, - // see the documentation for SVCTable. - // - // To unregister from the table, free the returned reference. - Register(ia addr.IA, public *net.UDPAddr, bind net.IP, svc addr.SVC, - value interface{}) (RegReference, error) - // LookupPublic returns the value associated with the selected public - // address. Wildcard addresses are supported. If an entry is found, the - // returned boolean is set to true. Otherwise, it is set to false. - LookupPublic(ia addr.IA, public *net.UDPAddr) (interface{}, bool) - // LookupService returns the entries associated with svc and bind. - // - // If SVC is an anycast address, at most one entry is returned. The bind - // address is used to narrow down the set of possible entries. If multiple - // entries exist, one is selected arbitrarily. - // - // Note that nil bind addresses are supported for anycasts (the address is - // in this case ignored), but support for this might be dropped in the - // future. - // - // If SVC is a multicast address, more than one entry can be returned. The - // bind address is ignored in this case. - LookupService(ia addr.IA, svc addr.SVC, bind net.IP) []interface{} - // LookupID returns the entry associated with the SCMP General class ID id. - // The ID is used for SCMP Echo, TraceRoute, and RecordPath functionality. - // If an entry is found, the returned boolean is set to true. Otherwise, it - // is set to false. - LookupID(ia addr.IA, id uint64) (interface{}, bool) -} - -// NewIATable creates a new UDP/IP port registration table. -// -// If the public address in a registration contains the port 0, a free port is -// allocated between minPort and maxPort. -// -// If minPort is <= 0 or maxPort is > 65535, the function panics. -func NewIATable(minPort, maxPort int) IATable { - return newIATable(minPort, maxPort) -} - -var _ IATable = (*iaTable)(nil) - -type iaTable struct { - mtx sync.RWMutex - ia map[addr.IA]*Table - minPort int - maxPort int -} - -func newIATable(minPort, maxPort int) *iaTable { - return &iaTable{ - ia: make(map[addr.IA]*Table), - minPort: minPort, - maxPort: maxPort, - } -} - -func (t *iaTable) Register(ia addr.IA, public *net.UDPAddr, bind net.IP, svc addr.SVC, - value interface{}) (RegReference, error) { - - t.mtx.Lock() - defer t.mtx.Unlock() - if ia.ISD() == 0 { - return nil, ErrBadISD - } - if ia.AS() == 0 { - return nil, ErrBadAS - } - table, ok := t.ia[ia] - if !ok { - table = NewTable(t.minPort, t.maxPort) - t.ia[ia] = table - } - reference, err := table.Register(public, bind, svc, value) - if err != nil { - return nil, err - } - return &iaTableReference{ - table: t, - ia: ia, - entryRef: reference, - svc: svc, - value: value, - }, nil -} - -func (t *iaTable) LookupPublic(ia addr.IA, public *net.UDPAddr) (interface{}, bool) { - t.mtx.RLock() - defer t.mtx.RUnlock() - if table, ok := t.ia[ia]; ok { - return table.LookupPublic(public) - } - return nil, false -} - -func (t *iaTable) LookupService(ia addr.IA, svc addr.SVC, bind net.IP) []interface{} { - t.mtx.RLock() - defer t.mtx.RUnlock() - if table, ok := t.ia[ia]; ok { - return table.LookupService(svc, bind) - } - return nil -} - -func (t *iaTable) LookupID(ia addr.IA, id uint64) (interface{}, bool) { - t.mtx.RLock() - defer t.mtx.RUnlock() - if table, ok := t.ia[ia]; ok { - return table.LookupID(id) - } - return nil, false -} - -var _ RegReference = (*iaTableReference)(nil) - -type iaTableReference struct { - table *iaTable - ia addr.IA - entryRef *TableReference - svc addr.SVC - // value is the main table information associated with this reference - value interface{} -} - -func (r *iaTableReference) Free() { - r.table.mtx.Lock() - defer r.table.mtx.Unlock() - r.entryRef.Free() - if r.table.ia[r.ia].Size() == 0 { - delete(r.table.ia, r.ia) - } -} - -func (r *iaTableReference) UDPAddr() *net.UDPAddr { - return r.entryRef.UDPAddr() -} - -func (r *iaTableReference) SVCAddr() addr.SVC { - return r.svc -} - -func (r *iaTableReference) RegisterID(id uint64) error { - r.table.mtx.Lock() - defer r.table.mtx.Unlock() - return r.entryRef.RegisterID(id, r.value) -} diff --git a/dispatcher/internal/registration/iatable_test.go b/dispatcher/internal/registration/iatable_test.go deleted file mode 100644 index f60db2694b..0000000000 --- a/dispatcher/internal/registration/iatable_test.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/xtest" -) - -var ( - public = &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - value = "test value" - ia = xtest.MustParseIA("1-ff00:0:1") -) - -func TestIATable(t *testing.T) { - - t.Run("Given a table with one entry that is only public and no svc", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - assert.NoError(t, err) - assert.NotNil(t, ref) - t.Run("lookups for the same AS", func(t *testing.T) { - retValue, ok := table.LookupPublic(ia, public) - assert.True(t, ok) - assert.Equal(t, retValue, value) - retValues := table.LookupService(ia, addr.SvcCS, net.IP{192, 0, 2, 1}) - assert.Empty(t, retValues) - }) - - t.Run("lookups for a different AS", func(t *testing.T) { - otherIA := xtest.MustParseIA("1-ff00:0:2") - retValue, ok := table.LookupPublic(otherIA, public) - assert.False(t, ok) - assert.Nil(t, retValue) - retValues := table.LookupService(otherIA, addr.SvcCS, net.IP{192, 0, 2, 1}) - assert.Empty(t, retValues) - }) - - t.Run("calling free twice panics", func(t *testing.T) { - ref.Free() - require.Panics(t, ref.Free) - }) - }) - - t.Run("Given a table with one entry that is only public and svc", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - t.Run("lookups for the same AS works", func(t *testing.T) { - ref, err := table.Register(ia, public, nil, addr.SvcCS, value) - assert.NoError(t, err) - assert.NotNil(t, ref) - retValue, ok := table.LookupPublic(ia, public) - assert.True(t, ok) - assert.Equal(t, retValue, value) - retValues := table.LookupService(ia, addr.SvcCS, net.IP{192, 0, 2, 1}) - assert.Equal(t, retValues, []interface{}{value}) - }) - }) -} - -func TestIATableRegister(t *testing.T) { - t.Log("Given an empty table") - - t.Run("ISD zero is error", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(addr.MustIAFrom(0, 1), public, nil, addr.SvcNone, value) - assert.EqualError(t, err, ErrBadISD.Error()) - assert.Nil(t, ref) - }) - - t.Run("AS zero is error", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(addr.MustIAFrom(1, 0), public, nil, addr.SvcNone, value) - assert.EqualError(t, err, ErrBadAS.Error()) - assert.Nil(t, ref) - }) - - t.Run("for a good AS number", func(t *testing.T) { - ia := xtest.MustParseIA("1-ff00:0:1") - t.Run("already registered ports will cause error", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - _, err := table.Register(ia, public, nil, addr.SvcNone, value) - require.NoError(t, err) - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - assert.Error(t, err) - assert.Nil(t, ref) - }) - - t.Run("good ports will return success", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - assert.NoError(t, err) - assert.NotNil(t, ref) - }) - }) -} - -func TestIATableSCMPRegistration(t *testing.T) { - table := NewIATable(minPort, maxPort) - - t.Log("Given a reference to an IATable registration") - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - require.NoError(t, err) - v, ok := table.LookupID(ia, 42) - assert.False(t, ok, "Performing SCMP lookup fails") - assert.Nil(t, v) - err = ref.RegisterID(42) - assert.NoError(t, err, "Registering an SCMP ID on the reference succeeds") -} - -func TestIATableSCMPExistingRegistration(t *testing.T) { - - t.Run("Registering a second SCMP ID on the same reference succeeds", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - require.NoError(t, err) - t.Log("Given an existing SCMP General ID registration") - err = ref.RegisterID(42) - require.NoError(t, err) - - t.Log("Performing an SCMP lookup on the same IA succeeds") - retValue, ok := table.LookupID(ia, 42) - assert.True(t, ok) - assert.Equal(t, retValue, value) - - t.Log("Performing an SCMP lookup on a different IA fails") - retValue, ok = table.LookupID(xtest.MustParseIA("1-ff00:0:2"), 42) - assert.False(t, ok) - assert.Nil(t, retValue) - - t.Log("Freeing the reference makes lookup fail") - ref.Free() - retValue, ok = table.LookupID(ia, 42) - assert.False(t, ok) - assert.Nil(t, retValue) - }) - - t.Run("Registering a second SCMP ID on the same reference succeeds", func(t *testing.T) { - table := NewIATable(minPort, maxPort) - ref, err := table.Register(ia, public, nil, addr.SvcNone, value) - require.NoError(t, err) - t.Log("Given an existing SCMP General ID registration") - err = ref.RegisterID(42) - require.NoError(t, err) - err = ref.RegisterID(43) - assert.NoError(t, err) - - t.Log("Freeing the reference makes lookup on first registered id fail") - ref.Free() - retValue, ok := table.LookupID(ia, 42) - assert.False(t, ok) - assert.Nil(t, retValue) - }) -} diff --git a/dispatcher/internal/registration/portlist.go b/dispatcher/internal/registration/portlist.go deleted file mode 100644 index 4b3a54e6b2..0000000000 --- a/dispatcher/internal/registration/portlist.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "container/ring" -) - -// portList is a linked list of ports with a round-robin getter. -type portList struct { - list *ring.Ring -} - -func newPortList() *portList { - return &portList{} -} - -func (l *portList) Insert(port int, v interface{}) *ring.Ring { - element := ring.New(1) - element.Value = &listItem{port: port, value: v} - if l.list == nil { - l.list = element - } else { - l.list.Link(element) - } - return element -} - -// Get returns an arbitrary object from the list. -// -// The objects are returned in round-robin fashion. Removing an element from -// the list can make the round-robin selection reset from the start. -func (l *portList) Get() interface{} { - if l.list == nil { - return nil - } - v := l.list.Value - l.list = l.list.Next() - return v.(*listItem).value -} - -func (l *portList) Find(port int) bool { - var found bool - l.list.Do( - func(p interface{}) { - if port == p.(*listItem).port { - found = true - } - }, - ) - return found -} - -func (l *portList) Remove(element *ring.Ring) { - if element.Len() == 1 { - l.list = nil - } else { - // always change the l.list since it could be that l.list == element. - l.list = element.Prev() - l.list.Unlink(1) - } -} - -func (l *portList) Len() int { - if l.list == nil { - return 0 - } - return l.list.Len() -} - -type listItem struct { - port int - value interface{} -} diff --git a/dispatcher/internal/registration/portlist_test.go b/dispatcher/internal/registration/portlist_test.go deleted file mode 100644 index 78f2952404..0000000000 --- a/dispatcher/internal/registration/portlist_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2019 Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestEmptyPortList(t *testing.T) { - pl := newPortList() - assert.Nil(t, pl.Get(), "Empty PortList should return nil on Get") - assert.False(t, pl.Find(1)) - assert.Equal(t, 0, pl.Len()) - // Remove can't be tested on empty port list since we don't have a ring to remove. -} - -func TestRemoval(t *testing.T) { - pl := newPortList() - r1 := pl.Insert(1, "1") - pl.Remove(r1) - require.Equal(t, pl.Len(), 0) - - r2 := pl.Insert(2, "2") - require.Equal(t, "2", pl.Get()) - - r1 = pl.Insert(1, "1") - r3 := pl.Insert(3, "3") - expectList(t, pl, "1", "2", "3") - - pl.Remove(r2) - expectList(t, pl, "1", "3") - r2 = pl.Insert(2, "2") - - pl.Remove(r1) - expectList(t, pl, "2", "3") - r1 = pl.Insert(1, "1") - - pl.Remove(r3) - expectList(t, pl, "1", "2") - pl.Remove(r2) - expectList(t, pl, "1") - pl.Remove(r1) - expectList(t, pl) -} - -func expectList(t *testing.T, pl *portList, expected ...string) { - var actual []string - for i := 0; i < pl.Len(); i++ { - actual = append(actual, pl.Get().(string)) - } - require.ElementsMatchf(t, actual, expected, "expected=%s actual=%s", expected, actual) -} diff --git a/dispatcher/internal/registration/scmp_table.go b/dispatcher/internal/registration/scmp_table.go deleted file mode 100644 index 9ea9fd3509..0000000000 --- a/dispatcher/internal/registration/scmp_table.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "github.com/scionproto/scion/pkg/private/serrors" -) - -// SCMPTable tracks SCMP General class IDs. -// -// Attempting to register the same ID more than once will return an error. -type SCMPTable struct { - m map[uint64]interface{} -} - -func NewSCMPTable() *SCMPTable { - return &SCMPTable{m: make(map[uint64]interface{})} -} - -func (t *SCMPTable) Lookup(id uint64) (interface{}, bool) { - value, ok := t.m[id] - return value, ok -} - -func (t *SCMPTable) Register(id uint64, value interface{}) error { - if value == nil { - return serrors.New("cannot register nil value") - } - if _, ok := t.m[id]; ok { - return serrors.New("id already registered", "id", id) - } - t.m[id] = value - return nil -} - -func (t *SCMPTable) Remove(id uint64) { - delete(t.m, id) -} diff --git a/dispatcher/internal/registration/scmp_table_test.go b/dispatcher/internal/registration/scmp_table_test.go deleted file mode 100644 index 5c376615ca..0000000000 --- a/dispatcher/internal/registration/scmp_table_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "testing" - - . "github.com/smartystreets/goconvey/convey" - "github.com/stretchr/testify/require" -) - -func TestSCMPEmptyTable(t *testing.T) { - Convey("Given an empty table", t, func() { - table := NewSCMPTable() - - Convey("Lookup for an id fails", func() { - value, ok := table.Lookup(42) - SoMsg("ok", ok, ShouldBeFalse) - SoMsg("value", value, ShouldBeNil) - }) - Convey("Adding an item succeeds", func() { - value := "test value" - err := table.Register(42, value) - So(err, ShouldBeNil) - }) - Convey("Adding an item with nil value fails", func() { - err := table.Register(42, nil) - So(err, ShouldNotBeNil) - }) - }) -} - -func TestSCMPTableWithOneItem(t *testing.T) { - Convey("Given a table with one element", t, func() { - table := NewSCMPTable() - value := "test value" - err := table.Register(42, value) - require.NoError(t, err) - Convey("Lookup for the id succeeds", func() { - retValue, ok := table.Lookup(42) - SoMsg("ok", ok, ShouldBeTrue) - SoMsg("value", retValue, ShouldEqual, value) - }) - Convey("Adding the same id fails", func() { - err := table.Register(42, "some other value") - So(err, ShouldNotBeNil) - }) - Convey("After removing the ID, lookup fails", func() { - table.Remove(42) - value, ok := table.Lookup(42) - SoMsg("ok", ok, ShouldBeFalse) - SoMsg("value", value, ShouldBeNil) - }) - }) -} diff --git a/dispatcher/internal/registration/svctable.go b/dispatcher/internal/registration/svctable.go deleted file mode 100644 index 7c93f41bd0..0000000000 --- a/dispatcher/internal/registration/svctable.go +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "container/ring" - "fmt" - "net" - - "github.com/scionproto/scion/pkg/addr" -) - -// SVCTable tracks SVC registrations. -// -// Entries are hierarchical, and conceptually look like the following: -// -// SVC CS: -// 10.2.3.4 -// :10080 -// :10081 -// 192.0.2.1 -// :20000 -// SVC PS: -// 192.0.2.2 -// :20001 -// 2001:db8::1 -// :30001 -// :30002 -// -// Call Register to add a new entry to the table. The IP and port are taken -// from the UDP address. IP must not be zero (so binding to multiple interfaces -// is not supported), and port must not be zero. -// -// Anycasting to a local application requires the service type (e.g., CS) and -// the IP. This is because for SCION, the IP is selected remotely by the border -// router. The local dispatcher then anycasts between all local ports listening on that IP. -// -// For example, in the table above, anycasting to CS-10.2.3.4 can either go to -// entry 10.2.3.4:10080 or 10.2.3.4:10081. Anycasts are chosen in round-robin -// fashion; the round-robin distribution is not strict, and can get skewed due -// to registrations and frees. -type SVCTable interface { - // Register adds a new entry for the select svc, IP address and port. Both - // IPv4 and IPv6 are supported. IP addresses 0.0.0.0 and :: are not - // supported. Port must not be 0. - // - // If an entry for the same svc, IP address and port exists, an error is - // returned and the reference is nil. - // - // To clean up resources, call Free on the returned Reference. Calling Free - // more than once will cause a panic. - Register(svc addr.SVC, address *net.UDPAddr, value interface{}) (Reference, error) - // Lookup returns the entries associated with svc and ip. - // - // If SVC is an anycast address, at most one entry is returned. The ip - // address is used in case to narrow down the set of possible entries. If - // multiple entries exist, one is selected arbitrarily. - // - // Note that nil addresses are supported for anycasts (the address is then - // ignored), but support for this might be dropped in the future. - // - // If SVC is a multicast address, more than one entry can be returned. The - // ip address is ignored in this case. - Lookup(svc addr.SVC, ip net.IP) []interface{} - String() string -} - -func NewSVCTable() SVCTable { - return newSvcTable() -} - -var _ SVCTable = (*svcTable)(nil) - -type svcTable struct { - m map[addr.SVC]unicastIpTable -} - -func newSvcTable() *svcTable { - return &svcTable{ - m: make(map[addr.SVC]unicastIpTable), - } -} - -func (t *svcTable) Register(svc addr.SVC, address *net.UDPAddr, - value interface{}) (Reference, error) { - - if err := validateUDPAddr(address); err != nil { - return nil, err - } - if svc == addr.SvcNone { - return nil, ErrSvcNone - } - // save a copy of the address to prevent callers from later affecting table - // state - address = copyUDPAddr(address) - - if svc == addr.SvcWildcard { - refCS, err := t.registerOne(addr.SvcCS, address, value) - if err != nil { - return nil, err - } - refDS, err := t.registerOne(addr.SvcDS, address, value) - if err != nil { - refCS.Free() - return nil, err - } - return &svcTableReference{ - cleanF: func() { - refCS.Free() - refDS.Free() - }, - }, nil - } - return t.registerOne(svc, address, value) -} - -func (t *svcTable) registerOne(svc addr.SVC, address *net.UDPAddr, - value interface{}) (Reference, error) { - if _, ok := t.m[svc]; !ok { - t.m[svc] = make(unicastIpTable) - } - - element, err := t.m[svc].insert(address, value) - if err != nil { - return nil, err - } - return &svcTableReference{ - cleanF: t.buildCleanupCallback(svc, address.IP, element), - }, nil -} - -func (t *svcTable) Lookup(svc addr.SVC, ip net.IP) []interface{} { - var values []interface{} - if svc.IsMulticast() { - values = t.multicast(svc) - } else { - if v, ok := t.anycast(svc, ip); ok { - values = []interface{}{v} - } - } - return values -} - -func (t *svcTable) multicast(svc addr.SVC) []interface{} { - var values []interface{} - ipTable, ok := t.m[svc.Base()] - if !ok { - return values - } - for _, v := range ipTable { - for i := 0; i < v.Len(); i++ { - values = append(values, v.Get()) - } - } - return values -} - -func (t *svcTable) anycast(svc addr.SVC, ip net.IP) (interface{}, bool) { - ipTable, ok := t.m[svc] - if !ok { - return nil, false - } - // XXX(scrye): This is a workaround s.t. a simple underlay socket - // that does not return IP-header information can still be used to - // deliver to SVC addresses. Once IP-header information is passed - // into the app, searching for nil should not return an entry. - var ports *portList - if ip == nil { - ports, ok = ipTable.any() - } else { - ports, ok = ipTable[ip.String()] - } - if !ok { - return nil, false - } - return ports.Get(), true -} - -func (t *svcTable) String() string { - return fmt.Sprintf("%v", t.m) -} - -func (t *svcTable) buildCleanupCallback(svc addr.SVC, ip net.IP, port *ring.Ring) func() { - return func() { - t.doCleanup(svc, ip, port) - } -} - -func (t *svcTable) doCleanup(svc addr.SVC, ip net.IP, port *ring.Ring) { - ipTable := t.m[svc] - portList := ipTable[ip.String()] - portList.Remove(port) - if portList.Len() == 0 { - delete(ipTable, ip.String()) - } - if len(ipTable) == 0 { - delete(t.m, svc) - } -} - -func validateUDPAddr(address *net.UDPAddr) error { - if address == nil { - return ErrNilAddress - } - if address.IP.IsUnspecified() { - return ErrZeroIP - } - if address.Port == 0 { - return ErrZeroPort - } - return nil -} - -type unicastIpTable map[string]*portList - -// insert adds an entry for address to the table, and returns a pointer to the -// entry. -func (t unicastIpTable) insert(address *net.UDPAddr, value interface{}) (*ring.Ring, error) { - var list *portList - str := address.IP.String() - list, ok := t[str] - if ok { - if list.Find(address.Port) { - return nil, ErrOverlappingAddress - } - } else { - list = newPortList() - t[str] = list - } - return list.Insert(address.Port, value), nil -} - -// any returns an arbitrary item from the table. The boolean return value is -// true if an entry was found, or false otherwise. -func (t unicastIpTable) any() (*portList, bool) { - for _, v := range t { - return v, true - } - return nil, false -} - -var _ Reference = (*svcTableReference)(nil) - -type svcTableReference struct { - freed bool - cleanF func() -} - -func (r *svcTableReference) Free() { - if r.freed { - panic("double free") - } - r.freed = true - r.cleanF() -} diff --git a/dispatcher/internal/registration/svctable_test.go b/dispatcher/internal/registration/svctable_test.go deleted file mode 100644 index f89e0c1a26..0000000000 --- a/dispatcher/internal/registration/svctable_test.go +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "fmt" - "net" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/pkg/addr" -) - -func TestSVCTableLookup(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - value := "test value" - - testCases := map[string]struct { - Svc addr.SVC - IP net.IP - Prepare func(t *testing.T, table SVCTable) - Expected []interface{} - }{ - // Empty table test cases: - "Anycast to nil address, not found": { - Svc: addr.SvcCS, - Prepare: func(*testing.T, SVCTable) {}, - }, - "Anycast to some IPv4 address, not found": { - Svc: addr.SvcCS, - IP: net.IP{10, 2, 3, 4}, - Prepare: func(*testing.T, SVCTable) {}, - }, - "Multicast to some IPv4 address, not found": { - Svc: addr.SvcCS.Multicast(), - Prepare: func(*testing.T, SVCTable) {}, - }, - - // Table with 1 entry test cases: - "anycasting to nil finds the entry": { - Svc: addr.SvcCS, - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Expected: []interface{}{value}, - }, - "multicasting to nil finds the entry": { - Svc: addr.SvcCS.Multicast(), - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Expected: []interface{}{value}, - }, - "anycasting to a different IP does not find the entry": { - Svc: addr.SvcCS, - IP: net.IP{10, 5, 6, 7}, - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - }, - "anycasting to a different SVC does not find the entry": { - Svc: addr.SvcDS, - IP: address.IP, - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - }, - "anycasting to the same SVC and IP finds the entry": { - Svc: addr.SvcCS, - IP: address.IP, - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Expected: []interface{}{value}, - }, - "multicasting to the same SVC and IP finds the entry": { - Svc: addr.SvcCS.Multicast(), - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Expected: []interface{}{value}, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - table := NewSVCTable() - tc.Prepare(t, table) - - retValues := table.Lookup(tc.Svc, tc.IP) - assert.Equal(t, tc.Expected, retValues) - }) - } -} - -func TestSVCTableRegistration(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - value := "test value" - - testCases := map[string]struct { - Prepare func(t *testing.T, table SVCTable) - // Input Register - Svc addr.SVC - Addr *net.UDPAddr - Value interface{} - // Assertions - ReferenceAssertion assert.ValueAssertionFunc - ErrAssertion assert.ErrorAssertionFunc - }{ - // Empty table test cases: - "Registering nil address fails": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcCS, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - "Registering IPv4 zero address fails": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcCS, - Addr: &net.UDPAddr{IP: net.IPv4zero, Port: address.Port}, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - "Registering IPv6 zero address fail": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcCS, - Addr: &net.UDPAddr{IP: net.IPv6zero, Port: address.Port}, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - "Registering port zero fails": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcCS, - Addr: &net.UDPAddr{IP: address.IP}, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - "Registering SvcNone fails": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcNone, - Addr: address, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - "Adding an address succeeds": { - Prepare: func(*testing.T, SVCTable) {}, - Svc: addr.SvcCS, - Addr: address, - Value: value, - ReferenceAssertion: assert.NotNil, - ErrAssertion: assert.NoError, - }, - - // Table with 1 entry test cases: - "Registering the same address and different port succeeds": { - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Svc: addr.SvcCS, - Addr: &net.UDPAddr{ - IP: address.IP, - Port: address.Port + 1, - }, - Value: value, - ReferenceAssertion: assert.NotNil, - ErrAssertion: assert.NoError, - }, - "Registering the same address and same port fails": { - Prepare: func(t *testing.T, table SVCTable) { - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - }, - Svc: addr.SvcCS, - Addr: address, - Value: value, - ReferenceAssertion: assert.Nil, - ErrAssertion: assert.Error, - }, - } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - table := NewSVCTable() - tc.Prepare(t, table) - - reference, err := table.Register(tc.Svc, tc.Addr, value) - tc.ErrAssertion(t, err) - tc.ReferenceAssertion(t, reference) - }) - } -} - -func TestSVCTableOneItemAnycast(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - diffIpSamePortAddress := &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: address.Port} - value := "test value" - otherValue := "other test value" - - prepare := func(t *testing.T) (SVCTable, Reference) { - table := NewSVCTable() - reference, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - return table, reference - } - - t.Run("Adding a second address, anycasting to first one returns correct value", - func(t *testing.T) { - table, _ := prepare(t) - _, err := table.Register(addr.SvcCS, diffIpSamePortAddress, otherValue) - assert.NoError(t, err) - retValues := table.Lookup(addr.SvcCS, address.IP) - assert.Equal(t, []interface{}{value}, retValues) - }) - t.Run("Freeing the reference yields nil on anycast", func(t *testing.T) { - table, reference := prepare(t) - reference.Free() - retValues := table.Lookup(addr.SvcCS, nil) - assert.Empty(t, retValues) - - // Check double free panicks - assert.Panics(t, func() { reference.Free() }) - - _, err := table.Register(addr.SvcCS, address, value) - assert.NoError(t, err) - }) -} -func TestSVCTableTwoItems(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - sameIpDiffPortAddress := &net.UDPAddr{IP: address.IP, Port: address.Port + 1} - value := "test value" - otherValue := "other test value" - - prepare := func(t *testing.T) SVCTable { - table := NewSVCTable() - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - _, err = table.Register(addr.SvcCS, sameIpDiffPortAddress, otherValue) - require.NoError(t, err) - return table - } - - t.Run("The anycasts will cycle between the values", func(t *testing.T) { - table := prepare(t) - retValues := table.Lookup(addr.SvcCS, address.IP) - assert.Equal(t, []interface{}{value}, retValues) - otherRetValue := table.Lookup(addr.SvcCS, address.IP) - assert.Equal(t, []interface{}{otherValue}, otherRetValue) - }) - - t.Run("A multicast will return both values", func(t *testing.T) { - table := prepare(t) - retValues := table.Lookup(addr.SvcCS.Multicast(), address.IP) - assert.Equal(t, len(retValues), 2) - }) -} - -func TestSVCTableMulticastTwoAddresses(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - diffAddress := &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: address.Port} - value := "test value" - otherValue := "other test value" - - table := NewSVCTable() - _, err := table.Register(addr.SvcCS, address, value) - require.NoError(t, err) - _, err = table.Register(addr.SvcCS, diffAddress, otherValue) - require.NoError(t, err) - - retValues := table.Lookup(addr.SvcCS.Multicast(), address.IP) - assert.ElementsMatch(t, []interface{}{otherValue, value}, retValues) -} - -func TestSVCTableStress(t *testing.T) { - registrationCount := 1000 - // Generate many random registrations, then free all - table := NewSVCTable() - references := runRandomRegistrations(registrationCount, table) - for _, ref := range references { - ref.Free() - } - // then generate some more, and free again - references = runRandomRegistrations(registrationCount, table) - for _, ref := range references { - ref.Free() - } - t.Run("Table should be empty", func(t *testing.T) { - assert.Equal(t, table.String(), "map[]") - }) -} - -func runRandomRegistrations(count int, table SVCTable) []Reference { - var references []Reference - for i := 0; i < count; i++ { - ref, err := table.Register(addr.SvcCS, getRandomUDPAddress(), getRandomValue()) - if err == nil { - references = append(references, ref) - } - } - return references -} - -func TestSVCTableFree(t *testing.T) { - ip := net.IP{10, 2, 3, 4} - prepare := func(t *testing.T) (SVCTable, []Reference) { - // Prepare a table with three entries on the same IP - table := NewSVCTable() - addressOne := &net.UDPAddr{IP: ip, Port: 10080} - refOne, err := table.Register(addr.SvcCS, addressOne, "1") - require.NoError(t, err) - addressTwo := &net.UDPAddr{IP: ip, Port: 10081} - refTwo, err := table.Register(addr.SvcCS, addressTwo, "2") - require.NoError(t, err) - addressThree := &net.UDPAddr{IP: ip, Port: 10082} - refThree, err := table.Register(addr.SvcCS, addressThree, "3") - require.NoError(t, err) - return table, []Reference{refOne, refTwo, refThree} - } - for i := 0; i < 3; i++ { - addrremainone := strconv.Itoa((i+1)%3 + 1) - addrremaintwo := strconv.Itoa((i+2)%3 + 1) - name := fmt.Sprintf("Addresses %s and %s must remain", addrremainone, addrremaintwo) - t.Run(name, func(t *testing.T) { - table, refs := prepare(t) - refs[i].Free() - retValues := table.Lookup(addr.SvcCS.Multicast(), ip) - assert.ElementsMatch(t, []interface{}{addrremainone, addrremaintwo}, retValues) - checkAnyCastCycles(t, - func() []interface{} { return table.Lookup(addr.SvcCS, ip) }, - []string{addrremainone, addrremaintwo}) - - if i == 2 { - // removing address 1, after removing address 3, should leave us with address 2 - refs[0].Free() - retValues := table.Lookup(addr.SvcCS.Multicast(), ip) - assert.ElementsMatch(t, []interface{}{"2"}, retValues) - checkAnyCastCycles(t, - func() []interface{} { return table.Lookup(addr.SvcCS, ip) }, - []string{"2"}) - } - }) - } - -} - -func checkAnyCastCycles(t *testing.T, lookup func() []interface{}, expected []string) { - t.Helper() - firstRes := lookup()[0].(string) - startIndex := -1 - for i := range expected { - if expected[i] == firstRes { - startIndex = i + 1 - break - } - } - if startIndex == -1 { - t.Fatalf("Initial value %s not in expected (%v)", firstRes, expected) - } - for cnt := 0; cnt < len(expected)+1; cnt++ { - idx := (startIndex + cnt) % len(expected) - res := lookup()[0].(string) - if res != expected[idx] { - t.Fatalf("Value %s was not expected in (%v)", res, expected) - } - } -} - -func TestSVCTableWildcard(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - value := "test value" - - table := NewSVCTable() - reference, err := table.Register(addr.SvcWildcard, address, value) - require.NoError(t, err) - defer reference.Free() - - testCases := map[string]struct { - Address addr.SVC - LookupResultCount int - }{ - "cs": { - Address: addr.SvcCS.Multicast(), - LookupResultCount: 1, - }, - "ds": { - Address: addr.SvcDS.Multicast(), - LookupResultCount: 1, - }, - } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - retValues := table.Lookup(tc.Address, nil) - assert.Equal(t, tc.LookupResultCount, len(retValues)) - }) - } -} - -func TestSVCTableWildcardFree(t *testing.T) { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - value := "test value" - - table := NewSVCTable() - reference, err := table.Register(addr.SvcWildcard, address, value) - require.NoError(t, err) - reference.Free() - - assert.Equal(t, 0, len(table.Lookup(addr.SvcCS, nil))) - assert.Equal(t, 0, len(table.Lookup(addr.SvcDS, nil))) -} - -func TestSVCTableWildcardRollback(t *testing.T) { - // If any SVC registration fails on a wildcard, none should remain - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - value := "test value" - - table := NewSVCTable() - - testCases := map[string]struct { - RegisteredAddress addr.SVC - LookupResultCSCount int - LookupResultDSCount int - }{ - "cs": { - RegisteredAddress: addr.SvcCS, - LookupResultCSCount: 1, - LookupResultDSCount: 0, - }, - "ds": { - RegisteredAddress: addr.SvcDS, - LookupResultCSCount: 0, - LookupResultDSCount: 1, - }, - } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - reference, err := table.Register(tc.RegisteredAddress, address, value) - require.NoError(t, err) - defer reference.Free() - - _, err = table.Register(addr.SvcWildcard, address, value) - assert.Error(t, err) - - assert.Equal(t, tc.LookupResultCSCount, len(table.Lookup(addr.SvcCS, nil))) - assert.Equal(t, tc.LookupResultDSCount, len(table.Lookup(addr.SvcDS, nil))) - }) - } -} diff --git a/dispatcher/internal/registration/table.go b/dispatcher/internal/registration/table.go deleted file mode 100644 index a0244c0dee..0000000000 --- a/dispatcher/internal/registration/table.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - - "github.com/scionproto/scion/pkg/addr" -) - -// Table manages the UDP/IP port registrations for a single AS. -// -// Table is not safe for concurrent use from multiple goroutines. -type Table struct { - udpPortTable *UDPPortTable - svcTable SVCTable - size int - // XXX(scrye): Note that SCMP General IDs are globally scoped inside an IA - // (i.e., all all hosts share the same ID namespace, and thus can collide - // with each other). Because the IDs are random, it is very unlikely for a - // collision to occur (although faulty coding can increase the chance, - // e.g., if apps start with an ID of 1 and increment from there). We should - // revisit if SCMP General IDs should be scoped to IPs. - scmpTable *SCMPTable -} - -func NewTable(minPort, maxPort int) *Table { - return &Table{ - udpPortTable: NewUDPPortTable(minPort, maxPort), - svcTable: NewSVCTable(), - scmpTable: NewSCMPTable(), - } -} - -func (t *Table) Register(public *net.UDPAddr, bind net.IP, svc addr.SVC, - value interface{}) (*TableReference, error) { - - if public == nil { - return nil, ErrNoPublicAddress - } - if bind != nil && svc == addr.SvcNone { - return nil, ErrBindWithoutSvc - } - address, err := t.udpPortTable.Insert(public, value) - if err != nil { - return nil, err - } - if bind == nil { - bind = public.IP - } - svcRef, err := t.insertSVCIfRequested(svc, bind, address.Port, value) - if err != nil { - t.udpPortTable.Remove(public) - return nil, err - } - t.size++ - return &TableReference{table: t, address: address, svcRef: svcRef}, nil -} - -func (t *Table) insertSVCIfRequested(svc addr.SVC, bind net.IP, port int, - value interface{}) (Reference, error) { - - if svc != addr.SvcNone { - bindUdpAddr := &net.UDPAddr{ - IP: bind, - Port: port, - } - return t.svcTable.Register(svc, bindUdpAddr, value) - } - return nil, nil -} - -func (t *Table) LookupPublic(address *net.UDPAddr) (interface{}, bool) { - return t.udpPortTable.Lookup(address) -} - -func (t *Table) LookupService(svc addr.SVC, bind net.IP) []interface{} { - return t.svcTable.Lookup(svc, bind) -} - -func (t *Table) Size() int { - return t.size -} - -func (t *Table) LookupID(id uint64) (interface{}, bool) { - return t.scmpTable.Lookup(id) -} - -func (t *Table) registerID(id uint64, value interface{}) error { - return t.scmpTable.Register(id, value) -} - -func (t *Table) removeID(id uint64) { - t.scmpTable.Remove(id) -} - -type TableReference struct { - table *Table - freed bool - address *net.UDPAddr - svcRef Reference - ids []uint64 -} - -func (r *TableReference) Free() { - if r.freed { - panic("double free") - } - r.freed = true - r.table.udpPortTable.Remove(r.address) - if r.svcRef != nil { - r.svcRef.Free() - } - r.table.size-- - for _, id := range r.ids { - r.table.removeID(id) - } -} - -func (r *TableReference) UDPAddr() *net.UDPAddr { - return r.address -} - -func (r *TableReference) RegisterID(id uint64, value interface{}) error { - if err := r.table.registerID(id, value); err != nil { - return err - } - r.ids = append(r.ids, id) - return nil -} diff --git a/dispatcher/internal/registration/table_test.go b/dispatcher/internal/registration/table_test.go deleted file mode 100644 index 000c06848f..0000000000 --- a/dispatcher/internal/registration/table_test.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/pkg/addr" -) - -var dummyValue = "test value" - -func TestRegister(t *testing.T) { - tests := map[string]struct { - a *net.UDPAddr - b net.IP - svc addr.SVC - af assert.ErrorAssertionFunc - }{ - "no public address fails": { - a: nil, - svc: addr.SvcNone, - af: assert.Error, - }, - "zero public IPv4 address succeeds": { - a: &net.UDPAddr{IP: net.IPv4zero, Port: 80}, - svc: addr.SvcNone, - af: assert.NoError, - }, - "zero public IPv6 address succeeds": { - a: &net.UDPAddr{IP: net.IPv6zero, Port: 80}, - svc: addr.SvcNone, - af: assert.NoError, - }, - "public address with port, no bind, no svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 5, 1}, Port: 8080}, - af: assert.NoError, - }, - "public address without port, no bind, no svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 9, 1}}, - svc: addr.SvcNone, - af: assert.NoError, - }, - "public address, bind, no svc fails": { - a: &net.UDPAddr{IP: net.IP{192, 0, 20, 1}, Port: 8880}, - b: net.IP{10, 2, 3, 4}, - svc: addr.SvcNone, - af: assert.Error, - }, - - "public address, no bind, svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 22, 1}, Port: 8889}, - svc: addr.SvcCS, - af: assert.NoError, - }, - - "zero bind IPv4 address fails": { - a: &net.UDPAddr{IP: net.IP{192, 0, 23, 1}, Port: 8888}, - b: net.IPv4zero, - svc: addr.SvcCS, - af: assert.Error, - }, - - "zero bind IPv6 address fails": { - a: &net.UDPAddr{IP: net.IP{192, 0, 23, 1}, Port: 8888}, - b: net.IPv6zero, - svc: addr.SvcCS, - af: assert.Error, - }, - "public address, bind, svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 23, 1}, Port: 8888}, - b: net.IP{10, 2, 3, 4}, - svc: addr.SvcCS, - af: assert.NoError, - }, - } - - for n, tc := range tests { - t.Run(n, func(t *testing.T) { - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - ref, err := table.Register(tc.a, tc.b, tc.svc, dummyValue) - tc.af(t, err) - if err != nil { - assert.Nil(t, ref) - return - } - assert.NotNil(t, ref) - }) - } - - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - -} - -func TestRegisterOnlyPublic(t *testing.T) { - - t.Run("Free reference, size is 0", func(t *testing.T) { - t.Log("Given a table with a public address registration") - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - public := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - ref, err := table.Register(public, nil, addr.SvcNone, dummyValue) - require.NoError(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - assert.NotNil(t, ref) - - t.Log("Lookup is successful") - retValue, ok := table.LookupPublic(public) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - ref.Free() - assert.Equal(t, table.Size(), 0, "size is 0") - assert.Panics(t, ref.Free, "Free same reference again, panic") - retValue, ok = table.LookupPublic(public) - assert.False(t, ok, "lookup should fail") - assert.Nil(t, retValue) - }) - - t.Run("Register", func(t *testing.T) { - t.Log("Given a table with a public address registration") - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - public := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - ref, err := table.Register(public, nil, addr.SvcNone, dummyValue) - require.NoError(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - assert.NotNil(t, ref) - - t.Log("Lookup is successful") - retValue, ok := table.LookupPublic(public) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - t.Log("Register same address returns error") - ref, err = table.Register(public, nil, addr.SvcNone, dummyValue) - assert.Error(t, err) - assert.Nil(t, ref) - - t.Log("Register 0.0.0.0, error due to overlap") - public = &net.UDPAddr{IP: net.IPv4zero, Port: 80} - ref, err = table.Register(public, nil, addr.SvcNone, dummyValue) - assert.Error(t, err) - assert.Nil(t, ref) - - t.Log("Register ::, success") - public = &net.UDPAddr{IP: net.IPv6zero, Port: 80} - ref, err = table.Register(public, nil, addr.SvcNone, dummyValue) - assert.NoError(t, err) - assert.NotNil(t, ref) - }) -} - -func TestRegisterPublicAndSVC(t *testing.T) { - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "size is 0") - - t.Log("Given a table with a public address registration") - p := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - _, err := table.Register(p, nil, addr.SvcCS, dummyValue) - require.NoError(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - - t.Log("Public lookup is successful") - retValue, ok := table.LookupPublic(p) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - t.Log("SVC lookup is successful (bind inherits from public)") - retValues := table.LookupService(addr.SvcCS, p.IP) - assert.Equal(t, retValues, []interface{}{dummyValue}) -} - -func TestRegisterWithBind(t *testing.T) { - table := NewTable(minPort, maxPort) - - t.Log("Given a table with a bind address registration") - p := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - bind := net.IP{10, 2, 3, 4} - ref, err := table.Register(p, bind, addr.SvcCS, dummyValue) - require.NoError(t, err) - assert.NotNil(t, ref) - assert.Equal(t, table.Size(), 1, "size is 1") - - t.Log("Public lookup is successful") - retValue, ok := table.LookupPublic(p) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - t.Log("SVC lookup is successful") - retValues := table.LookupService(addr.SvcCS, bind) - assert.Equal(t, retValues, []interface{}{dummyValue}) - - t.Log("Bind lookup on different svc fails") - retValues = table.LookupService(addr.SvcDS, bind) - assert.Empty(t, retValues) - - t.Log("Colliding binds returns error, and public port is released") - otherPublic := &net.UDPAddr{IP: net.IP{192, 0, 2, 2}, Port: 80} - _, err = table.Register(otherPublic, bind, addr.SvcCS, dummyValue) - assert.Error(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - _, err = table.Register(otherPublic, nil, addr.SvcNone, dummyValue) - assert.NoError(t, err) - - t.Log("Freeing the entry allows for reregistration") - ref.Free() - _, err = table.Register(p, bind, addr.SvcCS, dummyValue) - assert.NoError(t, err) -} diff --git a/dispatcher/internal/registration/udptable.go b/dispatcher/internal/registration/udptable.go deleted file mode 100644 index 0abdc97f16..0000000000 --- a/dispatcher/internal/registration/udptable.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "fmt" - "net" - - "github.com/scionproto/scion/pkg/private/serrors" -) - -// UDPPortTable stores port allocations for UDP/IPv4 and UDP/IPv6 sockets. -// -// Additionally, it allocates ports dynamically if the requested port is 0. -type UDPPortTable struct { - v4PortTable map[int]IPTable - v6PortTable map[int]IPTable - allocator *UDPPortAllocator -} - -func NewUDPPortTable(minPort, maxPort int) *UDPPortTable { - return NewUDPPortTableFromMap(minPort, maxPort, make(map[int]IPTable), make(map[int]IPTable)) -} - -func NewUDPPortTableFromMap(minPort, maxPort int, v4, v6 map[int]IPTable) *UDPPortTable { - return &UDPPortTable{ - v4PortTable: v4, - v6PortTable: v6, - allocator: NewUDPPortAllocator(minPort, maxPort), - } -} - -func (t *UDPPortTable) Lookup(address *net.UDPAddr) (interface{}, bool) { - if address.IP.IsUnspecified() { - return nil, false - } - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if !ok { - return nil, false - } - return ipTable.Route(address.IP) -} - -func (t *UDPPortTable) getPortTableByIP(ip net.IP) map[int]IPTable { - if ip.To4() != nil { - return t.v4PortTable - } - return t.v6PortTable -} - -func (t *UDPPortTable) overlapsWith(address *net.UDPAddr) bool { - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if !ok { - return false - } - return ipTable.OverlapsWith(address.IP) -} - -// Insert adds address into the allocation table. It will return an error if an -// entry overlaps, or if the value is nil. -func (t *UDPPortTable) Insert(address *net.UDPAddr, value interface{}) (*net.UDPAddr, error) { - if t.overlapsWith(address) { - return nil, serrors.WithCtx(ErrOverlappingAddress, "address", address) - } - if value == nil { - return nil, ErrNoValue - } - address = copyUDPAddr(address) - newAddress, err := t.computeAddressWithPort(address) - if err != nil { - return nil, err - } - t.insertUDPAddress(newAddress, value) - return newAddress, nil -} - -func (t *UDPPortTable) computeAddressWithPort(address *net.UDPAddr) (*net.UDPAddr, error) { - var err error - if address.Port == 0 { - address.Port, err = t.allocator.Allocate(address.IP, t) - } - return address, err -} - -func (t *UDPPortTable) insertUDPAddress(address *net.UDPAddr, value interface{}) { - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if !ok { - ipTable = make(IPTable) - portTable[address.Port] = ipTable - } - ipTable[address.IP.String()] = value -} - -func copyUDPAddr(address *net.UDPAddr) *net.UDPAddr { - return &net.UDPAddr{ - IP: copyIPAddr(address.IP), - Port: address.Port, - Zone: address.Zone, - } -} - -func copyIPAddr(ip net.IP) net.IP { - c := make(net.IP, len(ip)) - copy(c, ip) - return c -} - -func (t *UDPPortTable) Remove(address *net.UDPAddr) { - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if ok { - delete(ipTable, address.IP.String()) - if len(ipTable) == 0 { - delete(portTable, address.Port) - } - } -} - -// IPTable maps string representations of IP addresses to arbitrary values. -type IPTable map[string]interface{} - -// OverlapsWith returns true if ip overlaps with any entry in t. For example, -// 0.0.0.0 would overlap with any other IPv4 address. -func (t IPTable) OverlapsWith(ip net.IP) bool { - if ip.IsUnspecified() && len(t) > 0 { - return true - } - _, ok := t.Route(ip) - return ok -} - -// Route returns the object associated with destination ip. -// -// This can either be an entry matching argument ip exactly, or a zero IP -// address. -func (t IPTable) Route(ip net.IP) (interface{}, bool) { - if v, ok := t[getZeroString(ip)]; ok { - return v, true - } - if v, ok := t[ip.String()]; ok { - return v, true - } - return nil, false -} - -func getZeroString(ip net.IP) string { - if ip.To4() != nil { - return "0.0.0.0" - } else { - return "::" - } -} - -// UDPPortAllocator attempts to find a free port between a min port and a max port in -// an allocation table. Attempts wrap around when they reach max port. -// -// If no port is available, the allocation function panics. -type UDPPortAllocator struct { - minPort int - maxPort int - nextPort int -} - -// NewUDPPortAllocator returns an allocation. The function panics if min > max, or if -// min or max is not a valid port number. -func NewUDPPortAllocator(min, max int) *UDPPortAllocator { - if min <= 0 { - panic(fmt.Sprintf("bad min port value %d", min)) - } - if min > max { - panic(fmt.Sprintf("min port must be less than maxport, but %d > %d", min, max)) - } - if max >= (1 << 16) { - panic(fmt.Sprintf("max port cannot exceed %d (was %d)", (1<<16)-1, max)) - } - return &UDPPortAllocator{ - minPort: min, - maxPort: max, - nextPort: min, - } -} - -// Allocate returns the next available port for the IP address. It will panic -// if it runs out of ports. -func (a *UDPPortAllocator) Allocate(ip net.IP, t *UDPPortTable) (int, error) { - for i := a.minPort; i < a.maxPort+1; i++ { - candidate := &net.UDPAddr{ - IP: ip, - Port: a.nextPort, - } - a.nextPort++ - if a.nextPort == a.maxPort+1 { - a.nextPort = a.minPort - } - if !t.overlapsWith(candidate) { - return candidate.Port, nil - } - } - return 0, ErrNoPorts -} diff --git a/dispatcher/internal/registration/udptable_test.go b/dispatcher/internal/registration/udptable_test.go deleted file mode 100644 index 00a669291c..0000000000 --- a/dispatcher/internal/registration/udptable_test.go +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -var docIPv6AddressStr = "2001:db8::1" -var docIPv6Address = net.ParseIP(docIPv6AddressStr) - -var minPort = 1024 -var maxPort = 65535 - -func testUDPTableWithPorts(v4, v6 map[int]IPTable) *UDPPortTable { - if v4 == nil { - v4 = map[int]IPTable{} - } - if v6 == nil { - v6 = map[int]IPTable{} - } - return NewUDPPortTableFromMap(minPort, maxPort, v4, v6) -} - -func TestUDPPortTableLookup(t *testing.T) { - value := "test value" - Convey("", t, func() { - Convey("Given a non-zero IPv4 address", func() { - address := &net.UDPAddr{IP: net.IP{10, 1, 2, 3}, Port: 10080} - Convey("Lookup on an empty table returns nil", func() { - table := NewUDPPortTable(minPort, maxPort) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - Convey("Lookup on table with non-matching entries returns nil", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 10080: {"10.4.5.6": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - Convey("Lookup on table with exact match returns value", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 10080: {"10.1.2.3": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with matching 0.0.0.0 entry returns value", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 10080: {"0.0.0.0": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with non-matching 0.0.0.0 entry returns nil", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 80: {"0.0.0.0": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - }) - Convey("Lookup fails for zero IPv4 address", func() { - table := NewUDPPortTable(minPort, maxPort) - address := &net.UDPAddr{IP: net.IPv4zero, Port: 10080} - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - Convey("Given an IPv6 address", func() { - address := &net.UDPAddr{IP: docIPv6Address, Port: 10080} - Convey("Lookup on table with matching entry returns value", func() { - table := testUDPTableWithPorts(nil, map[int]IPTable{ - 10080: {docIPv6AddressStr: value}, - }) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with matching :: entry returns value", func() { - table := testUDPTableWithPorts(nil, map[int]IPTable{ - 10080: {"::": value}, - }) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with non-matching :: entry returns nil", func() { - table := testUDPTableWithPorts(nil, map[int]IPTable{ - 80: {"::": value}, - }) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - }) - }) -} - -func TestUDPPortTableInsert(t *testing.T) { - value := "Test value" - Convey("", t, func() { - Convey("Given an empty table", func() { - table := NewUDPPortTable(minPort, maxPort) - Convey("Inserting an IPv4 address with a port returns a copy of the same address", - func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(address) - SoMsg("err", err, ShouldBeNil) - SoMsg("address content", retAddress, ShouldResemble, address) - SoMsg("address not same object", retAddress, ShouldNotPointTo, address) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an IPv4 address with a 0 port returns an allocated port", - func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}} - expectedAddress := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 1024} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(expectedAddress) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, expectedAddress) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an IPv6 address with a port returns a copy of the same address", - func() { - address := &net.UDPAddr{IP: docIPv6Address, Port: 10080} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(address) - SoMsg("err", err, ShouldBeNil) - SoMsg("address content", retAddress, ShouldResemble, address) - SoMsg("address not same object", retAddress, ShouldNotPointTo, address) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an IPv6 address with a 0 port returns an allocated port", - func() { - address := &net.UDPAddr{IP: docIPv6Address} - expectedAddress := &net.UDPAddr{IP: docIPv6Address, Port: 1024} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(expectedAddress) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, expectedAddress) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an address without a value is not permitted", func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - retAddress, err := table.Insert(address, nil) - _, lookupOk := table.Lookup(address) - SoMsg("err", err, ShouldNotBeNil) - SoMsg("address", retAddress, ShouldBeNil) - SoMsg("lookup ok", lookupOk, ShouldBeFalse) - }) - Convey("Inserting a zero IPv4 address is permitted", func() { - address := &net.UDPAddr{IP: net.IPv4zero, Port: 10080} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, address) - }) - Convey("Inserting a zero IPv6 address is permitted", func() { - address := &net.UDPAddr{IP: net.IPv6zero, Port: 10080} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, address) - }) - }) - Convey("Given a table with a zero address", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 1024: {"0.0.0.0": value}}, nil) - Convey("A colliding allocation will return an error", func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 1024} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldNotBeNil) - SoMsg("address", retAddress, ShouldBeNil) - }) - }) - Convey("Given a table with a non-zero address", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 1024: {"10.0.0.0": value}}, nil) - Convey("Inserting zero IPv4 address on the same port fails", func() { - address := &net.UDPAddr{IP: net.IPv4zero, Port: 1024} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldNotBeNil) - SoMsg("address", retAddress, ShouldBeNil) - }) - Convey("Inserting zero IPv6 address on the same port succeeds", func() { - address := &net.UDPAddr{IP: net.IPv6zero, Port: 1024} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, address) - }) - }) - }) -} - -func TestUDPPortTableRemove(t *testing.T) { - value := "test value" - Convey("", t, func() { - Convey("", func() { - table := testUDPTableWithPorts( - map[int]IPTable{ - 10080: {"10.2.3.4": value}, - 10081: {"0.0.0.0": value}}, - map[int]IPTable{ - 10082: {docIPv6AddressStr: value}, - 10083: {"::": value}}, - ) - - Convey("Remove non-zero addresses", func() { - addrs := []*net.UDPAddr{ - {IP: net.IP{10, 2, 3, 4}, Port: 10080}, - {IP: docIPv6Address, Port: 10082}, - } - for _, address := range addrs { - _, lookupOk := table.Lookup(address) - SoMsg("lookup succeeds before removing", lookupOk, ShouldBeTrue) - table.Remove(address) - _, lookupOk = table.Lookup(address) - SoMsg("lookup fails after removing", lookupOk, ShouldBeFalse) - } - }) - Convey("Remove zero address", func() { - addrs := map[*net.UDPAddr]net.IP{ - {IP: net.IPv4zero, Port: 10081}: {10, 1, 2, 3}, - {IP: net.IPv6zero, Port: 10083}: docIPv6Address, - } - - for address, lookupAddress := range addrs { - _, lookupOk := table.Lookup(&net.UDPAddr{IP: lookupAddress, Port: address.Port}) - SoMsg("lookup succeeds before removing", lookupOk, ShouldBeTrue) - table.Remove(address) - _, lookupOk = table.Lookup(&net.UDPAddr{IP: lookupAddress, Port: address.Port}) - SoMsg("lookup fails after removing", lookupOk, ShouldBeFalse) - } - }) - }) - Convey("Removing non-existent entry does not panic", func() { - table := NewUDPPortTable(minPort, maxPort) - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 666} - So(func() { table.Remove(address) }, ShouldNotPanic) - }) - }) -} - -func TestUDPPortAllocator(t *testing.T) { - address := net.IP{10, 2, 3, 4} - value := "test value" - Convey("", t, func() { - Convey("Constructing an allocator with minport > maxport will panic", func() { - So(func() { NewUDPPortAllocator(10, 4) }, ShouldPanic) - }) - Convey("Constructing an allocator with a negative minport will panic", func() { - So(func() { NewUDPPortAllocator(-4, 4) }, ShouldPanic) - }) - Convey("Constructing an allocator with a minport of 0 will panic", func() { - So(func() { NewUDPPortAllocator(0, 4) }, ShouldPanic) - }) - Convey("Constructing an allocator with maxport > 65535 wil panic", func() { - So(func() { NewUDPPortAllocator(1, 65536) }, ShouldPanic) - }) - Convey("Given an allocator", func() { - allocator := NewUDPPortAllocator(1000, 1500) - table := NewUDPPortTable(minPort, maxPort) - Convey("if table is empty, first allocation gives min port", func() { - port, err := allocator.Allocate(address, table) - SoMsg("port", port, ShouldEqual, 1000) - SoMsg("err", err, ShouldBeNil) - }) - Convey("if table contains used first port, first allocation gives next port", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 1000: {"10.2.3.4": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 1001) - SoMsg("err", err, ShouldBeNil) - }) - Convey("if wildcard bind uses first port, first allocation gives next port", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 1000: {"0.0.0.0": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 1001) - SoMsg("err", err, ShouldBeNil) - }) - }) - Convey("Given an allocator with few ports", func() { - allocator := NewUDPPortAllocator(1, 3) - Convey("if all ports are taken except max, max is chosen", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 1: {"0.0.0.0": value}, - 2: {"0.0.0.0": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 3) - SoMsg("err", err, ShouldBeNil) - Convey("if first port is available, it is chosen after wrapping", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 2: {"0.0.0.0": value}, - 3: {"0.0.0.0": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 1) - SoMsg("err", err, ShouldBeNil) - }) - }) - Convey("if all ports are taken, error", func() { - table := testUDPTableWithPorts( - map[int]IPTable{ - 1: {"0.0.0.0": value}, - 2: {"0.0.0.0": value}, - 3: {"0.0.0.0": value}, - }, nil) - port, err := allocator.Allocate(address, table) - SoMsg("port", port, ShouldEqual, 0) - SoMsg("err", err, ShouldNotBeNil) - }) - }) - Convey("Given an allocator with IPv6 data", func() { - v6address := net.ParseIP(docIPv6AddressStr) - allocator := NewUDPPortAllocator(1000, 1500) - table := testUDPTableWithPorts(nil, - map[int]IPTable{ - 1000: {docIPv6AddressStr: value}, - }) - Convey("allocation skips ports correctly for IPv6", func() { - port, err := allocator.Allocate(v6address, table) - SoMsg("port", port, ShouldEqual, 1001) - SoMsg("err", err, ShouldBeNil) - }) - }) - }) -} diff --git a/dispatcher/internal/respool/BUILD.bazel b/dispatcher/internal/respool/BUILD.bazel deleted file mode 100644 index f66f9fa63e..0000000000 --- a/dispatcher/internal/respool/BUILD.bazel +++ /dev/null @@ -1,34 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "buffer.go", - "packet.go", - ], - importpath = "github.com/scionproto/scion/dispatcher/internal/respool", - visibility = ["//dispatcher:__subpackages__"], - deps = [ - "//dispatcher/internal/metrics:go_default_library", - "//pkg/private/common:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/slayers:go_default_library", - "@com_github_google_gopacket//:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["packet_test.go"], - embed = [":go_default_library"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/private/xtest:go_default_library", - "//pkg/slayers:go_default_library", - "//pkg/slayers/path:go_default_library", - "//pkg/slayers/path/scion:go_default_library", - "@com_github_google_gopacket//:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - "@com_github_stretchr_testify//require:go_default_library", - ], -) diff --git a/dispatcher/internal/respool/buffer.go b/dispatcher/internal/respool/buffer.go deleted file mode 100644 index 0c3c321729..0000000000 --- a/dispatcher/internal/respool/buffer.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package respool contains the Dispatcher's pool of free buffers/packets. -// -// FIXME(scrye): Currently the pools are elastic, but this is not ideal for -// traffic bursts. Consider converting these to fixed-size lists. -package respool - -import ( - "sync" - - "github.com/scionproto/scion/pkg/private/common" -) - -var bufferPool = sync.Pool{ - New: func() interface{} { - return make([]byte, common.SupportedMTU) - }, -} - -func GetBuffer() []byte { - b := bufferPool.Get().([]byte) - return b[:cap(b)] -} - -func PutBuffer(b []byte) { - if cap(b) == common.SupportedMTU { - bufferPool.Put(b) - } -} diff --git a/dispatcher/internal/respool/packet.go b/dispatcher/internal/respool/packet.go deleted file mode 100644 index a34dafd408..0000000000 --- a/dispatcher/internal/respool/packet.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package respool - -import ( - "net" - "sync" - - "github.com/google/gopacket" - - "github.com/scionproto/scion/dispatcher/internal/metrics" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/slayers" -) - -var packetPool = sync.Pool{ - New: func() interface{} { - return newPacket() - }, -} - -func GetPacket() *Packet { - pkt := packetPool.Get().(*Packet) - *pkt.refCount = 1 - return pkt -} - -// Packet describes a SCION packet. Fields might reference each other -// (including hidden fields), so callers should only write to freshly created -// packets, and readers should take care never to mutate data. -type Packet struct { - UnderlayRemote *net.UDPAddr - - SCION slayers.SCION - UDP slayers.UDP - SCMP slayers.SCMP - - // L4 indicates what type is at layer 4. - L4 gopacket.LayerType - - // parser is tied to the layers in this packet. - // IngoreUnsupported is set to true. - parser *gopacket.DecodingLayerParser - // buffer contains the raw slice that other fields reference - buffer []byte - - mtx sync.Mutex - refCount *int -} - -// Len returns the length of the packet. -func (p *Packet) Len() int { - return len(p.buffer) -} - -func newPacket() *Packet { - refCount := 1 - pkt := &Packet{ - buffer: GetBuffer(), - refCount: &refCount, - } - hbh := slayers.HopByHopExtnSkipper{} - e2e := slayers.EndToEndExtnSkipper{} - pkt.parser = gopacket.NewDecodingLayerParser(slayers.LayerTypeSCION, - &pkt.SCION, &hbh, &e2e, &pkt.UDP, &pkt.SCMP, - ) - pkt.parser.IgnoreUnsupported = true - return pkt -} - -// Dup increases pkt's reference count. -// -// Dup panics if it is called after the packet has been freed (i.e., it's -// reference count reached 0). -// -// Modifying a packet after the first call to Dup is racy, and callers should -// use external locking for it. -func (pkt *Packet) Dup() { - pkt.mtx.Lock() - if *pkt.refCount <= 0 { - panic("cannot reference freed packet") - } - *pkt.refCount++ - pkt.mtx.Unlock() -} - -// CopyTo copies the buffer into the provided bytearray. Returns number of bytes copied. -func (pkt *Packet) CopyTo(p []byte) int { - n := len(pkt.buffer) - p = p[:n] - copy(p, pkt.buffer) - return n -} - -// Free releases a reference to the packet. Free is safe to use from concurrent -// goroutines. -func (pkt *Packet) Free() { - pkt.mtx.Lock() - if *pkt.refCount <= 0 { - panic("reference count underflow") - } - *pkt.refCount-- - if *pkt.refCount == 0 { - pkt.reset() - pkt.mtx.Unlock() - packetPool.Put(pkt) - } else { - pkt.mtx.Unlock() - } -} - -func (pkt *Packet) DecodeFromConn(conn net.PacketConn) error { - n, readExtra, err := conn.ReadFrom(pkt.buffer) - if err != nil { - return err - } - pkt.buffer = pkt.buffer[:n] - metrics.M.NetReadBytes().Add(float64(n)) - - pkt.UnderlayRemote = readExtra.(*net.UDPAddr) - if err := pkt.decodeBuffer(); err != nil { - metrics.M.NetReadPkts( - metrics.IncomingPacket{ - Result: metrics.PacketResultParseError, - }, - ).Inc() - return err - } - return nil -} - -func (pkt *Packet) DecodeFromReliableConn(conn net.PacketConn) error { - n, readExtra, err := conn.ReadFrom(pkt.buffer) - if err != nil { - return err - } - pkt.buffer = pkt.buffer[:n] - - if readExtra == nil { - return serrors.New("missing next-hop") - } - pkt.UnderlayRemote = readExtra.(*net.UDPAddr) - return pkt.decodeBuffer() -} - -func (pkt *Packet) decodeBuffer() error { - decoded := make([]gopacket.LayerType, 0, 4) - - // Unsupported layers are ignored by the parser. - if err := pkt.parser.DecodeLayers(pkt.buffer, &decoded); err != nil { - return err - } - if len(decoded) < 2 { - return serrors.New("L4 not decoded") - } - l4 := decoded[len(decoded)-1] - if l4 != slayers.LayerTypeSCMP && l4 != slayers.LayerTypeSCIONUDP { - return serrors.New("unknown L4 layer decoded", "type", l4) - } - pkt.L4 = l4 - return nil -} - -func (pkt *Packet) SendOnConn(conn net.PacketConn, address net.Addr) (int, error) { - return conn.WriteTo(pkt.buffer, address) -} - -func (pkt *Packet) reset() { - pkt.buffer = pkt.buffer[:cap(pkt.buffer)] - pkt.UnderlayRemote = nil - pkt.L4 = 0 -} diff --git a/dispatcher/internal/respool/packet_test.go b/dispatcher/internal/respool/packet_test.go deleted file mode 100644 index f3ad215bbd..0000000000 --- a/dispatcher/internal/respool/packet_test.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2020 Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package respool - -import ( - "testing" - - "github.com/google/gopacket" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/slayers" - "github.com/scionproto/scion/pkg/slayers/path" - "github.com/scionproto/scion/pkg/slayers/path/scion" -) - -func TestDecodeBuffer(t *testing.T) { - testCases := map[string]struct { - Layers func(t *testing.T) []gopacket.SerializableLayer - Check func(t *testing.T, pkt *Packet) - ErrAssertion assert.ErrorAssertionFunc - }{ - "UDP": { - Layers: func(t *testing.T) []gopacket.SerializableLayer { - scion := scionLayer(t, slayers.L4UDP) - udp := &slayers.UDP{ - SrcPort: 1337, - DstPort: 42, - } - udp.SetNetworkLayerForChecksum(scion) - pld := gopacket.Payload("I am a payload") - return []gopacket.SerializableLayer{scion, udp, pld} - }, - Check: func(t *testing.T, pkt *Packet) { - assert.Equal(t, xtest.MustParseIA("1-ff00:0:110"), pkt.SCION.SrcIA) - assert.Equal(t, 1337, int(pkt.UDP.SrcPort)) - assert.Equal(t, slayers.LayerTypeSCIONUDP, pkt.L4) - }, - ErrAssertion: assert.NoError, - }, - "SCMP": { - Layers: func(t *testing.T) []gopacket.SerializableLayer { - scion := scionLayer(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeExternalInterfaceDown, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - scmpMsg := &slayers.SCMPExternalInterfaceDown{ - IA: xtest.MustParseIA("1-ff00:0:110"), - IfID: 42, - } - pld := gopacket.Payload("offending packet") - return []gopacket.SerializableLayer{scion, scmp, scmpMsg, pld} - }, - Check: func(t *testing.T, pkt *Packet) { - assert.Equal(t, xtest.MustParseIA("1-ff00:0:110"), pkt.SCION.SrcIA) - assert.Equal(t, slayers.SCMPTypeExternalInterfaceDown, pkt.SCMP.TypeCode.Type()) - assert.Equal(t, slayers.LayerTypeSCMP, pkt.L4) - }, - ErrAssertion: assert.NoError, - }, - "TCP": { - Layers: func(t *testing.T) []gopacket.SerializableLayer { - scion := scionLayer(t, slayers.L4TCP) - pld := gopacket.Payload("offending packet") - return []gopacket.SerializableLayer{scion, pld} - }, - ErrAssertion: assert.Error, - }, - } - for name, tc := range testCases { - name, tc := name, tc - t.Run(name, func(t *testing.T) { - opts := gopacket.SerializeOptions{ - ComputeChecksums: true, - FixLengths: true, - } - buf := gopacket.NewSerializeBuffer() - require.NoError(t, gopacket.SerializeLayers(buf, opts, tc.Layers(t)...)) - pkt := newPacket() - pkt.buffer = buf.Bytes() - err := pkt.decodeBuffer() - tc.ErrAssertion(t, err) - if err != nil { - return - } - tc.Check(t, pkt) - }) - } -} - -func scionLayer(t *testing.T, l4 slayers.L4ProtocolType) *slayers.SCION { - scion := &slayers.SCION{ - Version: 0, - TrafficClass: 0xb8, - FlowID: 0xdead, - NextHdr: l4, - PathType: scion.PathType, - SrcIA: xtest.MustParseIA("1-ff00:0:110"), - DstIA: xtest.MustParseIA("1-ff00:0:112"), - Path: &scion.Decoded{ - Base: scion.Base{ - PathMeta: scion.MetaHdr{ - CurrHF: 2, - SegLen: [3]uint8{3, 0, 0}, - }, - NumINF: 1, - NumHops: 3, - }, - InfoFields: []path.InfoField{ - {SegID: 0x111, ConsDir: true, Timestamp: 0x100}, - }, - HopFields: []path.HopField{ - {ConsIngress: 0, ConsEgress: 311}, - {ConsIngress: 131, ConsEgress: 141}, - {ConsIngress: 411, ConsEgress: 0}, - }, - }, - } - require.NoError(t, scion.SetSrcAddr(addr.MustParseHost("127.0.0.1"))) - require.NoError(t, scion.SetDstAddr(addr.MustParseHost("127.0.0.2"))) - return scion -} diff --git a/dispatcher/network/BUILD.bazel b/dispatcher/network/BUILD.bazel deleted file mode 100644 index f08a1a817a..0000000000 --- a/dispatcher/network/BUILD.bazel +++ /dev/null @@ -1,20 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "app_socket.go", - "dispatcher.go", - ], - importpath = "github.com/scionproto/scion/dispatcher/network", - visibility = ["//visibility:public"], - deps = [ - "//dispatcher:go_default_library", - "//dispatcher/internal/metrics:go_default_library", - "//dispatcher/internal/respool:go_default_library", - "//pkg/addr:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/sock/reliable:go_default_library", - ], -) diff --git a/dispatcher/network/app_socket.go b/dispatcher/network/app_socket.go deleted file mode 100644 index 2a6167de82..0000000000 --- a/dispatcher/network/app_socket.go +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package network - -import ( - "fmt" - "io" - "net" - - "github.com/scionproto/scion/dispatcher" - "github.com/scionproto/scion/dispatcher/internal/metrics" - "github.com/scionproto/scion/dispatcher/internal/respool" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable" -) - -// AppSocketServer accepts new connections coming from SCION apps, and -// hands them off to the registration + dataplane handler. -type AppSocketServer struct { - Listener *reliable.Listener - DispServer *dispatcher.Server -} - -func (s *AppSocketServer) Serve() error { - for { - conn, err := s.Listener.Accept() - if err != nil { - return err - } - pconn := conn.(net.PacketConn) - s.Handle(pconn) - } -} - -// Handle passes conn off to a per-connection state handler. -func (h *AppSocketServer) Handle(conn net.PacketConn) { - ch := &AppConnHandler{ - Conn: conn, - Logger: log.New("clientID", fmt.Sprintf("%p", conn)), - } - go func() { - defer log.HandlePanic() - ch.Handle(h.DispServer) - }() -} - -// AppConnHandler handles a single SCION application connection. -type AppConnHandler struct { - // Conn is the local socket to which the application is connected. - Conn net.PacketConn - DispConn *dispatcher.Conn - Logger log.Logger -} - -func (h *AppConnHandler) Handle(appServer *dispatcher.Server) { - h.Logger.Debug("Accepted new client") - defer h.Logger.Debug("Closed client socket") - defer h.Conn.Close() - - dispConn, err := h.doRegExchange(appServer) - if err != nil { - metrics.M.AppConnErrors().Inc() - h.Logger.Info("Registration error", "err", err) - return - } - h.DispConn = dispConn.(*dispatcher.Conn) - defer h.DispConn.Close() - svc := h.DispConn.SVCAddr().String() - metrics.M.OpenSockets(metrics.SVC{Type: svc}).Inc() - defer metrics.M.OpenSockets(metrics.SVC{Type: svc}).Dec() - - go func() { - defer log.HandlePanic() - h.RunRingToAppDataplane() - }() - - h.RunAppToNetDataplane() -} - -// doRegExchange manages an application's registration request, and returns a -// reference to registered data that should be freed at the end of the -// registration, information about allocated ring buffers and whether an error occurred. -func (h *AppConnHandler) doRegExchange(appServer *dispatcher.Server) (net.PacketConn, error) { - - b := respool.GetBuffer() - defer respool.PutBuffer(b) - - regInfo, err := h.recvRegistration(b) - if err != nil { - return nil, serrors.WrapStr("receiving registration message", err) - } - appConn, _, err := appServer.Register(nil, - regInfo.IA, regInfo.PublicAddress, regInfo.SVCAddress) - if err != nil { - return nil, serrors.WrapStr("add registration", err, "registration", regInfo) - } - udpAddr := appConn.(*dispatcher.Conn).LocalAddr().(*net.UDPAddr) - port := uint16(udpAddr.Port) - if err := h.sendConfirmation(b, &reliable.Confirmation{Port: port}); err != nil { - appConn.Close() - return nil, serrors.WrapStr("sending registration confirmation message", err) - } - h.logRegistration(regInfo.IA, udpAddr, getBindIP(regInfo.BindAddress), - regInfo.SVCAddress) - return appConn, nil -} - -func (h *AppConnHandler) logRegistration(ia addr.IA, public *net.UDPAddr, bind net.IP, - svc addr.SVC) { - - items := []interface{}{"ia", ia, "public", public} - if bind != nil { - items = append(items, "extra_bind", bind) - } - if svc != addr.SvcNone { - items = append(items, "svc", svc) - } - h.Logger.Debug("Client registered address", items...) -} - -func (h *AppConnHandler) recvRegistration(b []byte) (*reliable.Registration, error) { - n, _, err := h.Conn.ReadFrom(b) - if err != nil { - return nil, err - } - b = b[:n] - - var rm reliable.Registration - if err := rm.DecodeFromBytes(b); err != nil { - return nil, err - } - return &rm, nil -} - -func (h *AppConnHandler) sendConfirmation(b []byte, c *reliable.Confirmation) error { - n, err := c.SerializeTo(b) - if err != nil { - return err - } - b = b[:n] - - if _, err := h.Conn.WriteTo(b, nil); err != nil { - return err - } - return nil -} - -// RunAppToNetDataplane moves packets from the application's socket to the -// underlay socket. -func (h *AppConnHandler) RunAppToNetDataplane() { - - for { - pkt := respool.GetPacket() - // XXX(scrye): we don't release the reference on error conditions, and - // let the GC take care of this situation as they should be fairly - // rare. - - if err := pkt.DecodeFromReliableConn(h.Conn); err != nil { - if err == io.EOF { - h.Logger.Debug("[app->network] EOF received from client") - } else { - h.Logger.Debug("[app->network] Client connection error", "err", err) - metrics.M.AppReadErrors().Inc() - } - return - } - metrics.M.AppReadBytes().Add(float64(pkt.Len())) - metrics.M.AppReadPkts().Inc() - - n, err := h.DispConn.Write(pkt) - if err != nil { - metrics.M.NetWriteErrors().Inc() - h.Logger.Error("[app->network] Underlay socket error", "err", err) - } else { - metrics.M.NetWriteBytes().Add(float64(n)) - metrics.M.NetWritePkts().Inc() - } - pkt.Free() - } -} - -// RunRingToAppDataplane moves packets from the application's ingress ring to -// the application's socket. -func (h *AppConnHandler) RunRingToAppDataplane() { - for { - pkt := h.DispConn.Read() - if pkt == nil { - // Ring was closed because app shut down its data socket - return - } - n, err := pkt.SendOnConn(h.Conn, pkt.UnderlayRemote) - if err != nil { - metrics.M.AppWriteErrors().Inc() - h.Logger.Error("[network->app] App connection error.", "err", err) - h.Conn.Close() - return - } - metrics.M.AppWritePkts().Inc() - metrics.M.AppWriteBytes().Add(float64(n)) - pkt.Free() - } -} - -func getBindIP(address *net.UDPAddr) net.IP { - if address == nil { - return nil - } - return address.IP -} diff --git a/dispatcher/network/dispatcher.go b/dispatcher/network/dispatcher.go deleted file mode 100644 index bfbbfb53cd..0000000000 --- a/dispatcher/network/dispatcher.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package network - -import ( - "os" - - "github.com/scionproto/scion/dispatcher" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable" -) - -type Dispatcher struct { - UnderlaySocket string - ApplicationSocket string - SocketFileMode os.FileMode -} - -func (d *Dispatcher) ListenAndServe() error { - dispServer, err := dispatcher.NewServer(d.UnderlaySocket, nil, nil) - if err != nil { - return err - } - defer dispServer.Close() - - dispServerConn, err := reliable.Listen(d.ApplicationSocket) - if err != nil { - return err - } - defer dispServerConn.Close() - if err := os.Chmod(d.ApplicationSocket, d.SocketFileMode); err != nil { - return serrors.WrapStr("chmod failed", err, "socket file", d.ApplicationSocket) - } - - errChan := make(chan error) - go func() { - defer log.HandlePanic() - errChan <- dispServer.Serve() - }() - - go func() { - defer log.HandlePanic() - dispServer := &AppSocketServer{ - Listener: dispServerConn, - DispServer: dispServer, - } - errChan <- dispServer.Serve() - }() - - return <-errChan -} diff --git a/dispatcher/table.go b/dispatcher/table.go deleted file mode 100644 index e48b40af76..0000000000 --- a/dispatcher/table.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package dispatcher - -import ( - "net" - - "github.com/scionproto/scion/dispatcher/internal/registration" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/private/ringbuf" -) - -type TableEntry struct { - appIngressRing *ringbuf.Ring -} - -func newTableEntry() *TableEntry { - // Construct application ingress ring buffer - appIngressRing := ringbuf.New(128, nil, "net_to_app_ring") - return &TableEntry{ - appIngressRing: appIngressRing, - } -} - -// IATable is a type-safe convenience wrapper around a generic routing table. -type IATable struct { - registration.IATable -} - -func NewIATable(minPort, maxPort int) *IATable { - return &IATable{ - IATable: registration.NewIATable(minPort, maxPort), - } -} - -func (t *IATable) LookupPublic(ia addr.IA, public *net.UDPAddr) (*TableEntry, bool) { - e, ok := t.IATable.LookupPublic(ia, public) - if !ok { - return nil, false - } - return e.(*TableEntry), true -} - -func (t *IATable) LookupService(ia addr.IA, svc addr.SVC, bind net.IP) []*TableEntry { - ifaces := t.IATable.LookupService(ia, svc, bind) - entries := make([]*TableEntry, len(ifaces)) - for i := range ifaces { - entries[i] = ifaces[i].(*TableEntry) - } - return entries -} - -func (t *IATable) LookupID(ia addr.IA, id uint64) (*TableEntry, bool) { - e, ok := t.IATable.LookupID(ia, id) - if !ok { - return nil, false - } - return e.(*TableEntry), true -} diff --git a/dispatcher/underlay.go b/dispatcher/underlay.go deleted file mode 100644 index 4a09cc22b8..0000000000 --- a/dispatcher/underlay.go +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2020 ETH Zurich, Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package dispatcher - -import ( - "net" - - "github.com/google/gopacket" - - "github.com/scionproto/scion/dispatcher/internal/metrics" - "github.com/scionproto/scion/dispatcher/internal/respool" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/common" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/slayers" - "github.com/scionproto/scion/pkg/slayers/path/epic" - "github.com/scionproto/scion/pkg/slayers/path/scion" - "github.com/scionproto/scion/private/ringbuf" -) - -const ( - ErrUnsupportedL4 common.ErrMsg = "unsupported SCION L4 protocol" - ErrUnsupportedDestination common.ErrMsg = "unsupported destination address type" - ErrUnsupportedSCMPDestination common.ErrMsg = "unsupported SCMP destination address type" - ErrUnsupportedQuotedL4Type common.ErrMsg = "unsupported quoted L4 protocol type" - ErrMalformedL4Quote common.ErrMsg = "malformed L4 quote" -) - -// NetToRingDataplane reads SCION packets from the underlay socket, routes them -// to determine the destination process, and then enqueues the packets on the -// application's ingress ring. -// -// The rings are used to provide non-blocking IO for the underlay receiver. -type NetToRingDataplane struct { - UnderlayConn net.PacketConn - RoutingTable *IATable -} - -func (dp *NetToRingDataplane) Run() error { - for { - pkt := respool.GetPacket() - // XXX(scrye): we don't release the reference on error conditions, and - // let the GC take care of this situation as they should be fairly - // rare. - - if err := pkt.DecodeFromConn(dp.UnderlayConn); err != nil { - log.Debug("error receiving next packet from underlay conn", "err", err) - continue - } - dst, err := getDst(pkt) - if err != nil { - log.Debug("unable to route packet", "err", err) - metrics.M.NetReadPkts( - metrics.IncomingPacket{Result: metrics.PacketResultRouteNotFound}, - ).Inc() - continue - } - metrics.M.NetReadPkts(metrics.IncomingPacket{Result: metrics.PacketResultOk}).Inc() - dst.Send(dp, pkt) - } -} - -func getDst(pkt *respool.Packet) (Destination, error) { - switch pkt.L4 { - case slayers.LayerTypeSCIONUDP: - return getDstUDP(pkt) - case slayers.LayerTypeSCMP: - return getDstSCMP(pkt) - default: - return nil, serrors.WithCtx(ErrUnsupportedL4, "type", pkt.L4) - } -} - -func getDstUDP(pkt *respool.Packet) (Destination, error) { - dst, err := pkt.SCION.DstAddr() - if err != nil { - return nil, err - } - switch dst.Type() { - case addr.HostTypeIP: - return UDPDestination{ - IA: pkt.SCION.DstIA, - Public: &net.UDPAddr{ - IP: dst.IP().AsSlice(), - Port: int(pkt.UDP.DstPort), - }, - }, nil - case addr.HostTypeSVC: - return SVCDestination{ - IA: pkt.SCION.DstIA, - Svc: dst.SVC(), - }, nil - default: - return nil, serrors.WithCtx(ErrUnsupportedDestination, "type", common.TypeOf(dst)) - } -} - -func getDstSCMP(pkt *respool.Packet) (Destination, error) { - if !pkt.SCMP.TypeCode.InfoMsg() { - dst, err := getDstSCMPErr(pkt) - if err != nil { - return nil, serrors.WrapStr("delivering SCMP error message", err) - } - return dst, nil - } - return getDstSCMPInfo(pkt) -} - -func getDstSCMPInfo(pkt *respool.Packet) (Destination, error) { - t := pkt.SCMP.TypeCode.Type() - if t == slayers.SCMPTypeEchoRequest || t == slayers.SCMPTypeTracerouteRequest { - return SCMPHandler{}, nil - } - if t == slayers.SCMPTypeEchoReply || t == slayers.SCMPTypeTracerouteReply { - id, err := extractSCMPIdentifier(&pkt.SCMP) - if err != nil { - return nil, err - } - return SCMPDestination{IA: pkt.SCION.DstIA, ID: id}, nil - } - return nil, serrors.New("unsupported SCMP info message", "type", t) -} - -func getDstSCMPErr(pkt *respool.Packet) (Destination, error) { - // Drop unknown SCMP error messages. - if pkt.SCMP.NextLayerType() == gopacket.LayerTypePayload { - return nil, serrors.New("unsupported SCMP error message", "type", pkt.SCMP.TypeCode.Type()) - } - l, err := decodeSCMP(&pkt.SCMP) - if err != nil { - return nil, err - } - if len(l) != 2 { - return nil, serrors.New("SCMP error message without payload") - } - gpkt := gopacket.NewPacket(*l[1].(*gopacket.Payload), slayers.LayerTypeSCION, - gopacket.DecodeOptions{ - NoCopy: true, - }, - ) - - // If the offending packet was UDP/SCION, use the source port to deliver. - if udp := gpkt.Layer(slayers.LayerTypeSCIONUDP); udp != nil { - port := int(udp.(*slayers.UDP).SrcPort) - // XXX(roosd): We assume that the zero value means the UDP header is - // truncated. This flags packets of misbehaving senders as truncated, if - // they set the source port to 0. But there is no harm, since those - // packets are destined to be dropped anyway. - if port == 0 { - return nil, serrors.New("SCMP error with truncated UDP header") - } - dst, err := pkt.SCION.DstAddr() - if err != nil { - return nil, err - } - if dst.Type() != addr.HostTypeIP { - return nil, serrors.WithCtx(ErrUnsupportedDestination, "type", dst.Type()) - } - return UDPDestination{ - IA: pkt.SCION.DstIA, - Public: &net.UDPAddr{ - IP: dst.IP().AsSlice(), - Port: port, - }, - }, nil - } - - // If the offending packet was SCMP/SCION, and it is an echo or traceroute, - // use the Identifier to deliver. In all other cases, the message is dropped. - if scmp := gpkt.Layer(slayers.LayerTypeSCMP); scmp != nil { - - tc := scmp.(*slayers.SCMP).TypeCode - // SCMP Error messages in response to an SCMP error message are not allowed. - if !tc.InfoMsg() { - return nil, serrors.New("SCMP error message in response to SCMP error message", - "type", tc.Type()) - } - // We only support echo and traceroute requests. - t := tc.Type() - if t != slayers.SCMPTypeEchoRequest && t != slayers.SCMPTypeTracerouteRequest { - return nil, serrors.New("unsupported SCMP info message", "type", t) - } - - var id uint16 - // Extract the ID from the echo or traceroute layer. - if echo := gpkt.Layer(slayers.LayerTypeSCMPEcho); echo != nil { - id = echo.(*slayers.SCMPEcho).Identifier - } else if tr := gpkt.Layer(slayers.LayerTypeSCMPTraceroute); tr != nil { - id = tr.(*slayers.SCMPTraceroute).Identifier - } else { - return nil, serrors.New("SCMP error with truncated payload") - } - return SCMPDestination{ - IA: pkt.SCION.DstIA, - ID: id, - }, nil - } - return nil, ErrUnsupportedL4 -} - -// UDPDestination delivers packets to the app that registered for the configured -// public address. -type UDPDestination struct { - IA addr.IA - Public *net.UDPAddr -} - -func (d UDPDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) { - routingEntry, ok := dp.RoutingTable.LookupPublic(d.IA, d.Public) - if !ok { - metrics.M.AppNotFoundErrors().Inc() - log.Debug("destination address not found", "isd_as", d.IA, "udp_addr", d.Public) - return - } - sendPacket(routingEntry, pkt) -} - -// SVCDestination delivers packets to apps that registered for the configured -// service. -type SVCDestination struct { - IA addr.IA - Svc addr.SVC -} - -func (d SVCDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) { - // FIXME(scrye): This should deliver to the correct IP address, based on - // information found in the underlay IP header. - routingEntries := dp.RoutingTable.LookupService(d.IA, d.Svc, nil) - if len(routingEntries) == 0 { - metrics.M.AppNotFoundErrors().Inc() - log.Debug("destination address not found", "isd_as", d.IA, "svc", d.Svc) - return - } - // Increase reference count for all extra copies - for i := 0; i < len(routingEntries)-1; i++ { - pkt.Dup() - } - for _, routingEntry := range routingEntries { - metrics.M.AppWriteSVCPkts(metrics.SVC{Type: d.Svc.String()}).Inc() - sendPacket(routingEntry, pkt) - } -} - -type SCMPDestination struct { - IA addr.IA - ID uint16 -} - -func (d SCMPDestination) Send(dp *NetToRingDataplane, pkt *respool.Packet) { - routingEntry, ok := dp.RoutingTable.LookupID(d.IA, uint64(d.ID)) - if !ok { - metrics.M.AppNotFoundErrors().Inc() - log.Debug("destination address not found", "SCMP", d.ID) - return - } - sendPacket(routingEntry, pkt) -} - -// SCMPHandler replies to SCMP echo and traceroute requests. -type SCMPHandler struct{} - -func (h SCMPHandler) Send(dp *NetToRingDataplane, pkt *respool.Packet) { - // FIXME(roosd): introduce metrics again. - raw, err := h.reverse(pkt) - if err != nil { - log.Info("Failed to reverse SCMP packet, dropping", "err", err) - return - } - _, err = dp.UnderlayConn.WriteTo(raw, pkt.UnderlayRemote) - if err != nil { - log.Info("Unable to write to underlay socket", "err", err) - return - } - pkt.Free() -} - -func (h SCMPHandler) reverse(pkt *respool.Packet) ([]byte, error) { - l, err := decodeSCMP(&pkt.SCMP) - if err != nil { - return nil, err - } - // Translate request to a reply. - switch l[0].LayerType() { - case slayers.LayerTypeSCMPEcho: - pkt.SCMP.TypeCode = slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0) - case slayers.LayerTypeSCMPTraceroute: - pkt.SCMP.TypeCode = slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0) - default: - return nil, serrors.New("unsupported SCMP informational message") - } - if err := h.reverseSCION(pkt); err != nil { - return nil, err - } - // XXX(roosd): This does not take HBH and E2E extensions into consideration. - // See: https://github.com/scionproto/scion/issues/4128 - pkt.SCION.NextHdr = slayers.L4SCMP - // FIXME(roosd): Consider moving this to a resource pool. - buf := gopacket.NewSerializeBuffer() - pkt.SCMP.SetNetworkLayerForChecksum(&pkt.SCION) - err = gopacket.SerializeLayers( - buf, - gopacket.SerializeOptions{ - ComputeChecksums: true, - FixLengths: true, - }, - append([]gopacket.SerializableLayer{&pkt.SCION, &pkt.SCMP}, l...)..., - ) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func (h SCMPHandler) reverseSCION(pkt *respool.Packet) error { - // Reverse the SCION packet. - pkt.SCION.DstIA, pkt.SCION.SrcIA = pkt.SCION.SrcIA, pkt.SCION.DstIA - src, err := pkt.SCION.SrcAddr() - if err != nil { - return serrors.WrapStr("parsing source address", err) - } - dst, err := pkt.SCION.DstAddr() - if err != nil { - return serrors.WrapStr("parsing destination address", err) - } - if err := pkt.SCION.SetSrcAddr(dst); err != nil { - return serrors.WrapStr("setting source address", err) - } - if err := pkt.SCION.SetDstAddr(src); err != nil { - return serrors.WrapStr("setting destination address", err) - } - if pkt.SCION.PathType == epic.PathType { - // Received packet with EPIC path type, hence extract the SCION path - epicPath, ok := pkt.SCION.Path.(*epic.Path) - if !ok { - return serrors.New("path type and path data do not match") - } - pkt.SCION.Path = epicPath.ScionPath - pkt.SCION.PathType = scion.PathType - } - if pkt.SCION.Path, err = pkt.SCION.Path.Reverse(); err != nil { - return serrors.WrapStr("reversing path", err) - } - return nil -} - -func extractSCMPIdentifier(scmp *slayers.SCMP) (uint16, error) { - l, err := decodeSCMP(scmp) - if err != nil { - return 0, err - } - switch info := l[0].(type) { - case *slayers.SCMPEcho: - return info.Identifier, nil - case *slayers.SCMPTraceroute: - return info.Identifier, nil - default: - return 0, serrors.New("invalid SCMP info message", "type_code", scmp.TypeCode) - } -} - -// decodeSCMP decodes the SCMP payload. WARNING: Decoding is done with NoCopy set. -func decodeSCMP(scmp *slayers.SCMP) ([]gopacket.SerializableLayer, error) { - gpkt := gopacket.NewPacket(scmp.Payload, scmp.NextLayerType(), - gopacket.DecodeOptions{NoCopy: true}) - layers := gpkt.Layers() - if len(layers) == 0 || len(layers) > 2 { - return nil, serrors.New("invalid number of SCMP layers", "count", len(layers)) - } - ret := make([]gopacket.SerializableLayer, len(layers)) - for i, l := range layers { - s, ok := l.(gopacket.SerializableLayer) - if !ok { - return nil, serrors.New("invalid SCMP layer, not serializable", "index", i) - } - ret[i] = s - } - return ret, nil -} - -type Destination interface { - // Send takes ownership of pkt, and then sends it to the location described - // by this destination. - Send(dp *NetToRingDataplane, pkt *respool.Packet) -} - -// sendPacket puts pkt on the routing entry's ring buffer, and releases the -// reference to pkt. -func sendPacket(routingEntry *TableEntry, pkt *respool.Packet) { - // Move packet reference to other goroutine. - count, _ := routingEntry.appIngressRing.Write(ringbuf.EntryList{pkt}, false) - if count <= 0 { - // Release buffer if we couldn't transmit it to the other goroutine. - pkt.Free() - } -} diff --git a/dispatcher/underlay_test.go b/dispatcher/underlay_test.go deleted file mode 100644 index e7e72a1ed5..0000000000 --- a/dispatcher/underlay_test.go +++ /dev/null @@ -1,957 +0,0 @@ -// Copyright 2019 ETH Zurich -// Copyright 2020 ETH Zurich, Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package dispatcher - -import ( - "bytes" - "net" - "testing" - - "github.com/golang/mock/gomock" - "github.com/google/gopacket" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scionproto/scion/dispatcher/internal/respool" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/slayers" - "github.com/scionproto/scion/pkg/slayers/path" - "github.com/scionproto/scion/pkg/slayers/path/scion" -) - -func TestGetDst(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - testCases := map[string]struct { - Pkt func(t *testing.T) *respool.Packet - ExpectedDst Destination - ErrAssertion assert.ErrorAssertionFunc - }{ - "unsupported L4": { - Pkt: func(t *testing.T) *respool.Packet { - return &respool.Packet{ - L4: 1337, - } - }, - ErrAssertion: assert.Error, - }, - "UDP/SCION with IP destination is delivered by IP": { - Pkt: func(t *testing.T) *respool.Packet { - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - UDP: slayers.UDP{ - DstPort: 1337, - }, - L4: slayers.LayerTypeSCIONUDP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: UDPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - Public: &net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1337}, - }, - ErrAssertion: assert.NoError, - }, - "UDP/SCION with SVC destination is delivered by SVC": { - Pkt: func(t *testing.T) *respool.Packet { - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - UDP: slayers.UDP{ - DstPort: 1337, - }, - L4: slayers.LayerTypeSCIONUDP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.HostSVC(addr.SvcCS))) - return pkt - }, - ExpectedDst: SVCDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - Svc: addr.SvcCS, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION EchoRequest, is sent to SCMP handler": { - Pkt: func(t *testing.T) *respool.Packet { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 13, - }, - ) - require.NoError(t, err) - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - return pkt - }, - ExpectedDst: SCMPHandler{}, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION EchoReply, is sent to SCMP destination": { - Pkt: func(t *testing.T) *respool.Packet { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 13, - }, - ) - require.NoError(t, err) - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0), - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION TracerouteRequest, is sent to SCMP handler": { - Pkt: func(t *testing.T) *respool.Packet { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - }, - ) - require.NoError(t, err) - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0), - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - return pkt - }, - ExpectedDst: SCMPHandler{}, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION TracerouteReply, is sent to SCMP destination": { - Pkt: func(t *testing.T) *respool.Packet { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - }, - ) - require.NoError(t, err) - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0), - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with offending UDP/SCION is delivered by IP": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4UDP) - udp := &slayers.UDP{ - SrcPort: 1337, - DstPort: 42, - } - udp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - udp, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPExternalInterfaceDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - IfID: 141, - }, - gopacket.Payload(buf.Bytes()), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeExternalInterfaceDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: UDPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - Public: &net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1337}, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with offending SCMP/SCION EchoRequest is delivered by ID": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - scmp, - &slayers.SCMPEcho{Identifier: 42, SeqNumber: 16}, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPInternalConnectivityDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - Ingress: 131, - Egress: 141, - }, - gopacket.Payload(buf.Bytes()), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeInternalConnectivityDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with offending SCMP/SCION TracerouteRequest is delivered by ID": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - scmp, - &slayers.SCMPTraceroute{Identifier: 42}, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPInternalConnectivityDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - Ingress: 131, - Egress: 141, - }, - gopacket.Payload(buf.Bytes()), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeInternalConnectivityDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with truncated UDP/SCION payload is delivered by IP": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4UDP) - udp := &slayers.UDP{ - SrcPort: 1337, - DstPort: 42, - } - udp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - udp, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPExternalInterfaceDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - IfID: 141, - }, - gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-20]), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeExternalInterfaceDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: UDPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - Public: &net.UDPAddr{IP: net.IP{192, 168, 0, 1}, Port: 1337}, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with offending truncated EchoRequest is delivered by ID": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - scmp, - &slayers.SCMPEcho{Identifier: 42, SeqNumber: 16}, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPInternalConnectivityDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - Ingress: 131, - Egress: 141, - }, - // Truncate the SCMP Echo data. - gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-20]), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeInternalConnectivityDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with offending truncated TracerouteRequest is delivered by ID": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - scmp, - &slayers.SCMPTraceroute{Identifier: 42}, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPInternalConnectivityDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - Ingress: 131, - Egress: 141, - }, - // Truncate the SCMP Traceroute data. - gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-20]), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeInternalConnectivityDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ExpectedDst: SCMPDestination{ - IA: xtest.MustParseIA("1-ff00:0:110"), - ID: 42, - }, - ErrAssertion: assert.NoError, - }, - "SCMP/SCION Error with partial UDP/SCION header is dropped": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4UDP) - udp := &slayers.UDP{ - SrcPort: 1337, - DstPort: 42, - } - udp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - udp, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPExternalInterfaceDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - IfID: 141, - }, - gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-21]), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeExternalInterfaceDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ErrAssertion: assert.Error, - }, - "SCMP/SCION Error with partial EchoRequest is dropped": { - Pkt: func(t *testing.T) *respool.Packet { - // Construct offending packet. - scion := newSCIONHdr(t, slayers.L4SCMP) - scmp := &slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - } - scmp.SetNetworkLayerForChecksum(scion) - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - }, - scion, - scmp, - &slayers.SCMPEcho{Identifier: 42, SeqNumber: 16}, - gopacket.Payload(bytes.Repeat([]byte{0xff}, 20)), - ) - require.NoError(t, err) - - scmpPld := gopacket.NewSerializeBuffer() - err = gopacket.SerializeLayers(scmpPld, - gopacket.SerializeOptions{}, - &slayers.SCMPInternalConnectivityDown{ - IA: xtest.MustParseIA("1-ff00:0:111"), - Ingress: 131, - Egress: 141, - }, - // Only partially include the echo request information. - gopacket.Payload(buf.Bytes()[:len(buf.Bytes())-21]), - ) - require.NoError(t, err) - - // Construct packet received by dispatcher. - pkt := &respool.Packet{ - SCION: slayers.SCION{ - DstIA: xtest.MustParseIA("1-ff00:0:110"), - }, - SCMP: slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode( - slayers.SCMPTypeInternalConnectivityDown, 0), - BaseLayer: slayers.BaseLayer{ - Payload: scmpPld.Bytes(), - }, - }, - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("192.168.0.1"))) - return pkt - }, - ErrAssertion: assert.Error, - }, - } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - destination, err := getDst(tc.Pkt(t)) - tc.ErrAssertion(t, err) - assert.Equal(t, tc.ExpectedDst, destination) - }) - } -} - -func TestSCMPHandlerReverse(t *testing.T) { - testCases := map[string]struct { - L4 func(t *testing.T) slayers.SCMP - ExpectedTypeCode slayers.SCMPTypeCode - ExpectedL4 func(t *testing.T) []gopacket.SerializableLayer - }{ - "echo without data": { - L4: func(t *testing.T) slayers.SCMP { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 12, - }, - ) - require.NoError(t, err) - return slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - Checksum: 1337, - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - } - }, - ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0), - ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 12, - }, - ) - require.NoError(t, err) - pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPEcho, - gopacket.DecodeOptions{}) - echo := pkt.Layer(slayers.LayerTypeSCMPEcho) - require.NotNil(t, echo) - return []gopacket.SerializableLayer{echo.(gopacket.SerializableLayer)} - }, - }, - "echo with data": { - L4: func(t *testing.T) slayers.SCMP { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 12, - }, - gopacket.Payload("I am the payload, please don't forget about me :)"), - ) - require.NoError(t, err) - return slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoRequest, 0), - Checksum: 1337, - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - } - }, - ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeEchoReply, 0), - ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer { - pld := gopacket.Payload("I am the payload, please don't forget about me :)") - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPEcho{ - Identifier: 42, - SeqNumber: 12, - }, - pld, - ) - require.NoError(t, err) - pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPEcho, - gopacket.DecodeOptions{}) - echo := pkt.Layer(slayers.LayerTypeSCMPEcho) - require.NotNil(t, echo) - return []gopacket.SerializableLayer{echo.(gopacket.SerializableLayer), &pld} - }, - }, - "traceroute without data": { - L4: func(t *testing.T) slayers.SCMP { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - IA: xtest.MustParseIA("1-ff00:0:110"), - Interface: 12, - }, - ) - require.NoError(t, err) - return slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0), - Checksum: 1337, - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - } - }, - ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0), - ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - IA: xtest.MustParseIA("1-ff00:0:110"), - Interface: 12, - }, - ) - require.NoError(t, err) - pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPTraceroute, - gopacket.DecodeOptions{}) - tr := pkt.Layer(slayers.LayerTypeSCMPTraceroute) - require.NotNil(t, tr) - return []gopacket.SerializableLayer{tr.(gopacket.SerializableLayer)} - }, - }, - "traceroute with data": { - L4: func(t *testing.T) slayers.SCMP { - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - IA: xtest.MustParseIA("1-ff00:0:110"), - Interface: 12, - }, - gopacket.Payload("I am the payload, please don't forget about me :)"), - ) - require.NoError(t, err) - return slayers.SCMP{ - TypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteRequest, 0), - Checksum: 1337, - BaseLayer: slayers.BaseLayer{ - Payload: buf.Bytes(), - }, - } - }, - ExpectedTypeCode: slayers.CreateSCMPTypeCode(slayers.SCMPTypeTracerouteReply, 0), - ExpectedL4: func(t *testing.T) []gopacket.SerializableLayer { - pld := gopacket.Payload("I am the payload, please don't forget about me :)") - buf := gopacket.NewSerializeBuffer() - err := gopacket.SerializeLayers(buf, - gopacket.SerializeOptions{}, - &slayers.SCMPTraceroute{ - Identifier: 42, - IA: xtest.MustParseIA("1-ff00:0:110"), - Interface: 12, - }, - pld, - ) - require.NoError(t, err) - pkt := gopacket.NewPacket(buf.Bytes(), slayers.LayerTypeSCMPTraceroute, - gopacket.DecodeOptions{}) - tr := pkt.Layer(slayers.LayerTypeSCMPTraceroute) - require.NotNil(t, tr) - return []gopacket.SerializableLayer{tr.(gopacket.SerializableLayer), &pld} - }, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - // Prepare original packet - pkt := &respool.Packet{ - SCION: slayers.SCION{ - Version: 0, - TrafficClass: 0xb8, - FlowID: 0xdead, - NextHdr: slayers.L4SCMP, - PathType: scion.PathType, - SrcIA: xtest.MustParseIA("1-ff00:0:110"), - DstIA: xtest.MustParseIA("1-ff00:0:112"), - Path: &scion.Decoded{ - Base: scion.Base{ - PathMeta: scion.MetaHdr{ - CurrHF: 2, - SegLen: [3]uint8{3, 0, 0}, - }, - NumINF: 1, - NumHops: 3, - }, - InfoFields: []path.InfoField{ - {SegID: 0x111, ConsDir: true, Timestamp: 0x100}, - }, - HopFields: []path.HopField{ - {ConsIngress: 0, ConsEgress: 311, - Mac: [path.MacLen]byte{0, 0, 0, 0, 0, 0}}, - {ConsIngress: 131, ConsEgress: 141, - Mac: [path.MacLen]byte{1, 1, 1, 1, 1, 1}}, - {ConsIngress: 411, ConsEgress: 0, - Mac: [path.MacLen]byte{2, 2, 2, 2, 2, 2}}, - }, - }, - }, - SCMP: tc.L4(t), - L4: slayers.LayerTypeSCMP, - } - require.NoError(t, pkt.SCION.SetSrcAddr(addr.MustParseHost("127.0.0.1"))) - require.NoError(t, pkt.SCION.SetDstAddr(addr.MustParseHost("127.0.0.2"))) - - // Reverse packet - raw, err := SCMPHandler{}.reverse(pkt) - require.NoError(t, err) - - gpkt := gopacket.NewPacket(raw, slayers.LayerTypeSCION, gopacket.DecodeOptions{}) - - t.Run("check SCION header", func(t *testing.T) { - scionL := gpkt.Layer(slayers.LayerTypeSCION).(*slayers.SCION) - expected := &slayers.SCION{ - Version: 0, - TrafficClass: 0xb8, - FlowID: 0xdead, - HdrLen: 21, - NextHdr: slayers.L4SCMP, - PayloadLen: uint16(4 + len(pkt.SCMP.Payload)), - PathType: scion.PathType, - SrcIA: xtest.MustParseIA("1-ff00:0:112"), - DstIA: xtest.MustParseIA("1-ff00:0:110"), - Path: &scion.Decoded{ - Base: scion.Base{ - PathMeta: scion.MetaHdr{ - CurrHF: 0, - SegLen: [3]uint8{3, 0, 0}, - }, - NumINF: 1, - NumHops: 3, - }, - InfoFields: []path.InfoField{ - {SegID: 0x111, ConsDir: false, Timestamp: 0x100}, - }, - HopFields: []path.HopField{ - {ConsIngress: 411, ConsEgress: 0, - Mac: [path.MacLen]byte{2, 2, 2, 2, 2, 2}}, - {ConsIngress: 131, ConsEgress: 141, - Mac: [path.MacLen]byte{1, 1, 1, 1, 1, 1}}, - {ConsIngress: 0, ConsEgress: 311, - Mac: [path.MacLen]byte{0, 0, 0, 0, 0, 0}}, - }, - }, - } - require.NoError(t, expected.SetSrcAddr(addr.MustParseHost("127.0.0.2"))) - require.NoError(t, expected.SetDstAddr(addr.MustParseHost("127.0.0.1"))) - - scionL.BaseLayer = slayers.BaseLayer{} - var decodedPath scion.Decoded - require.NoError(t, decodedPath.DecodeFromBytes(scionL.Path.(*scion.Raw).Raw)) - scionL.Path = &decodedPath - - assert.Equal(t, expected, scionL) - }) - t.Run("check L4", func(t *testing.T) { - scmp := gpkt.Layer(slayers.LayerTypeSCMP) - require.NotNil(t, scmp) - assert.Equal(t, tc.ExpectedTypeCode, scmp.(*slayers.SCMP).TypeCode) - assert.NotZero(t, scmp.(*slayers.SCMP).Checksum) - - for _, l := range tc.ExpectedL4(t) { - assert.Equal(t, l, gpkt.Layer(l.LayerType()), l.LayerType().String()) - } - }) - }) - } -} - -func newSCIONHdr(t *testing.T, l4 slayers.L4ProtocolType) *slayers.SCION { - scion := &slayers.SCION{ - NextHdr: l4, - PathType: scion.PathType, - SrcIA: xtest.MustParseIA("1-ff00:0:110"), - DstIA: xtest.MustParseIA("1-ff00:0:112"), - Path: &scion.Decoded{ - Base: scion.Base{ - PathMeta: scion.MetaHdr{ - CurrHF: 2, - SegLen: [3]uint8{3, 0, 0}, - }, - NumINF: 1, - NumHops: 3, - }, - InfoFields: []path.InfoField{ - {SegID: 0x111, ConsDir: true, Timestamp: 0x100}, - }, - HopFields: []path.HopField{ - {ConsIngress: 0, ConsEgress: 311}, - {ConsIngress: 131, ConsEgress: 141}, - {ConsIngress: 411, ConsEgress: 0}, - }, - }, - } - require.NoError(t, scion.SetSrcAddr(addr.MustParseHost("192.168.0.1"))) - require.NoError(t, scion.SetDstAddr(addr.MustParseHost("192.168.0.2"))) - return scion -} diff --git a/dist/conffiles/daemon.toml b/dist/conffiles/daemon.toml index 3abf018ab7..2401c8f1ca 100644 --- a/dist/conffiles/daemon.toml +++ b/dist/conffiles/daemon.toml @@ -1,7 +1,6 @@ [general] id = "sd" config_dir = "/etc/scion" -reconnect_to_dispatcher = true [path_db] connection = "/var/lib/scion/sd.path.db" diff --git a/dist/conffiles/dispatcher.toml b/dist/conffiles/dispatcher.toml index d6f83a1ddc..1394e70d99 100644 --- a/dist/conffiles/dispatcher.toml +++ b/dist/conffiles/dispatcher.toml @@ -1,10 +1,14 @@ [dispatcher] id = "dispatcher" -socket_file_mode = "0777" [log.console] level = "info" +# If infra SVCs need to run with old br +# [dispatcher.service_addresses] +# "1-ff00:0:110,CS" = "172.20.0.20:30252" +# "1-ff00:0:110,DS" = "172.20.0.20:30252" + # Optionally expose metrics and other local inspection endpoints. # [metrics] # prometheus = "[127.0.0.1]:30441" diff --git a/dist/openwrt/test_configs/control.toml b/dist/openwrt/test_configs/control.toml index 2b28fcb246..abb10a893f 100644 --- a/dist/openwrt/test_configs/control.toml +++ b/dist/openwrt/test_configs/control.toml @@ -1,7 +1,6 @@ [general] id = "cs-1" config_dir = "/etc/scion" -reconnect_to_dispatcher = true [trust_db] connection = "/var/lib/scion/cs-1.trust.db" diff --git a/dist/openwrt/test_configs/topology.json b/dist/openwrt/test_configs/topology.json index d141753877..3f68320221 100644 --- a/dist/openwrt/test_configs/topology.json +++ b/dist/openwrt/test_configs/topology.json @@ -3,6 +3,7 @@ "core" ], "isd_as": "1-ff00:0:a", + "dispatched_ports": "1024-65535", "mtu": 1472, "control_service": { "cs-1": { diff --git a/dist/systemd/scion-dispatcher.service b/dist/systemd/scion-dispatcher.service index 8621330f70..a5ad58aac7 100644 --- a/dist/systemd/scion-dispatcher.service +++ b/dist/systemd/scion-dispatcher.service @@ -9,7 +9,6 @@ StartLimitInterval=1s Type=simple User=scion Group=scion -ExecStartPre=/bin/rm -rf /run/shm/dispatcher/ ExecStart=/usr/bin/scion-dispatcher --config /etc/scion/dispatcher.toml LimitNOFILE=4096 Restart=on-failure diff --git a/dist/test/deb_test.sh b/dist/test/deb_test.sh index 2ae6179774..295c85097c 100755 --- a/dist/test/deb_test.sh +++ b/dist/test/deb_test.sh @@ -61,6 +61,7 @@ INNER_EOF { "isd_as": "1-ff00:0:a", "mtu": 1472, + "dispatched_ports": "1024-65535", "border_routers": { "br-1": { "internal_addr": "127.0.0.1:30001" diff --git a/gateway/BUILD.bazel b/gateway/BUILD.bazel index 902fb0db7b..b258a55983 100644 --- a/gateway/BUILD.bazel +++ b/gateway/BUILD.bazel @@ -31,8 +31,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/metrics:go_default_library", "//pkg/snet/squic:go_default_library", - "//pkg/sock/reliable:go_default_library", - "//pkg/sock/reliable/reconnect:go_default_library", "//private/app/appnet:go_default_library", "//private/periodic:go_default_library", "//private/service:go_default_library", diff --git a/gateway/cmd/gateway/BUILD.bazel b/gateway/cmd/gateway/BUILD.bazel index 565138a633..07fae88a63 100644 --- a/gateway/cmd/gateway/BUILD.bazel +++ b/gateway/cmd/gateway/BUILD.bazel @@ -15,7 +15,6 @@ go_library( "//pkg/log:go_default_library", "//pkg/private/serrors:go_default_library", "//pkg/snet/addrutil:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/app:go_default_library", "//private/app/launcher:go_default_library", "//private/service:go_default_library", diff --git a/gateway/cmd/gateway/main.go b/gateway/cmd/gateway/main.go index 8796b1863f..ca64ebc203 100644 --- a/gateway/cmd/gateway/main.go +++ b/gateway/cmd/gateway/main.go @@ -34,7 +34,6 @@ import ( "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet/addrutil" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app" "github.com/scionproto/scion/private/app/launcher" "github.com/scionproto/scion/private/service" @@ -145,7 +144,6 @@ func realMain(ctx context.Context) error { ProbeClientIP: controlAddress.IP, DataServerAddr: dataAddress, DataClientIP: dataAddress.IP, - Dispatcher: reliable.NewDispatcher(""), Daemon: daemon, RouteSourceIPv4: globalCfg.Tunnel.SrcIPv4, RouteSourceIPv6: globalCfg.Tunnel.SrcIPv6, diff --git a/gateway/control/grpc/BUILD.bazel b/gateway/control/grpc/BUILD.bazel index b020d0a8a2..8e57b2c6a5 100644 --- a/gateway/control/grpc/BUILD.bazel +++ b/gateway/control/grpc/BUILD.bazel @@ -21,7 +21,6 @@ go_library( "//pkg/proto/discovery:go_default_library", "//pkg/proto/gateway:go_default_library", "//pkg/snet:go_default_library", - "//pkg/sock/reliable:go_default_library", "@org_golang_google_grpc//codes:go_default_library", "@org_golang_google_grpc//peer:go_default_library", "@org_golang_google_grpc//status:go_default_library", diff --git a/gateway/control/grpc/probeserver.go b/gateway/control/grpc/probeserver.go index 4af7500f96..8d58dc6a6f 100644 --- a/gateway/control/grpc/probeserver.go +++ b/gateway/control/grpc/probeserver.go @@ -24,7 +24,6 @@ import ( "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" gpb "github.com/scionproto/scion/pkg/proto/gateway" - "github.com/scionproto/scion/pkg/sock/reliable" ) // ProbeDispatcher handles incoming gateway protocol messages. @@ -46,9 +45,6 @@ func (d *ProbeDispatcher) Listen(ctx context.Context, conn net.PacketConn) error default: n, addr, err := conn.ReadFrom(buf) if err != nil { - if reliable.IsDispatcherError(err) { - return err - } logger.Info("ProbeDispatcher: Error reading from connection", "err", err) // FIXME(shitz): Continuing here is only a temporary solution. Different // errors need to be handled different, for some it should break and others diff --git a/gateway/dataplane/BUILD.bazel b/gateway/dataplane/BUILD.bazel index d6049a4214..a6e554f9e7 100644 --- a/gateway/dataplane/BUILD.bazel +++ b/gateway/dataplane/BUILD.bazel @@ -30,7 +30,6 @@ go_library( "//pkg/slayers:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/ringbuf:go_default_library", "@com_github_google_gopacket//:go_default_library", "@com_github_google_gopacket//layers:go_default_library", diff --git a/gateway/dataplane/ingressserver.go b/gateway/dataplane/ingressserver.go index 3f94b548fa..cdc11c92e5 100644 --- a/gateway/dataplane/ingressserver.go +++ b/gateway/dataplane/ingressserver.go @@ -26,7 +26,6 @@ import ( "github.com/scionproto/scion/pkg/metrics" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/ringbuf" ) @@ -90,9 +89,6 @@ func (d *IngressServer) read(ctx context.Context) error { read, src, err := d.Conn.ReadFrom(frame.raw) if err != nil { logger.Error("IngressServer: Unable to read from external ingress", "err", err) - if reliable.IsDispatcherError(err) { - return serrors.WrapStr("problems speaking to dispatcher", err) - } increaseCounterMetric(d.Metrics.ReceiveExternalError, 1) frame.Release() continue diff --git a/gateway/gateway.go b/gateway/gateway.go index 7682e41a47..31d1448dfe 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -46,8 +46,6 @@ import ( gatewaypb "github.com/scionproto/scion/pkg/proto/gateway" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/squic" - "github.com/scionproto/scion/pkg/sock/reliable" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" infraenv "github.com/scionproto/scion/private/app/appnet" "github.com/scionproto/scion/private/periodic" "github.com/scionproto/scion/private/service" @@ -101,34 +99,13 @@ type PacketConnFactory struct { func (pcf PacketConnFactory) New() (net.PacketConn, error) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() - conn, err := pcf.Network.Listen(ctx, "udp", pcf.Addr, addr.SvcNone) + conn, err := pcf.Network.Listen(ctx, "udp", pcf.Addr) if err != nil { return nil, serrors.WrapStr("creating packet conn", err) } return conn, nil } -type ProbeConnFactory struct { - Dispatcher *reconnect.DispatcherService - LocalIA addr.IA - LocalIP netip.Addr -} - -func (f ProbeConnFactory) New(ctx context.Context) (net.PacketConn, error) { - pathMonitorConnection, pathMonitorPort, err := f.Dispatcher.Register( - context.Background(), - f.LocalIA, - &net.UDPAddr{IP: f.LocalIP.AsSlice()}, - addr.SvcNone, - ) - if err != nil { - return nil, serrors.WrapStr("unable to open control socket", err) - } - log.FromCtx(ctx).Debug("Path monitor connection opened on Raw UDP/SCION", - "local_ip", f.LocalIP, "local_port", pathMonitorPort) - return pathMonitorConnection, nil -} - type RoutingTableFactory struct { RoutePublisherFactory control.PublisherFactory } @@ -191,9 +168,6 @@ type Gateway struct { // DataIP is the IP that should be used for dataplane traffic. DataAddr *net.UDPAddr - // Dispatcher is the API of the SCION Dispatcher on the local host. - Dispatcher reliable.Dispatcher - // Daemon is the API of the SCION Daemon. Daemon daemon.Connector @@ -263,17 +237,15 @@ func (g *Gateway) Run(ctx context.Context) error { routePublisherFactory := createRouteManager(ctx, deviceManager) - // ************************************************************************* - // Initialize base SCION network information: IA + Dispatcher connectivity - // ************************************************************************* + // ********************************************* + // Initialize base SCION network information: IA + // ********************************************* localIA, err := g.Daemon.LocalIA(context.Background()) if err != nil { return serrors.WrapStr("unable to learn local ISD-AS number", err) } logger.Info("Learned local IA from SCION Daemon", "ia", localIA) - reconnectingDispatcher := reconnect.NewDispatcherService(g.Dispatcher) - // ************************************************************************* // Set up path monitoring. The path monitor runs an the SCION/UDP stack // using the control address and uses traceroute packets to check if paths @@ -318,20 +290,16 @@ func (g *Gateway) Run(ctx context.Context) error { RemoteWatcherFactory: &pathhealth.DefaultRemoteWatcherFactory{ Router: pathRouter, PathWatcherFactory: &pathhealth.DefaultPathWatcherFactory{ - LocalIA: localIA, - LocalIP: g.PathMonitorIP, - RevocationHandler: revocationHandler, - ConnFactory: ProbeConnFactory{ - Dispatcher: reconnectingDispatcher, - LocalIA: localIA, - LocalIP: g.PathMonitorIP, - }, + LocalIA: localIA, + LocalIP: g.PathMonitorIP, + RevocationHandler: revocationHandler, ProbeInterval: 0, // using default for now ProbesSent: probesSent, ProbesReceived: probesReceived, ProbesSendErrors: probesSendErrors, SCMPErrors: g.Metrics.SCMPErrors, SCIONPacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Topology: g.Daemon, }, PathUpdateInterval: PathUpdateInterval(ctx), PathFetchTimeout: 0, // using default for now @@ -441,22 +409,18 @@ func (g *Gateway) Run(ctx context.Context) error { // scionNetworkNoSCMP is the network for the QUIC server connection. Because SCMP errors // will cause the server's accepts to fail, we ignore SCMP. scionNetworkNoSCMP := &snet.SCIONNetwork{ - LocalIA: localIA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - // Enable transparent reconnections to the dispatcher - Dispatcher: reconnectingDispatcher, - // Discard all SCMP propagation, to avoid accept/read errors on the - // QUIC server/client. - SCMPHandler: snet.SCMPPropagationStopper{ - Handler: snet.DefaultSCMPHandler{ - RevocationHandler: revocationHandler, - SCMPErrors: g.Metrics.SCMPErrors, - }, - Log: log.FromCtx(ctx).Debug, + Topology: g.Daemon, + // Discard all SCMP propagation, to avoid accept/read errors on the + // QUIC server/client. + SCMPHandler: snet.SCMPPropagationStopper{ + Handler: snet.DefaultSCMPHandler{ + RevocationHandler: revocationHandler, + SCMPErrors: g.Metrics.SCMPErrors, }, - SCIONPacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Log: log.FromCtx(ctx).Debug, }, - Metrics: g.Metrics.SCIONNetworkMetrics, + PacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Metrics: g.Metrics.SCIONNetworkMetrics, } // Initialize the UDP/SCION QUIC conn for outgoing Gateway Discovery RPCs and outgoing Prefix @@ -465,7 +429,6 @@ func (g *Gateway) Run(ctx context.Context) error { context.TODO(), "udp", &net.UDPAddr{IP: g.ControlClientIP}, - addr.SvcNone, ) if err != nil { return serrors.WrapStr("unable to initialize client QUIC connection", err) @@ -509,18 +472,13 @@ func (g *Gateway) Run(ctx context.Context) error { // scionNetwork is the network for all SCION connections, with the exception of the QUIC server // and client connection. scionNetwork := &snet.SCIONNetwork{ - LocalIA: localIA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - // Enable transparent reconnections to the dispatcher - Dispatcher: reconnectingDispatcher, - // Forward revocations to Daemon - SCMPHandler: snet.DefaultSCMPHandler{ - RevocationHandler: revocationHandler, - SCMPErrors: g.Metrics.SCMPErrors, - }, - SCIONPacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Topology: g.Daemon, + SCMPHandler: snet.DefaultSCMPHandler{ + RevocationHandler: revocationHandler, + SCMPErrors: g.Metrics.SCMPErrors, }, - Metrics: g.Metrics.SCIONNetworkMetrics, + PacketConnMetrics: g.Metrics.SCIONPacketConnMetrics, + Metrics: g.Metrics.SCIONNetworkMetrics, } remoteMonitor := &control.RemoteMonitor{ IAs: remoteIAsChannel, @@ -544,8 +502,8 @@ func (g *Gateway) Run(ctx context.Context) error { Resolver: &svc.Resolver{ LocalIA: localIA, // Reuse the network with SCMP error support. - ConnFactory: scionNetwork.Dispatcher, - LocalIP: g.ServiceDiscoveryClientIP, + Network: scionNetwork, + LocalIP: g.ServiceDiscoveryClientIP, }, SVCResolutionFraction: 1.337, }, @@ -565,7 +523,6 @@ func (g *Gateway) Run(ctx context.Context) error { context.TODO(), "udp", g.ControlServerAddr, - addr.SvcNone, ) if err != nil { return serrors.WrapStr("unable to initialize server QUIC connection", err) @@ -613,7 +570,7 @@ func (g *Gateway) Run(ctx context.Context) error { // received from the session monitors of the remote gateway. // ********************************************************************************* - probeConn, err := scionNetwork.Listen(context.TODO(), "udp", g.ProbeServerAddr, addr.SvcNone) + probeConn, err := scionNetwork.Listen(context.TODO(), "udp", g.ProbeServerAddr) if err != nil { return serrors.WrapStr("creating server probe conn", err) } @@ -811,7 +768,6 @@ func StartIngress(ctx context.Context, scionNetwork *snet.SCIONNetwork, dataAddr context.TODO(), "udp", dataAddr, - addr.SvcNone, ) if err != nil { return serrors.WrapStr("creating ingress conn", err) diff --git a/gateway/pathhealth/pathwatcher.go b/gateway/pathhealth/pathwatcher.go index 99f13d175d..ac8befb025 100644 --- a/gateway/pathhealth/pathwatcher.go +++ b/gateway/pathhealth/pathwatcher.go @@ -16,9 +16,7 @@ package pathhealth import ( "context" - "errors" "fmt" - "io" "net" "net/netip" "sync" @@ -39,22 +37,17 @@ const ( defaultProbeInterval = 500 * time.Millisecond ) -// ProbeConnFactory is used to construct net.PacketConn objects for sending and -// receiving probes. -type ProbeConnFactory interface { - New(context.Context) (net.PacketConn, error) -} - // DefaultPathWatcherFactory creates PathWatchers. type DefaultPathWatcherFactory struct { // LocalIA is the ID of the local AS. LocalIA addr.IA + // Topology is the helper class to get control-plane information for the + // local AS. + Topology snet.Topology // LocalIP is the IP address of the local host. LocalIP netip.Addr // RevocationHandler is the revocation handler. RevocationHandler snet.RevocationHandler - // ConnFactory is used to create probe connections. - ConnFactory ProbeConnFactory // Probeinterval defines the interval at which probes are sent. If it is not // set a default is used. ProbeInterval time.Duration @@ -66,8 +59,7 @@ type DefaultPathWatcherFactory struct { ProbesReceived func(remote addr.IA) metrics.Counter // ProbesSendErrors keeps track of how many time sending probes failed per // remote. - ProbesSendErrors func(remote addr.IA) metrics.Counter - + ProbesSendErrors func(remote addr.IA) metrics.Counter SCMPErrors metrics.Counter SCIONPacketConnMetrics snet.SCIONPacketConnMetrics } @@ -77,13 +69,8 @@ func (f *DefaultPathWatcherFactory) New( ctx context.Context, remote addr.IA, path snet.Path, - id uint16, ) (PathWatcher, error) { - nc, err := f.ConnFactory.New(ctx) - if err != nil { - return nil, serrors.WrapStr("creating connection for probing", err) - } pktChan := make(chan traceroutePkt, 10) createCounter := func( create func(addr.IA) metrics.Counter, remote addr.IA, @@ -93,21 +80,25 @@ func (f *DefaultPathWatcherFactory) New( } return create(remote) } + conn, err := (&snet.SCIONNetwork{ + SCMPHandler: scmpHandler{ + wrappedHandler: snet.DefaultSCMPHandler{ + RevocationHandler: f.RevocationHandler, + SCMPErrors: f.SCMPErrors, + }, + pkts: pktChan, + }, + PacketConnMetrics: f.SCIONPacketConnMetrics, + Topology: f.Topology, + }).OpenRaw(ctx, &net.UDPAddr{IP: f.LocalIP.AsSlice()}) + if err != nil { + return nil, serrors.WrapStr("creating connection for probing", err) + } return &pathWatcher{ remote: remote, probeInterval: f.ProbeInterval, - conn: &snet.SCIONPacketConn{ - Conn: nc, - SCMPHandler: scmpHandler{ - wrappedHandler: snet.DefaultSCMPHandler{ - RevocationHandler: f.RevocationHandler, - SCMPErrors: f.SCMPErrors, - }, - pkts: pktChan, - }, - Metrics: f.SCIONPacketConnMetrics, - }, - id: id, + conn: conn, + id: uint16(conn.LocalAddr().(*net.UDPAddr).Port), localAddr: snet.SCIONAddress{ IA: f.LocalIA, Host: addr.HostIP(f.LocalIP), @@ -245,11 +236,6 @@ func (w *pathWatcher) drainConn(ctx context.Context) { if ctx.Err() != nil { return } - if errors.Is(err, io.EOF) { - // dispatcher is currently down so back off. - time.Sleep(500 * time.Millisecond) - continue - } if err != nil { if _, ok := err.(*snet.OpError); ok { // ignore SCMP errors they are already dealt with in the SCMP diff --git a/gateway/pathhealth/remotewatcher.go b/gateway/pathhealth/remotewatcher.go index 7ee9baeb51..fea1a5fa3f 100644 --- a/gateway/pathhealth/remotewatcher.go +++ b/gateway/pathhealth/remotewatcher.go @@ -50,7 +50,7 @@ type PathWatcher interface { // PathWatcherFactory constructs a PathWatcher. type PathWatcherFactory interface { - New(ctx context.Context, remote addr.IA, path snet.Path, id uint16) (PathWatcher, error) + New(ctx context.Context, remote addr.IA, path snet.Path) (PathWatcher, error) } // DefaultRemoteWatcherFactory is a default factory for creating RemoteWatchers. @@ -84,7 +84,6 @@ func (f *DefaultRemoteWatcherFactory) New(remote addr.IA) RemoteWatcher { router: f.Router, pathWatcherFactory: f.PathWatcherFactory, pathWatchers: make(map[snet.PathFingerprint]*pathWatcherItem), - pathWatchersByID: make(map[uint16]*pathWatcherItem), // Set this to true so that first failure to get paths is logged. hasPaths: true, pathUpdateInterval: f.PathUpdateInterval, @@ -107,8 +106,6 @@ type remoteWatcher struct { // pathWatchers is a map of all the paths being currently monitored, indexed by path // fingerprint. pathWatchers map[snet.PathFingerprint]*pathWatcherItem - // pathWatchersByID contains the same paths as above, but indexed by SCMP Traceroute ID. - pathWatchersByID map[uint16]*pathWatcherItem // hasPaths is true if, at the moment, there is at least one path known. hasPaths bool @@ -162,7 +159,6 @@ func (w *remoteWatcher) cleanup(ctx context.Context) { } pm.cancel() delete(w.pathWatchers, fingerprint) - delete(w.pathWatchersByID, pm.id) } metrics.GaugeSet(w.pathsMonitored, float64(len(w.pathWatchers))) } @@ -221,14 +217,9 @@ func (w *remoteWatcher) updatePaths(ctx context.Context) { for fingerprint, path := range pathmap { pw, ok := w.pathWatchers[fingerprint] if !ok { - id, found := w.selectID() - if !found { - logger.Info("All traceroute IDs are occupied") - continue - } - pathW, err := w.pathWatcherFactory.New(ctx, w.remote, path, id) + pathW, err := w.pathWatcherFactory.New(ctx, w.remote, path) if err != nil { - logger.Error("Failed to create path watcher", "path", fmt.Sprint(path)) + logger.Error("Failed to create path watcher", "path", fmt.Sprint(path), "err", err) continue } pathWCtx, cancel := context.WithCancel(ctx) @@ -240,11 +231,9 @@ func (w *remoteWatcher) updatePaths(ctx context.Context) { // This is a new path, add an entry. pw = &pathWatcherItem{ pathWatcher: pathW, - id: id, cancel: cancel, } w.pathWatchers[fingerprint] = pw - w.pathWatchersByID[id] = pw } else { // If the path already exists, update it. Needed to keep expirations fresh. pw.pathWatcher.UpdatePath(path) @@ -254,21 +243,9 @@ func (w *remoteWatcher) updatePaths(ctx context.Context) { metrics.GaugeSet(w.pathsMonitored, float64(len(w.pathWatchers))) } -func (w *remoteWatcher) selectID() (uint16, bool) { - for i := 0; i < 100; i++ { - id := snet.RandomSCMPIdentifer() - if _, ok := w.pathWatchersByID[id]; !ok { - return id, true - } - } - return 0, false -} - // pathWatcherItem is an wrapper type that adds RemoteWatcher-specific data to pathWatcher. type pathWatcherItem struct { pathWatcher PathWatcher - // id is the traceroute ID for this pathWatcher - id uint16 // lastUsed is the time when the path ceased to be used. // If the path is used right now, set to time.Time{}. // Paths that are not used will be removed after a certain period of time. diff --git a/nogo.json b/nogo.json index f5a358ddae..d4a69c7f3d 100644 --- a/nogo.json +++ b/nogo.json @@ -46,7 +46,8 @@ "/in_gopkg_yaml_v2/decode.go": "https://github.com/go-yaml/yaml/pull/492", "/com_github_mattn_go_sqlite3": "", "/com_github_cloudflare_sidh": "", - "/com_github_vishvananda_netlink": "" + "/com_github_vishvananda_netlink": "", + "/com_github_spf13_pflag": "" } }, "printf": { @@ -98,7 +99,12 @@ }, "exclude_files": { "/com_github_mattn_go_sqlite3/": "", - "/com_github_google_gopacket/afpacket/": "" + "/com_github_google_gopacket/afpacket/": "", + "/org_modernc_memory/": "", + "/org_golang_x_sys/": "", + "org_modernc_libc": "", + "org_modernc_sqlite": "", + "com_github_mailru_easyjson": "" } }, "shift": { diff --git a/pkg/daemon/BUILD.bazel b/pkg/daemon/BUILD.bazel index 1d0f0d9fdc..8a8ca23be8 100644 --- a/pkg/daemon/BUILD.bazel +++ b/pkg/daemon/BUILD.bazel @@ -27,6 +27,7 @@ go_library( "//pkg/snet/path:go_default_library", "//private/topology:go_default_library", "@org_golang_google_grpc//:go_default_library", + "@org_golang_google_protobuf//types/known/emptypb:go_default_library", "@org_golang_google_protobuf//types/known/timestamppb:go_default_library", ], ) diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 24d0b6b9f8..a9ba3397ee 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -18,13 +18,12 @@ package daemon import ( "context" - "net" + "net/netip" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon/internal/metrics" "github.com/scionproto/scion/pkg/drkey" libmetrics "github.com/scionproto/scion/pkg/metrics" - "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/ctrl/path_mgmt" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" @@ -63,16 +62,18 @@ func NewService(name string) Service { // either an error occurs, or the method successfully returns. type Connector interface { // LocalIA requests from the daemon the local ISD-AS number. + // TODO: Caching this value to avoid contacting the daemon, since this never changes. LocalIA(ctx context.Context) (addr.IA, error) + // PortRange returns the beginning and the end of the SCION/UDP endhost port range, configured + // for the local IA. + PortRange(ctx context.Context) (uint16, uint16, error) + // Interfaces returns the map of interface identifiers to the underlay internal address. + Interfaces(ctx context.Context) (map[uint16]netip.AddrPort, error) // Paths requests from the daemon a set of end to end paths between the source and destination. Paths(ctx context.Context, dst, src addr.IA, f PathReqFlags) ([]snet.Path, error) // ASInfo requests from the daemon information about AS ia, the zero IA can be // use to detect the local IA. ASInfo(ctx context.Context, ia addr.IA) (ASInfo, error) - // IFInfo requests from SCION Daemon addresses and ports of interfaces. Slice - // ifs contains interface IDs of BRs. If empty, a fresh (i.e., uncached) - // answer containing all interfaces is returned. - IFInfo(ctx context.Context, ifs []common.IFIDType) (map[common.IFIDType]*net.UDPAddr, error) // SVCInfo requests from the daemon information about addresses and ports of // infrastructure services. Slice svcTypes contains a list of desired // service types. If unset, a fresh (i.e., uncached) answer containing all diff --git a/pkg/daemon/grpc.go b/pkg/daemon/grpc.go index 26ae049d7b..4cbec6036c 100644 --- a/pkg/daemon/grpc.go +++ b/pkg/daemon/grpc.go @@ -17,9 +17,11 @@ package daemon import ( "context" "net" + "net/netip" "time" "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" "github.com/scionproto/scion/pkg/addr" @@ -74,6 +76,35 @@ func (c grpcConn) LocalIA(ctx context.Context) (addr.IA, error) { return ia, nil } +func (c grpcConn) PortRange(ctx context.Context) (uint16, uint16, error) { + client := sdpb.NewDaemonServiceClient(c.conn) + response, err := client.PortRange(ctx, &emptypb.Empty{}) + if err != nil { + return 0, 0, err + } + return uint16(response.DispatchedPortStart), uint16(response.DispatchedPortEnd), nil +} + +func (c grpcConn) Interfaces(ctx context.Context) (map[uint16]netip.AddrPort, error) { + client := sdpb.NewDaemonServiceClient(c.conn) + response, err := client.Interfaces(ctx, &sdpb.InterfacesRequest{}) + if err != nil { + c.metrics.incInterface(err) + return nil, err + } + result := make(map[uint16]netip.AddrPort, len(response.Interfaces)) + for ifID, intf := range response.Interfaces { + a, err := netip.ParseAddrPort(intf.Address.Address) + if err != nil { + c.metrics.incInterface(err) + return nil, serrors.WrapStr("parsing reply", err, "raw_uri", intf.Address.Address) + } + result[uint16(ifID)] = a + } + c.metrics.incInterface(nil) + return result, nil +} + func (c grpcConn) Paths(ctx context.Context, dst, src addr.IA, f PathReqFlags) ([]snet.Path, error) { @@ -107,28 +138,6 @@ func (c grpcConn) ASInfo(ctx context.Context, ia addr.IA) (ASInfo, error) { }, nil } -func (c grpcConn) IFInfo(ctx context.Context, - _ []common.IFIDType) (map[common.IFIDType]*net.UDPAddr, error) { - - client := sdpb.NewDaemonServiceClient(c.conn) - response, err := client.Interfaces(ctx, &sdpb.InterfacesRequest{}) - if err != nil { - c.metrics.incInterface(err) - return nil, err - } - result := make(map[common.IFIDType]*net.UDPAddr) - for ifID, intf := range response.Interfaces { - a, err := net.ResolveUDPAddr("udp", intf.Address.Address) - if err != nil { - c.metrics.incInterface(err) - return nil, serrors.WrapStr("parsing reply", err, "raw_uri", intf.Address.Address) - } - result[common.IFIDType(ifID)] = a - } - c.metrics.incInterface(nil) - return result, nil -} - func (c grpcConn) SVCInfo( ctx context.Context, _ []addr.SVC, diff --git a/pkg/daemon/mock_daemon/BUILD.bazel b/pkg/daemon/mock_daemon/BUILD.bazel index 468b330d17..3423d8d8b0 100644 --- a/pkg/daemon/mock_daemon/BUILD.bazel +++ b/pkg/daemon/mock_daemon/BUILD.bazel @@ -18,7 +18,6 @@ go_library( "//pkg/addr:go_default_library", "//pkg/daemon:go_default_library", "//pkg/drkey:go_default_library", - "//pkg/private/common:go_default_library", "//pkg/private/ctrl/path_mgmt:go_default_library", "//pkg/snet:go_default_library", "@com_github_golang_mock//gomock:go_default_library", diff --git a/pkg/daemon/mock_daemon/mock.go b/pkg/daemon/mock_daemon/mock.go index 61c85508c3..05a868518c 100644 --- a/pkg/daemon/mock_daemon/mock.go +++ b/pkg/daemon/mock_daemon/mock.go @@ -6,14 +6,13 @@ package mock_daemon import ( context "context" - net "net" + netip "net/netip" reflect "reflect" gomock "github.com/golang/mock/gomock" addr "github.com/scionproto/scion/pkg/addr" daemon "github.com/scionproto/scion/pkg/daemon" drkey "github.com/scionproto/scion/pkg/drkey" - common "github.com/scionproto/scion/pkg/private/common" path_mgmt "github.com/scionproto/scion/pkg/private/ctrl/path_mgmt" snet "github.com/scionproto/scion/pkg/snet" ) @@ -115,19 +114,19 @@ func (mr *MockConnectorMockRecorder) DRKeyGetHostHostKey(arg0, arg1 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DRKeyGetHostHostKey", reflect.TypeOf((*MockConnector)(nil).DRKeyGetHostHostKey), arg0, arg1) } -// IFInfo mocks base method. -func (m *MockConnector) IFInfo(arg0 context.Context, arg1 []common.IFIDType) (map[common.IFIDType]*net.UDPAddr, error) { +// Interfaces mocks base method. +func (m *MockConnector) Interfaces(arg0 context.Context) (map[uint16]netip.AddrPort, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IFInfo", arg0, arg1) - ret0, _ := ret[0].(map[common.IFIDType]*net.UDPAddr) + ret := m.ctrl.Call(m, "Interfaces", arg0) + ret0, _ := ret[0].(map[uint16]netip.AddrPort) ret1, _ := ret[1].(error) return ret0, ret1 } -// IFInfo indicates an expected call of IFInfo. -func (mr *MockConnectorMockRecorder) IFInfo(arg0, arg1 interface{}) *gomock.Call { +// Interfaces indicates an expected call of Interfaces. +func (mr *MockConnectorMockRecorder) Interfaces(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IFInfo", reflect.TypeOf((*MockConnector)(nil).IFInfo), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Interfaces", reflect.TypeOf((*MockConnector)(nil).Interfaces), arg0) } // LocalIA mocks base method. @@ -160,6 +159,22 @@ func (mr *MockConnectorMockRecorder) Paths(arg0, arg1, arg2, arg3 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Paths", reflect.TypeOf((*MockConnector)(nil).Paths), arg0, arg1, arg2, arg3) } +// PortRange mocks base method. +func (m *MockConnector) PortRange(arg0 context.Context) (uint16, uint16, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PortRange", arg0) + ret0, _ := ret[0].(uint16) + ret1, _ := ret[1].(uint16) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// PortRange indicates an expected call of PortRange. +func (mr *MockConnectorMockRecorder) PortRange(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortRange", reflect.TypeOf((*MockConnector)(nil).PortRange), arg0) +} + // RevNotification mocks base method. func (m *MockConnector) RevNotification(arg0 context.Context, arg1 *path_mgmt.RevInfo) error { m.ctrl.T.Helper() diff --git a/pkg/experimental/hiddenpath/testdata/topology.json b/pkg/experimental/hiddenpath/testdata/topology.json index a7e182c4ea..e8981cf165 100644 --- a/pkg/experimental/hiddenpath/testdata/topology.json +++ b/pkg/experimental/hiddenpath/testdata/topology.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:111", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [], "border_routers": { "br1-ff00_0_111-1": { diff --git a/pkg/proto/daemon/daemon.pb.go b/pkg/proto/daemon/daemon.pb.go index 93b3b19c6f..75850fa7fe 100644 --- a/pkg/proto/daemon/daemon.pb.go +++ b/pkg/proto/daemon/daemon.pb.go @@ -15,6 +15,7 @@ import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" durationpb "google.golang.org/protobuf/types/known/durationpb" + emptypb "google.golang.org/protobuf/types/known/emptypb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" @@ -1066,6 +1067,61 @@ func (*NotifyInterfaceDownResponse) Descriptor() ([]byte, []int) { return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{17} } +type PortRangeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DispatchedPortStart uint32 `protobuf:"varint,1,opt,name=dispatched_port_start,json=dispatchedPortStart,proto3" json:"dispatched_port_start,omitempty"` + DispatchedPortEnd uint32 `protobuf:"varint,2,opt,name=dispatched_port_end,json=dispatchedPortEnd,proto3" json:"dispatched_port_end,omitempty"` +} + +func (x *PortRangeResponse) Reset() { + *x = PortRangeResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PortRangeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PortRangeResponse) ProtoMessage() {} + +func (x *PortRangeResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PortRangeResponse.ProtoReflect.Descriptor instead. +func (*PortRangeResponse) Descriptor() ([]byte, []int) { + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{18} +} + +func (x *PortRangeResponse) GetDispatchedPortStart() uint32 { + if x != nil { + return x.DispatchedPortStart + } + return 0 +} + +func (x *PortRangeResponse) GetDispatchedPortEnd() uint32 { + if x != nil { + return x.DispatchedPortEnd + } + return 0 +} + type DRKeyHostASRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1081,7 +1137,7 @@ type DRKeyHostASRequest struct { func (x *DRKeyHostASRequest) Reset() { *x = DRKeyHostASRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1094,7 +1150,7 @@ func (x *DRKeyHostASRequest) String() string { func (*DRKeyHostASRequest) ProtoMessage() {} func (x *DRKeyHostASRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[18] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1107,7 +1163,7 @@ func (x *DRKeyHostASRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyHostASRequest.ProtoReflect.Descriptor instead. func (*DRKeyHostASRequest) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{18} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{19} } func (x *DRKeyHostASRequest) GetValTime() *timestamppb.Timestamp { @@ -1158,7 +1214,7 @@ type DRKeyHostASResponse struct { func (x *DRKeyHostASResponse) Reset() { *x = DRKeyHostASResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1171,7 +1227,7 @@ func (x *DRKeyHostASResponse) String() string { func (*DRKeyHostASResponse) ProtoMessage() {} func (x *DRKeyHostASResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[19] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1184,7 +1240,7 @@ func (x *DRKeyHostASResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyHostASResponse.ProtoReflect.Descriptor instead. func (*DRKeyHostASResponse) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{19} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{20} } func (x *DRKeyHostASResponse) GetEpochBegin() *timestamppb.Timestamp { @@ -1223,7 +1279,7 @@ type DRKeyASHostRequest struct { func (x *DRKeyASHostRequest) Reset() { *x = DRKeyASHostRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1236,7 +1292,7 @@ func (x *DRKeyASHostRequest) String() string { func (*DRKeyASHostRequest) ProtoMessage() {} func (x *DRKeyASHostRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[20] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1249,7 +1305,7 @@ func (x *DRKeyASHostRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyASHostRequest.ProtoReflect.Descriptor instead. func (*DRKeyASHostRequest) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{20} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{21} } func (x *DRKeyASHostRequest) GetValTime() *timestamppb.Timestamp { @@ -1300,7 +1356,7 @@ type DRKeyASHostResponse struct { func (x *DRKeyASHostResponse) Reset() { *x = DRKeyASHostResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1313,7 +1369,7 @@ func (x *DRKeyASHostResponse) String() string { func (*DRKeyASHostResponse) ProtoMessage() {} func (x *DRKeyASHostResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[21] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1326,7 +1382,7 @@ func (x *DRKeyASHostResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyASHostResponse.ProtoReflect.Descriptor instead. func (*DRKeyASHostResponse) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{21} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{22} } func (x *DRKeyASHostResponse) GetEpochBegin() *timestamppb.Timestamp { @@ -1366,7 +1422,7 @@ type DRKeyHostHostRequest struct { func (x *DRKeyHostHostRequest) Reset() { *x = DRKeyHostHostRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1379,7 +1435,7 @@ func (x *DRKeyHostHostRequest) String() string { func (*DRKeyHostHostRequest) ProtoMessage() {} func (x *DRKeyHostHostRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[22] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1392,7 +1448,7 @@ func (x *DRKeyHostHostRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyHostHostRequest.ProtoReflect.Descriptor instead. func (*DRKeyHostHostRequest) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{22} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{23} } func (x *DRKeyHostHostRequest) GetValTime() *timestamppb.Timestamp { @@ -1450,7 +1506,7 @@ type DRKeyHostHostResponse struct { func (x *DRKeyHostHostResponse) Reset() { *x = DRKeyHostHostResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1463,7 +1519,7 @@ func (x *DRKeyHostHostResponse) String() string { func (*DRKeyHostHostResponse) ProtoMessage() {} func (x *DRKeyHostHostResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_daemon_v1_daemon_proto_msgTypes[23] + mi := &file_proto_daemon_v1_daemon_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1476,7 +1532,7 @@ func (x *DRKeyHostHostResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DRKeyHostHostResponse.ProtoReflect.Descriptor instead. func (*DRKeyHostHostResponse) Descriptor() ([]byte, []int) { - return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{23} + return file_proto_daemon_v1_daemon_proto_rawDescGZIP(), []int{24} } func (x *DRKeyHostHostResponse) GetEpochBegin() *timestamppb.Timestamp { @@ -1510,169 +1566,132 @@ var file_proto_daemon_v1_daemon_proto_rawDesc = []byte{ 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x1a, 0x1a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2f, 0x76, 0x31, - 0x2f, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x92, 0x01, 0x0a, - 0x0c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, - 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x73, 0x64, 0x41, - 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x73, 0x64, 0x41, 0x73, 0x12, - 0x18, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x69, 0x64, - 0x64, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, - 0x6e, 0x22, 0x3c, 0x0a, 0x0d, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x22, - 0x94, 0x04, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x66, 0x61, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x06, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, - 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x61, 0x6e, 0x64, 0x77, - 0x69, 0x64, 0x74, 0x68, 0x18, 0x07, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x62, 0x61, 0x6e, 0x64, - 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x03, 0x67, 0x65, 0x6f, 0x18, 0x08, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, - 0x74, 0x65, 0x73, 0x52, 0x03, 0x67, 0x65, 0x6f, 0x12, 0x36, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x70, - 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x48, 0x6f, 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x0b, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x65, - 0x70, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x45, 0x70, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x52, 0x09, 0x65, 0x70, 0x69, - 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x22, 0x45, 0x0a, 0x09, 0x45, 0x70, 0x69, 0x63, 0x41, 0x75, - 0x74, 0x68, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x68, 0x76, 0x66, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x50, 0x68, 0x76, 0x66, - 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6c, 0x68, 0x76, 0x66, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x4c, 0x68, 0x76, 0x66, 0x22, 0x36, 0x0a, - 0x0d, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x15, - 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, - 0x69, 0x73, 0x64, 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x64, 0x0a, 0x0e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, - 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, - 0x75, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, - 0x75, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x22, 0x0a, 0x09, 0x41, - 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, - 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, 0x41, 0x73, 0x22, - 0x49, 0x0a, 0x0a, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, - 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, - 0x73, 0x64, 0x41, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x22, 0x13, 0x0a, 0x11, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0xc4, 0x01, 0x0a, 0x12, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x72, 0x6f, + 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x72, + 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x92, 0x01, 0x0a, 0x0c, 0x50, 0x61, + 0x74, 0x68, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x73, 0x64, 0x41, 0x73, 0x12, 0x2c, + 0x0a, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x73, + 0x64, 0x5f, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x73, 0x64, 0x41, 0x73, 0x12, 0x18, 0x0a, 0x07, + 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, + 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x22, 0x3c, + 0x0a, 0x0d, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2b, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x22, 0x94, 0x04, 0x0a, + 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x1a, 0x59, 0x0a, 0x0f, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x40, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x52, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xba, 0x01, 0x0a, 0x10, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4b, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x1a, 0x59, 0x0a, - 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x32, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x1b, 0x0a, - 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x22, 0x24, 0x0a, 0x08, 0x55, 0x6e, - 0x64, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x22, 0x43, 0x0a, 0x1a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, - 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, - 0x69, 0x73, 0x64, 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1d, 0x0a, 0x1b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, - 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, - 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, 0x54, 0x69, - 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x15, 0x0a, - 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, - 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x73, - 0x72, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, - 0x72, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x13, 0x44, 0x52, 0x4b, 0x65, 0x79, - 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, - 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x65, - 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, - 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x44, 0x52, 0x4b, 0x65, 0x79, - 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, - 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, - 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, - 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, - 0x08, 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x13, 0x44, 0x52, 0x4b, - 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x37, 0x0a, - 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, - 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xec, 0x01, 0x0a, 0x14, 0x44, 0x52, 0x4b, - 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, + 0x65, 0x12, 0x3e, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, + 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, + 0x6d, 0x74, 0x75, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x33, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x6c, 0x61, 0x74, + 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x18, 0x07, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x62, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x12, 0x31, 0x0a, 0x03, 0x67, 0x65, 0x6f, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73, + 0x52, 0x03, 0x67, 0x65, 0x6f, 0x12, 0x36, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x70, 0x73, 0x18, 0x0a, + 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x48, 0x6f, + 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x65, 0x70, 0x69, 0x63, + 0x5f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x45, + 0x70, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x52, 0x09, 0x65, 0x70, 0x69, 0x63, 0x41, 0x75, + 0x74, 0x68, 0x73, 0x22, 0x45, 0x0a, 0x09, 0x45, 0x70, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, + 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x68, 0x76, 0x66, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x50, 0x68, 0x76, 0x66, 0x12, 0x1b, 0x0a, + 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6c, 0x68, 0x76, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x4c, 0x68, 0x76, 0x66, 0x22, 0x36, 0x0a, 0x0d, 0x50, 0x61, + 0x74, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x69, + 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, + 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x64, 0x0a, 0x0e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x22, 0x0a, 0x09, 0x41, 0x53, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, 0x41, 0x73, 0x22, 0x49, 0x0a, 0x0a, + 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, + 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, 0x41, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x22, 0x13, 0x0a, 0x11, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xc4, 0x01, 0x0a, + 0x12, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, + 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x1a, 0x59, 0x0a, 0x0f, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x40, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, + 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xba, 0x01, 0x0a, 0x10, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, + 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x1a, 0x59, 0x0a, 0x0d, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, + 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x1b, 0x0a, 0x07, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x22, 0x24, 0x0a, 0x08, 0x55, 0x6e, 0x64, 0x65, 0x72, + 0x6c, 0x61, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x43, 0x0a, + 0x1a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, + 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, + 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, + 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x1d, 0x0a, 0x1b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x77, 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, + 0x64, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, + 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x65, 0x6e, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x11, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, + 0x68, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x45, 0x6e, 0x64, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x44, + 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, @@ -1684,75 +1703,126 @@ var file_proto_daemon_v1_daemon_proto_rawDesc = []byte{ 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, - 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x15, 0x44, 0x52, 0x4b, 0x65, - 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, + 0x13, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, + 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, + 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xcf, 0x01, 0x0a, + 0x12, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, + 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, + 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, + 0x01, 0x0a, 0x13, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, + 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, + 0x67, 0x69, 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x37, - 0x0a, 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, - 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x2a, 0x6c, 0x0a, 0x08, 0x4c, 0x69, 0x6e, - 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x49, - 0x52, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x48, 0x4f, 0x50, 0x10, 0x02, 0x12, - 0x16, 0x0a, 0x12, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x50, 0x45, - 0x4e, 0x5f, 0x4e, 0x45, 0x54, 0x10, 0x03, 0x32, 0xd4, 0x05, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, - 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x05, 0x50, 0x61, 0x74, - 0x68, 0x73, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x02, 0x41, 0x53, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x53, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, - 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, - 0x65, 0x73, 0x12, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, - 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, - 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x72, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, - 0x6f, 0x73, 0x74, 0x12, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, - 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, - 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x12, - 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, - 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x0d, - 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x25, 0x2e, + 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xec, + 0x01, 0x0a, 0x14, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, + 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, + 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, + 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, + 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, 0x68, + 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x48, 0x6f, + 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9f, 0x01, + 0x0a, 0x15, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, + 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, + 0x65, 0x67, 0x69, 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x2a, + 0x6c, 0x0a, 0x08, 0x4c, 0x69, 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x4c, + 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, + 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, + 0x48, 0x4f, 0x50, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x4e, 0x45, 0x54, 0x10, 0x03, 0x32, 0x9f, 0x06, + 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x48, 0x0a, 0x05, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x02, 0x41, 0x53, 0x12, + 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x53, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0a, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, + 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x72, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, - 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2e, - 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x63, 0x69, - 0x6f, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x6b, - 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, + 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x09, 0x50, 0x6f, + 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, + 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, + 0x48, 0x6f, 0x73, 0x74, 0x12, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, + 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, + 0x12, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, + 0x74, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, + 0x0d, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x25, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, + 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x63, + 0x69, 0x6f, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x2f, 0x70, + 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1768,7 +1838,7 @@ func file_proto_daemon_v1_daemon_proto_rawDescGZIP() []byte { } var file_proto_daemon_v1_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_daemon_v1_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 26) +var file_proto_daemon_v1_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_proto_daemon_v1_daemon_proto_goTypes = []interface{}{ (LinkType)(0), // 0: proto.daemon.v1.LinkType (*PathsRequest)(nil), // 1: proto.daemon.v1.PathsRequest @@ -1789,43 +1859,45 @@ var file_proto_daemon_v1_daemon_proto_goTypes = []interface{}{ (*Underlay)(nil), // 16: proto.daemon.v1.Underlay (*NotifyInterfaceDownRequest)(nil), // 17: proto.daemon.v1.NotifyInterfaceDownRequest (*NotifyInterfaceDownResponse)(nil), // 18: proto.daemon.v1.NotifyInterfaceDownResponse - (*DRKeyHostASRequest)(nil), // 19: proto.daemon.v1.DRKeyHostASRequest - (*DRKeyHostASResponse)(nil), // 20: proto.daemon.v1.DRKeyHostASResponse - (*DRKeyASHostRequest)(nil), // 21: proto.daemon.v1.DRKeyASHostRequest - (*DRKeyASHostResponse)(nil), // 22: proto.daemon.v1.DRKeyASHostResponse - (*DRKeyHostHostRequest)(nil), // 23: proto.daemon.v1.DRKeyHostHostRequest - (*DRKeyHostHostResponse)(nil), // 24: proto.daemon.v1.DRKeyHostHostResponse - nil, // 25: proto.daemon.v1.InterfacesResponse.InterfacesEntry - nil, // 26: proto.daemon.v1.ServicesResponse.ServicesEntry - (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 28: google.protobuf.Duration - (drkey.Protocol)(0), // 29: proto.drkey.v1.Protocol + (*PortRangeResponse)(nil), // 19: proto.daemon.v1.PortRangeResponse + (*DRKeyHostASRequest)(nil), // 20: proto.daemon.v1.DRKeyHostASRequest + (*DRKeyHostASResponse)(nil), // 21: proto.daemon.v1.DRKeyHostASResponse + (*DRKeyASHostRequest)(nil), // 22: proto.daemon.v1.DRKeyASHostRequest + (*DRKeyASHostResponse)(nil), // 23: proto.daemon.v1.DRKeyASHostResponse + (*DRKeyHostHostRequest)(nil), // 24: proto.daemon.v1.DRKeyHostHostRequest + (*DRKeyHostHostResponse)(nil), // 25: proto.daemon.v1.DRKeyHostHostResponse + nil, // 26: proto.daemon.v1.InterfacesResponse.InterfacesEntry + nil, // 27: proto.daemon.v1.ServicesResponse.ServicesEntry + (*timestamppb.Timestamp)(nil), // 28: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 29: google.protobuf.Duration + (drkey.Protocol)(0), // 30: proto.drkey.v1.Protocol + (*emptypb.Empty)(nil), // 31: google.protobuf.Empty } var file_proto_daemon_v1_daemon_proto_depIdxs = []int32{ 3, // 0: proto.daemon.v1.PathsResponse.paths:type_name -> proto.daemon.v1.Path 11, // 1: proto.daemon.v1.Path.interface:type_name -> proto.daemon.v1.Interface 5, // 2: proto.daemon.v1.Path.interfaces:type_name -> proto.daemon.v1.PathInterface - 27, // 3: proto.daemon.v1.Path.expiration:type_name -> google.protobuf.Timestamp - 28, // 4: proto.daemon.v1.Path.latency:type_name -> google.protobuf.Duration + 28, // 3: proto.daemon.v1.Path.expiration:type_name -> google.protobuf.Timestamp + 29, // 4: proto.daemon.v1.Path.latency:type_name -> google.protobuf.Duration 6, // 5: proto.daemon.v1.Path.geo:type_name -> proto.daemon.v1.GeoCoordinates 0, // 6: proto.daemon.v1.Path.link_type:type_name -> proto.daemon.v1.LinkType 4, // 7: proto.daemon.v1.Path.epic_auths:type_name -> proto.daemon.v1.EpicAuths - 25, // 8: proto.daemon.v1.InterfacesResponse.interfaces:type_name -> proto.daemon.v1.InterfacesResponse.InterfacesEntry + 26, // 8: proto.daemon.v1.InterfacesResponse.interfaces:type_name -> proto.daemon.v1.InterfacesResponse.InterfacesEntry 16, // 9: proto.daemon.v1.Interface.address:type_name -> proto.daemon.v1.Underlay - 26, // 10: proto.daemon.v1.ServicesResponse.services:type_name -> proto.daemon.v1.ServicesResponse.ServicesEntry + 27, // 10: proto.daemon.v1.ServicesResponse.services:type_name -> proto.daemon.v1.ServicesResponse.ServicesEntry 15, // 11: proto.daemon.v1.ListService.services:type_name -> proto.daemon.v1.Service - 27, // 12: proto.daemon.v1.DRKeyHostASRequest.val_time:type_name -> google.protobuf.Timestamp - 29, // 13: proto.daemon.v1.DRKeyHostASRequest.protocol_id:type_name -> proto.drkey.v1.Protocol - 27, // 14: proto.daemon.v1.DRKeyHostASResponse.epoch_begin:type_name -> google.protobuf.Timestamp - 27, // 15: proto.daemon.v1.DRKeyHostASResponse.epoch_end:type_name -> google.protobuf.Timestamp - 27, // 16: proto.daemon.v1.DRKeyASHostRequest.val_time:type_name -> google.protobuf.Timestamp - 29, // 17: proto.daemon.v1.DRKeyASHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol - 27, // 18: proto.daemon.v1.DRKeyASHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp - 27, // 19: proto.daemon.v1.DRKeyASHostResponse.epoch_end:type_name -> google.protobuf.Timestamp - 27, // 20: proto.daemon.v1.DRKeyHostHostRequest.val_time:type_name -> google.protobuf.Timestamp - 29, // 21: proto.daemon.v1.DRKeyHostHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol - 27, // 22: proto.daemon.v1.DRKeyHostHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp - 27, // 23: proto.daemon.v1.DRKeyHostHostResponse.epoch_end:type_name -> google.protobuf.Timestamp + 28, // 12: proto.daemon.v1.DRKeyHostASRequest.val_time:type_name -> google.protobuf.Timestamp + 30, // 13: proto.daemon.v1.DRKeyHostASRequest.protocol_id:type_name -> proto.drkey.v1.Protocol + 28, // 14: proto.daemon.v1.DRKeyHostASResponse.epoch_begin:type_name -> google.protobuf.Timestamp + 28, // 15: proto.daemon.v1.DRKeyHostASResponse.epoch_end:type_name -> google.protobuf.Timestamp + 28, // 16: proto.daemon.v1.DRKeyASHostRequest.val_time:type_name -> google.protobuf.Timestamp + 30, // 17: proto.daemon.v1.DRKeyASHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol + 28, // 18: proto.daemon.v1.DRKeyASHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp + 28, // 19: proto.daemon.v1.DRKeyASHostResponse.epoch_end:type_name -> google.protobuf.Timestamp + 28, // 20: proto.daemon.v1.DRKeyHostHostRequest.val_time:type_name -> google.protobuf.Timestamp + 30, // 21: proto.daemon.v1.DRKeyHostHostRequest.protocol_id:type_name -> proto.drkey.v1.Protocol + 28, // 22: proto.daemon.v1.DRKeyHostHostResponse.epoch_begin:type_name -> google.protobuf.Timestamp + 28, // 23: proto.daemon.v1.DRKeyHostHostResponse.epoch_end:type_name -> google.protobuf.Timestamp 11, // 24: proto.daemon.v1.InterfacesResponse.InterfacesEntry.value:type_name -> proto.daemon.v1.Interface 14, // 25: proto.daemon.v1.ServicesResponse.ServicesEntry.value:type_name -> proto.daemon.v1.ListService 1, // 26: proto.daemon.v1.DaemonService.Paths:input_type -> proto.daemon.v1.PathsRequest @@ -1833,19 +1905,21 @@ var file_proto_daemon_v1_daemon_proto_depIdxs = []int32{ 9, // 28: proto.daemon.v1.DaemonService.Interfaces:input_type -> proto.daemon.v1.InterfacesRequest 12, // 29: proto.daemon.v1.DaemonService.Services:input_type -> proto.daemon.v1.ServicesRequest 17, // 30: proto.daemon.v1.DaemonService.NotifyInterfaceDown:input_type -> proto.daemon.v1.NotifyInterfaceDownRequest - 21, // 31: proto.daemon.v1.DaemonService.DRKeyASHost:input_type -> proto.daemon.v1.DRKeyASHostRequest - 19, // 32: proto.daemon.v1.DaemonService.DRKeyHostAS:input_type -> proto.daemon.v1.DRKeyHostASRequest - 23, // 33: proto.daemon.v1.DaemonService.DRKeyHostHost:input_type -> proto.daemon.v1.DRKeyHostHostRequest - 2, // 34: proto.daemon.v1.DaemonService.Paths:output_type -> proto.daemon.v1.PathsResponse - 8, // 35: proto.daemon.v1.DaemonService.AS:output_type -> proto.daemon.v1.ASResponse - 10, // 36: proto.daemon.v1.DaemonService.Interfaces:output_type -> proto.daemon.v1.InterfacesResponse - 13, // 37: proto.daemon.v1.DaemonService.Services:output_type -> proto.daemon.v1.ServicesResponse - 18, // 38: proto.daemon.v1.DaemonService.NotifyInterfaceDown:output_type -> proto.daemon.v1.NotifyInterfaceDownResponse - 22, // 39: proto.daemon.v1.DaemonService.DRKeyASHost:output_type -> proto.daemon.v1.DRKeyASHostResponse - 20, // 40: proto.daemon.v1.DaemonService.DRKeyHostAS:output_type -> proto.daemon.v1.DRKeyHostASResponse - 24, // 41: proto.daemon.v1.DaemonService.DRKeyHostHost:output_type -> proto.daemon.v1.DRKeyHostHostResponse - 34, // [34:42] is the sub-list for method output_type - 26, // [26:34] is the sub-list for method input_type + 31, // 31: proto.daemon.v1.DaemonService.PortRange:input_type -> google.protobuf.Empty + 22, // 32: proto.daemon.v1.DaemonService.DRKeyASHost:input_type -> proto.daemon.v1.DRKeyASHostRequest + 20, // 33: proto.daemon.v1.DaemonService.DRKeyHostAS:input_type -> proto.daemon.v1.DRKeyHostASRequest + 24, // 34: proto.daemon.v1.DaemonService.DRKeyHostHost:input_type -> proto.daemon.v1.DRKeyHostHostRequest + 2, // 35: proto.daemon.v1.DaemonService.Paths:output_type -> proto.daemon.v1.PathsResponse + 8, // 36: proto.daemon.v1.DaemonService.AS:output_type -> proto.daemon.v1.ASResponse + 10, // 37: proto.daemon.v1.DaemonService.Interfaces:output_type -> proto.daemon.v1.InterfacesResponse + 13, // 38: proto.daemon.v1.DaemonService.Services:output_type -> proto.daemon.v1.ServicesResponse + 18, // 39: proto.daemon.v1.DaemonService.NotifyInterfaceDown:output_type -> proto.daemon.v1.NotifyInterfaceDownResponse + 19, // 40: proto.daemon.v1.DaemonService.PortRange:output_type -> proto.daemon.v1.PortRangeResponse + 23, // 41: proto.daemon.v1.DaemonService.DRKeyASHost:output_type -> proto.daemon.v1.DRKeyASHostResponse + 21, // 42: proto.daemon.v1.DaemonService.DRKeyHostAS:output_type -> proto.daemon.v1.DRKeyHostASResponse + 25, // 43: proto.daemon.v1.DaemonService.DRKeyHostHost:output_type -> proto.daemon.v1.DRKeyHostHostResponse + 35, // [35:44] is the sub-list for method output_type + 26, // [26:35] is the sub-list for method input_type 26, // [26:26] is the sub-list for extension type_name 26, // [26:26] is the sub-list for extension extendee 0, // [0:26] is the sub-list for field type_name @@ -2074,7 +2148,7 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DRKeyHostASRequest); i { + switch v := v.(*PortRangeResponse); i { case 0: return &v.state case 1: @@ -2086,7 +2160,7 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DRKeyHostASResponse); i { + switch v := v.(*DRKeyHostASRequest); i { case 0: return &v.state case 1: @@ -2098,7 +2172,7 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DRKeyASHostRequest); i { + switch v := v.(*DRKeyHostASResponse); i { case 0: return &v.state case 1: @@ -2110,7 +2184,7 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DRKeyASHostResponse); i { + switch v := v.(*DRKeyASHostRequest); i { case 0: return &v.state case 1: @@ -2122,7 +2196,7 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DRKeyHostHostRequest); i { + switch v := v.(*DRKeyASHostResponse); i { case 0: return &v.state case 1: @@ -2134,6 +2208,18 @@ func file_proto_daemon_v1_daemon_proto_init() { } } file_proto_daemon_v1_daemon_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DRKeyHostHostRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_daemon_v1_daemon_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DRKeyHostHostResponse); i { case 0: return &v.state @@ -2152,7 +2238,7 @@ func file_proto_daemon_v1_daemon_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_daemon_v1_daemon_proto_rawDesc, NumEnums: 1, - NumMessages: 26, + NumMessages: 27, NumExtensions: 0, NumServices: 1, }, @@ -2184,6 +2270,7 @@ type DaemonServiceClient interface { Interfaces(ctx context.Context, in *InterfacesRequest, opts ...grpc.CallOption) (*InterfacesResponse, error) Services(ctx context.Context, in *ServicesRequest, opts ...grpc.CallOption) (*ServicesResponse, error) NotifyInterfaceDown(ctx context.Context, in *NotifyInterfaceDownRequest, opts ...grpc.CallOption) (*NotifyInterfaceDownResponse, error) + PortRange(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*PortRangeResponse, error) DRKeyASHost(ctx context.Context, in *DRKeyASHostRequest, opts ...grpc.CallOption) (*DRKeyASHostResponse, error) DRKeyHostAS(ctx context.Context, in *DRKeyHostASRequest, opts ...grpc.CallOption) (*DRKeyHostASResponse, error) DRKeyHostHost(ctx context.Context, in *DRKeyHostHostRequest, opts ...grpc.CallOption) (*DRKeyHostHostResponse, error) @@ -2242,6 +2329,15 @@ func (c *daemonServiceClient) NotifyInterfaceDown(ctx context.Context, in *Notif return out, nil } +func (c *daemonServiceClient) PortRange(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*PortRangeResponse, error) { + out := new(PortRangeResponse) + err := c.cc.Invoke(ctx, "/proto.daemon.v1.DaemonService/PortRange", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *daemonServiceClient) DRKeyASHost(ctx context.Context, in *DRKeyASHostRequest, opts ...grpc.CallOption) (*DRKeyASHostResponse, error) { out := new(DRKeyASHostResponse) err := c.cc.Invoke(ctx, "/proto.daemon.v1.DaemonService/DRKeyASHost", in, out, opts...) @@ -2276,6 +2372,7 @@ type DaemonServiceServer interface { Interfaces(context.Context, *InterfacesRequest) (*InterfacesResponse, error) Services(context.Context, *ServicesRequest) (*ServicesResponse, error) NotifyInterfaceDown(context.Context, *NotifyInterfaceDownRequest) (*NotifyInterfaceDownResponse, error) + PortRange(context.Context, *emptypb.Empty) (*PortRangeResponse, error) DRKeyASHost(context.Context, *DRKeyASHostRequest) (*DRKeyASHostResponse, error) DRKeyHostAS(context.Context, *DRKeyHostASRequest) (*DRKeyHostASResponse, error) DRKeyHostHost(context.Context, *DRKeyHostHostRequest) (*DRKeyHostHostResponse, error) @@ -2300,6 +2397,9 @@ func (*UnimplementedDaemonServiceServer) Services(context.Context, *ServicesRequ func (*UnimplementedDaemonServiceServer) NotifyInterfaceDown(context.Context, *NotifyInterfaceDownRequest) (*NotifyInterfaceDownResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method NotifyInterfaceDown not implemented") } +func (*UnimplementedDaemonServiceServer) PortRange(context.Context, *emptypb.Empty) (*PortRangeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PortRange not implemented") +} func (*UnimplementedDaemonServiceServer) DRKeyASHost(context.Context, *DRKeyASHostRequest) (*DRKeyASHostResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DRKeyASHost not implemented") } @@ -2404,6 +2504,24 @@ func _DaemonService_NotifyInterfaceDown_Handler(srv interface{}, ctx context.Con return interceptor(ctx, in, info, handler) } +func _DaemonService_PortRange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).PortRange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.daemon.v1.DaemonService/PortRange", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).PortRange(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _DaemonService_DRKeyASHost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DRKeyASHostRequest) if err := dec(in); err != nil { @@ -2482,6 +2600,10 @@ var _DaemonService_serviceDesc = grpc.ServiceDesc{ MethodName: "NotifyInterfaceDown", Handler: _DaemonService_NotifyInterfaceDown_Handler, }, + { + MethodName: "PortRange", + Handler: _DaemonService_PortRange_Handler, + }, { MethodName: "DRKeyASHost", Handler: _DaemonService_DRKeyASHost_Handler, diff --git a/pkg/snet/BUILD.bazel b/pkg/snet/BUILD.bazel index 66f0c6f674..d9f7e410e0 100644 --- a/pkg/snet/BUILD.bazel +++ b/pkg/snet/BUILD.bazel @@ -3,9 +3,7 @@ load("//tools/lint:go.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ - "base.go", "conn.go", - "dispatcher.go", "interface.go", "packet.go", "packet_conn.go", @@ -13,6 +11,7 @@ go_library( "reader.go", "reply_pather.go", "router.go", + "scmp.go", "snet.go", "svcaddr.go", "udpaddr.go", @@ -30,8 +29,11 @@ go_library( "//pkg/private/util:go_default_library", "//pkg/slayers:go_default_library", "//pkg/slayers/path:go_default_library", + "//pkg/slayers/path/empty:go_default_library", "//pkg/slayers/path/epic:go_default_library", - "//pkg/sock/reliable:go_default_library", + "//pkg/slayers/path/onehop:go_default_library", + "//pkg/slayers/path/scion:go_default_library", + "//private/topology:go_default_library", "//private/topology/underlay:go_default_library", "@com_github_google_gopacket//:go_default_library", ], diff --git a/pkg/snet/base.go b/pkg/snet/base.go deleted file mode 100644 index a06f956f0c..0000000000 --- a/pkg/snet/base.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package snet - -import ( - "net" - - "github.com/scionproto/scion/pkg/addr" -) - -type scionConnBase struct { - // Local and remote SCION addresses (IA, L3, L4) - listen *UDPAddr - remote *UDPAddr - - // svc address - svc addr.SVC - - // Reference to SCION networking context - scionNet *SCIONNetwork -} - -func (c *scionConnBase) LocalAddr() net.Addr { - return c.listen -} - -func (c *scionConnBase) RemoteAddr() net.Addr { - return c.remote -} - -func (c *scionConnBase) SVC() addr.SVC { - return c.svc -} diff --git a/pkg/snet/conn.go b/pkg/snet/conn.go index 95b9d2f13e..3ec657f299 100644 --- a/pkg/snet/conn.go +++ b/pkg/snet/conn.go @@ -16,11 +16,13 @@ package snet import ( + "context" "net" "time" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/ctrl/path_mgmt" + "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/slayers" ) @@ -42,28 +44,69 @@ var _ net.PacketConn = (*Conn)(nil) type Conn struct { conn PacketConn - scionConnBase scionConnWriter scionConnReader + + // Local and remote SCION addresses (IA, L3, L4) + local *UDPAddr + remote *UDPAddr } -func newConn(base scionConnBase, conn PacketConn, replyPather ReplyPather) *Conn { - c := &Conn{ - conn: conn, - scionConnBase: base, +// NewCookedConn returns a "cooked" Conn. The Conn object can be used to +// send/receive SCION traffic with the usual methods. +// It takes as arguments a non-nil PacketConn and a non-nil Topology parameter. +// Nil or unspecified addresses for the PacketConn object are not supported. +// This is an advanced API, that allows fine-tunning of the Conn underlay functionality. +// The general methods for obtaining a Conn object are still SCIONNetwork.Listen and +// SCIONNetwork.Dial. +func NewCookedConn( + pconn PacketConn, + topo Topology, + options ...ConnOption, +) (*Conn, error) { + o := apply(options) + localIA, err := topo.LocalIA(context.Background()) + if err != nil { + return nil, err + } + local := &UDPAddr{ + IA: localIA, + Host: pconn.LocalAddr().(*net.UDPAddr), } - c.scionConnWriter = scionConnWriter{ - base: &c.scionConnBase, - conn: conn, - buffer: make([]byte, common.SupportedMTU), + if local.Host == nil || local.Host.IP.IsUnspecified() { + return nil, serrors.New("nil or unspecified address is not supported.") } - c.scionConnReader = scionConnReader{ - base: &c.scionConnBase, - conn: conn, - buffer: make([]byte, common.SupportedMTU), - replyPather: replyPather, + start, end, err := topo.PortRange(context.Background()) + if err != nil { + return nil, err } - return c + return &Conn{ + conn: pconn, + local: local, + remote: o.remote, + scionConnWriter: scionConnWriter{ + conn: pconn, + buffer: make([]byte, common.SupportedMTU), + local: local, + remote: o.remote, + dispatchedPortStart: start, + dispatchedPortEnd: end, + }, + scionConnReader: scionConnReader{ + conn: pconn, + buffer: make([]byte, common.SupportedMTU), + replyPather: o.replyPather, + local: local, + }, + }, nil +} + +func (c *Conn) LocalAddr() net.Addr { + return c.local +} + +func (c *Conn) RemoteAddr() net.Addr { + return c.remote } func (c *Conn) SetDeadline(t time.Time) error { @@ -79,3 +122,39 @@ func (c *Conn) SetDeadline(t time.Time) error { func (c *Conn) Close() error { return c.conn.Close() } + +// ConnOption is a functional option type for configuring a Conn. +type ConnOption func(o *options) + +// WithReplyPather sets the reply pather for the connection. +// The reply pather is responsible for determining the path to send replies to. +// If the provided replyPather is not nil, it will be set as the reply pather for the connection. +func WithReplyPather(replyPather ReplyPather) ConnOption { + return func(o *options) { + if replyPather != nil { + o.replyPather = replyPather + } + } +} + +// WithRemote sets the remote address for the connection. +func WithRemote(addr *UDPAddr) ConnOption { + return func(o *options) { + o.remote = addr + } +} + +type options struct { + replyPather ReplyPather + remote *UDPAddr +} + +func apply(opts []ConnOption) options { + o := options{ + replyPather: DefaultReplyPather{}, + } + for _, option := range opts { + option(&o) + } + return o +} diff --git a/pkg/snet/interface.go b/pkg/snet/interface.go index 343ec1a442..1e333b56dd 100644 --- a/pkg/snet/interface.go +++ b/pkg/snet/interface.go @@ -18,13 +18,10 @@ package snet import ( "context" "net" - - "github.com/scionproto/scion/pkg/addr" ) type Network interface { - Listen(ctx context.Context, network string, listen *net.UDPAddr, - svc addr.SVC) (*Conn, error) - Dial(ctx context.Context, network string, listen *net.UDPAddr, remote *UDPAddr, - svc addr.SVC) (*Conn, error) + OpenRaw(ctx context.Context, addr *net.UDPAddr) (PacketConn, error) + Listen(ctx context.Context, network string, listen *net.UDPAddr) (*Conn, error) + Dial(ctx context.Context, network string, listen *net.UDPAddr, remote *UDPAddr) (*Conn, error) } diff --git a/pkg/snet/metrics/metrics.go b/pkg/snet/metrics/metrics.go index d70fe5b009..0498703bf0 100644 --- a/pkg/snet/metrics/metrics.go +++ b/pkg/snet/metrics/metrics.go @@ -78,9 +78,9 @@ func NewSCIONPacketConnMetrics(opts ...Option) snet.SCIONPacketConnMetrics { WritePackets: metrics.NewPromCounter(auto.NewCounterVec(prometheus.CounterOpts{ Name: "lib_snet_write_total_pkts", Help: "Total number of packets written"}, []string{})), - DispatcherErrors: metrics.NewPromCounter(auto.NewCounterVec(prometheus.CounterOpts{ - Name: "lib_snet_dispatcher_error_total", - Help: "Total number of dispatcher errors"}, []string{})), + UnderlayConnectionErrors: metrics.NewPromCounter(auto.NewCounterVec(prometheus.CounterOpts{ + Name: "lib_snet_underlay_error_total", + Help: "Total number of underlay connection errors"}, []string{})), ParseErrors: metrics.NewPromCounter(auto.NewCounterVec(prometheus.CounterOpts{ Name: "lib_snet_parse_error_total", Help: "Total number of parse errors"}, []string{})), diff --git a/pkg/snet/mock_snet/BUILD.bazel b/pkg/snet/mock_snet/BUILD.bazel index 5ff72264f9..9ca0f74569 100644 --- a/pkg/snet/mock_snet/BUILD.bazel +++ b/pkg/snet/mock_snet/BUILD.bazel @@ -5,7 +5,6 @@ gomock( name = "go_default_mock", out = "mock.go", interfaces = [ - "PacketDispatcherService", "Network", "PacketConn", "Path", diff --git a/pkg/snet/mock_snet/mock.go b/pkg/snet/mock_snet/mock.go index 181ac9a929..c4a1d854b1 100644 --- a/pkg/snet/mock_snet/mock.go +++ b/pkg/snet/mock_snet/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/scionproto/scion/pkg/snet (interfaces: PacketDispatcherService,Network,PacketConn,Path,PathQuerier,Router,RevocationHandler) +// Source: github.com/scionproto/scion/pkg/snet (interfaces: Network,PacketConn,Path,PathQuerier,Router,RevocationHandler) // Package mock_snet is a generated GoMock package. package mock_snet @@ -8,6 +8,7 @@ import ( context "context" net "net" reflect "reflect" + syscall "syscall" time "time" gomock "github.com/golang/mock/gomock" @@ -16,45 +17,6 @@ import ( snet "github.com/scionproto/scion/pkg/snet" ) -// MockPacketDispatcherService is a mock of PacketDispatcherService interface. -type MockPacketDispatcherService struct { - ctrl *gomock.Controller - recorder *MockPacketDispatcherServiceMockRecorder -} - -// MockPacketDispatcherServiceMockRecorder is the mock recorder for MockPacketDispatcherService. -type MockPacketDispatcherServiceMockRecorder struct { - mock *MockPacketDispatcherService -} - -// NewMockPacketDispatcherService creates a new mock instance. -func NewMockPacketDispatcherService(ctrl *gomock.Controller) *MockPacketDispatcherService { - mock := &MockPacketDispatcherService{ctrl: ctrl} - mock.recorder = &MockPacketDispatcherServiceMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockPacketDispatcherService) EXPECT() *MockPacketDispatcherServiceMockRecorder { - return m.recorder -} - -// Register mocks base method. -func (m *MockPacketDispatcherService) Register(arg0 context.Context, arg1 addr.IA, arg2 *net.UDPAddr, arg3 addr.SVC) (snet.PacketConn, uint16, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Register", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(snet.PacketConn) - ret1, _ := ret[1].(uint16) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Register indicates an expected call of Register. -func (mr *MockPacketDispatcherServiceMockRecorder) Register(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockPacketDispatcherService)(nil).Register), arg0, arg1, arg2, arg3) -} - // MockNetwork is a mock of Network interface. type MockNetwork struct { ctrl *gomock.Controller @@ -79,33 +41,48 @@ func (m *MockNetwork) EXPECT() *MockNetworkMockRecorder { } // Dial mocks base method. -func (m *MockNetwork) Dial(arg0 context.Context, arg1 string, arg2 *net.UDPAddr, arg3 *snet.UDPAddr, arg4 addr.SVC) (*snet.Conn, error) { +func (m *MockNetwork) Dial(arg0 context.Context, arg1 string, arg2 *net.UDPAddr, arg3 *snet.UDPAddr) (*snet.Conn, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Dial", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "Dial", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*snet.Conn) ret1, _ := ret[1].(error) return ret0, ret1 } // Dial indicates an expected call of Dial. -func (mr *MockNetworkMockRecorder) Dial(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockNetworkMockRecorder) Dial(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dial", reflect.TypeOf((*MockNetwork)(nil).Dial), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dial", reflect.TypeOf((*MockNetwork)(nil).Dial), arg0, arg1, arg2, arg3) } // Listen mocks base method. -func (m *MockNetwork) Listen(arg0 context.Context, arg1 string, arg2 *net.UDPAddr, arg3 addr.SVC) (*snet.Conn, error) { +func (m *MockNetwork) Listen(arg0 context.Context, arg1 string, arg2 *net.UDPAddr) (*snet.Conn, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Listen", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "Listen", arg0, arg1, arg2) ret0, _ := ret[0].(*snet.Conn) ret1, _ := ret[1].(error) return ret0, ret1 } // Listen indicates an expected call of Listen. -func (mr *MockNetworkMockRecorder) Listen(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockNetworkMockRecorder) Listen(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Listen", reflect.TypeOf((*MockNetwork)(nil).Listen), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Listen", reflect.TypeOf((*MockNetwork)(nil).Listen), arg0, arg1, arg2) +} + +// OpenRaw mocks base method. +func (m *MockNetwork) OpenRaw(arg0 context.Context, arg1 *net.UDPAddr) (snet.PacketConn, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenRaw", arg0, arg1) + ret0, _ := ret[0].(snet.PacketConn) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OpenRaw indicates an expected call of OpenRaw. +func (mr *MockNetworkMockRecorder) OpenRaw(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenRaw", reflect.TypeOf((*MockNetwork)(nil).OpenRaw), arg0, arg1) } // MockPacketConn is a mock of PacketConn interface. @@ -145,6 +122,20 @@ func (mr *MockPacketConnMockRecorder) Close() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockPacketConn)(nil).Close)) } +// LocalAddr mocks base method. +func (m *MockPacketConn) LocalAddr() net.Addr { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LocalAddr") + ret0, _ := ret[0].(net.Addr) + return ret0 +} + +// LocalAddr indicates an expected call of LocalAddr. +func (mr *MockPacketConnMockRecorder) LocalAddr() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockPacketConn)(nil).LocalAddr)) +} + // ReadFrom mocks base method. func (m *MockPacketConn) ReadFrom(arg0 *snet.Packet, arg1 *net.UDPAddr) error { m.ctrl.T.Helper() @@ -201,6 +192,21 @@ func (mr *MockPacketConnMockRecorder) SetWriteDeadline(arg0 interface{}) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockPacketConn)(nil).SetWriteDeadline), arg0) } +// SyscallConn mocks base method. +func (m *MockPacketConn) SyscallConn() (syscall.RawConn, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyscallConn") + ret0, _ := ret[0].(syscall.RawConn) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyscallConn indicates an expected call of SyscallConn. +func (mr *MockPacketConnMockRecorder) SyscallConn() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyscallConn", reflect.TypeOf((*MockPacketConn)(nil).SyscallConn)) +} + // WriteTo mocks base method. func (m *MockPacketConn) WriteTo(arg0 *snet.Packet, arg1 *net.UDPAddr) error { m.ctrl.T.Helper() diff --git a/pkg/snet/packet_conn.go b/pkg/snet/packet_conn.go index 6e257f3095..4c2f57777e 100644 --- a/pkg/snet/packet_conn.go +++ b/pkg/snet/packet_conn.go @@ -16,6 +16,8 @@ package snet import ( "net" + "net/netip" + "syscall" "time" "github.com/scionproto/scion/pkg/addr" @@ -23,6 +25,11 @@ import ( "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/path/empty" + "github.com/scionproto/scion/pkg/slayers/path/epic" + "github.com/scionproto/scion/pkg/slayers/path/onehop" + "github.com/scionproto/scion/pkg/slayers/path/scion" + "github.com/scionproto/scion/private/topology/underlay" ) // PacketConn gives applications easy access to writing and reading custom @@ -33,6 +40,8 @@ type PacketConn interface { SetReadDeadline(t time.Time) error SetWriteDeadline(t time.Time) error SetDeadline(t time.Time) error + SyscallConn() (syscall.RawConn, error) + LocalAddr() net.Addr Close() error } @@ -98,21 +107,26 @@ type SCIONPacketConnMetrics struct { ParseErrors metrics.Counter // SCMPErrors records the total number of SCMP Errors encountered. SCMPErrors metrics.Counter - // DispatcherErrors records the number of dispatcher errors encountered. - DispatcherErrors metrics.Counter + // UnderlayConnectionErrors records the number of underlay connection errors encountered. + UnderlayConnectionErrors metrics.Counter } // SCIONPacketConn gives applications full control over the content of valid SCION // packets. type SCIONPacketConn struct { // Conn is the connection to send/receive serialized packets on. - Conn net.PacketConn + Conn *net.UDPConn // SCMPHandler is invoked for packets that contain an SCMP L4. If the // handler is nil, errors are returned back to applications every time an // SCMP message is received. SCMPHandler SCMPHandler // Metrics are the metrics exported by the conn. - Metrics SCIONPacketConnMetrics + Metrics SCIONPacketConnMetrics + interfaceMap interfaceMap +} + +func (c *SCIONPacketConn) SetReadBuffer(bytes int) error { + return c.Conn.SetReadBuffer(bytes) } func (c *SCIONPacketConn) SetDeadline(d time.Time) error { @@ -139,6 +153,10 @@ func (c *SCIONPacketConn) WriteTo(pkt *Packet, ov *net.UDPAddr) error { return nil } +func (c *SCIONPacketConn) SetWriteBuffer(bytes int) error { + return c.Conn.SetWriteBuffer(bytes) +} + func (c *SCIONPacketConn) SetWriteDeadline(d time.Time) error { return c.Conn.SetWriteDeadline(d) } @@ -146,9 +164,11 @@ func (c *SCIONPacketConn) SetWriteDeadline(d time.Time) error { func (c *SCIONPacketConn) ReadFrom(pkt *Packet, ov *net.UDPAddr) error { for { // Read until we get an error or a data packet - if err := c.readFrom(pkt, ov); err != nil { + remoteAddr, err := c.readFrom(pkt) + if err != nil { return err } + *ov = *remoteAddr if scmp, ok := pkt.Payload.(SCMPPayload); ok { if c.SCMPHandler == nil { metrics.CounterInc(c.Metrics.SCMPErrors) @@ -169,41 +189,145 @@ func (c *SCIONPacketConn) ReadFrom(pkt *Packet, ov *net.UDPAddr) error { } } -func (c *SCIONPacketConn) readFrom(pkt *Packet, ov *net.UDPAddr) error { +func (c *SCIONPacketConn) SyscallConn() (syscall.RawConn, error) { + return c.Conn.SyscallConn() +} + +func (c *SCIONPacketConn) readFrom(pkt *Packet) (*net.UDPAddr, error) { pkt.Prepare() - n, lastHopNetAddr, err := c.Conn.ReadFrom(pkt.Bytes) + n, remoteAddr, err := c.Conn.ReadFrom(pkt.Bytes) if err != nil { - metrics.CounterInc(c.Metrics.DispatcherErrors) - return serrors.WrapStr("Reliable socket read error", err) + metrics.CounterInc(c.Metrics.UnderlayConnectionErrors) + return nil, serrors.WrapStr("reading underlay connection", err) } metrics.CounterAdd(c.Metrics.ReadBytes, float64(n)) metrics.CounterInc(c.Metrics.ReadPackets) pkt.Bytes = pkt.Bytes[:n] - var lastHop *net.UDPAddr - - var ok bool - lastHop, ok = lastHopNetAddr.(*net.UDPAddr) - if !ok { - return serrors.New("Invalid lastHop address Type", - "Actual", lastHopNetAddr) - } - if err := pkt.Decode(); err != nil { metrics.CounterInc(c.Metrics.ParseErrors) - return serrors.WrapStr("decoding packet", err) + return nil, serrors.WrapStr("decoding packet", err) } - if ov != nil { - *ov = *lastHop + udpRemoteAddr := remoteAddr.(*net.UDPAddr) + lastHop := udpRemoteAddr + if c.isShimDispatcher(udpRemoteAddr) { + // XXX(JordiSubira): As stated in `SCIONPacketConn.isShimDispatcher()`, we consider + // *loopback:30041* as a shim address. + // However, if in an alternative setup we find an actual endhost behind + // *loopback:30041* `SCIONPacketConn.lastHop()` should yield the right next hop address. + lastHop, err = c.lastHop(pkt) + if err != nil { + return nil, serrors.WrapStr("extracting last hop based on packet path", err) + } } - return nil + return lastHop, nil } func (c *SCIONPacketConn) SetReadDeadline(d time.Time) error { return c.Conn.SetReadDeadline(d) } +func (c *SCIONPacketConn) LocalAddr() net.Addr { + return c.Conn.LocalAddr() +} + +// isShimDispatcher checks that udpAddr corresponds to the address where the +// shim is/should listen on. The shim only forwards packets whose underlay +// IP (i.e., the address on the UDP/IP header) corresponds to the SCION Destination +// address (i.e., the address on the UDP/SCION header). Therefore, the underlay address +// for the application using SCIONPacketConn will be the same as the underlay from where +// the shim dispatcher forwards the packets. +// +// A special case is the developer setup: we use a single shim dispatcher instance +// listening on *[::]* serving all services (sometimes from multiple ASes). +// In IPv4 context, the OS will pick *loopback* as the source IP when reflecting the packet +// from the shim dispatcher to the destination endhost. Thus, we check here if the packet +// comes from *loopback:30041*. +func (c *SCIONPacketConn) isShimDispatcher(udpAddr *net.UDPAddr) bool { + localAddr := c.LocalAddr().(*net.UDPAddr) + if udpAddr.IP.Equal(localAddr.IP) || + udpAddr.IP.IsLoopback() && + udpAddr.Port == underlay.EndhostPort { + return true + } + return false +} + +func (c *SCIONPacketConn) lastHop(p *Packet) (*net.UDPAddr, error) { + rpath, ok := p.Path.(RawPath) + if !ok { + return nil, serrors.New("path type not supported", "type", common.TypeOf(p.Path)) + } + switch rpath.PathType { + case empty.PathType: + if p.Source.Host.Type() != addr.HostTypeIP { + return nil, serrors.New("unexpected source address in packet", + "type", p.Source.Host.Type().String()) + } + return &net.UDPAddr{ + IP: p.Source.Host.IP().AsSlice(), + Port: func() int { + switch p := p.PacketInfo.Payload.(type) { + case UDPPayload: + return int(p.SrcPort) + default: + // Use endhost port for SCMP and unknown payloads. + return underlay.EndhostPort + } + }(), + }, nil + case onehop.PathType: + var path onehop.Path + if err := path.DecodeFromBytes(rpath.Raw); err != nil { + return nil, err + } + ifid := path.SecondHop.ConsIngress + if !path.Info.ConsDir { + ifid = path.SecondHop.ConsEgress + } + return c.interfaceMap.get(ifid) + case epic.PathType: + var path epic.Path + if err := path.DecodeFromBytes(rpath.Raw); err != nil { + return nil, err + } + infoField, err := path.ScionPath.GetCurrentInfoField() + if err != nil { + return nil, err + } + hf, err := path.ScionPath.GetCurrentHopField() + if err != nil { + return nil, err + } + ifid := hf.ConsIngress + if !infoField.ConsDir { + ifid = hf.ConsEgress + } + return c.interfaceMap.get(ifid) + case scion.PathType: + var path scion.Raw + if err := path.DecodeFromBytes(rpath.Raw); err != nil { + return nil, err + } + infoField, err := path.GetCurrentInfoField() + if err != nil { + return nil, err + } + hf, err := path.GetCurrentHopField() + if err != nil { + return nil, err + } + ifid := hf.ConsIngress + if !infoField.ConsDir { + ifid = hf.ConsEgress + } + return c.interfaceMap.get(ifid) + default: + return nil, serrors.New("unknown path type", "type", rpath.PathType.String()) + } +} + type SerializationOptions struct { // If ComputeChecksums is true, the checksums in sent Packets are // recomputed. Otherwise, the checksum value is left intact. @@ -218,3 +342,13 @@ type SerializationOptions struct { // unchanged. InitializePaths bool } + +type interfaceMap map[uint16]netip.AddrPort + +func (m interfaceMap) get(id uint16) (*net.UDPAddr, error) { + addrPort, ok := m[id] + if !ok { + return nil, serrors.New("interface number not found", "interface", id) + } + return net.UDPAddrFromAddrPort(addrPort), nil +} diff --git a/pkg/snet/reader.go b/pkg/snet/reader.go index d09ff6926f..06de4f5a38 100644 --- a/pkg/snet/reader.go +++ b/pkg/snet/reader.go @@ -16,6 +16,7 @@ package snet import ( "net" + "net/netip" "sync" "time" @@ -32,9 +33,8 @@ type ReplyPather interface { type scionConnReader struct { replyPather ReplyPather - - base *scionConnBase - conn PacketConn + conn PacketConn + local *UDPAddr mtx sync.Mutex buffer []byte @@ -61,10 +61,7 @@ func (c *scionConnReader) Read(b []byte) (int, error) { // read returns the number of bytes read, the address that sent the bytes and // an error (if one occurred). func (c *scionConnReader) read(b []byte) (int, *UDPAddr, error) { - if c.base.scionNet == nil { - return 0, nil, serrors.New("SCION network not initialized") - } - + // TODO(JordiSubira): Add UTs for this c.mtx.Lock() defer c.mtx.Unlock() @@ -90,7 +87,21 @@ func (c *scionConnReader) read(b []byte) (int, *UDPAddr, error) { if !ok { return 0, nil, serrors.New("unexpected payload", "type", common.TypeOf(pkt.Payload)) } - n := copy(b, udp.Payload) + + // XXX(JordiSubira): We explicitly forbid nil or unspecified address in the current constructor + // for Conn. + // If this were ever to change, we would always fall into the following if statement, then + // we would like to replace this logic (e.g., using IP_PKTINFO, with its caveats). + pktAddrPort := netip.AddrPortFrom(pkt.Destination.Host.IP(), udp.DstPort) + if c.local.IA != pkt.Destination.IA || + c.local.Host.AddrPort() != pktAddrPort { + return 0, nil, serrors.New("packet is destined to a different host", + "local_isd_as", c.local.IA, + "local_host", c.local.Host, + "pkt_destination_isd_as", pkt.Destination.IA, + "pkt_destination_host", pktAddrPort, + ) + } // Extract remote address. // Copy the address data to prevent races. See @@ -104,6 +115,7 @@ func (c *scionConnReader) read(b []byte) (int, *UDPAddr, error) { Path: replyPath, NextHop: CopyUDPAddr(&lastHop), } + n := copy(b, udp.Payload) return n, remote, nil } diff --git a/pkg/snet/dispatcher.go b/pkg/snet/scmp.go similarity index 75% rename from pkg/snet/dispatcher.go rename to pkg/snet/scmp.go index 99fb8cde08..45f1940c43 100644 --- a/pkg/snet/dispatcher.go +++ b/pkg/snet/scmp.go @@ -16,10 +16,8 @@ package snet import ( "context" - "net" "time" - "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/metrics" "github.com/scionproto/scion/pkg/private/common" @@ -27,45 +25,8 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/private/util" "github.com/scionproto/scion/pkg/slayers" - "github.com/scionproto/scion/pkg/sock/reliable" ) -// PacketDispatcherService constructs SCION sockets where applications have -// fine-grained control over header fields. -type PacketDispatcherService interface { - Register(ctx context.Context, ia addr.IA, registration *net.UDPAddr, - svc addr.SVC) (PacketConn, uint16, error) -} - -var _ PacketDispatcherService = (*DefaultPacketDispatcherService)(nil) - -// DefaultPacketDispatcherService parses/serializes packets received from / -// sent to the dispatcher. -type DefaultPacketDispatcherService struct { - // Dispatcher is used to get packets from the local SCION Dispatcher process. - Dispatcher reliable.Dispatcher - // SCMPHandler is invoked for packets that contain an SCMP L4. If the - // handler is nil, errors are returned back to applications every time an - // SCMP message is received. - SCMPHandler SCMPHandler - // Metrics injected into SCIONPacketConn. - SCIONPacketConnMetrics SCIONPacketConnMetrics -} - -func (s *DefaultPacketDispatcherService) Register(ctx context.Context, ia addr.IA, - registration *net.UDPAddr, svc addr.SVC) (PacketConn, uint16, error) { - - rconn, port, err := s.Dispatcher.Register(ctx, ia, registration, svc) - if err != nil { - return nil, 0, err - } - return &SCIONPacketConn{ - Conn: rconn, - SCMPHandler: s.SCMPHandler, - Metrics: s.SCIONPacketConnMetrics, - }, port, nil -} - // RevocationHandler is called by the default SCMP Handler whenever revocations are encountered. type RevocationHandler interface { // RevokeRaw handles a revocation received as raw bytes. @@ -79,7 +40,7 @@ type SCMPHandler interface { // // If the handler returns an error value, snet will propagate the error // back to the caller. If the return value is nil, snet will reattempt to - // read a data packet from the underlying dispatcher connection. + // read a data packet from the underlying connection. // // Handlers that wish to ignore SCMP can just return nil. // @@ -131,7 +92,6 @@ func (h DefaultSCMPHandler) Handle(pkt *Packet) error { return nil } } - func (h *DefaultSCMPHandler) handleSCMPRev(typeCode slayers.SCMPTypeCode, revInfo *path_mgmt.RevInfo) error { diff --git a/pkg/snet/snet.go b/pkg/snet/snet.go index c41c269e8c..e74235ecf7 100644 --- a/pkg/snet/snet.go +++ b/pkg/snet/snet.go @@ -20,31 +20,28 @@ // Listen methods on the networking context yields connections that run in that // context. // -// A connection can be created by calling Dial or Listen; both functions -// register an address-port pair with the local dispatcher. For Dial, the +// A connection can be created by calling Dial or Listen. For Dial, the // remote address is fixed, meaning only Read and Write can be used. Attempting // to ReadFrom or WriteTo a connection created by Dial is an invalid operation. // For Listen, the remote address cannot be fixed. ReadFrom can be used to read // from the connection and find out the sender's address; and WriteTo can be // used to send a message to a chosen destination. // -// Multiple networking contexts can share the same SCIOND and/or dispatcher. +// Multiple networking contexts can share the same SCIOND. // // Write calls never return SCMP errors directly. If a write call caused an // SCMP message to be received by the Conn, it can be inspected by calling // Read. In this case, the error value is non-nil and can be type asserted to // *OpError. Method SCMP() can be called on the error to extract the SCMP // header. -// -// Important: not draining SCMP errors via Read calls can cause the dispatcher -// to shutdown the socket (see https://github.com/scionproto/scion/pull/1356). -// To prevent this on a Conn object with only Write calls, run a separate -// goroutine that continuously calls Read on the Conn. package snet import ( "context" + "errors" "net" + "net/netip" + "syscall" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" @@ -52,6 +49,13 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" ) +// Topology provides local-IA topology information +type Topology interface { + LocalIA(ctx context.Context) (addr.IA, error) + PortRange(ctx context.Context) (uint16, uint16, error) + Interfaces(ctx context.Context) (map[uint16]netip.AddrPort, error) +} + var _ Network = (*SCIONNetwork)(nil) type SCIONNetworkMetrics struct { @@ -63,92 +67,152 @@ type SCIONNetworkMetrics struct { // SCIONNetwork is the SCION networking context. type SCIONNetwork struct { - LocalIA addr.IA - Dispatcher PacketDispatcherService + // Topology provides local AS information, needed to handle sockets and + // traffic. + Topology Topology // ReplyPather is used to create reply paths when reading packets on Conn // (that implements net.Conn). If unset, the default reply pather is used, // which parses the incoming path as a path.Path and reverses it. ReplyPather ReplyPather // Metrics holds the metrics emitted by the network. Metrics SCIONNetworkMetrics + // SCMPHandler describes the network behaviour upon receiving SCMP traffic. + SCMPHandler SCMPHandler + PacketConnMetrics SCIONPacketConnMetrics +} + +// OpenRaw returns a PacketConn which listens on the specified address. +// Nil or unspecified addresses are not supported. +// If the address port is 0 a valid and free SCION/UDP port is automatically chosen. +// Otherwise, the specified port must be a valid SCION/UDP port. +func (n *SCIONNetwork) OpenRaw(ctx context.Context, addr *net.UDPAddr) (PacketConn, error) { + var pconn *net.UDPConn + var err error + if addr == nil || addr.IP.IsUnspecified() { + return nil, serrors.New("nil or unspecified address is not supported") + } + start, end, err := n.Topology.PortRange(ctx) + if err != nil { + return nil, err + } + ifAddrs, err := n.Topology.Interfaces(ctx) + if err != nil { + return nil, err + } + if addr.Port == 0 { + pconn, err = listenUDPRange(addr, start, end) + } else { + if addr.Port < int(start) || addr.Port > int(end) { + // XXX(JordiSubira): We allow listening UDP/SCION outside the endhost range, + // however, in this setup the shim dispacher is needed to receive packets, i.e., + // BRs send packet to fix port 30041 (where the shim should be listening on) and + // the shim forwards it to underlay UDP/IP port (where we bind the UDP/SCION + // socket). + log.Info("Provided port is outside the SCION/UDP range, "+ + "it will only receive packets if shim dispatcher is configured", + "start", start, "end", end, "port", addr.Port) + } + pconn, err = net.ListenUDP(addr.Network(), addr) + } + if err != nil { + return nil, err + } + return &SCIONPacketConn{ + Conn: pconn, + SCMPHandler: n.SCMPHandler, + Metrics: n.PacketConnMetrics, + interfaceMap: ifAddrs, + }, nil } -// Dial returns a SCION connection to remote. Nil values for listen are not -// supported yet. Parameter network must be "udp". The returned connection's -// Read and Write methods can be used to receive and send SCION packets. -// Remote address requires a path and the underlay net hop to be set if the +// Dial returns a SCION connection to remote. Parameter network must be "udp". +// The returned connection's Read and Write methods can be used to receive +// and send SCION packets. +// Remote address requires a path and the underlay next hop to be set if the // destination is in a remote AS. // // The context is used for connection setup, it doesn't affect the returned // connection. func (n *SCIONNetwork) Dial(ctx context.Context, network string, listen *net.UDPAddr, - remote *UDPAddr, svc addr.SVC) (*Conn, error) { + remote *UDPAddr) (*Conn, error) { + // XXX(JordiSubira): Currently Dial does not check that received packets are + // originated from the expected remote address. This should be adapted to + // check that the remote packets are originated from the expected remote address. metrics.CounterInc(n.Metrics.Dials) + if network != "udp" { + return nil, serrors.New("Unknown network", "network", network) + } if remote == nil { return nil, serrors.New("Unable to dial to nil remote") } - conn, err := n.Listen(ctx, network, listen, svc) + packetConn, err := n.OpenRaw(ctx, listen) if err != nil { return nil, err } - conn.remote = remote.Copy() - return conn, nil + log.FromCtx(ctx).Debug("UDP socket opened on", "addr", packetConn.LocalAddr(), "to", remote) + return NewCookedConn(packetConn, n.Topology, WithReplyPather(n.ReplyPather), WithRemote(remote)) } -// Listen registers listen with the dispatcher. Nil values for listen are -// not supported yet. The returned connection's ReadFrom and WriteTo methods +// Listen opens a Conn. The returned connection's ReadFrom and WriteTo methods // can be used to receive and send SCION packets with per-packet addressing. // Parameter network must be "udp". +// Nil or unspecified addresses are not supported. // // The context is used for connection setup, it doesn't affect the returned // connection. -func (n *SCIONNetwork) Listen(ctx context.Context, network string, listen *net.UDPAddr, - svc addr.SVC) (*Conn, error) { +func (n *SCIONNetwork) Listen( + ctx context.Context, + network string, + listen *net.UDPAddr, +) (*Conn, error) { metrics.CounterInc(n.Metrics.Listens) - if network != "udp" { return nil, serrors.New("Unknown network", "network", network) } - - // FIXME(scrye): If no local address is specified, we want to - // bind to the address of the outbound interface on a random - // free port. However, the current dispatcher version cannot - // expose that address. Additionally, the dispatcher does not follow - // normal operating system semantics for binding on 0.0.0.0 (it - // considers it to be a fixed address instead of a wildcard). To avoid - // misuse, disallow binding to nil or 0.0.0.0 addresses for now. - if listen == nil { - return nil, serrors.New("nil listen addr not supported") - } - if listen.IP == nil { - return nil, serrors.New("nil listen IP not supported") - } - if listen.IP.IsUnspecified() { - return nil, serrors.New("unspecified listen IP not supported") - } - conn := scionConnBase{ - scionNet: n, - svc: svc, - listen: &UDPAddr{ - IA: n.LocalIA, - Host: CopyUDPAddr(listen), - }, - } - packetConn, port, err := n.Dispatcher.Register(ctx, n.LocalIA, listen, svc) + packetConn, err := n.OpenRaw(ctx, listen) if err != nil { return nil, err } - if port != uint16(listen.Port) { - conn.listen.Host.Port = int(port) - } - log.Debug("Registered with dispatcher", "addr", conn.listen) + log.FromCtx(ctx).Debug("UDP socket openned on", "addr", packetConn.LocalAddr()) + return NewCookedConn(packetConn, n.Topology, WithReplyPather(n.ReplyPather)) +} - replyPather := n.ReplyPather - if replyPather == nil { - replyPather = DefaultReplyPather{} +func listenUDPRange(addr *net.UDPAddr, start, end uint16) (*net.UDPConn, error) { + // XXX(JordiSubira): For now, we iterate on the complete SCION/UDP + // range, in decreasing order, taking the first unused port. + // + // If the defined range, intersects with the well-known port range, i.e., + // 1-1023, we just start considering from 1024 onwards. + // The decreasing order first try to use the higher port numbers, normally used + // by ephemeral connections, letting free the lower port numbers, normally used + // by longer-lived applications, e.g., server applications. + // + // Ideally we would only take a standard ephemeral range, e.g., 32768-65535, + // Unfortunately, this range was ocuppied by the old dispatcher. + // The default range for the dispatched ports is 31000-32767. + // By configuration other port ranges may be defined and restricting to the default + // range for applications may cause problems. + // + // TODO: Replace this implementation with pseudorandom port checking. + restrictedStart := start + if start < 1024 { + restrictedStart = 1024 } - - return newConn(conn, packetConn, replyPather), nil + for port := end; port >= restrictedStart; port-- { + pconn, err := net.ListenUDP(addr.Network(), &net.UDPAddr{ + IP: addr.IP, + Port: int(port), + }) + if err == nil { + return pconn, nil + } + if errors.Is(err, syscall.EADDRINUSE) { + continue + } + return nil, err + } + return nil, serrors.WrapStr("binding to port range", syscall.EADDRINUSE, + "start", restrictedStart, "end", end) } diff --git a/pkg/snet/writer.go b/pkg/snet/writer.go index 70609f1023..f6661b1d51 100644 --- a/pkg/snet/writer.go +++ b/pkg/snet/writer.go @@ -24,12 +24,15 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/private/topology/underlay" + "github.com/scionproto/scion/private/topology" ) type scionConnWriter struct { - base *scionConnBase - conn PacketConn + conn PacketConn + local *UDPAddr + remote *UDPAddr + dispatchedPortStart uint16 + dispatchedPortEnd uint16 mtx sync.Mutex buffer []byte @@ -55,10 +58,14 @@ func (c *scionConnWriter) WriteTo(b []byte, raddr net.Addr) (int, error) { dst = SCIONAddress{IA: a.IA, Host: addr.HostIP(hostIP)} port, path = a.Host.Port, a.Path nextHop = a.NextHop - if nextHop == nil && c.base.scionNet.LocalIA.Equal(a.IA) { + if nextHop == nil && c.local.IA.Equal(a.IA) { + port := a.Host.Port + if !c.isWithinRange(port) { + port = topology.EndhostPort + } nextHop = &net.UDPAddr{ IP: a.Host.IP, - Port: underlay.EndhostPort, + Port: port, Zone: a.Host.Zone, } @@ -71,9 +78,9 @@ func (c *scionConnWriter) WriteTo(b []byte, raddr net.Addr) (int, error) { "addr", fmt.Sprintf("%v(%T)", a, a)) } - listenHostIP, ok := netip.AddrFromSlice(c.base.listen.Host.IP) + listenHostIP, ok := netip.AddrFromSlice(c.local.Host.IP) if !ok { - return 0, serrors.New("invalid listen host IP", "ip", c.base.listen.Host.IP) + return 0, serrors.New("invalid listen host IP", "ip", c.local.Host.IP) } pkt := &Packet{ @@ -81,12 +88,12 @@ func (c *scionConnWriter) WriteTo(b []byte, raddr net.Addr) (int, error) { PacketInfo: PacketInfo{ Destination: dst, Source: SCIONAddress{ - IA: c.base.scionNet.LocalIA, + IA: c.local.IA, Host: addr.HostIP(listenHostIP), }, Path: path, Payload: UDPPayload{ - SrcPort: uint16(c.base.listen.Host.Port), + SrcPort: uint16(c.local.Host.Port), DstPort: uint16(port), Payload: b, }, @@ -104,9 +111,13 @@ func (c *scionConnWriter) WriteTo(b []byte, raddr net.Addr) (int, error) { // Write sends b through a connection with fixed remote address. If the remote // address for the connection is unknown, Write returns an error. func (c *scionConnWriter) Write(b []byte) (int, error) { - return c.WriteTo(b, c.base.remote) + return c.WriteTo(b, c.remote) } func (c *scionConnWriter) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } + +func (c *scionConnWriter) isWithinRange(port int) bool { + return port >= int(c.dispatchedPortStart) && port <= int(c.dispatchedPortEnd) +} diff --git a/pkg/sock/reliable/BUILD.bazel b/pkg/sock/reliable/BUILD.bazel deleted file mode 100644 index 8a54bb26a7..0000000000 --- a/pkg/sock/reliable/BUILD.bazel +++ /dev/null @@ -1,41 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "errors.go", - "frame.go", - "packetizer.go", - "registration.go", - "reliable.go", - "util.go", - ], - importpath = "github.com/scionproto/scion/pkg/sock/reliable", - visibility = ["//visibility:public"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/common:go_default_library", - "//pkg/private/prom:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/sock/reliable/internal/metrics:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "errors_test.go", - "frame_test.go", - "packetizer_test.go", - "registration_test.go", - ], - embed = [":go_default_library"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/private/mocks/net/mock_net:go_default_library", - "//pkg/private/xtest:go_default_library", - "@com_github_golang_mock//gomock:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - ], -) diff --git a/pkg/sock/reliable/errors.go b/pkg/sock/reliable/errors.go deleted file mode 100644 index 33a0ff9b99..0000000000 --- a/pkg/sock/reliable/errors.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2018 ETH Zurich, Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "errors" - "io" - "syscall" - - "github.com/scionproto/scion/pkg/private/common" -) - -// Possible errors -var ( - ErrNoAddress common.ErrMsg = "no address found" - ErrNoPort common.ErrMsg = "missing port" - ErrPayloadTooLong common.ErrMsg = "payload too long" - ErrIncompleteFrameHeader common.ErrMsg = "incomplete frame header" - ErrBadFrameLength common.ErrMsg = "bad frame length" - ErrBadCookie common.ErrMsg = "bad cookie" - ErrBadAddressType common.ErrMsg = "bad address type" - ErrIncompleteAddress common.ErrMsg = "incomplete IP address" - ErrIncompletePort common.ErrMsg = "incomplete UDP port" - ErrIncompleteMessage common.ErrMsg = "incomplete message" - ErrBadLength common.ErrMsg = "bad length" - ErrBufferTooSmall common.ErrMsg = "buffer too small" -) - -func IsDispatcherError(err error) bool { - // On Linux, the following errors should prompt a reconnect: - // - An EOF, when a Read happens to a connection that was closed at the - // other end, and there is no outstanding outgoing data. - // - An EPIPE, when a Write happens to a connection that was closed at - // the other end. - // - An ECONNRESET, when a Read happens to a connection that was - // closed at the other end, and there is outstanding outgoing data. An - // ECONNRESET may be followed by EOF on repeated attempts. - // All other errors can be immediately propagated back to the application. - return errors.Is(err, io.EOF) || - errors.Is(err, syscall.EPIPE) || - errors.Is(err, syscall.ECONNRESET) -} diff --git a/pkg/sock/reliable/errors_test.go b/pkg/sock/reliable/errors_test.go deleted file mode 100644 index 097db199e2..0000000000 --- a/pkg/sock/reliable/errors_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2022 SCION Association -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable_test - -import ( - "fmt" - "io" - "net" - "os" - "syscall" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/scionproto/scion/pkg/sock/reliable" -) - -func TestIsDispatcherError(t *testing.T) { - cases := map[string]struct { - err error - expected bool - }{ - "nil": { - err: nil, - expected: false, - }, - "io.EOF": { - err: io.EOF, - expected: true, - }, - "io.EOF wrapped": { - err: fmt.Errorf("aha, end of the file %w", io.EOF), - expected: true, - }, - "syscall EPIPE": { - err: syscall.EPIPE, - expected: true, - }, - "OpError EPIPE": { - err: &net.OpError{Err: &os.SyscallError{Err: syscall.EPIPE}}, - expected: true, - }, - "Wrapped OpError EPIPE": { - err: fmt.Errorf("foo %w", - &net.OpError{Err: &os.SyscallError{Err: syscall.ECONNRESET}}), - expected: true, - }, - "OpError ECONNRESET": { - err: &net.OpError{Err: &os.SyscallError{Err: syscall.ECONNRESET}}, - expected: true, - }, - "OpError other errno": { - err: &net.OpError{Err: &os.SyscallError{Err: syscall.EACCES}}, - expected: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := reliable.IsDispatcherError(c.err) - assert.Equal(t, c.expected, actual, c.err) - }) - } -} diff --git a/pkg/sock/reliable/frame.go b/pkg/sock/reliable/frame.go deleted file mode 100644 index 31b8b938af..0000000000 --- a/pkg/sock/reliable/frame.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "encoding/binary" - "net" - - "github.com/scionproto/scion/pkg/private/serrors" -) - -// UnderlayPacket contains metadata about a SCION packet going through the -// reliable socket framing protocol. -type UnderlayPacket struct { - Address *net.UDPAddr - Payload []byte -} - -func (p *UnderlayPacket) SerializeTo(b []byte) (int, error) { - var f frame - f.Cookie = expectedCookie - f.AddressType = byte(getAddressType(p.Address)) - f.Length = uint32(len(p.Payload)) - if p.Address != nil { - if err := f.insertAddress(p.Address); err != nil { - return 0, err - } - } - f.Payload = p.Payload - return f.SerializeTo(b) -} - -func (p *UnderlayPacket) DecodeFromBytes(b []byte) error { - var f frame - if err := f.DecodeFromBytes(b); err != nil { - return err - } - if f.Cookie != expectedCookie { - return ErrBadCookie - } - p.Address = f.extractAddress() - p.Payload = f.Payload - return nil -} - -// frame describes the wire format of the reliable socket framing protocol. -type frame struct { - Cookie uint64 - AddressType byte - Length uint32 - Address []byte - Port []byte - Payload []byte -} - -func (f *frame) SerializeTo(b []byte) (int, error) { - totalLength := f.length() - if totalLength > len(b) { - return 0, serrors.WithCtx(ErrBufferTooSmall, "have", len(b), "want", totalLength) - } - binary.BigEndian.PutUint64(b, f.Cookie) - b[8] = f.AddressType - binary.BigEndian.PutUint32(b[9:], f.Length) - copy(b[13:], f.Address) - copy(b[13+len(f.Address):], f.Port) - copy(b[13+len(f.Address)+len(f.Port):], f.Payload) - return totalLength, nil -} - -func (f *frame) DecodeFromBytes(data []byte) error { - if len(data) < f.headerLength() { - return ErrIncompleteFrameHeader - } - f.Cookie = binary.BigEndian.Uint64(data) - f.AddressType = data[8] - f.Length = binary.BigEndian.Uint32(data[9:]) - offset := 13 - addressType := hostAddrType(f.AddressType) - if !isValidReliableSockDestination(addressType) { - return serrors.WithCtx(ErrBadAddressType, "type", addressType) - } - addrLen := getAddressLength(addressType) - portLen := getPortLength(addressType) - if len(data[offset:]) < addrLen { - return ErrIncompleteAddress - } - f.Address = data[offset : offset+addrLen] - offset += addrLen - if len(data[offset:]) < portLen { - return ErrIncompletePort - } - f.Port = data[offset : offset+portLen] - offset += portLen - f.Payload = data[offset:] - if len(f.Payload) != int(f.Length) { - return ErrBadLength - } - return nil -} - -// length returns the total length of the frame (including payload). -func (f *frame) length() int { - return f.headerLength() + len(f.Address) + len(f.Port) + len(f.Payload) -} - -// header length returns the length of the fixed size start of the frame -// (cookie, address type and payload length field). -func (f *frame) headerLength() int { - return 8 + 1 + 4 -} - -func (f *frame) insertAddress(address *net.UDPAddr) error { - if address.IP == nil || address.IP.IsUnspecified() { - return ErrNoAddress - } - if address.Port == 0 { - return ErrNoPort - } - f.Address = []byte(normalizeIP(address.IP)) - f.Port = make([]byte, 2) - binary.BigEndian.PutUint16(f.Port, uint16(address.Port)) - return nil -} - -func (f *frame) extractAddress() *net.UDPAddr { - t := hostAddrType(f.AddressType) - if t == hostTypeIPv4 || t == hostTypeIPv6 { - return &net.UDPAddr{ - IP: net.IP(f.Address), - Port: int(binary.BigEndian.Uint16(f.Port)), - } - } - return nil -} diff --git a/pkg/sock/reliable/frame_test.go b/pkg/sock/reliable/frame_test.go deleted file mode 100644 index a93dfe0554..0000000000 --- a/pkg/sock/reliable/frame_test.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "net" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestUnderlayPacketSerializeTo(t *testing.T) { - type TestCase struct { - Name string - Packet *UnderlayPacket - ExpectedData []byte - ExpectedError error - } - testCases := []TestCase{ - { - Name: "none type address, no data", - Packet: &UnderlayPacket{}, - ExpectedData: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 0, 0, 0, 0, 0}, - }, - { - Name: "empty IP address", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{}, - }, - ExpectedError: ErrNoAddress, - ExpectedData: []byte{}, - }, - { - Name: "IPv4 host, with address, no port, no data", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("1.2.3.4")}, - }, - ExpectedError: ErrNoPort, - ExpectedData: []byte{}, - }, - { - Name: "IPv4 host, with address, with port, no data", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("10.2.3.4"), Port: 80}, - }, - ExpectedData: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 0, - 10, 2, 3, 4, 0, 80}, - }, - { - Name: "IPv6 host, with address, with port, no data", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80}, - }, - ExpectedData: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 2, 0, 0, 0, 0, - 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 80}, - }, - { - Name: "IPv4 host, with address, big port, no data", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("10.2.3.4"), Port: 0x1234}, - }, - ExpectedData: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 0, - 10, 2, 3, 4, 0x12, 0x34}, - }, - { - Name: "long payload", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 80}, - Payload: make([]byte, 2000), - }, - ExpectedError: ErrBufferTooSmall, - ExpectedData: []byte{}, - }, - { - Name: "good payload", - Packet: &UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("10.2.3.4"), Port: 80}, - Payload: []byte{10, 5, 6, 7}, - }, - ExpectedData: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 4, - 10, 2, 3, 4, 0, 80, 10, 5, 6, 7}, - }, - } - t.Run("Different packets serialize correctly", func(t *testing.T) { - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - b := make([]byte, 1500) - n, err := tc.Packet.SerializeTo(b) - assert.ErrorIs(t, err, tc.ExpectedError) - assert.Equal(t, tc.ExpectedData, b[:n]) - }) - } - }) -} - -func TestUnderlayPacketDecodeFromBytes(t *testing.T) { - type TestCase struct { - Name string - Buffer []byte - ExpectedPacket UnderlayPacket - ExpectedError error - } - testCases := []TestCase{ - { - Name: "incomplete header", - Buffer: []byte{0xaa}, - ExpectedError: ErrIncompleteFrameHeader, - }, - { - Name: "bad cookie", - Buffer: []byte{0xaa, 0xbb, 0xaa, 0xbb, 0xaa, 0xbb, 0xaa, 0xbb, 0, 0, 0, 0, 0}, - ExpectedError: ErrBadCookie, - }, - { - Name: "bad address type", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 3, 0, 0, 0, 0}, - ExpectedError: ErrBadAddressType, - }, - { - Name: "incomplete address", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 0, - 10, 2, 3}, - ExpectedError: ErrIncompleteAddress, - }, - { - Name: "incomplete port", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 0, - 10, 2, 3, 4, 0}, - ExpectedError: ErrIncompletePort, - }, - { - Name: "bad length (underflow)", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 0, - 10, 2, 3, 4, 0, 80, 42}, - ExpectedError: ErrBadLength, - }, - { - Name: "bad length (overflow)", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 2, - 10, 2, 3, 4, 0, 80, 42}, - ExpectedError: ErrBadLength, - }, - { - Name: "good packet (none type address)", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 0, 0, 0, 0, 1, 42}, - ExpectedPacket: UnderlayPacket{ - Payload: []byte{42}, - }, - }, - { - Name: "good packet (IPv4)", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 1, 0, 0, 0, 1, - 10, 2, 3, 4, 0, 80, 42}, - ExpectedPacket: UnderlayPacket{ - Address: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - Payload: []byte{42}, - }, - }, - { - Name: "good packet (IPv6)", - Buffer: []byte{0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 2, 0, 0, 0, 1, - 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 80, 42}, - ExpectedPacket: UnderlayPacket{ - Address: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80}, - Payload: []byte{42}, - }, - }, - } - t.Run("Different packets decode correctly", func(t *testing.T) { - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - var p UnderlayPacket - err := p.DecodeFromBytes(tc.Buffer) - assert.ErrorIs(t, err, tc.ExpectedError) - assert.Equal(t, tc.ExpectedPacket, p) - }) - } - }) -} diff --git a/pkg/sock/reliable/internal/metrics/BUILD.bazel b/pkg/sock/reliable/internal/metrics/BUILD.bazel deleted file mode 100644 index 6ca88bea68..0000000000 --- a/pkg/sock/reliable/internal/metrics/BUILD.bazel +++ /dev/null @@ -1,21 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["metrics.go"], - importpath = "github.com/scionproto/scion/pkg/sock/reliable/internal/metrics", - visibility = ["//pkg/sock/reliable:__subpackages__"], - deps = [ - "//pkg/private/prom:go_default_library", - "@com_github_prometheus_client_golang//prometheus:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["metrics_test.go"], - deps = [ - ":go_default_library", - "//pkg/private/prom/promtest:go_default_library", - ], -) diff --git a/pkg/sock/reliable/internal/metrics/metrics.go b/pkg/sock/reliable/internal/metrics/metrics.go deleted file mode 100644 index 774869b257..0000000000 --- a/pkg/sock/reliable/internal/metrics/metrics.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "github.com/prometheus/client_golang/prometheus" - - "github.com/scionproto/scion/pkg/private/prom" -) - -// Namespace is the metrics namespace for the metrics in this package. -const Namespace = "lib" - -const sub = "reliable" - -var ( - // M exposes all the initialized metrics for this package. - M = newMetrics() -) - -// DialLabels contains the labels for Dial calls. -type DialLabels struct { - Result string -} - -// Labels returns the list of labels. -func (l DialLabels) Labels() []string { - return []string{prom.LabelResult} -} - -// Values returns the label values in the order defined by Labels. -func (l DialLabels) Values() []string { - return []string{l.Result} -} - -// RegisterLabels contains the labels for Register calls. -type RegisterLabels struct { - Result string - SVC string -} - -// Labels returns the list of labels. -func (l RegisterLabels) Labels() []string { - return []string{prom.LabelResult, "svc"} -} - -// Values returns the label values in the order defined by Labels. -func (l RegisterLabels) Values() []string { - return []string{l.Result, l.SVC} -} - -// IOLabels contains the labels for Read and Write calls. -type IOLabels struct { - Result string -} - -// Labels returns the list of labels. -func (l IOLabels) Labels() []string { - return []string{prom.LabelResult} -} - -// Values returns the label values in the order defined by Labels. -func (l IOLabels) Values() []string { - return []string{l.Result} -} - -type metrics struct { - dials *prometheus.CounterVec - registers *prometheus.CounterVec - reads *prometheus.HistogramVec - writes *prometheus.HistogramVec -} - -func newMetrics() metrics { - return metrics{ - dials: prom.NewCounterVecWithLabels(Namespace, sub, "dials_total", - "Total number of Dial calls.", DialLabels{}), - registers: prom.NewCounterVecWithLabels(Namespace, sub, "registers_total", - "Total number of Register calls.", RegisterLabels{}), - reads: prom.NewHistogramVecWithLabels(Namespace, sub, "reads_total", - "Total number of Read calls", IOLabels{}, prom.DefaultSizeBuckets), - writes: prom.NewHistogramVecWithLabels(Namespace, sub, "writes_total", - "Total number of Write calls", IOLabels{}, prom.DefaultSizeBuckets), - } -} - -func (m metrics) Dials(l DialLabels) prometheus.Counter { - return m.dials.WithLabelValues(l.Values()...) -} - -func (m metrics) Registers(l RegisterLabels) prometheus.Counter { - return m.registers.WithLabelValues(l.Values()...) -} - -func (m metrics) Reads(l IOLabels) prometheus.Observer { - return m.reads.WithLabelValues(l.Values()...) -} - -func (m metrics) Writes(l IOLabels) prometheus.Observer { - return m.writes.WithLabelValues(l.Values()...) -} diff --git a/pkg/sock/reliable/internal/metrics/metrics_test.go b/pkg/sock/reliable/internal/metrics/metrics_test.go deleted file mode 100644 index 4df6f08e97..0000000000 --- a/pkg/sock/reliable/internal/metrics/metrics_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics_test - -import ( - "testing" - - "github.com/scionproto/scion/pkg/private/prom/promtest" - "github.com/scionproto/scion/pkg/sock/reliable/internal/metrics" -) - -func TestLabels(t *testing.T) { - promtest.CheckLabelsStruct(t, metrics.DialLabels{}) - promtest.CheckLabelsStruct(t, metrics.RegisterLabels{}) - promtest.CheckLabelsStruct(t, metrics.IOLabels{}) -} diff --git a/pkg/sock/reliable/mock_reliable/BUILD.bazel b/pkg/sock/reliable/mock_reliable/BUILD.bazel deleted file mode 100644 index baaefd8ff1..0000000000 --- a/pkg/sock/reliable/mock_reliable/BUILD.bazel +++ /dev/null @@ -1,21 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") -load("@io_bazel_rules_go//go:def.bzl", "gomock") - -gomock( - name = "go_default_mock", - out = "mock.go", - interfaces = ["Dispatcher"], - library = "//pkg/sock/reliable:go_default_library", - package = "mock_reliable", -) - -go_library( - name = "go_default_library", - srcs = ["mock.go"], - importpath = "github.com/scionproto/scion/pkg/sock/reliable/mock_reliable", - visibility = ["//visibility:public"], - deps = [ - "//pkg/addr:go_default_library", - "@com_github_golang_mock//gomock:go_default_library", - ], -) diff --git a/pkg/sock/reliable/mock_reliable/mock.go b/pkg/sock/reliable/mock_reliable/mock.go deleted file mode 100644 index 6dd9215859..0000000000 --- a/pkg/sock/reliable/mock_reliable/mock.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/scionproto/scion/pkg/sock/reliable (interfaces: Dispatcher) - -// Package mock_reliable is a generated GoMock package. -package mock_reliable - -import ( - context "context" - net "net" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - addr "github.com/scionproto/scion/pkg/addr" -) - -// MockDispatcher is a mock of Dispatcher interface. -type MockDispatcher struct { - ctrl *gomock.Controller - recorder *MockDispatcherMockRecorder -} - -// MockDispatcherMockRecorder is the mock recorder for MockDispatcher. -type MockDispatcherMockRecorder struct { - mock *MockDispatcher -} - -// NewMockDispatcher creates a new mock instance. -func NewMockDispatcher(ctrl *gomock.Controller) *MockDispatcher { - mock := &MockDispatcher{ctrl: ctrl} - mock.recorder = &MockDispatcherMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockDispatcher) EXPECT() *MockDispatcherMockRecorder { - return m.recorder -} - -// Register mocks base method. -func (m *MockDispatcher) Register(arg0 context.Context, arg1 addr.IA, arg2 *net.UDPAddr, arg3 addr.SVC) (net.PacketConn, uint16, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Register", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(net.PacketConn) - ret1, _ := ret[1].(uint16) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Register indicates an expected call of Register. -func (mr *MockDispatcherMockRecorder) Register(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockDispatcher)(nil).Register), arg0, arg1, arg2, arg3) -} diff --git a/pkg/sock/reliable/packetizer.go b/pkg/sock/reliable/packetizer.go deleted file mode 100644 index 38485bf395..0000000000 --- a/pkg/sock/reliable/packetizer.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "encoding/binary" - "net" - - "github.com/scionproto/scion/pkg/private/common" - "github.com/scionproto/scion/pkg/private/serrors" -) - -// ReadPacketizer splits a stream of reliable socket frames into packets. -// -// FIXME(scrye): This will be deleted when we move to SEQPACKET. -type ReadPacketizer struct { - buffer [common.SupportedMTU]byte - data []byte - freeSpace []byte - conn net.Conn -} - -func NewReadPacketizer(conn net.Conn) *ReadPacketizer { - packetizer := &ReadPacketizer{conn: conn} - packetizer.freeSpace = packetizer.buffer[:] - packetizer.data = packetizer.buffer[0:0] - return packetizer -} - -func (r *ReadPacketizer) Read(b []byte) (int, error) { - for { - if packet := r.haveNextPacket(r.data); packet != nil { - if len(packet) > len(b) { - return 0, serrors.WithCtx(ErrBufferTooSmall, - "have", len(b), "want", len(packet)) - } - copy(b, packet) - r.deleteData(len(packet)) - return len(packet), nil - } - n, err := r.conn.Read(r.freeSpace) - if err != nil { - return 0, err - } - r.addData(n) - } -} - -func (r *ReadPacketizer) deleteData(count int) { - copy(r.buffer[:], r.buffer[count:r.availableData()]) - r.updateSlices(r.availableData() - count) -} - -func (r *ReadPacketizer) addData(count int) { - r.updateSlices(r.availableData() + count) -} - -func (r *ReadPacketizer) availableData() int { - return len(r.data) -} - -func (r *ReadPacketizer) updateSlices(availableData int) { - r.data = r.buffer[:availableData] - r.freeSpace = r.buffer[availableData:] -} - -// haveNextPacket returns a slice with the next packet in b, or nil, if a full -// packet is not available. -func (reader *ReadPacketizer) haveNextPacket(b []byte) []byte { - if len(b) < 13 { - return nil - } - rcvdAddrType := b[8] - payloadLength := binary.BigEndian.Uint32(b[9:13]) - addressLength := getAddressLength(hostAddrType(rcvdAddrType)) - portLength := getPortLength(hostAddrType(rcvdAddrType)) - totalLength := 13 + addressLength + portLength + int(payloadLength) - if len(b) < totalLength { - return nil - } - return b[:totalLength] -} - -// WriteStreamer sends a packet via a stream. It is guaranteed to block until -// the whole packet has been sent (or an error occurred). -// -// FIXME(scrye): This will be delete when we move to SEQPACKET. -type WriteStreamer struct { - conn net.Conn -} - -func NewWriteStreamer(conn net.Conn) *WriteStreamer { - return &WriteStreamer{conn: conn} -} - -func (writer *WriteStreamer) Write(b []byte) error { - var err error - for bytesWritten, n := 0, 0; bytesWritten != len(b); bytesWritten += n { - n, err = writer.conn.Write(b[bytesWritten:]) - if err != nil { - return err - } - } - return nil -} diff --git a/pkg/sock/reliable/packetizer_test.go b/pkg/sock/reliable/packetizer_test.go deleted file mode 100644 index cda6ad3903..0000000000 --- a/pkg/sock/reliable/packetizer_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "testing" - - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" - - "github.com/scionproto/scion/pkg/private/mocks/net/mock_net" -) - -func TestReadPacketizer(t *testing.T) { - // FIXME(scrye): This will get deleted when we move from to SEQPACKET. - t.Run("Packetizer should extract multiple packets from an input stream", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - data := []byte{ - 0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 2, 0, 0, 0, 1, - 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 80, 42, - 0xde, 0, 0xad, 1, 0xbe, 2, 0xef, 3, 2, 0, 0, 0, 1, - 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 80, 42, - } - conn := mock_net.NewMockConn(ctrl) - conn.EXPECT().Read(gomock.Any()).DoAndReturn( - func(b []byte) (int, error) { - max := 5 - if max > len(data) { - max = len(data) - } - n := copy(b, data[:max]) - data = data[n:] - return n, nil - }).AnyTimes() - packetizer := NewReadPacketizer(conn) - b := make([]byte, 128) - n, err := packetizer.Read(b) - assert.NoError(t, err, "first packet err") - assert.Equal(t, 32, n, "first packet size") - n, err = packetizer.Read(b) - assert.NoError(t, err, "second packet err") - assert.Equal(t, 32, n, "second packet err") - }) -} - -func TestWriteStreamer(t *testing.T) { - // FIXME(scrye): This will get deleted when we move from to SEQPACKET. - t.Run("Streamer should do repeated calls to send a full message", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - data := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} - conn := mock_net.NewMockConn(ctrl) - gomock.InOrder( - conn.EXPECT().Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}).Return(4, nil), - conn.EXPECT().Write([]byte{5, 6, 7, 8, 9, 10}).Return(4, nil), - conn.EXPECT().Write([]byte{9, 10}).Return(2, nil), - ) - streamer := NewWriteStreamer(conn) - err := streamer.Write(data) - assert.NoError(t, err) - }) -} diff --git a/pkg/sock/reliable/reconnect/BUILD.bazel b/pkg/sock/reliable/reconnect/BUILD.bazel deleted file mode 100644 index 0ef4bfcc84..0000000000 --- a/pkg/sock/reliable/reconnect/BUILD.bazel +++ /dev/null @@ -1,48 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "conn.go", - "doc.go", - "errors.go", - "io.go", - "network.go", - "reconnecter.go", - "util.go", - ], - importpath = "github.com/scionproto/scion/pkg/sock/reliable/reconnect", - visibility = ["//visibility:public"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/sock/reliable:go_default_library", - "//pkg/sock/reliable/reconnect/internal/metrics:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "conn_io_test.go", - "main_test.go", - "network_test.go", - "reconnecter_test.go", - "util_test.go", - ], - deps = [ - ":go_default_library", - "//pkg/addr:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/mocks/net/mock_net:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/private/xtest:go_default_library", - "//pkg/snet:go_default_library", - "//pkg/sock/reliable/mock_reliable:go_default_library", - "//pkg/sock/reliable/reconnect/mock_reconnect:go_default_library", - "@com_github_golang_mock//gomock:go_default_library", - "@com_github_smartystreets_goconvey//convey:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - ], -) diff --git a/pkg/sock/reliable/reconnect/conn.go b/pkg/sock/reliable/reconnect/conn.go deleted file mode 100644 index 866bc06294..0000000000 --- a/pkg/sock/reliable/reconnect/conn.go +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import ( - "context" - "net" - "sync" - "time" - - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable" -) - -var _ net.PacketConn = (*PacketConn)(nil) - -type PacketConn struct { - // connMtx protects read/write access to connection information. connMtx must - // not be held when running methods on the connection. - connMtx sync.Mutex - dispConn net.PacketConn - - // readMtx is used to ensure only one reader enters the main I/O loop. - readMtx sync.Mutex - // writeMtx is used to ensure only one writer enters the main I/O loop. - writeMtx sync.Mutex - // spawnReconnecterMtx is used to ensure a single goroutine starts the - // reconnecter. This must be acquired with either readMtx or writeMtx - // taken. - spawnReconnecterMtx sync.Mutex - - writeDeadlineMtx sync.Mutex - writeDeadline time.Time - - readDeadlineMtx sync.Mutex - readDeadline time.Time - - dispatcherState *State - reconnecter Reconnecter - deadlineChangedEvent chan struct{} - // fatalError is written to by the async reconnecter on fatal errors, and then closed - fatalError chan error - // closeCh is closed when Close() is called, thus starting clean-up - closeCh chan struct{} - // closeMtx is used to guarantee that a single goroutine enters Close - closeMtx sync.Mutex -} - -func NewPacketConn(dispConn net.PacketConn, reconnecter Reconnecter) *PacketConn { - return &PacketConn{ - dispConn: dispConn, - dispatcherState: NewState(), - reconnecter: reconnecter, - deadlineChangedEvent: make(chan struct{}, 1), - fatalError: make(chan error, 1), - closeCh: make(chan struct{}), - } -} - -func (conn *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { - op := &ReadFromOperation{} - op.buffer = b - err := conn.DoIO(op) - return op.numBytes, op.address, err -} - -func (conn *PacketConn) WriteTo(b []byte, address net.Addr) (int, error) { - op := &WriteToOperation{} - op.buffer = b - op.address = address - err := conn.DoIO(op) - return op.numBytes, err -} - -func (conn *PacketConn) DoIO(op IOOperation) error { - conn.lockMutexForOpType(op) - defer conn.unlockMutexForOpType(op) - var err error -Loop: - for { - deadline := conn.getDeadlineForOpType(op) - select { - case <-conn.closeCh: - return ErrClosed - case <-conn.dispatcherState.Up(): - err = op.Do(conn.getConn()) - if err != nil { - if reliable.IsDispatcherError(err) && !conn.isClosing() { - conn.spawnAsyncReconnecterOnce() - continue - } else { - return err - } - } - break Loop - case err := <-conn.fatalError: - return err - case <-conn.deadlineChangedEvent: - case <-returnOnDeadline(deadline): - return ErrDispatcherDead - } - } - return nil -} - -func (conn *PacketConn) lockMutexForOpType(op IOOperation) { - if op.IsWrite() { - conn.writeMtx.Lock() - } else { - conn.readMtx.Lock() - } -} - -func (conn *PacketConn) unlockMutexForOpType(op IOOperation) { - if op.IsWrite() { - conn.writeMtx.Unlock() - } else { - conn.readMtx.Unlock() - } -} - -func (conn *PacketConn) spawnAsyncReconnecterOnce() { - conn.spawnReconnecterMtx.Lock() - select { - case <-conn.dispatcherState.Up(): - conn.dispatcherState.SetDown() - go func() { - defer log.HandlePanic() - conn.asyncReconnectWrapper() - }() - default: - } - conn.spawnReconnecterMtx.Unlock() -} - -func (conn *PacketConn) asyncReconnectWrapper() { - newConn, err := conn.Reconnect() - if err != nil { - conn.fatalError <- err - close(conn.fatalError) - return - } - if err := serrors.Join( - newConn.SetReadDeadline(conn.getReadDeadline()), - newConn.SetWriteDeadline(conn.getWriteDeadline()), - ); err != nil { - conn.fatalError <- err - close(conn.fatalError) - return - } - conn.setConn(newConn) - conn.dispatcherState.SetUp() -} - -// Reconnect is only used internally and should never be called from outside -// the package. -func (conn *PacketConn) Reconnect() (net.PacketConn, error) { - newConn, _, err := conn.reconnecter.Reconnect(context.Background()) - if err != nil { - return nil, err - } - return newConn, nil -} - -func (conn *PacketConn) Close() error { - conn.closeMtx.Lock() - defer conn.closeMtx.Unlock() - if conn.isClosing() { - panic("double close") - } - close(conn.closeCh) - conn.reconnecter.Stop() - // Once Stop() returns, it is guaranteed that snetConn is never recreated - // by the reconnecter. - err := conn.getConn().Close() - return err -} - -func (conn *PacketConn) isClosing() bool { - select { - case <-conn.closeCh: - return true - default: - return false - } -} - -func (conn *PacketConn) LocalAddr() net.Addr { - return conn.getConn().LocalAddr() -} - -func (conn *PacketConn) SetWriteDeadline(deadline time.Time) error { - conn.writeDeadlineMtx.Lock() - err := conn.getConn().SetWriteDeadline(deadline) - conn.writeDeadline = deadline - select { - case conn.deadlineChangedEvent <- struct{}{}: - default: - // The channel contains an event already, so we are guaranteed the - // channel reader sees the new deadline. - } - conn.writeDeadlineMtx.Unlock() - return err -} - -func (conn *PacketConn) SetReadDeadline(deadline time.Time) error { - conn.readDeadlineMtx.Lock() - err := conn.getConn().SetReadDeadline(deadline) - conn.readDeadline = deadline - select { - case conn.deadlineChangedEvent <- struct{}{}: - default: - // The channel contains an event already, so we are guaranteed the - // channel reader sees the new deadline. - } - conn.readDeadlineMtx.Unlock() - return err -} - -func (conn *PacketConn) SetDeadline(deadline time.Time) error { - return serrors.Join( - conn.SetWriteDeadline(deadline), - conn.SetReadDeadline(deadline), - ) -} - -func (conn *PacketConn) getDeadlineForOpType(op IOOperation) time.Time { - if op.IsWrite() { - return conn.getWriteDeadline() - } - return conn.getReadDeadline() -} - -func (conn *PacketConn) getWriteDeadline() time.Time { - conn.writeDeadlineMtx.Lock() - deadline := conn.writeDeadline - conn.writeDeadlineMtx.Unlock() - return deadline -} - -func (conn *PacketConn) getReadDeadline() time.Time { - conn.readDeadlineMtx.Lock() - deadline := conn.readDeadline - conn.readDeadlineMtx.Unlock() - return deadline -} - -func (conn *PacketConn) getConn() net.PacketConn { - conn.connMtx.Lock() - c := conn.dispConn - conn.connMtx.Unlock() - return c -} - -func (conn *PacketConn) setConn(newConn net.PacketConn) { - conn.connMtx.Lock() - conn.dispConn = newConn - conn.connMtx.Unlock() -} - -func returnOnDeadline(deadline time.Time) <-chan time.Time { - var deadlineChannel <-chan time.Time - if !deadline.IsZero() { - deadlineChannel = time.After(time.Until(deadline)) - } - return deadlineChannel -} diff --git a/pkg/sock/reliable/reconnect/conn_io_test.go b/pkg/sock/reliable/reconnect/conn_io_test.go deleted file mode 100644 index 3e618b13fd..0000000000 --- a/pkg/sock/reliable/reconnect/conn_io_test.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect_test - -import ( - "context" - "net" - "testing" - "time" - - "github.com/golang/mock/gomock" - . "github.com/smartystreets/goconvey/convey" - "github.com/stretchr/testify/assert" - - "github.com/scionproto/scion/pkg/private/mocks/net/mock_net" - "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect/mock_reconnect" -) - -func TestPacketConnIO(t *testing.T) { - Convey("Given an underlying connection, a reconnecter and an IO operation", t, func() { - ctrl := gomock.NewController(&xtest.PanickingReporter{T: t}) - defer ctrl.Finish() - mockConn := mock_net.NewMockPacketConn(ctrl) - mockReconnecter := mock_reconnect.NewMockReconnecter(ctrl) - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - mockIO := mock_reconnect.NewMockIOOperation(ctrl) - mockIO.EXPECT().IsWrite().Return(true).AnyTimes() - Convey("IO must reconnect after dispatcher error, and do op on new conn", func() { - connFromReconnect := mock_net.NewMockPacketConn(ctrl) - connFromReconnect.EXPECT().SetWriteDeadline(Any()).Return(nil).AnyTimes() - connFromReconnect.EXPECT().SetReadDeadline(Any()).Return(nil).AnyTimes() - gomock.InOrder( - mockIO.EXPECT().Do(mockConn).Return(dispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()).Return(connFromReconnect, uint16(0), nil), - mockIO.EXPECT().Do(connFromReconnect).Return(nil), - ) - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldBeNil) - }) - Convey("IO must return a nil error if successful", func() { - mockIO.EXPECT().Do(mockConn).Return(nil) - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldBeNil) - }) - Convey("IO must return non-dispatcher errors", func() { - mockIO.EXPECT().Do(mockConn).Return(writeNonDispatcherError) - err := packetConn.DoIO(mockIO) - assert.ErrorIs(t, err, writeNonDispatcherError) - }) - Convey("IO must return an error if reconnect got an error from the dispatcher", func() { - // If reconnection failed while the dispatcher was up (e.g., - // requested port is no longer available, registration message was - // malformed) the caller must be informed because reattempts will - // probably get the same error again. - gomock.InOrder( - mockIO.EXPECT().Do(mockConn).Return(writeDispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()). - Return(nil, uint16(0), connectErrorFromDispatcher), - ) - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldNotBeNil) - }) - Convey("IO returns dispatcher dead if write deadline reached when disconnected", func() { - mockConn.EXPECT().SetWriteDeadline(Any()).Return(nil).AnyTimes() - mockConn.EXPECT().SetReadDeadline(Any()).Return(nil).AnyTimes() - gomock.InOrder( - mockIO.EXPECT().Do(mockConn).Return(writeDispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()).DoAndReturn( - func(_ context.Context) (net.PacketConn, uint16, error) { - time.Sleep(tickerMultiplier(4)) - return mockConn, uint16(0), nil - }), - ) - packetConn.SetWriteDeadline(time.Now().Add(tickerMultiplier(2))) - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldEqual, reconnect.ErrDispatcherDead) - }) - Convey("SetWriteDeadline in the past unblocks a blocked writer", func() { - mockConn.EXPECT().SetWriteDeadline(Any()).Return(nil).AnyTimes() - mockConn.EXPECT().SetReadDeadline(Any()).Return(nil).AnyTimes() - gomock.InOrder( - mockIO.EXPECT().Do(mockConn).Return(writeDispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()).DoAndReturn( - func(_ context.Context) (net.PacketConn, uint16, error) { - time.Sleep(tickerMultiplier(6)) - return mockConn, uint16(0), nil - }), - ) - // Set a deadline that is sufficient to Reconnect. We later move - // the deadline in the past, thus cancelling the write prior to the - // Reconnect completing. - packetConn.SetWriteDeadline(time.Now().Add(tickerMultiplier(10))) - go func() { - // Give write time to block on the existing deadline - time.Sleep(tickerMultiplier(2)) - packetConn.SetWriteDeadline(time.Now().Add(tickerMultiplier(-1))) - }() - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldEqual, reconnect.ErrDispatcherDead) - }) - Convey("SetReadDeadline in the past unblocks a blocked reader", func() { - mockConn.EXPECT().SetWriteDeadline(Any()).Return(nil).AnyTimes() - mockConn.EXPECT().SetReadDeadline(Any()).Return(nil).AnyTimes() - mockIO := mock_reconnect.NewMockIOOperation(ctrl) - mockIO.EXPECT().IsWrite().Return(false).AnyTimes() - gomock.InOrder( - mockIO.EXPECT().Do(mockConn).Return(writeDispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()).DoAndReturn( - func(_ context.Context) (net.PacketConn, uint16, error) { - time.Sleep(tickerMultiplier(6)) - return mockConn, uint16(0), nil - }), - ) - // Set a deadline that is sufficient to Reconnect. We later move - // the deadline in the past, thus cancelling the write prior to the - // Reconnect completing. - packetConn.SetReadDeadline(time.Now().Add(tickerMultiplier(10))) - go func() { - // Give write time to block on the existing deadline - time.Sleep(tickerMultiplier(2)) - packetConn.SetReadDeadline(time.Now().Add(tickerMultiplier(-1))) - }() - err := packetConn.DoIO(mockIO) - SoMsg("err", err, ShouldEqual, reconnect.ErrDispatcherDead) - }) - Convey("After reconnect, IO deadline is inherited by the new connection", func() { - deadline := time.Now().Add(tickerMultiplier(1)) - connFromReconnect := mock_net.NewMockPacketConn(ctrl) - gomock.InOrder( - mockConn.EXPECT().SetWriteDeadline(deadline).Return(nil), - mockIO.EXPECT().Do(mockConn).Return(dispatcherError), - mockReconnecter.EXPECT().Reconnect(Any()).Return(connFromReconnect, uint16(0), nil), - connFromReconnect.EXPECT().SetReadDeadline(time.Time{}).Return(nil), - connFromReconnect.EXPECT().SetWriteDeadline(deadline).Return(nil), - mockIO.EXPECT().Do(connFromReconnect).Return(nil), - ) - packetConn.SetWriteDeadline(deadline) - packetConn.DoIO(mockIO) - }) - }) -} - -func TestPacketConnAddrs(t *testing.T) { - Convey("Given a packet conn running on an underlying connection with a reconnecter", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockConn := mock_net.NewMockPacketConn(ctrl) - mockReconnecter := mock_reconnect.NewMockReconnecter(ctrl) - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - Convey("Local address must call the same function on the underlying connection", func() { - mockConn.EXPECT().LocalAddr().Return(localAddr) - address := packetConn.LocalAddr() - SoMsg("address", address, ShouldEqual, localAddr) - }) - }) -} - -func TestPacketConnReadWrite(t *testing.T) { - Convey("Given a packet conn running on an underlying connection with a reconnecter", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockConn := mock_net.NewMockPacketConn(ctrl) - mockReconnecter := mock_reconnect.NewMockReconnecter(ctrl) - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - Convey("Writes on packet conn must call the same function on the underlying conn", func() { - buffer := []byte{1, 2, 3} - Convey("WriteTo", func() { - mockConn.EXPECT().WriteTo(buffer, remoteAddr).Return(len(buffer), nil) - n, err := packetConn.WriteTo(buffer, remoteAddr) - SoMsg("n", n, ShouldEqual, len(buffer)) - SoMsg("err", err, ShouldBeNil) - }) - }) - Convey("Reads on packet conn must call the same function on the underlying conn", func() { - buffer := make([]byte, 3) - readData := []byte{4, 5} - mockReadFunc := func(b []byte) (int, *snet.UDPAddr, error) { - copy(b, readData) - return len(readData), remoteAddr, nil - } - Convey("ReadFrom", func() { - mockConn.EXPECT().ReadFrom(buffer).DoAndReturn(mockReadFunc) - n, remoteAddress, err := packetConn.ReadFrom(buffer) - SoMsg("n", n, ShouldEqual, len(readData)) - SoMsg("address", remoteAddress, ShouldEqual, remoteAddr) - SoMsg("buffer", buffer[:n], ShouldResemble, readData) - SoMsg("err", err, ShouldBeNil) - }) - }) - }) -} - -func TestPacketConnConcurrentReadWrite(t *testing.T) { - Convey("Given a server blocked in reading, writes still go through", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockConn := mock_net.NewMockPacketConn(ctrl) - mockReconnecter := mock_reconnect.NewMockReconnecter(ctrl) - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - mockConn.EXPECT().ReadFrom(Any()).DoAndReturn( - func(_ []byte) (int, net.Addr, error) { - // Keep the read blocked "forever" - time.Sleep(tickerMultiplier(50)) - return 3, nil, nil - }, - ) - mockConn.EXPECT().WriteTo(Any(), Any()) - - barrierCh := make(chan struct{}) - go func() { - buffer := make([]byte, 3) - packetConn.ReadFrom(buffer) - }() - time.Sleep(tickerMultiplier(2)) - go func() { - packetConn.WriteTo(testBuffer, nil) - close(barrierCh) - }() - xtest.AssertReadReturnsBefore(t, barrierCh, tickerMultiplier(3)) - }) -} - -func TestPacketConnClose(t *testing.T) { - Convey("Given a packet conn running on an underlying connection with a reconnecter", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockConn := mock_net.NewMockPacketConn(ctrl) - mockReconnecter := mock_reconnect.NewMockReconnecter(ctrl) - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - Convey("Calling close on packet conn calls close on underlying conn", func() { - mockReconnecter.EXPECT().Stop().AnyTimes() - mockConn.EXPECT().Close() - packetConn := reconnect.NewPacketConn(mockConn, mockReconnecter) - packetConn.Close() - }) - Convey("Calling close while blocked in IO does not cause a reconnect attempt", func() { - mockReconnecter.EXPECT().Stop().AnyTimes() - mockIO := mock_reconnect.NewMockIOOperation(ctrl) - mockIO.EXPECT().IsWrite().Return(true).AnyTimes() - mockIO.EXPECT().Do(mockConn).DoAndReturn( - func(_ net.PacketConn) error { - time.Sleep(tickerMultiplier(2)) - return writeDispatcherError - }) - mockConn.EXPECT().Close() - go func() { - packetConn.DoIO(mockIO) - }() - time.Sleep(tickerMultiplier(1)) - packetConn.Close() - // Wait for mocked IO to finish (note that real IO would be - // unblocked immediately by the go runtime) - time.Sleep(tickerMultiplier(10)) - }) - Convey("Calling close while IO is blocked waiting for reconnect unblocks waiter", func() { - mockReconnecter.EXPECT().Stop().AnyTimes() - mockReconnecter.EXPECT(). - Reconnect(Any()). - DoAndReturn(func(_ context.Context) (net.PacketConn, uint16, error) { - select {} - }) - mockIO := mock_reconnect.NewMockIOOperation(ctrl) - mockIO.EXPECT().IsWrite().Return(true).AnyTimes() - mockIO.EXPECT().Do(mockConn).Return(writeDispatcherError) - mockConn.EXPECT().Close() - barrierCh := make(chan struct{}) - go func() { - packetConn.DoIO(mockIO) - close(barrierCh) - }() - time.Sleep(tickerMultiplier(1)) - packetConn.Close() - select { - case <-barrierCh: - case <-time.After(tickerMultiplier(20)): - t.Fatalf("goroutine took too long to finish") - } - }) - Convey("Calling close twice panics", func() { - mockReconnecter.EXPECT().Stop().AnyTimes() - mockConn.EXPECT().Close() - packetConn.Close() - SoMsg("close panic", func() { packetConn.Close() }, ShouldPanicWith, "double close") - }) - Convey("Calling close shuts down the reconnecting goroutine (if any)", func() { - mockReconnecter.EXPECT().Stop() - mockConn.EXPECT().Close() - packetConn.Close() - }) - }) -} diff --git a/pkg/sock/reliable/reconnect/doc.go b/pkg/sock/reliable/reconnect/doc.go deleted file mode 100644 index 9c49116e1b..0000000000 --- a/pkg/sock/reliable/reconnect/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package reconnect implements transparent logic for reconnecting to the -// dispatcher. -package reconnect diff --git a/pkg/sock/reliable/reconnect/errors.go b/pkg/sock/reliable/reconnect/errors.go deleted file mode 100644 index a43a47f8d1..0000000000 --- a/pkg/sock/reliable/reconnect/errors.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import "github.com/scionproto/scion/pkg/private/serrors" - -var ( - ErrDispatcherDead = serrors.New("dispatcher dead") - // FIXME(scrye): Change this s.t. it's serrors.IsTimeout compatible. - ErrReconnecterTimeoutExpired = serrors.New("timeout expired") - ErrReconnecterStopped = serrors.New("stop method was called") - ErrClosed = serrors.New("closed") -) diff --git a/pkg/sock/reliable/reconnect/internal/metrics/BUILD.bazel b/pkg/sock/reliable/reconnect/internal/metrics/BUILD.bazel deleted file mode 100644 index 4de0d23ca7..0000000000 --- a/pkg/sock/reliable/reconnect/internal/metrics/BUILD.bazel +++ /dev/null @@ -1,12 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["metrics.go"], - importpath = "github.com/scionproto/scion/pkg/sock/reliable/reconnect/internal/metrics", - visibility = ["//pkg/sock/reliable/reconnect:__subpackages__"], - deps = [ - "//pkg/private/prom:go_default_library", - "@com_github_prometheus_client_golang//prometheus:go_default_library", - ], -) diff --git a/pkg/sock/reliable/reconnect/internal/metrics/metrics.go b/pkg/sock/reliable/reconnect/internal/metrics/metrics.go deleted file mode 100644 index 542fdef45e..0000000000 --- a/pkg/sock/reliable/reconnect/internal/metrics/metrics.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "github.com/prometheus/client_golang/prometheus" - - "github.com/scionproto/scion/pkg/private/prom" -) - -// Namespace is the metrics namespace for the metrics in this package. -const Namespace = "lib" - -const sub = "reconnect" - -var ( - // M exposes all the initialized metrics for this package. - M = newMetrics() -) - -type metrics struct { - timeouts prometheus.Counter - retries prometheus.Counter -} - -func newMetrics() metrics { - return metrics{ - timeouts: prom.NewCounter(Namespace, sub, "timeouts_total", - "Total number of reconnection attempt timeouts"), - retries: prom.NewCounter(Namespace, sub, "retries_total", - "Total number of reconnection attempt retries"), - } -} - -// Timeouts returns a counter for timeout errors. -func (m metrics) Timeouts() prometheus.Counter { - return m.timeouts -} - -// Retries returns a counter for individual reconnection attempts. -func (m metrics) Retries() prometheus.Counter { - return m.retries -} diff --git a/pkg/sock/reliable/reconnect/io.go b/pkg/sock/reliable/reconnect/io.go deleted file mode 100644 index c3a828d58b..0000000000 --- a/pkg/sock/reliable/reconnect/io.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import "net" - -// IOOperation provides an abstraction around any Conn reads and writes. Types -// that implement this interface contain the Read/Write arguments and return -// values as fields, thus allowing the reconnection loop to run any I/O -// function without caring what it is. -type IOOperation interface { - // Runs the I/O operation on conn - Do(conn net.PacketConn) error - // IsWrite returns true for types implementing write operations - IsWrite() bool -} - -type BaseOperation struct { - buffer []byte //nolint:golint,structcheck - numBytes int //nolint:golint,structcheck -} - -type WriteOperation struct { - BaseOperation -} - -func (_ *WriteOperation) IsWrite() bool { - return true -} - -type WriteToOperation struct { - WriteOperation - address net.Addr -} - -func (op *WriteToOperation) Do(conn net.PacketConn) error { - n, err := conn.WriteTo(op.buffer, op.address) - op.numBytes = n - return err -} - -type ReadOperation struct { - BaseOperation -} - -func (_ *ReadOperation) IsWrite() bool { - return false -} - -type ReadFromOperation struct { - ReadOperation - address net.Addr -} - -func (op *ReadFromOperation) Do(conn net.PacketConn) error { - n, address, err := conn.ReadFrom(op.buffer) - op.numBytes = n - op.address = address - return err -} diff --git a/pkg/sock/reliable/reconnect/main_test.go b/pkg/sock/reliable/reconnect/main_test.go deleted file mode 100644 index c0362bf9f2..0000000000 --- a/pkg/sock/reliable/reconnect/main_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect_test - -import ( - "context" - "fmt" - "net" - "os" - "syscall" - "testing" - "time" - - "github.com/golang/mock/gomock" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" -) - -var ( - Any = gomock.Any -) - -var ( - localNoPortAddr = MustParseSnet("1-ff00:0:1,[192.168.0.1]:0") - localAddr = MustParseSnet("1-ff00:0:1,[192.168.0.1]:80") - remoteAddr = MustParseSnet("2-ff00:0:2,[172.16.0.1]:80") - svc = addr.SvcNone - testBuffer = []byte{1, 2, 3} -) - -var ( - dispatcherError = &net.OpError{Err: os.NewSyscallError("write", syscall.ECONNRESET)} - writeDispatcherError = &net.OpError{Err: os.NewSyscallError("write", syscall.EPIPE)} - writeNonDispatcherError = serrors.New("Misc error") - connectErrorFromDispatcher = serrors.New("Port unavailable") -) - -func MustParseSnet(str string) *snet.UDPAddr { - var a snet.UDPAddr - if err := a.Set(str); err != nil { - panic(fmt.Sprintf("bad snet string %v, err=%v", str, err)) - } - return &a -} - -// tickerMultiplier computes durations relative to the default reconnect -// ticking interval. This is needed for some timing tests that need sleep -// values to stay fairly close to the ticking interval. -func tickerMultiplier(multiplier time.Duration) time.Duration { - return multiplier * reconnect.DefaultTickerInterval -} - -func ctxMultiplier(multiplier time.Duration) context.Context { - ctx, cancelF := context.WithTimeout(context.Background(), tickerMultiplier(multiplier)) - _ = cancelF - return ctx -} - -func TestMain(m *testing.M) { - // Inject a smaller timeout s.t. tests run quickly - reconnect.DefaultTickerInterval = 10 * time.Millisecond - log.Discard() - os.Exit(m.Run()) -} diff --git a/pkg/sock/reliable/reconnect/mock_reconnect/BUILD.bazel b/pkg/sock/reliable/reconnect/mock_reconnect/BUILD.bazel deleted file mode 100644 index 76ef55fecc..0000000000 --- a/pkg/sock/reliable/reconnect/mock_reconnect/BUILD.bazel +++ /dev/null @@ -1,21 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") -load("@io_bazel_rules_go//go:def.bzl", "gomock") - -gomock( - name = "go_default_mock", - out = "mock.go", - interfaces = [ - "IOOperation", - "Reconnecter", - ], - library = "//pkg/sock/reliable/reconnect:go_default_library", - package = "mock_reconnect", -) - -go_library( - name = "go_default_library", - srcs = ["mock.go"], - importpath = "github.com/scionproto/scion/pkg/sock/reliable/reconnect/mock_reconnect", - visibility = ["//visibility:public"], - deps = ["@com_github_golang_mock//gomock:go_default_library"], -) diff --git a/pkg/sock/reliable/reconnect/mock_reconnect/mock.go b/pkg/sock/reliable/reconnect/mock_reconnect/mock.go deleted file mode 100644 index 61dff8f55b..0000000000 --- a/pkg/sock/reliable/reconnect/mock_reconnect/mock.go +++ /dev/null @@ -1,115 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/scionproto/scion/pkg/sock/reliable/reconnect (interfaces: IOOperation,Reconnecter) - -// Package mock_reconnect is a generated GoMock package. -package mock_reconnect - -import ( - context "context" - net "net" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" -) - -// MockIOOperation is a mock of IOOperation interface. -type MockIOOperation struct { - ctrl *gomock.Controller - recorder *MockIOOperationMockRecorder -} - -// MockIOOperationMockRecorder is the mock recorder for MockIOOperation. -type MockIOOperationMockRecorder struct { - mock *MockIOOperation -} - -// NewMockIOOperation creates a new mock instance. -func NewMockIOOperation(ctrl *gomock.Controller) *MockIOOperation { - mock := &MockIOOperation{ctrl: ctrl} - mock.recorder = &MockIOOperationMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockIOOperation) EXPECT() *MockIOOperationMockRecorder { - return m.recorder -} - -// Do mocks base method. -func (m *MockIOOperation) Do(arg0 net.PacketConn) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Do", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// Do indicates an expected call of Do. -func (mr *MockIOOperationMockRecorder) Do(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockIOOperation)(nil).Do), arg0) -} - -// IsWrite mocks base method. -func (m *MockIOOperation) IsWrite() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsWrite") - ret0, _ := ret[0].(bool) - return ret0 -} - -// IsWrite indicates an expected call of IsWrite. -func (mr *MockIOOperationMockRecorder) IsWrite() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsWrite", reflect.TypeOf((*MockIOOperation)(nil).IsWrite)) -} - -// MockReconnecter is a mock of Reconnecter interface. -type MockReconnecter struct { - ctrl *gomock.Controller - recorder *MockReconnecterMockRecorder -} - -// MockReconnecterMockRecorder is the mock recorder for MockReconnecter. -type MockReconnecterMockRecorder struct { - mock *MockReconnecter -} - -// NewMockReconnecter creates a new mock instance. -func NewMockReconnecter(ctrl *gomock.Controller) *MockReconnecter { - mock := &MockReconnecter{ctrl: ctrl} - mock.recorder = &MockReconnecterMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockReconnecter) EXPECT() *MockReconnecterMockRecorder { - return m.recorder -} - -// Reconnect mocks base method. -func (m *MockReconnecter) Reconnect(arg0 context.Context) (net.PacketConn, uint16, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Reconnect", arg0) - ret0, _ := ret[0].(net.PacketConn) - ret1, _ := ret[1].(uint16) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Reconnect indicates an expected call of Reconnect. -func (mr *MockReconnecterMockRecorder) Reconnect(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reconnect", reflect.TypeOf((*MockReconnecter)(nil).Reconnect), arg0) -} - -// Stop mocks base method. -func (m *MockReconnecter) Stop() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "Stop") -} - -// Stop indicates an expected call of Stop. -func (mr *MockReconnecterMockRecorder) Stop() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockReconnecter)(nil).Stop)) -} diff --git a/pkg/sock/reliable/reconnect/network.go b/pkg/sock/reliable/reconnect/network.go deleted file mode 100644 index 84358f2837..0000000000 --- a/pkg/sock/reliable/reconnect/network.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import ( - "context" - "errors" - "net" - "time" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/sock/reliable" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect/internal/metrics" -) - -// DispatcherService is a dispatcher wrapper that creates conns -// with transparent reconnection capabilities. Connections created by -// DispatcherService also validate that dispatcher registrations do -// not change addresses. -// -// Callers interested in providing their own reconnection callbacks and -// validating the new connection themselves should use the connection -// constructors directly. -type DispatcherService struct { - dispatcher reliable.Dispatcher -} - -// NewDispatcherService adds transparent reconnection capabilities -// to dispatcher connections. -func NewDispatcherService(dispatcher reliable.Dispatcher) *DispatcherService { - return &DispatcherService{dispatcher: dispatcher} -} - -func (pn *DispatcherService) Register(ctx context.Context, ia addr.IA, public *net.UDPAddr, - svc addr.SVC) (net.PacketConn, uint16, error) { - - // Perform initial connection to allocate port. We use a reconnecter here - // to set up the initial connection using the same retry logic we use when - // losing the connection to the dispatcher. - reconnecter := pn.newReconnecterFromListenArgs(ctx, ia, public, svc) - conn, port, err := reconnecter.Reconnect(ctx) - if err != nil { - return nil, 0, err - } - - updatePort := func(a *net.UDPAddr, port int) *net.UDPAddr { - if a == nil { - return nil - } - return &net.UDPAddr{ - IP: append(a.IP[:0:0], a.IP...), - Port: port, - } - } - newPublic := updatePort(public, int(port)) - reconnecter = pn.newReconnecterFromListenArgs(ctx, ia, newPublic, svc) - return NewPacketConn(conn, reconnecter), port, nil -} - -func (pn *DispatcherService) newReconnecterFromListenArgs(ctx context.Context, ia addr.IA, - public *net.UDPAddr, svc addr.SVC) *TickingReconnecter { - - // f represents individual connection attempts - f := func(timeout time.Duration) (net.PacketConn, uint16, error) { - metrics.M.Retries().Inc() - ctx := context.Background() - if timeout != 0 { - var cancelF context.CancelFunc - ctx, cancelF = context.WithTimeout(ctx, timeout) - defer cancelF() - } - conn, port, err := pn.dispatcher.Register(ctx, ia, public, svc) - if errors.Is(err, ErrReconnecterTimeoutExpired) { - metrics.M.Timeouts().Inc() - } - return conn, port, err - } - return NewTickingReconnecter(f) -} diff --git a/pkg/sock/reliable/reconnect/network_test.go b/pkg/sock/reliable/reconnect/network_test.go deleted file mode 100644 index 9c34cd4200..0000000000 --- a/pkg/sock/reliable/reconnect/network_test.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect_test - -import ( - "context" - "net" - "os" - "syscall" - "testing" - - "github.com/golang/mock/gomock" - . "github.com/smartystreets/goconvey/convey" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/mocks/net/mock_net" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable/mock_reliable" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" -) - -func TestReconnect(t *testing.T) { - Convey("Reconnections must conserve local address", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockDispatcher := mock_reliable.NewMockDispatcher(ctrl) - Convey("Given a mocked underlying connection with local", func() { - mockConn := mock_net.NewMockPacketConn(ctrl) - Convey("Allocated ports are reused on subsequent attempts", func() { - mockDispatcher.EXPECT(). - Register(context.Background(), localAddr.IA, localNoPortAddr.Host, svc). - Return(mockConn, uint16(80), nil) - - want := &net.UDPAddr{ - IP: append(localNoPortAddr.Host.IP[:0:0], localNoPortAddr.Host.IP...), - Port: 80, - } - - mockDispatcher.EXPECT(). - Register(context.Background(), localAddr.IA, want, svc). - Return(mockConn, uint16(80), nil) - - network := reconnect.NewDispatcherService(mockDispatcher) - packetConn, _, _ := network.Register(context.Background(), localAddr.IA, - localNoPortAddr.Host, svc) - packetConn.(*reconnect.PacketConn).Reconnect() - }) - }) - }) -} - -func TestNetworkFatalError(t *testing.T) { - Convey("Given a network running over an underlying mocked network", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - err := serrors.New("Not dispatcher dead error, e.g., malformed register msg") - mockNetwork := mock_reliable.NewMockDispatcher(ctrl) - network := reconnect.NewDispatcherService(mockNetwork) - Convey("The network returns non-dispatcher dial errors from the mock", func() { - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), err) - _, _, err := network.Register(context.Background(), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldNotBeNil) - }) - Convey("The network returns non-dispatcher listen errors from the mock", func() { - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), err) - _, _, err := network.Register(context.Background(), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldNotBeNil) - }) - }) -} - -func TestNetworkDispatcherDeadError(t *testing.T) { - dispatcherError := &net.OpError{Err: os.NewSyscallError("connect", syscall.ECONNREFUSED)} - Convey("Listen and Dial should reattempt to connect on dispatcher down errors", t, func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockNetwork := mock_reliable.NewMockDispatcher(ctrl) - network := reconnect.NewDispatcherService(mockNetwork) - Convey("Dial tries to reconnect if no timeout set", func() { - mockConn := mock_net.NewMockPacketConn(ctrl) - gomock.InOrder( - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), dispatcherError). - Times(2), - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(mockConn, uint16(0), nil), - ) - _, _, err := network.Register(context.Background(), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldBeNil) - }) - Convey("Dial only retries for limited time if timeout set", func() { - gomock.InOrder( - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), dispatcherError). - MinTimes(2).MaxTimes(5), - ) - _, _, err := network.Register(ctxMultiplier(4), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldNotBeNil) - }) - Convey("Listen tries to reconnect if no timeout set", func() { - mockConn := mock_net.NewMockPacketConn(ctrl) - gomock.InOrder( - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), dispatcherError). - Times(2), - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(mockConn, uint16(0), nil), - ) - _, _, err := network.Register(context.Background(), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldBeNil) - }) - Convey("Listen only retries for limited time if timeout set", func() { - gomock.InOrder( - mockNetwork.EXPECT(). - Register(Any(), Any(), Any(), Any()). - Return(nil, uint16(0), dispatcherError). - MinTimes(3).MaxTimes(5), - ) - _, _, err := network.Register(ctxMultiplier(4), 0, nil, addr.SvcNone) - SoMsg("err", err, ShouldNotBeNil) - }) - }) -} diff --git a/pkg/sock/reliable/reconnect/reconnecter.go b/pkg/sock/reliable/reconnect/reconnecter.go deleted file mode 100644 index d5d36cec69..0000000000 --- a/pkg/sock/reliable/reconnect/reconnecter.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import ( - "context" - "errors" - "net" - "os" - "sync" - "time" - - "github.com/scionproto/scion/pkg/log" -) - -// Use a var here to allow tests to inject shorter intervals for fast testing. -var ( - DefaultTickerInterval = time.Second -) - -type Reconnecter interface { - Reconnect(ctx context.Context) (net.PacketConn, uint16, error) - Stop() -} - -var _ Reconnecter = (*TickingReconnecter)(nil) - -type TickingReconnecter struct { - mtx sync.Mutex - // XXX(scrye): reconnectF does not support cancellation because adding - // context-aware dials in reliable socket is tricky. This can make stopping - // the reconnecter take significant time, depending on the timeout of the - // reconnection function. - reconnectF func(timeout time.Duration) (net.PacketConn, uint16, error) - stopping *AtomicBool -} - -// NewTickingReconnecter creates a new dispatcher reconnecter. Calling -// Reconnect in turn calls f periodically to obtain a new connection to the -// dispatcher, -func NewTickingReconnecter( - f func(timeout time.Duration) (net.PacketConn, uint16, error)) *TickingReconnecter { - - return &TickingReconnecter{ - reconnectF: f, - stopping: &AtomicBool{}, - } -} - -// Reconnect repeatedly attempts to reestablish a connection to the dispatcher, -// subject to timeout. Attempts that receive dispatcher connection errors are -// followed by reattempts. Critical errors (e.g., port mismatches) return -// immediately. -func (r *TickingReconnecter) Reconnect(ctx context.Context) (net.PacketConn, uint16, error) { - r.mtx.Lock() - defer r.mtx.Unlock() - start := time.Now() - t := time.NewTicker(DefaultTickerInterval) - defer t.Stop() - - var timeout time.Duration - if deadline, ok := ctx.Deadline(); ok { - timeout = time.Until(deadline) - } - - timeoutExpired := afterTimeout(timeout) - for r.stopping.IsFalse() { - newTimeout, ok := getNewTimeout(timeout, start) - if !ok { - return nil, 0, ErrReconnecterTimeoutExpired - } - conn, port, err := r.reconnectF(newTimeout) - var sysErr *os.SyscallError - switch { - case errors.As(err, &sysErr): - // Wait until next tick to retry. If the overall timeout expires - // before the next tick, return immediately with an error. - // time.Ticker will ensure that no more than one attempt is made - // per interval (even if the reconnection function takes longer - // than the interval). - log.Debug("Registering with dispatcher failed, retrying...") - select { - case <-t.C: - case <-timeoutExpired: - return nil, 0, ErrReconnecterTimeoutExpired - } - continue - case err != nil: - return nil, 0, err - default: - return conn, port, nil - } - } - return nil, 0, ErrReconnecterStopped -} - -// Stop shuts down the reconnection attempt (if any), and waits for the -// reconnecting goroutine to finish. -// -// It is safe to call Stop while Reconnect is running. -func (r *TickingReconnecter) Stop() { - r.stopping.Set(true) - // Grab lock to make sure the reconnection function finished - r.mtx.Lock() - r.mtx.Unlock() -} - -func getNewTimeout(timeout time.Duration, start time.Time) (time.Duration, bool) { - if timeout == 0 { - return 0, true - } - newTimeout := timeout - time.Since(start) - if newTimeout > 0 { - return newTimeout, true - } - return 0, false -} - -// afterTimeout waits for the timeout to elapse and then sends the current -// time on the returned channel. If the timeout is 0, the current time is never -// sent. -func afterTimeout(timeout time.Duration) <-chan time.Time { - var timeoutExpired <-chan time.Time - if timeout != 0 { - timeoutExpired = time.After(timeout) - } - return timeoutExpired -} diff --git a/pkg/sock/reliable/reconnect/reconnecter_test.go b/pkg/sock/reliable/reconnect/reconnecter_test.go deleted file mode 100644 index e196f69d79..0000000000 --- a/pkg/sock/reliable/reconnect/reconnecter_test.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect_test - -import ( - "context" - "net" - "testing" - "time" - - . "github.com/smartystreets/goconvey/convey" - - "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" -) - -// newErrorReconnF returns a dispatcher error after the duration elapses. -func newErrorReconnF(sleep time.Duration) func(time.Duration) (net.PacketConn, uint16, error) { - return func(_ time.Duration) (net.PacketConn, uint16, error) { - time.Sleep(sleep) - // return dispatcher error s.t. reconnecter reattempts - return nil, 0, dispatcherError - } -} - -func TestTickingReconnectorStop(t *testing.T) { - Convey("Calling stop terminates a reconnect running in the background", t, func() { - reconnecter := reconnect.NewTickingReconnecter(newErrorReconnF(tickerMultiplier(1))) - barrierCh := make(chan struct{}) - Convey("Stop returns immediately if a reconnect is not running", func() { - go func() { - stopAfter(reconnecter, 0) - close(barrierCh) - }() - xtest.AssertReadReturnsBefore(t, barrierCh, tickerMultiplier(2)) - }) - Convey("Stop causes reconnect to return right after the current attempt finishes", func() { - // Note that because it is not possible right now to interrupt the - // listen/dial step of a reconnection, the soonest we can return - // after a Stop() is after the next Listen/Dial returns - go func() { - reconnectWithoutTimeoutAfter(reconnecter, 0) - close(barrierCh) - }() - go stopAfter(reconnecter, tickerMultiplier(1)) - xtest.AssertReadReturnsBefore(t, barrierCh, tickerMultiplier(4)) - }) - Convey("Error must be non-nil when timing out due to stop", func() { - var err error - go func() { - err = reconnectWithoutTimeoutAfter(reconnecter, tickerMultiplier(1)) - close(barrierCh) - }() - go reconnecter.Stop() - xtest.AssertReadReturnsBefore(t, barrierCh, tickerMultiplier(4)) - SoMsg("err", err, ShouldEqual, reconnect.ErrReconnecterStopped) - }) - }) - Convey("Given a reconnection function that takes a long time", t, func() { - reconnecter := reconnect.NewTickingReconnecter(newErrorReconnF(tickerMultiplier(4))) - barrierCh := make(chan struct{}) - Convey("Stop waits for a running reconnection attempt to finish before returning", func() { - go func() { - reconnectWithoutTimeoutAfter(reconnecter, 0) - }() - go func() { - stopAfter(reconnecter, tickerMultiplier(1)) - close(barrierCh) - }() - xtest.AssertReadReturnsBetween(t, barrierCh, tickerMultiplier(3), tickerMultiplier(8)) - }) - }) -} - -func reconnectWithoutTimeoutAfter(reconnecter *reconnect.TickingReconnecter, - sleepAtStart time.Duration) error { - - time.Sleep(sleepAtStart) - _, _, err := reconnecter.Reconnect(context.Background()) - return err -} - -func stopAfter(reconnecter *reconnect.TickingReconnecter, sleepAtStart time.Duration) { - time.Sleep(sleepAtStart) - reconnecter.Stop() -} diff --git a/pkg/sock/reliable/reconnect/util.go b/pkg/sock/reliable/reconnect/util.go deleted file mode 100644 index 5ab8eefae9..0000000000 --- a/pkg/sock/reliable/reconnect/util.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect - -import ( - "sync" -) - -type AtomicBool struct { - m sync.Mutex - v bool -} - -func (f *AtomicBool) Set(v bool) { - f.m.Lock() - f.v = v - f.m.Unlock() -} - -func (f *AtomicBool) IsTrue() bool { - f.m.Lock() - result := f.v - f.m.Unlock() - return result -} - -func (f *AtomicBool) IsFalse() bool { - return !f.IsTrue() -} - -// A State objects encodes an up or down state in a way that can be used -// directly in selects. Note that not all methods are safe for concurrent use -// (see their documentation for more information). -type State struct { - ch chan struct{} -} - -// NewState returns a new state. The state is initially set to up. -func NewState() *State { - s := &State{ch: make(chan struct{})} - s.SetUp() - return s -} - -// SetDown sets the state to down. -// -// It is not safe to call SetDown concurrently with other methods. -func (s *State) SetDown() { - s.ch = make(chan struct{}) -} - -// SetUp sets the state to up. -// -// It is safe to call SetUp concurrently with Up. -func (s *State) SetUp() { - close(s.ch) -} - -// Up yields a channel that will be closed once SetUp() is called. -// -// It is safe to call SetUp concurrently with Up. -func (s *State) Up() <-chan struct{} { - return s.ch -} diff --git a/pkg/sock/reliable/reconnect/util_test.go b/pkg/sock/reliable/reconnect/util_test.go deleted file mode 100644 index e9341817ae..0000000000 --- a/pkg/sock/reliable/reconnect/util_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reconnect_test - -import ( - "testing" - - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" -) - -// TestState tests that State check returns immediately after creating a new object. -func TestState(t *testing.T) { - s := reconnect.NewState() - select { - case <-s.Up(): - default: - t.Fatalf("Expected method to return immediately, but it didn't") - } -} diff --git a/pkg/sock/reliable/registration.go b/pkg/sock/reliable/registration.go deleted file mode 100644 index e4e7c87c99..0000000000 --- a/pkg/sock/reliable/registration.go +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "encoding/binary" - "net" - - "github.com/scionproto/scion/pkg/addr" -) - -type CommandBitField uint8 - -const ( - CmdBindAddress CommandBitField = 0x04 - CmdEnableSCMP CommandBitField = 0x02 - CmdAlwaysOn CommandBitField = 0x01 -) - -// Registration contains metadata for a SCION Dispatcher registration message. -type Registration struct { - IA addr.IA - PublicAddress *net.UDPAddr - BindAddress *net.UDPAddr - SVCAddress addr.SVC -} - -func (r *Registration) SerializeTo(b []byte) (int, error) { - if r.PublicAddress == nil || r.PublicAddress.IP == nil { - return 0, ErrNoAddress - } - - var msg registrationMessage - msg.Command = CmdAlwaysOn | CmdEnableSCMP - msg.L4Proto = 17 - msg.IA = uint64(r.IA) - msg.PublicData.SetFromUDPAddr(r.PublicAddress) - if r.BindAddress != nil { - msg.Command |= CmdBindAddress - var bindAddress registrationAddressField - msg.BindData = &bindAddress - bindAddress.SetFromUDPAddr(r.BindAddress) - } - if r.SVCAddress != addr.SvcNone { - buffer := make([]byte, 2) - binary.BigEndian.PutUint16(buffer, uint16(r.SVCAddress)) - msg.SVC = buffer - } - return msg.SerializeTo(b) -} - -func (r *Registration) DecodeFromBytes(b []byte) error { - var msg registrationMessage - err := msg.DecodeFromBytes(b) - if err != nil { - return err - } - - r.IA = addr.IA(msg.IA) - r.PublicAddress = &net.UDPAddr{ - IP: net.IP(msg.PublicData.Address), - Port: int(msg.PublicData.Port), - } - - if len(msg.SVC) == 0 { - r.SVCAddress = addr.SvcNone - } else { - r.SVCAddress = addr.SVC(binary.BigEndian.Uint16(msg.SVC)) - } - if (msg.Command & CmdBindAddress) != 0 { - r.BindAddress = &net.UDPAddr{ - IP: net.IP(msg.BindData.Address), - Port: int(msg.BindData.Port), - } - } - return nil -} - -// registrationMessage is the wire format for a SCION Dispatcher registration -// message. -type registrationMessage struct { - Command CommandBitField - L4Proto uint8 - IA uint64 - PublicData registrationAddressField - BindData *registrationAddressField - SVC []byte -} - -func (m *registrationMessage) SerializeTo(b []byte) (int, error) { - if len(b) < 13 { - return 0, ErrBufferTooSmall - } - b[0] = byte(m.Command) - b[1] = m.L4Proto - binary.BigEndian.PutUint64(b[2:], m.IA) - offset := 10 - if _, err := m.PublicData.SerializeTo(b[offset:]); err != nil { - return 0, err - } - offset += m.PublicData.length() - if m.BindData != nil { - if _, err := m.BindData.SerializeTo(b[offset:]); err != nil { - return 0, err - } - offset += m.BindData.length() - } - copy(b[offset:], m.SVC) - offset += len(m.SVC) - return offset, nil -} - -func (l *registrationMessage) DecodeFromBytes(b []byte) error { - if len(b) < 13 { - return ErrIncompleteMessage - } - l.Command = CommandBitField(b[0]) - l.L4Proto = b[1] - l.IA = binary.BigEndian.Uint64(b[2:]) - offset := 10 - if err := l.PublicData.DecodeFromBytes(b[offset:]); err != nil { - return err - } - offset += l.PublicData.length() - if (l.Command & CmdBindAddress) != 0 { - l.BindData = ®istrationAddressField{} - if err := l.BindData.DecodeFromBytes(b[offset:]); err != nil { - return err - } - offset += l.BindData.length() - } - switch len(b[offset:]) { - case 0: - return nil - case 2: - l.SVC = b[offset:] - return nil - default: - return ErrPayloadTooLong - } -} - -type registrationAddressField struct { - Port uint16 - AddressType byte - Address []byte -} - -func (l *registrationAddressField) SerializeTo(b []byte) (int, error) { - if len(b) < l.length() { - return 0, ErrBufferTooSmall - } - binary.BigEndian.PutUint16(b, l.Port) - b[2] = l.AddressType - copy(b[3:], l.Address) - return l.length(), nil -} - -func (l *registrationAddressField) DecodeFromBytes(b []byte) error { - if len(b) < 3 { - return ErrIncompleteMessage - } - l.Port = binary.BigEndian.Uint16(b[:2]) - l.AddressType = b[2] - if !isValidReliableSockDestination(hostAddrType(l.AddressType)) { - return ErrBadAddressType - } - addressLength := getAddressLength(hostAddrType(l.AddressType)) - if len(b[3:]) < addressLength { - return ErrIncompleteAddress - } - l.Address = b[3 : 3+addressLength] - return nil -} - -func (l *registrationAddressField) SetFromUDPAddr(u *net.UDPAddr) { - l.Port = uint16(u.Port) - l.AddressType = byte(getIPAddressType(u.IP)) - l.Address = normalizeIP(u.IP) -} - -func (l *registrationAddressField) length() int { - if l == nil { - return 0 - } - return 2 + 1 + len(l.Address) -} - -type Confirmation struct { - Port uint16 -} - -func (c *Confirmation) SerializeTo(b []byte) (int, error) { - if len(b) < 2 { - return 0, ErrBufferTooSmall - } - binary.BigEndian.PutUint16(b, c.Port) - return 2, nil -} - -func (c *Confirmation) DecodeFromBytes(b []byte) error { - if len(b) < 2 { - return ErrIncompletePort - } - c.Port = binary.BigEndian.Uint16(b) - return nil -} diff --git a/pkg/sock/reliable/registration_test.go b/pkg/sock/reliable/registration_test.go deleted file mode 100644 index 0cf96e4164..0000000000 --- a/pkg/sock/reliable/registration_test.go +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "net" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/private/xtest" -) - -func TestRegistrationMessageSerializeTo(t *testing.T) { - type TestCase struct { - Name string - Registration *Registration - ExpectedError error - ExpectedData []byte - } - testCases := []TestCase{ - { - Name: "nil public address", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - SVCAddress: addr.SvcNone, - }, - ExpectedData: []byte{}, - ExpectedError: ErrNoAddress, - }, - { - Name: "nil public address IP", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{Port: 80}, - SVCAddress: addr.SvcNone, - }, - ExpectedData: []byte{}, - ExpectedError: ErrNoAddress, - }, - { - Name: "public IPv4 address only", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - SVCAddress: addr.SvcNone, - }, - ExpectedData: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, 0, 80, 1, - 10, 2, 3, 4}, - }, - { - Name: "public IPv6 address only", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80}, - SVCAddress: addr.SvcNone, - }, - ExpectedData: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 2, 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, - }, - { - Name: "public address with bind", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - BindAddress: &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: 81}, - SVCAddress: addr.SvcNone, - }, - ExpectedData: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, 0, 81, 1, 10, 5, 6, 7}, - }, - { - Name: "public IPv4 address with SVC", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - SVCAddress: addr.SvcCS, - }, - ExpectedData: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, 0, - 80, 1, 10, 2, 3, 4, 0x00, 0x02}, - }, - { - Name: "public address with bind and SVC", - Registration: &Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - BindAddress: &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: 81}, - SVCAddress: addr.SvcCS, - }, - ExpectedData: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 5, 6, 7, 0, 2}, - }, - } - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - b := make([]byte, 1500) - n, err := tc.Registration.SerializeTo(b) - assert.ErrorIs(t, err, tc.ExpectedError) - assert.Equal(t, tc.ExpectedData, b[:n]) - }) - } -} - -func TestRegistrationMessageDecodeFromBytes(t *testing.T) { - type TestCase struct { - Name string - Data []byte - ExpectedError error - ExpectedRegistration Registration - } - testCases := []TestCase{ - { - Name: "incomplete message", - Data: []byte{0x03, 17, 0, 1}, - ExpectedError: ErrIncompleteMessage, - }, - { - Name: "incomplete address", - Data: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 0, 0}, - ExpectedError: ErrIncompleteAddress, - }, - { - Name: "bad address type", - Data: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 9, 10, 2, 3, 4}, - ExpectedError: ErrBadAddressType, - }, - { - Name: "public IPv4 address only", - Data: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4}, - ExpectedRegistration: Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - SVCAddress: addr.SvcNone, - }, - }, - { - Name: "public IPv6 address only", - Data: []byte{0x03, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 2, 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, - ExpectedRegistration: Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80}, - SVCAddress: addr.SvcNone, - }, - }, - { - Name: "public address with bind", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 5, 6, 7}, - ExpectedRegistration: Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - BindAddress: &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: 81}, - SVCAddress: addr.SvcNone, - }, - }, - { - Name: "incomplete bind starting information", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81}, - ExpectedError: ErrIncompleteMessage, - }, - { - Name: "incomplete bind address", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 0, 0}, - ExpectedError: ErrIncompleteAddress, - }, - { - Name: "bad bind address type", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 0, 0, 1, - 0, 81, 9, 10, 0, 0, 2}, - ExpectedError: ErrBadAddressType, - }, - { - Name: "public IPv6 address with bind", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 2, 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 0, 81, 2, 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, - }, - ExpectedRegistration: Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.ParseIP("2001:db8::1"), Port: 80}, - BindAddress: &net.UDPAddr{IP: net.ParseIP("2001:db8::2"), Port: 81}, - SVCAddress: addr.SvcNone, - }, - }, - { - Name: "excess of 1 byte is error", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 5, 6, 7, - 42}, - ExpectedError: ErrPayloadTooLong, - }, - { - Name: "excess of 3 bytes (or more) is error", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 5, 6, 7, - 42, 42, 42}, - ExpectedError: ErrPayloadTooLong, - }, - { - Name: "excess of 2 bytes is SVC address", - Data: []byte{0x07, 17, 0, 1, 0xff, 0, 0, 0, 0, 0x01, - 0, 80, 1, 10, 2, 3, 4, - 0, 81, 1, 10, 5, 6, 7, - 0x00, 0x02}, - ExpectedRegistration: Registration{ - IA: xtest.MustParseIA("1-ff00:0:1"), - PublicAddress: &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 80}, - BindAddress: &net.UDPAddr{IP: net.IP{10, 5, 6, 7}, Port: 81}, - SVCAddress: addr.SvcCS, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.Name, func(t *testing.T) { - var r Registration - err := r.DecodeFromBytes(tc.Data) - assert.ErrorIs(t, err, tc.ExpectedError) - assert.Equal(t, tc.ExpectedRegistration, r) - }) - } -} - -func TestConfirmationMessageSerializeTo(t *testing.T) { - confirmation := &Confirmation{Port: 0xaabb} - t.Run("bad buffer", func(t *testing.T) { - b := make([]byte, 1) - n, err := confirmation.SerializeTo(b) - assert.ErrorIs(t, err, ErrBufferTooSmall) - assert.Zero(t, n) - }) - t.Run("success", func(t *testing.T) { - b := make([]byte, 1500) - n, err := confirmation.SerializeTo(b) - assert.NoError(t, err) - assert.Equal(t, []byte{0xaa, 0xbb}, b[:n]) - }) -} - -func TestConfirmationDecodeFromBytes(t *testing.T) { - var confirmation Confirmation - t.Run("bad buffer", func(t *testing.T) { - b := []byte{0xaa} - err := confirmation.DecodeFromBytes(b) - assert.ErrorIs(t, err, ErrIncompletePort) - assert.Equal(t, Confirmation{}, confirmation) - }) - t.Run("success", func(t *testing.T) { - b := []byte{0xaa, 0xbb} - err := confirmation.DecodeFromBytes(b) - assert.NoError(t, err) - assert.Equal(t, Confirmation{Port: 0xaabb}, confirmation) - }) -} diff --git a/pkg/sock/reliable/reliable.go b/pkg/sock/reliable/reliable.go deleted file mode 100644 index b3b3ed1484..0000000000 --- a/pkg/sock/reliable/reliable.go +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright 2017 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package reliable implements the SCION ReliableSocket protocol -// -// Servers should first call Listen on a UNIX socket address, and then call -// Accept on the received Listener. -// -// Clients should either call: -// -// Dial, if they do not want to register a receiving address with the remote end -// (e.g., when connecting to SCIOND); -// Register, to register the address argument with the remote end -// (e.g., when connecting to a dispatcher). -// -// ReliableSocket common header message format: -// -// 8-bytes: COOKIE (0xde00ad01be02ef03) -// 1-byte: ADDR TYPE (NONE=0, IPv4=1, IPv6=2, SVC=3) -// 4-byte: data length -// var-byte: Destination address (0 bytes for SCIOND API) -// +2-byte: If destination address not NONE, destination port -// var-byte: Payload -// -// ReliableSocket registration message format: -// -// 13-bytes: [Common header with address type NONE] -// 1-byte: Command (bit mask with 0x04=Bind address, 0x02=SCMP enable, 0x01 always set) -// 1-byte: L4 Proto (IANA number) -// 8-bytes: ISD-AS -// 2-bytes: L4 port -// 1-byte: Address type -// var-byte: Address -// +2-bytes: L4 bind port \ -// +1-byte: Address type ) (optional bind address) -// +var-byte: Bind Address / -// +2-bytes: SVC (optional SVC type) -// -// To communicate with SCIOND, clients must first connect to SCIOND's UNIX socket. Messages -// for SCIOND must set the ADDR TYPE field in the common header to NONE. The payload contains -// the query for SCIOND (e.g., a request for paths to a SCION destination). The reply header -// contains the same fields, and the reply payload contains the query answer. -// -// To receive messages from remote SCION hosts, hosts can register their address and -// port with the SCION dispatcher. The common header of a registration message uses an address -// of type NONE. The payload contains the address type of the registered address, the address -// itself and the layer 4 port. -// -// To send messages to remote SCION hosts, hosts fill in the common header -// with the address type, the address and the layer 4 port of the remote host. -// -// Reads and writes to the connection are thread safe. -package reliable - -import ( - "context" - "fmt" - "math" - "net" - "sync" - "time" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/common" - "github.com/scionproto/scion/pkg/private/prom" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable/internal/metrics" -) - -var ( - expectedCookie = uint64(0xde00ad01be02ef03) -) - -const ( - // DefaultDispPath contains the system default for a dispatcher socket. - DefaultDispPath = "/run/shm/dispatcher/default.sock" - // DefaultDispSocketFileMode allows read/write to the user and group only. - DefaultDispSocketFileMode = 0770 -) - -// Dispatcher controls how SCION applications open sockets in the SCION world. -type Dispatcher interface { - // Register connects to a SCION Dispatcher's UNIX socket. Future messages for the address in AS - // ia which arrive at the dispatcher can be read by calling Read on the returned connection. - Register(ctx context.Context, ia addr.IA, address *net.UDPAddr, - svc addr.SVC) (net.PacketConn, uint16, error) -} - -// NewDispatcher creates a new dispatcher API endpoint on top of a UNIX -// STREAM reliable socket. If name is empty, the default dispatcher path is -// chosen. -func NewDispatcher(name string) Dispatcher { - if name == "" { - name = DefaultDispPath - } - return &dispatcherService{Address: name} -} - -type dispatcherService struct { - Address string -} - -func (d *dispatcherService) Register(ctx context.Context, ia addr.IA, public *net.UDPAddr, - svc addr.SVC) (net.PacketConn, uint16, error) { - - return registerMetricsWrapper(ctx, d.Address, ia, public, svc) -} - -var _ net.Conn = (*Conn)(nil) -var _ net.PacketConn = (*Conn)(nil) - -// Conn implements the ReliableSocket framing protocol over UNIX sockets. -type Conn struct { - *net.UnixConn - - readMutex sync.Mutex - readBuffer []byte - readPacketizer *ReadPacketizer - - writeMutex sync.Mutex - writeBuffer []byte - writeStreamer *WriteStreamer -} - -func newConn(c net.Conn) *Conn { - conn := c.(*net.UnixConn) - return &Conn{ - UnixConn: c.(*net.UnixConn), - writeBuffer: make([]byte, common.SupportedMTU), - writeStreamer: NewWriteStreamer(conn), - readBuffer: make([]byte, common.SupportedMTU), - readPacketizer: NewReadPacketizer(conn), - } -} - -// Dial connects to the UNIX socket specified by address. -// -// The provided context must be non-nil. If the context expires before the connection is complete, -// an error is returned. Once successfully connected, any expiration of the context will not affect -// the connection. -func Dial(ctx context.Context, address string) (*Conn, error) { - dialer := net.Dialer{} - c, err := dialer.DialContext(ctx, "unix", address) - metrics.M.Dials(metrics.DialLabels{Result: labelResult(err)}).Inc() - if err != nil { - return nil, err - } - return newConn(c), nil -} - -func registerMetricsWrapper(ctx context.Context, dispatcher string, ia addr.IA, - public *net.UDPAddr, svc addr.SVC) (*Conn, uint16, error) { - - conn, port, err := register(ctx, dispatcher, ia, public, svc) - labels := metrics.RegisterLabels{Result: labelResult(err), SVC: svc.BaseString()} - metrics.M.Registers(labels).Inc() - return conn, port, err -} - -func register(ctx context.Context, dispatcher string, ia addr.IA, public *net.UDPAddr, - svc addr.SVC) (*Conn, uint16, error) { - - reg := &Registration{ - IA: ia, - PublicAddress: public, - SVCAddress: svc, - } - - conn, err := Dial(ctx, dispatcher) - if err != nil { - return nil, 0, err - } - - type RegistrationReturn struct { - port uint16 - err error - } - resultChannel := make(chan RegistrationReturn) - go func() { - defer log.HandlePanic() - - // If a timeout was specified, make reads and writes return if deadline exceeded. - if deadline, ok := ctx.Deadline(); ok { - if err := conn.SetDeadline(deadline); err != nil { - resultChannel <- RegistrationReturn{err: err} - return - } - } - - port, err := registrationExchange(conn, reg) - resultChannel <- RegistrationReturn{port: port, err: err} - }() - - select { - case registrationReturn := <-resultChannel: - if registrationReturn.err != nil { - conn.Close() - return nil, 0, registrationReturn.err - } - if public.Port < 0 || public.Port > math.MaxUint16 { - return nil, 0, serrors.New(fmt.Sprintf("invalid port, range [0 - %v]", math.MaxUint16), - "requested", public.Port) - } - if public.Port != 0 && public.Port != int(registrationReturn.port) { - conn.Close() - return nil, 0, serrors.New("port mismatch", "requested", public.Port, - "received", registrationReturn.port) - } - // Disable deadline to not affect future I/O - err = conn.SetDeadline(time.Time{}) - return conn, registrationReturn.port, err - case <-ctx.Done(): - // Unblock registration worker I/O - conn.Close() - // Wait for registration worker to finish before exiting. Worker should exit quickly - // because all pending I/O immediately times out. - <-resultChannel - // The returned values aren't needed, we already decided to error out when the connection - // was closed. Note that the registration might succeed in the short window of time between - // the context being marked as done (canceled) and the I/O getting informed of the new - // deadline. - return nil, 0, serrors.WrapStr("timed out during dispatcher registration", ctx.Err()) - } -} - -func registrationExchange(conn *Conn, reg *Registration) (uint16, error) { - b := make([]byte, 1500) - n, err := reg.SerializeTo(b) - if err != nil { - return 0, err - } - _, err = conn.WriteTo(b[:n], nil) - if err != nil { - return 0, err - } - - n, _, err = conn.ReadFrom(b) - if err != nil { - conn.Close() - return 0, err - } - - var c Confirmation - err = c.DecodeFromBytes(b[:n]) - if err != nil { - conn.Close() - return 0, err - } - return c.Port, nil - -} - -// ReadFrom works similarly to Read. In addition to Read, it also returns the last hop -// (usually, the border router) which sent the message. -func (conn *Conn) ReadFrom(buf []byte) (int, net.Addr, error) { - n, addr, err := conn.readFrom(buf) - metrics.M.Reads(metrics.IOLabels{Result: labelResult(err)}).Observe(float64(n)) - return n, addr, err -} - -func (conn *Conn) readFrom(buf []byte) (int, net.Addr, error) { - conn.readMutex.Lock() - defer conn.readMutex.Unlock() - - n, err := conn.readPacketizer.Read(conn.readBuffer) - if err != nil { - return 0, nil, err - } - var p UnderlayPacket - if err := p.DecodeFromBytes(conn.readBuffer[:n]); err != nil { - return 0, nil, err - } - var underlayAddr *net.UDPAddr - if p.Address != nil { - underlayAddr = &net.UDPAddr{ - IP: append(p.Address.IP[:0:0], p.Address.IP...), - Port: p.Address.Port, - } - } - if len(buf) < len(p.Payload) { - return 0, nil, serrors.New("buffer too small") - } - copy(buf, p.Payload) - return len(p.Payload), underlayAddr, nil -} - -// WriteTo blocks until it sends buf as a single framed message through conn. -// The ReliableSocket message header will contain the address and port information in dst. -// On error, the number of bytes returned is meaningless. On success, the number of bytes -// is always len(buf). -func (conn *Conn) WriteTo(buf []byte, dst net.Addr) (int, error) { - n, err := conn.writeTo(buf, dst) - metrics.M.Writes(metrics.IOLabels{Result: labelResult(err)}).Observe(float64(n)) - return n, err -} - -func (conn *Conn) writeTo(buf []byte, dst net.Addr) (int, error) { - conn.writeMutex.Lock() - defer conn.writeMutex.Unlock() - - var udpAddr *net.UDPAddr - if dst != nil { - var ok bool - udpAddr, ok = dst.(*net.UDPAddr) - if !ok { - return 0, serrors.New("unsupported address type, must be UDP", - "address", fmt.Sprintf("%#v", dst)) - } - } - p := &UnderlayPacket{Address: udpAddr, Payload: buf} - n, err := p.SerializeTo(conn.writeBuffer) - if err != nil { - return 0, err - } - err = conn.writeStreamer.Write(conn.writeBuffer[:n]) - if err != nil { - return 0, err - } - return len(buf), nil -} - -// Read blocks until it reads the next framed message payload from conn and stores it in buf. -// The first return value contains the number of payload bytes read. -// buf must be large enough to fit the entire message. No addressing data is returned, -// only the payload. On error, the number of bytes returned is meaningless. -func (conn *Conn) Read(buf []byte) (int, error) { - n, _, err := conn.ReadFrom(buf) - return n, err -} - -// Listener listens on Unix sockets and returns Conn sockets on Accept(). -type Listener struct { - *net.UnixListener -} - -// Listen listens on UNIX socket laddr. -func Listen(laddr string) (*Listener, error) { - l, err := net.Listen("unix", laddr) - if err != nil { - return nil, serrors.WrapStr("Unable to listen on address", err, "addr", laddr) - } - return &Listener{l.(*net.UnixListener)}, nil -} - -// Accept returns sockets which implement the SCION ReliableSocket protocol for reading -// and writing. -func (listener *Listener) Accept() (net.Conn, error) { - c, err := listener.UnixListener.Accept() - if err != nil { - return nil, err - } - return newConn(c), nil -} - -func (listener *Listener) String() string { - return fmt.Sprintf("&{addr: %v}", listener.UnixListener.Addr()) -} - -func labelResult(err error) string { - switch { - case err == nil: - return prom.Success - case serrors.IsTimeout(err): - return prom.ErrTimeout - default: - return prom.ErrNotClassified - } -} diff --git a/pkg/sock/reliable/util.go b/pkg/sock/reliable/util.go deleted file mode 100644 index f0e49b0f5c..0000000000 --- a/pkg/sock/reliable/util.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package reliable - -import ( - "net" -) - -type hostAddrType uint8 - -const ( - hostTypeNone = iota - hostTypeIPv4 - hostTypeIPv6 - hostTypeSVC -) - -func getAddressType(address *net.UDPAddr) hostAddrType { - if address == nil || address.IP == nil { - return hostTypeNone - } - return getIPAddressType(address.IP) -} - -func getIPAddressType(ip net.IP) hostAddrType { - if ip.To4() != nil { - return hostTypeIPv4 - } - return hostTypeIPv6 -} - -// normalizeIP returns a 4-byte slice for an IPv4 address, and 16-byte slice -// for an IPv6 address. -func normalizeIP(ip net.IP) net.IP { - if ip := ip.To4(); ip != nil { - return ip - } - return ip -} - -func isValidReliableSockDestination(t hostAddrType) bool { - return t == hostTypeNone || t == hostTypeIPv4 || t == hostTypeIPv6 -} - -func getAddressLength(t hostAddrType) int { - switch t { - case hostTypeNone: - return 0 - case hostTypeIPv4: - return 4 - case hostTypeIPv6: - return 16 - case hostTypeSVC: - return 2 - } - return 0 -} - -func getPortLength(t hostAddrType) int { - if t == hostTypeIPv4 || t == hostTypeIPv6 { - return 2 - } - return 0 -} diff --git a/private/app/appnet/BUILD.bazel b/private/app/appnet/BUILD.bazel index 6762e1fd67..ab84c7c0fa 100644 --- a/private/app/appnet/BUILD.bazel +++ b/private/app/appnet/BUILD.bazel @@ -18,8 +18,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", "//pkg/snet/squic:go_default_library", - "//pkg/sock/reliable:go_default_library", - "//pkg/sock/reliable/reconnect:go_default_library", "//private/env:go_default_library", "//private/svc:go_default_library", "//private/trust:go_default_library", diff --git a/private/app/appnet/infraenv.go b/private/app/appnet/infraenv.go index e1f73c73c2..b18a7db2dc 100644 --- a/private/app/appnet/infraenv.go +++ b/private/app/appnet/infraenv.go @@ -25,8 +25,6 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" - "errors" - "fmt" "math/big" "net" "time" @@ -39,8 +37,6 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/squic" - "github.com/scionproto/scion/pkg/sock/reliable" - "github.com/scionproto/scion/pkg/sock/reliable/reconnect" "github.com/scionproto/scion/private/env" "github.com/scionproto/scion/private/svc" "github.com/scionproto/scion/private/trust" @@ -48,9 +44,6 @@ import ( // QUIC contains the QUIC configuration for control-plane speakers. type QUIC struct { - // Address is the UDP address to start the QUIC server on. - Address string - GetCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error) GetClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error) TLSVerifier *trust.TLSCryptoVerifier @@ -64,12 +57,7 @@ type NetworkConfig struct { // Public is the Internet-reachable address in the case where the service // is behind NAT. Public *net.UDPAddr - // ReconnectToDispatcher sets up sockets that automatically reconnect if - // the dispatcher closes the connection (e.g., if the dispatcher goes - // down). - ReconnectToDispatcher bool - // QUIC contains configuration details for QUIC servers. If the listening - // address is the empty string, then no QUIC socket is opened. + // QUIC contains configuration details for QUIC servers. QUIC QUIC // SVCResolver is used to discover the underlay addresses of intra-AS SVC // servers. @@ -81,10 +69,13 @@ type NetworkConfig struct { SCMPHandler snet.SCMPHandler // Metrics injected into SCIONNetwork. SCIONNetworkMetrics snet.SCIONNetworkMetrics - // Metrics injected into DefaultPacketDispatcherService. + // Metrics injected into SCIONPacketConn. SCIONPacketConnMetrics snet.SCIONPacketConnMetrics // MTU of the local AS MTU uint16 + // Topology is the helper class to get control-plane information for the + // local AS. + Topology snet.Topology } // QUICStack contains everything to run a QUIC based RPC stack. @@ -92,7 +83,6 @@ type QUICStack struct { Listener *squic.ConnListener InsecureDialer *squic.ConnDialer Dialer *squic.ConnDialer - RedirectCloser func() } func (nc *NetworkConfig) TCPStack() (net.Listener, error) { @@ -104,9 +94,6 @@ func (nc *NetworkConfig) TCPStack() (net.Listener, error) { } func (nc *NetworkConfig) QUICStack() (*QUICStack, error) { - if nc.QUIC.Address == "" { - nc.QUIC.Address = nc.Public.String() - } client, server, err := nc.initQUICSockets() if err != nil { @@ -127,18 +114,6 @@ func (nc *NetworkConfig) QUICStack() (*QUICStack, error) { return nil, serrors.WrapStr("listening QUIC/SCION", err) } - serverAddr, ok := server.LocalAddr().(*snet.UDPAddr) - if !ok { - return nil, serrors.New("unexpected server address type", - "type", fmt.Sprintf("%T", server.LocalAddr()), - ) - } - - cancel, err := nc.initSvcRedirect(serverAddr.Host.String()) - if err != nil { - return nil, serrors.WrapStr("starting service redirection", err) - } - insecureClientTLSConfig := &tls.Config{ InsecureSkipVerify: true, NextProtos: []string{"SCION"}, @@ -164,7 +139,6 @@ func (nc *NetworkConfig) QUICStack() (*QUICStack, error) { Transport: clientTransport, TLSConfig: clientTLSConfig, }, - RedirectCloser: cancel, }, nil } @@ -215,147 +189,81 @@ func GenerateTLSConfig() (*tls.Config, error) { // AddressRewriter initializes path and svc resolvers for infra servers. // -// The connection factory is used to open sockets for SVC resolution requests. -// If the connection factory is nil, the default connection factory is used. -func (nc *NetworkConfig) AddressRewriter( - connFactory snet.PacketDispatcherService) *AddressRewriter { - - if connFactory == nil { - connFactory = &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), - SCMPHandler: nc.SCMPHandler, - } - } +// The connector is used to open sockets for SVC resolution requests. +// If the connector is nil, the default connection factory is used. +func (nc *NetworkConfig) AddressRewriter() *AddressRewriter { return &AddressRewriter{ Router: &snet.BaseRouter{Querier: IntraASPathQuerier{IA: nc.IA, MTU: nc.MTU}}, SVCRouter: nc.SVCResolver, Resolver: &svc.Resolver{ - LocalIA: nc.IA, - ConnFactory: connFactory, - LocalIP: nc.Public.IP, + LocalIA: nc.IA, + Network: &snet.SCIONNetwork{ + Topology: nc.Topology, + SCMPHandler: nc.SCMPHandler, + Metrics: nc.SCIONNetworkMetrics, + PacketConnMetrics: nc.SCIONPacketConnMetrics, + }, + LocalIP: nc.Public.IP, }, SVCResolutionFraction: 1.337, } } -// initSvcRedirect creates the main control-plane UDP socket. SVC anycasts will be -// delivered to this socket, which replies to SVC resolution requests. The -// address will be included as the QUIC address in SVC resolution replies. -func (nc *NetworkConfig) initSvcRedirect(quicAddress string) (func(), error) { +func (nc *NetworkConfig) initQUICSockets() (net.PacketConn, net.PacketConn, error) { reply := &svc.Reply{ Transports: map[svc.Transport]string{ - svc.QUIC: quicAddress, + svc.QUIC: nc.Public.String(), }, } svcResolutionReply, err := reply.Marshal() if err != nil { - return nil, serrors.WrapStr("building SVC resolution reply", err) + return nil, nil, serrors.WrapStr("building SVC resolution reply", err) } - dispatcherService := reliable.NewDispatcher("") - if nc.ReconnectToDispatcher { - dispatcherService = reconnect.NewDispatcherService(dispatcherService) - } - packetDispatcher := svc.NewResolverPacketDispatcher( - &snet.DefaultPacketDispatcherService{ - Dispatcher: dispatcherService, - SCMPHandler: nc.SCMPHandler, - SCIONPacketConnMetrics: nc.SCIONPacketConnMetrics, - }, - &svc.BaseHandler{ - Message: svcResolutionReply, - }, - ) - network := &snet.SCIONNetwork{ - LocalIA: nc.IA, - Dispatcher: packetDispatcher, - Metrics: nc.SCIONNetworkMetrics, + serverNet := &snet.SCIONNetwork{ + Topology: nc.Topology, + // XXX(roosd): This is essential, the server must not read SCMP + // errors. Otherwise, the accept loop will always return that error + // on every subsequent call to accept. + SCMPHandler: ignoreSCMP{}, + PacketConnMetrics: nc.SCIONPacketConnMetrics, } - - // The service resolution address gets a dynamic port. In reality, neither the - // address nor the port are needed to address the resolver, but the dispatcher still - // requires them and checks unicity. At least a dynamic port is allowed. - srAddr := &net.UDPAddr{IP: nc.Public.IP, Port: 0} - conn, err := network.Listen(context.Background(), "udp", srAddr, addr.SvcWildcard) + pconn, err := serverNet.OpenRaw(context.Background(), nc.Public) if err != nil { - log.Info("Listen failed", "err", err) - return nil, serrors.WrapStr("listening on SCION", err, "addr", srAddr) + return nil, nil, serrors.WrapStr("creating server raw PacketConn", err) } - - ctx, cancel := context.WithCancel(context.Background()) - go func() { - defer log.HandlePanic() - buf := make([]byte, 1500) - done := ctx.Done() - for { - select { - case <-done: - return - default: - // All the resolution logic is "hidden" in the - // svc.ResolverPacketDispatcher. Here, we just need to Read to - // drive this. Ignore errors from reading, just keep going. - _, err := conn.Read(buf) - if errors.Is(err, svc.ErrHandler) { - log.Debug("Error handling SVC request", "err", err) - } else if errors.Is(err, net.ErrClosed) { - log.Error("SVC resolution socket was closed", "err", err) - return - } - } - } - }() - return cancel, nil -} - -func (nc *NetworkConfig) initQUICSockets() (net.PacketConn, net.PacketConn, error) { - dispatcherService := reliable.NewDispatcher("") - if nc.ReconnectToDispatcher { - dispatcherService = reconnect.NewDispatcherService(dispatcherService) - } - - serverNet := &snet.SCIONNetwork{ - LocalIA: nc.IA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - Dispatcher: dispatcherService, - // XXX(roosd): This is essential, the server must not read SCMP - // errors. Otherwise, the accept loop will always return that error - // on every subsequent call to accept. - SCMPHandler: ignoreSCMP{}, - SCIONPacketConnMetrics: nc.SCIONPacketConnMetrics, + resolvedPacketConn := &svc.ResolverPacketConn{ + PacketConn: pconn, + Source: snet.SCIONAddress{ + IA: nc.IA, + Host: addr.HostIP(nc.Public.AddrPort().Addr()), + }, + Handler: &svc.BaseHandler{ + Message: svcResolutionReply, }, - Metrics: nc.SCIONNetworkMetrics, - } - serverAddr, err := net.ResolveUDPAddr("udp", nc.QUIC.Address) - if err != nil { - return nil, nil, serrors.WrapStr("parsing server QUIC address", err) } - server, err := serverNet.Listen(context.Background(), "udp", serverAddr, addr.SvcNone) + server, err := snet.NewCookedConn(resolvedPacketConn, nc.Topology) if err != nil { return nil, nil, serrors.WrapStr("creating server connection", err) } clientNet := &snet.SCIONNetwork{ - LocalIA: nc.IA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - Dispatcher: dispatcherService, - // Discard all SCMP propagation, to avoid read errors on the QUIC - // client. - SCMPHandler: snet.SCMPPropagationStopper{ - Handler: nc.SCMPHandler, - Log: log.Debug, - }, - SCIONPacketConnMetrics: nc.SCIONPacketConnMetrics, + Topology: nc.Topology, + // Discard all SCMP propagation, to avoid read errors on the QUIC + // client. + SCMPHandler: snet.SCMPPropagationStopper{ + Handler: nc.SCMPHandler, + Log: log.Debug, }, - Metrics: nc.SCIONNetworkMetrics, + Metrics: nc.SCIONNetworkMetrics, + PacketConnMetrics: nc.SCIONPacketConnMetrics, } - // Let the dispatcher decide on the port for the client connection. clientAddr := &net.UDPAddr{ - IP: serverAddr.IP, - Zone: serverAddr.Zone, + IP: nc.Public.IP, + Zone: nc.Public.Zone, } - client, err := clientNet.Listen(context.Background(), "udp", clientAddr, addr.SvcNone) + client, err := clientNet.Listen(context.Background(), "udp", clientAddr) if err != nil { return nil, nil, serrors.WrapStr("creating client connection", err) } diff --git a/private/app/env/env.go b/private/app/env/env.go index cc134e5ad3..8b3a743ab1 100644 --- a/private/app/env/env.go +++ b/private/app/env/env.go @@ -49,8 +49,6 @@ type General struct { // DefaultIA is the ISD-AS that will be used by default as a source AS in case multiple SCION // ASes are available on the host. DefaultIA addr.IA `json:"default_isd_as,omitempty"` - // DispatcherSocket is the path to the dispatcher socket. - DispatcherSocket string `json:"dispatcher_socket,omitempty"` } func (g *General) Validate() error { diff --git a/private/app/env/env_test.go b/private/app/env/env_test.go index 74cf2705bc..bb6c58f305 100644 --- a/private/app/env/env_test.go +++ b/private/app/env/env_test.go @@ -33,8 +33,7 @@ func TestSCION(t *testing.T) { Input: ` { "general": { - "default_isd_as": "1-ff00:0:1", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-ff00:0:1" }, "ases": { "1-ff00:0:1": { @@ -50,8 +49,7 @@ func TestSCION(t *testing.T) { Input: ` { "general": { - "default_isd_as": "1-ff00:0:1", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-ff00:0:1" }, "ases": { "invalid-ia": { @@ -67,8 +65,7 @@ func TestSCION(t *testing.T) { Input: ` { "general": { - "default_isd_as": "1-0", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-0" }, "ases": { "1-ff00:0:1": { @@ -84,8 +81,7 @@ func TestSCION(t *testing.T) { Input: ` { "general": { - "default_isd_as": "1-ff00:0:1", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-ff00:0:1" }, "ases": { "1-ff00:0:1": { @@ -123,8 +119,7 @@ func TestGeneral(t *testing.T) { "valid": { Input: ` { - "default_isd_as": "1-ff00:0:1", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-ff00:0:1" } `, parseError: assert.NoError, @@ -133,8 +128,7 @@ func TestGeneral(t *testing.T) { "parse error": { Input: ` { - "default_isd_as": "invalid", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "invalid" } `, parseError: assert.Error, @@ -143,8 +137,7 @@ func TestGeneral(t *testing.T) { "validation error": { Input: ` { - "default_isd_as": "1-0", - "dispatcher_socket": "/var/run/dispatcher/" + "default_isd_as": "1-0" } `, parseError: assert.NoError, diff --git a/private/app/flag/BUILD.bazel b/private/app/flag/BUILD.bazel index 2dddf09adb..fca4f76dcb 100644 --- a/private/app/flag/BUILD.bazel +++ b/private/app/flag/BUILD.bazel @@ -14,7 +14,6 @@ go_library( "//pkg/daemon:go_default_library", "//pkg/private/serrors:go_default_library", "//pkg/private/util:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/app/env:go_default_library", "@com_github_spf13_pflag//:go_default_library", ], @@ -31,7 +30,6 @@ go_test( "//pkg/addr:go_default_library", "//pkg/daemon:go_default_library", "//pkg/private/xtest:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/app/env:go_default_library", "@com_github_spf13_pflag//:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", diff --git a/private/app/flag/env.go b/private/app/flag/env.go index a585a9017c..9f5820fe00 100644 --- a/private/app/flag/env.go +++ b/private/app/flag/env.go @@ -27,13 +27,11 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app/env" ) const ( - defaultDaemon = daemon.DefaultAPIAddress - defaultDispatcher = reliable.DefaultDispPath + defaultDaemon = daemon.DefaultAPIAddress defaultEnvironmentFile = "/etc/scion/environment.json" ) @@ -77,15 +75,12 @@ func (v *ipVal) Type() string { return "ip" } func (v *ipVal) String() string { return netip.Addr(*v).String() } // SCIONEnvironment can be used to access the common SCION configuration values, -// like the SCION daemon address, the dispatcher socket address and the local IP -// as well as the local ISD-AS. +// like the SCION daemon address and the local IP as well as the local ISD-AS. type SCIONEnvironment struct { sciondFlag *pflag.Flag sciondEnv *string ia addr.IA iaFlag *pflag.Flag - dispFlag *pflag.Flag - dispEnv *string local netip.Addr localEnv *netip.Addr localFlag *pflag.Flag @@ -104,13 +99,10 @@ func (e *SCIONEnvironment) Register(flagSet *pflag.FlagSet) { defer e.mtx.Unlock() sciond := defaultDaemon - dispatcher := defaultDispatcher e.sciondFlag = flagSet.VarPF((*stringVal)(&sciond), "sciond", "", "SCION Daemon address.") e.iaFlag = flagSet.VarPF((*iaVal)(&e.ia), "isd-as", "", "The local ISD-AS to use.") - e.dispFlag = flagSet.VarPF((*stringVal)(&dispatcher), "dispatcher", "", - "Path to the dispatcher socket") e.localFlag = flagSet.VarPF((*ipVal)(&e.local), "local", "l", "Local IP address to listen on.") } @@ -161,9 +153,6 @@ func (e *SCIONEnvironment) loadEnv() error { if d, ok := os.LookupEnv("SCION_DAEMON"); ok { e.sciondEnv = &d } - if d, ok := os.LookupEnv("SCION_DISPATCHER"); ok { - e.dispEnv = &d - } if l, ok := os.LookupEnv("SCION_LOCAL_ADDR"); ok { a, err := netip.ParseAddr(l) if err != nil { @@ -200,29 +189,7 @@ func (e *SCIONEnvironment) Daemon() string { return defaultDaemon } -// Dispatcher returns the path to the SCION dispatcher socket. The value is -// loaded from one of the following sources with the precedence as listed: -// 1. Command line flag -// 2. Environment variable -// 3. Environment configuration file -// 4. Default value. -func (e *SCIONEnvironment) Dispatcher() string { - e.mtx.Lock() - defer e.mtx.Unlock() - - if e.dispFlag != nil && e.dispFlag.Changed { - return e.dispFlag.Value.String() - } - if e.dispEnv != nil { - return *e.dispEnv - } - if s := e.file.General.DispatcherSocket; s != "" { - return s - } - return defaultDispatcher -} - -// Local returns the local IP to listen on. The value is loaded from one of the +// Local returns the loca IP to listen on. The value is loaded from one of the // following sources with the precedence as listed: // 1. Command line flag // 2. Environment variable diff --git a/private/app/flag/env_test.go b/private/app/flag/env_test.go index cb5d9737cb..262f0efc33 100644 --- a/private/app/flag/env_test.go +++ b/private/app/flag/env_test.go @@ -27,7 +27,6 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon" "github.com/scionproto/scion/pkg/private/xtest" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app/env" "github.com/scionproto/scion/private/app/flag" ) @@ -40,8 +39,7 @@ func TestSCIONEnvironment(t *testing.T) { t.Cleanup(func() { os.Remove(fName) }) e := env.SCION{ General: env.General{ - DispatcherSocket: "/test/dispatcher_file.socket", - DefaultIA: xtest.MustParseIA("1-ff00:0:110"), + DefaultIA: xtest.MustParseIA("1-ff00:0:110"), }, ASes: map[addr.IA]env.AS{ xtest.MustParseIA("1-ff00:0:110"): { @@ -58,14 +56,12 @@ func TestSCIONEnvironment(t *testing.T) { } setupEnv := func(t *testing.T) { tempEnv(t, "SCION_DAEMON", "scion_env:1234") - tempEnv(t, "SCION_DISPATCHER", "/test/dispatcher_env.socket") tempEnv(t, "SCION_LOCAL_ADDR", "10.0.42.0") } noEnv := func(t *testing.T) {} setupFlags := func(t *testing.T, fs *pflag.FlagSet) { err := fs.Parse([]string{ "--sciond", "scion:1234", - "--dispatcher", "/test/dispatcher.socket", "--local", "10.0.0.42", }) require.NoError(t, err) @@ -82,52 +78,46 @@ func TestSCIONEnvironment(t *testing.T) { local netip.Addr }{ "no flag, no file, no env, defaults only": { - flags: noFlags, - env: noEnv, - file: noFile, - daemon: daemon.DefaultAPIAddress, - dispatcher: reliable.DefaultDispPath, - local: netip.Addr{}, + flags: noFlags, + env: noEnv, + file: noFile, + daemon: daemon.DefaultAPIAddress, + local: netip.Addr{}, }, "flag values set": { - flags: setupFlags, - env: noEnv, - file: noFile, - daemon: "scion:1234", - dispatcher: "/test/dispatcher.socket", - local: netip.MustParseAddr("10.0.0.42"), + flags: setupFlags, + env: noEnv, + file: noFile, + daemon: "scion:1234", + local: netip.MustParseAddr("10.0.0.42"), }, "env values set": { - flags: noFlags, - env: setupEnv, - file: noFile, - daemon: "scion_env:1234", - dispatcher: "/test/dispatcher_env.socket", - local: netip.MustParseAddr("10.0.42.0"), + flags: noFlags, + env: setupEnv, + file: noFile, + daemon: "scion_env:1234", + local: netip.MustParseAddr("10.0.42.0"), }, "file values set": { - flags: noFlags, - env: noEnv, - file: setupFile, - daemon: "scion_file:1234", - dispatcher: "/test/dispatcher_file.socket", - local: netip.Addr{}, + flags: noFlags, + env: noEnv, + file: setupFile, + daemon: "scion_file:1234", + local: netip.Addr{}, }, "all set, flag precedence": { - flags: setupFlags, - env: setupEnv, - file: setupFile, - daemon: "scion:1234", - dispatcher: "/test/dispatcher.socket", - local: netip.MustParseAddr("10.0.0.42"), + flags: setupFlags, + env: setupEnv, + file: setupFile, + daemon: "scion:1234", + local: netip.MustParseAddr("10.0.0.42"), }, "env set, file set, env precedence": { - flags: noFlags, - env: setupEnv, - file: setupFile, - daemon: "scion_env:1234", - dispatcher: "/test/dispatcher_env.socket", - local: netip.MustParseAddr("10.0.42.0"), + flags: noFlags, + env: setupEnv, + file: setupFile, + daemon: "scion_env:1234", + local: netip.MustParseAddr("10.0.42.0"), }, } for name, tc := range testCases { @@ -140,7 +130,6 @@ func TestSCIONEnvironment(t *testing.T) { tc.file(t, &env) require.NoError(t, env.LoadExternalVars()) assert.Equal(t, tc.daemon, env.Daemon()) - assert.Equal(t, tc.dispatcher, env.Dispatcher()) assert.Equal(t, tc.local, env.Local()) }) } diff --git a/private/app/path/path.go b/private/app/path/path.go index b89e53a09a..96666805e3 100644 --- a/private/app/path/path.go +++ b/private/app/path/path.go @@ -143,9 +143,8 @@ func filterUnhealthy( DstIA: remote, LocalIA: cfg.LocalIA, LocalIP: cfg.LocalIP, - ID: snet.RandomSCMPIdentifer(), SCIONPacketConnMetrics: cfg.SCIONPacketConnMetrics, - Dispatcher: cfg.Dispatcher, + Topology: sd, }.GetStatuses(subCtx, nonEmptyPaths, pathprobe.WithEPIC(epic)) if err != nil { return nil, serrors.WrapStr("probing paths", err) @@ -305,9 +304,8 @@ func (cs ColorScheme) Path(path snet.Path) string { } type ProbeConfig struct { - LocalIA addr.IA - LocalIP net.IP - Dispatcher string + LocalIA addr.IA + LocalIP net.IP // Metrics injected into Prober. SCIONPacketConnMetrics snet.SCIONPacketConnMetrics diff --git a/private/app/path/pathprobe/BUILD.bazel b/private/app/path/pathprobe/BUILD.bazel index 281d5880ce..8d64118959 100644 --- a/private/app/path/pathprobe/BUILD.bazel +++ b/private/app/path/pathprobe/BUILD.bazel @@ -14,7 +14,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/addrutil:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", "@org_golang_x_sync//errgroup:go_default_library", ], ) diff --git a/private/app/path/pathprobe/paths.go b/private/app/path/pathprobe/paths.go index 951d27d59d..62df6c737a 100644 --- a/private/app/path/pathprobe/paths.go +++ b/private/app/path/pathprobe/paths.go @@ -36,7 +36,6 @@ import ( "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/addrutil" snetpath "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" ) // StatusName defines the different states a path can be in. @@ -104,13 +103,11 @@ type Prober struct { // an appropriate local IP endpoint depending on the path that should be probed. Note, LocalIP // should not be set, unless you know what you are doing. LocalIP net.IP - // ID is the SCMP traceroute ID used by the Prober. - ID uint16 - // Dispatcher is the path to the dispatcher socket. Leaving this empty uses - // the default dispatcher socket value. - Dispatcher string - // Metrics injected into snet.DefaultPacketDispatcherService. + // Metrics injected into snet.DefaultConnector. SCIONPacketConnMetrics snet.SCIONPacketConnMetrics + // Topology is the helper class to get control-plane information for the + // local AS. + Topology snet.Topology } type options struct { @@ -164,11 +161,11 @@ func (p Prober) GetStatuses(ctx context.Context, paths []snet.Path, statuses[key] = status } - // Instantiate dispatcher service - disp := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(p.Dispatcher), - SCMPHandler: &scmpHandler{}, - SCIONPacketConnMetrics: p.SCIONPacketConnMetrics, + // Instantiate network + sn := &snet.SCIONNetwork{ + SCMPHandler: &scmpHandler{}, + PacketConnMetrics: p.SCIONPacketConnMetrics, + Topology: p.Topology, } // Resolve all the local IPs per path. We will open one connection @@ -199,8 +196,7 @@ func (p Prober) GetStatuses(ctx context.Context, paths []snet.Path, g.Go(func() error { defer log.HandlePanic() - conn, _, err := disp.Register(ctx, p.LocalIA, - &net.UDPAddr{IP: localIP.AsSlice()}, addr.SvcNone) + conn, err := sn.OpenRaw(ctx, &net.UDPAddr{IP: localIP.AsSlice()}) if err != nil { return serrors.WrapStr("creating packet conn", err, "local", localIP) } @@ -246,7 +242,7 @@ func (p Prober) GetStatuses(ctx context.Context, paths []snet.Path, }, Path: alertPath, Payload: snet.SCMPTracerouteRequest{ - Identifier: p.ID, + Identifier: uint16(conn.LocalAddr().(*net.UDPAddr).Port), Sequence: uint16(atomic.AddInt32(&seq, 1)), }, }, diff --git a/private/env/env.go b/private/env/env.go index 1c59a1822f..584de18020 100644 --- a/private/env/env.go +++ b/private/env/env.go @@ -79,9 +79,6 @@ type General struct { ID string `toml:"id,omitempty"` // ConfigDir for loading extra files (currently, only topology.json and staticInfoConfig.json) ConfigDir string `toml:"config_dir,omitempty"` - // ReconnectToDispatcher can be set to true to enable transparent dispatcher - // reconnects. - ReconnectToDispatcher bool `toml:"reconnect_to_dispatcher,omitempty"` } // InitDefaults sets the default value for Topology if not already set. diff --git a/private/env/envtest/config.go b/private/env/envtest/config.go index ff792bf85e..bdc6aae320 100644 --- a/private/env/envtest/config.go +++ b/private/env/envtest/config.go @@ -44,10 +44,7 @@ func InitTest(general *env.General, metrics *env.Metrics, } } -func InitTestGeneral(cfg *env.General) { - cfg.ReconnectToDispatcher = true -} - +func InitTestGeneral(cfg *env.General) {} func InitTestMetrics(cfg *env.Metrics) {} func InitTestTracing(cfg *env.Tracing) { @@ -80,7 +77,6 @@ func CheckTestGeneral(t *testing.T, cfg *env.General, id string) { assert.Equal(t, id, cfg.ID) assert.Equal(t, "/etc/scion", cfg.ConfigDir) assert.Equal(t, filepath.Join(cfg.ConfigDir, env.TopologyFile), cfg.Topology()) - assert.False(t, cfg.ReconnectToDispatcher) } func CheckTestMetrics(t *testing.T, cfg *env.Metrics) { diff --git a/private/env/sample.go b/private/env/sample.go index b242e7ca08..edccf9e179 100644 --- a/private/env/sample.go +++ b/private/env/sample.go @@ -20,9 +20,6 @@ id = "%s" # Directory for loading AS information, certs, keys, path policy, topology. config_dir = "/etc/scion" - -# Enable the snetproxy reconnecter. (default false) -reconnect_to_dispatcher = false ` const featuresSample = ` diff --git a/private/svc/resolver.go b/private/svc/resolver.go index fb01fb173f..8e32a3e910 100644 --- a/private/svc/resolver.go +++ b/private/svc/resolver.go @@ -57,10 +57,9 @@ func init() { // Resolver performs SVC address resolution. type Resolver struct { + Network snet.Network // LocalIA is the local AS. LocalIA addr.IA - // ConnFactory is used to open ports for SVC resolution messages. - ConnFactory snet.PacketDispatcherService // LocalIP is the default L3 address for connections originating from this process. LocalIP net.IP // RoundTripper performs the request/reply exchange for SVC resolutions. If @@ -84,7 +83,7 @@ func (r *Resolver) LookupSVC(ctx context.Context, p snet.Path, svc addr.SVC) (*R return nil, serrors.New("invalid local IP", "ip", r.LocalIP) } - conn, port, err := r.ConnFactory.Register(ctx, r.LocalIA, u, addr.SvcNone) + conn, err := r.Network.OpenRaw(ctx, u) if err != nil { ext.Error.Set(span, true) return nil, serrors.Wrap(errRegistration, err) @@ -108,7 +107,7 @@ func (r *Resolver) LookupSVC(ctx context.Context, p snet.Path, svc addr.SVC) (*R }, Path: p.Dataplane(), Payload: snet.UDPPayload{ - SrcPort: port, + SrcPort: uint16(conn.LocalAddr().(*net.UDPAddr).Port), Payload: requestPayload, }, }, diff --git a/private/svc/resolver_test.go b/private/svc/resolver_test.go index 86a47bf579..f8b26d2d89 100644 --- a/private/svc/resolver_test.go +++ b/private/svc/resolver_test.go @@ -48,14 +48,13 @@ func TestResolver(t *testing.T) { mockPath.EXPECT().Destination().Return(dstIA).AnyTimes() t.Run("If opening up port fails, return error and no reply", func(t *testing.T) { - mockPacketDispatcherService := mock_snet.NewMockPacketDispatcherService(ctrl) - mockPacketDispatcherService.EXPECT().Register(gomock.Any(), gomock.Any(), - gomock.Any(), gomock.Any()). - Return(nil, uint16(0), errors.New("no conn")) + mockNet := mock_snet.NewMockNetwork(ctrl) + mockNet.EXPECT().OpenRaw(gomock.Any(), gomock.Any()). + Return(nil, errors.New("no conn")) resolver := &svc.Resolver{ - LocalIA: srcIA, - ConnFactory: mockPacketDispatcherService, - LocalIP: net.IP{0, 0, 0, 0}, + LocalIA: srcIA, + LocalIP: xtest.MustParseIP(t, "127.0.0.1"), + Network: mockNet, } reply, err := resolver.LookupSVC(context.Background(), mockPath, addr.SvcCS) @@ -64,12 +63,13 @@ func TestResolver(t *testing.T) { }) t.Run("Local machine information is used to build conns", func(t *testing.T) { - mockPacketDispatcherService := mock_snet.NewMockPacketDispatcherService(ctrl) + mockNet := mock_snet.NewMockNetwork(ctrl) mockConn := mock_snet.NewMockPacketConn(ctrl) - mockPacketDispatcherService.EXPECT().Register(gomock.Any(), srcIA, - &net.UDPAddr{IP: net.IP{192, 0, 2, 1}}, - addr.SvcNone).Return(mockConn, uint16(42), nil) - mockConn.EXPECT().Close() + mockConn.EXPECT().LocalAddr().Return(&net.UDPAddr{ + IP: net.IP{192, 0, 2, 1}, Port: 30001}) + mockNet.EXPECT().OpenRaw(gomock.Any(), &net.UDPAddr{ + IP: net.IP{192, 0, 2, 1}}).Return(mockConn, nil) + mockConn.EXPECT().Close().Return(nil) mockRoundTripper := mock_svc.NewMockRoundTripper(ctrl) mockRoundTripper.EXPECT().RoundTrip(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do( @@ -80,7 +80,7 @@ func TestResolver(t *testing.T) { resolver := &svc.Resolver{ LocalIA: srcIA, - ConnFactory: mockPacketDispatcherService, + Network: mockNet, LocalIP: net.IP{192, 0, 2, 1}, RoundTripper: mockRoundTripper, } diff --git a/private/svc/svc.go b/private/svc/svc.go index e7451f25c1..c757538fe6 100644 --- a/private/svc/svc.go +++ b/private/svc/svc.go @@ -16,11 +16,10 @@ package svc import ( - "context" "net" - "net/netip" "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" @@ -40,102 +39,42 @@ const ( Forward ) -// NewResolverPacketDispatcher creates a dispatcher service that returns -// sockets with built-in SVC address resolution capabilities. -// -// RequestHandler results during connection read operations are handled in the -// following way: -// - on error result, the error is sent back to the reader -// - on forwarding result, the packet is sent back to the app for processing. -// - on handled result, the packet is discarded after processing, and a new -// read is attempted from the connection, and the entire decision process -// repeats. -func NewResolverPacketDispatcher(d snet.PacketDispatcherService, - h RequestHandler) *ResolverPacketDispatcher { - - return &ResolverPacketDispatcher{dispService: d, handler: h} -} - -var _ snet.PacketDispatcherService = (*ResolverPacketDispatcher)(nil) - -// ResolverPacketDispatcher is a dispatcher service that returns sockets with -// built-in SVC address resolution capabilities. Every packet received with a -// destination SVC address is intercepted inside the socket, and sent to an SVC -// resolution handler which responds back to the client. -// -// Redirected packets are not returned by the connection, so they cannot be -// seen via ReadFrom. After redirecting a packet, the connection attempts to -// read another packet before returning, until a non SVC packet is received or -// an error occurs. -type ResolverPacketDispatcher struct { - dispService snet.PacketDispatcherService - handler RequestHandler -} - -func (d *ResolverPacketDispatcher) Register(ctx context.Context, ia addr.IA, - registration *net.UDPAddr, svc addr.SVC) (snet.PacketConn, uint16, error) { - - registrationIP, ok := netip.AddrFromSlice(registration.IP) - if !ok { - return nil, 0, serrors.New("invalid registration IP", "ip", registration.IP) - } - c, port, err := d.dispService.Register(ctx, ia, registration, svc) - if err != nil { - return nil, 0, err - } - packetConn := &resolverPacketConn{ - PacketConn: c, - source: snet.SCIONAddress{ - IA: ia, - Host: addr.HostIP(registrationIP), - }, - handler: d.handler, - } - return packetConn, port, err -} - -// resolverPacketConn redirects SVC destination packets to SVC resolution +// ResolverPacketConn redirects SVC destination packets to SVC resolution // handler logic. -type resolverPacketConn struct { +type ResolverPacketConn struct { // PacketConn is the conn to receive and send packets. snet.PacketConn - // source contains the address from which packets should be sent. - source snet.SCIONAddress - // handler handles packets for SVC destinations. - handler RequestHandler + // Source contains the address from which packets should be sent. + Source snet.SCIONAddress + // Handler handles packets for SVC destinations. + Handler RequestHandler } -func (c *resolverPacketConn) ReadFrom(pkt *snet.Packet, ov *net.UDPAddr) error { +func (c *ResolverPacketConn) ReadFrom(pkt *snet.Packet, ov *net.UDPAddr) error { for { if err := c.PacketConn.ReadFrom(pkt, ov); err != nil { return err } - // XXX(scrye): destination address is guaranteed to not be nil if pkt.Destination.Host.Type() != addr.HostTypeSVC { // Normal packet, return to caller because data is already parsed and ready return nil } - svc := pkt.Destination.Host.SVC() - - // Multicasts do not trigger SVC resolution logic - if svc.IsMulticast() { - return nil - } - // XXX(scrye): This might block, causing the read to wait for the // write to go through. The solution would be to run the logic in a // goroutine, but because UDP writes rarely block, the current // solution should be good enough for now. r := &Request{ Conn: c.PacketConn, - Source: c.source, + Source: c.Source, Packet: pkt, Underlay: ov, } - switch result, err := c.handler.Handle(r); result { + switch result, err := c.Handler.Handle(r); result { case Error: - return serrors.Wrap(ErrHandler, err) + // We do not propagate error to caller, to avoid the connection fails, + // e.g., within QUIC layer. + log.Error("Error handling SVC request", "err", err) case Forward: return nil default: diff --git a/private/svc/svc_test.go b/private/svc/svc_test.go index f6aed72334..0d33ba53e7 100644 --- a/private/svc/svc_test.go +++ b/private/svc/svc_test.go @@ -25,6 +25,7 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/private/xtest" "github.com/scionproto/scion/pkg/slayers/path/empty" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/mock_snet" @@ -35,28 +36,29 @@ import ( func TestSVCResolutionServer(t *testing.T) { testCases := map[string]struct { - DispService func(ctrl *gomock.Controller) snet.PacketDispatcherService + Network func(ctrl *gomock.Controller) snet.Network ReqHandler func(ctrl *gomock.Controller) svc.RequestHandler - ErrRegister assert.ErrorAssertionFunc + ErrOpen assert.ErrorAssertionFunc ErrConnRead assert.ErrorAssertionFunc }{ - "Underlying dispatcher service fails to set up underlying conn": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(nil, uint16(0), errors.New("conn error")) - return s + "Underlying service fails to set up underlying conn": { + Network: func(ctrl *gomock.Controller) snet.Network { + c := mock_snet.NewMockNetwork(ctrl) + c.EXPECT().OpenRaw(gomock.Any(), gomock.Any()).Return(nil, errors.New("conn error")) + return c }, ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { return mock_svc.NewMockRequestHandler(ctrl) }, - ErrRegister: assert.Error, + ErrOpen: assert.Error, }, - "If handler fails, caller sees error": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + "If handler fails, caller doesn't see an error": { + Network: func(ctrl *gomock.Controller) snet.Network { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) - mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( + firstCall := mockPacketConn.EXPECT().ReadFrom( + gomock.Any(), + gomock.Any(), + ).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { pkt.Destination = snet.SCIONAddress{ Host: addr.HostSVC(addr.SvcCS), @@ -64,121 +66,95 @@ func TestSVCResolutionServer(t *testing.T) { return nil }, ) - - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(mockPacketConn, uint16(1337), nil) - return s - }, - ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { - h := mock_svc.NewMockRequestHandler(ctrl) - h.EXPECT().Handle(gomock.Any()).Return(svc.Error, errors.New("err")).AnyTimes() - return h - }, - ErrRegister: assert.NoError, - ErrConnRead: assert.Error, - }, - "If handler returns forward, caller sees data": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { - mockPacketConn := mock_snet.NewMockPacketConn(ctrl) - mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( + mockPacketConn.EXPECT().ReadFrom( + gomock.Any(), + gomock.Any(), + ).After(firstCall).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { pkt.Destination = snet.SCIONAddress{ - Host: addr.HostSVC(addr.SvcCS), + Host: addr.MustParseHost("127.0.0.1"), } return nil }, ) - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(mockPacketConn, uint16(1337), nil) - return s + c := mock_snet.NewMockNetwork(ctrl) + c.EXPECT().OpenRaw(gomock.Any(), gomock.Any()).Return(mockPacketConn, nil) + return c }, ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { h := mock_svc.NewMockRequestHandler(ctrl) - h.EXPECT().Handle(gomock.Any()).Return(svc.Forward, nil).AnyTimes() + h.EXPECT().Handle(gomock.Any()).Return(svc.Error, errors.New("err")) return h }, - ErrRegister: assert.NoError, + ErrOpen: assert.NoError, ErrConnRead: assert.NoError, }, - "return from conn with no error next internal read yields data": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + "If non-SVC addr, caller receives request": { + Network: func(ctrl *gomock.Controller) snet.Network { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { pkt.Destination = snet.SCIONAddress{ - Host: addr.MustParseHost("192.168.0.1"), + Host: addr.MustParseHost("127.0.0.1"), } return nil }, ) - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(mockPacketConn, uint16(1337), nil) - return s + c := mock_snet.NewMockNetwork(ctrl) + c.EXPECT().OpenRaw(gomock.Any(), gomock.Any()).Return(mockPacketConn, nil) + return c }, ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { - h := mock_svc.NewMockRequestHandler(ctrl) - h.EXPECT().Handle(gomock.Any()).Return(svc.Handled, nil).AnyTimes() - return h + return mock_svc.NewMockRequestHandler(ctrl) }, - ErrRegister: assert.NoError, + ErrOpen: assert.NoError, ErrConnRead: assert.NoError, }, - "return from socket with error if next internal read fails": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + "handled first, keep reading forwards following packet to caller": { + Network: func(ctrl *gomock.Controller) snet.Network { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { - return serrors.New("forced exit") + pkt.Destination = snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + } + return nil }, - ) + ).AnyTimes() - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(mockPacketConn, uint16(1337), nil) - return s + c := mock_snet.NewMockNetwork(ctrl) + c.EXPECT().OpenRaw(gomock.Any(), gomock.Any()).Return(mockPacketConn, nil) + return c }, ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { h := mock_svc.NewMockRequestHandler(ctrl) - h.EXPECT().Handle(gomock.Any()).Return(svc.Handled, nil).AnyTimes() + firstCall := h.EXPECT().Handle(gomock.Any()).Return(svc.Handled, nil) + h.EXPECT().Handle(gomock.Any()).After(firstCall).Return(svc.Forward, nil) return h }, - ErrRegister: assert.NoError, - ErrConnRead: assert.Error, + ErrOpen: assert.NoError, + ErrConnRead: assert.NoError, }, - "Multicast SVC packets get delivered to caller": { - DispService: func(ctrl *gomock.Controller) snet.PacketDispatcherService { + "Return from socket with error if next internal read fails": { + Network: func(ctrl *gomock.Controller) snet.Network { mockPacketConn := mock_snet.NewMockPacketConn(ctrl) mockPacketConn.EXPECT().ReadFrom(gomock.Any(), gomock.Any()).DoAndReturn( func(pkt *snet.Packet, ov *net.UDPAddr) error { - pkt.Destination = snet.SCIONAddress{ - Host: addr.HostSVC(addr.SvcCS.Multicast()), - } - return nil + return serrors.New("forced exit") }, ) - s := mock_snet.NewMockPacketDispatcherService(ctrl) - s.EXPECT().Register( - gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), - ).Return(mockPacketConn, uint16(1337), nil) - return s + c := mock_snet.NewMockNetwork(ctrl) + c.EXPECT().OpenRaw(gomock.Any(), gomock.Any()).Return(mockPacketConn, nil) + return c }, ReqHandler: func(ctrl *gomock.Controller) svc.RequestHandler { - h := mock_svc.NewMockRequestHandler(ctrl) - h.EXPECT().Handle(gomock.Any()).Return(svc.Handled, nil).AnyTimes() - return h + return mock_svc.NewMockRequestHandler(ctrl) }, - ErrRegister: assert.NoError, - ErrConnRead: assert.NoError, + ErrOpen: assert.NoError, + ErrConnRead: assert.Error, }, } for name, tc := range testCases { @@ -189,21 +165,24 @@ func TestSVCResolutionServer(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - disp := svc.NewResolverPacketDispatcher(tc.DispService(ctrl), tc.ReqHandler(ctrl)) - conn, port, err := disp.Register(context.Background(), 0, - &net.UDPAddr{IP: net.ParseIP("198.51.100.1")}, - addr.SvcCS) - - tc.ErrRegister(t, err) + pconn, err := tc.Network(ctrl).OpenRaw( + context.Background(), + &net.UDPAddr{IP: xtest.MustParseIP(t, "127.0.0.1")}, + ) + tc.ErrOpen(t, err) if err != nil { - assert.Nil(t, conn) - assert.Zero(t, port) + assert.Nil(t, pconn) return } else { - assert.NotNil(t, conn) - assert.Equal(t, port, uint16(1337)) + assert.NotNil(t, pconn) + } + + resolvedPacketConn := &svc.ResolverPacketConn{ + PacketConn: pconn, + Handler: tc.ReqHandler(ctrl), } - err = conn.ReadFrom(&snet.Packet{}, &net.UDPAddr{}) + + err = resolvedPacketConn.ReadFrom(&snet.Packet{}, &net.UDPAddr{}) tc.ErrConnRead(t, err) }) } @@ -222,6 +201,9 @@ func TestDefaultHandler(t *testing.T) { "path cannot be reversed": { InputPacket: &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Path: path.SCION{ Raw: []byte{0x00, 0x01, 0x02, 0x03}, }, @@ -233,6 +215,9 @@ func TestDefaultHandler(t *testing.T) { "empty UDP payload, success": { InputPacket: &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Payload: snet.UDPPayload{}, Path: snet.RawPath{}, }, @@ -250,6 +235,9 @@ func TestDefaultHandler(t *testing.T) { "UDP payload with ports": { InputPacket: &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Payload: snet.UDPPayload{SrcPort: 42, DstPort: 73}, Path: snet.RawPath{}, }, @@ -267,6 +255,9 @@ func TestDefaultHandler(t *testing.T) { ReplyPayload: []byte{1, 2, 3, 4}, InputPacket: &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Payload: snet.UDPPayload{}, Path: snet.RawPath{}, }, @@ -284,6 +275,9 @@ func TestDefaultHandler(t *testing.T) { ReplySource: snet.SCIONAddress{Host: addr.MustParseHost("192.168.0.1")}, InputPacket: &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Payload: snet.UDPPayload{}, Path: snet.RawPath{}, }, @@ -334,6 +328,9 @@ func TestDefaultHandler(t *testing.T) { conn := mock_snet.NewMockPacketConn(ctrl) packet := &snet.Packet{ PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + Host: addr.HostSVC(addr.SvcCS), + }, Payload: snet.UDPPayload{}, Path: snet.RawPath{}, }, diff --git a/private/topology/interface.go b/private/topology/interface.go index 9680d08bf8..5100f70438 100644 --- a/private/topology/interface.go +++ b/private/topology/interface.go @@ -40,6 +40,9 @@ type Topology interface { Core() bool // InterfaceIDs returns all interface IDS from the local AS. InterfaceIDs() []common.IFIDType + // PortRange returns the first and last ports of the port range (both included), + // in which endhost listen for SCION/UDP application using the UDP/IP underlay. + PortRange() (uint16, uint16) // PublicAddress gets the public address of a server with the requested type and name, and nil // if no such server exists. @@ -149,6 +152,10 @@ func (t *topologyS) InterfaceIDs() []common.IFIDType { return intfs } +func (t *topologyS) PortRange() (uint16, uint16) { + return t.Topology.DispatchedPortStart, t.Topology.DispatchedPortEnd +} + func (t *topologyS) UnderlayNextHop(ifid common.IFIDType) (*net.UDPAddr, bool) { ifInfo, ok := t.Topology.IFInfoMap[ifid] if !ok { diff --git a/private/topology/json/json.go b/private/topology/json/json.go index 0af0c692ec..175cd2e344 100644 --- a/private/topology/json/json.go +++ b/private/topology/json/json.go @@ -71,10 +71,11 @@ func (as *Attributes) UnmarshalJSON(b []byte) error { // Topology is the JSON type for the entire AS topology file. type Topology struct { - Timestamp int64 `json:"timestamp,omitempty"` - TimestampHuman string `json:"timestamp_human,omitempty"` - IA string `json:"isd_as"` - MTU int `json:"mtu"` + Timestamp int64 `json:"timestamp,omitempty"` + TimestampHuman string `json:"timestamp_human,omitempty"` + IA string `json:"isd_as"` + MTU int `json:"mtu"` + EndhostPortRange string `json:"dispatched_ports"` // Attributes specify whether this is a core AS or not. Attributes Attributes `json:"attributes"` BorderRouters map[string]*BRInfo `json:"border_routers,omitempty"` diff --git a/private/topology/json/json_test.go b/private/topology/json/json_test.go index 17e5637048..4968571bec 100644 --- a/private/topology/json/json_test.go +++ b/private/topology/json/json_test.go @@ -37,11 +37,12 @@ var ( func TestLoadRawFromFile(t *testing.T) { referenceTopology := &jsontopo.Topology{ - Timestamp: 168562800, - TimestampHuman: "May 6 00:00:00 CET 1975", - IA: "6-ff00:0:362", - MTU: 1472, - Attributes: []jsontopo.Attribute{jsontopo.AttrCore}, + Timestamp: 168562800, + TimestampHuman: "May 6 00:00:00 CET 1975", + IA: "6-ff00:0:362", + MTU: 1472, + EndhostPortRange: "1024-65535", + Attributes: []jsontopo.Attribute{jsontopo.AttrCore}, BorderRouters: map[string]*jsontopo.BRInfo{ "borderrouter6-f00:0:362-1": { InternalAddr: "10.1.0.1:0", diff --git a/private/topology/json/testdata/topology-deprecated-attrs.json b/private/topology/json/testdata/topology-deprecated-attrs.json index 16e9f352e8..ff264e76d6 100644 --- a/private/topology/json/testdata/topology-deprecated-attrs.json +++ b/private/topology/json/testdata/topology-deprecated-attrs.json @@ -3,6 +3,7 @@ "timestamp_human": "May 6 00:00:00 CET 1975", "isd_as": "6-ff00:0:362", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [ "authoritative", "core", diff --git a/private/topology/json/testdata/topology.json b/private/topology/json/testdata/topology.json index 02817faa64..4a8a8fc3df 100644 --- a/private/topology/json/testdata/topology.json +++ b/private/topology/json/testdata/topology.json @@ -3,6 +3,7 @@ "timestamp_human": "May 6 00:00:00 CET 1975", "isd_as": "6-ff00:0:362", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/private/topology/mock_topology/mock.go b/private/topology/mock_topology/mock.go index 66d237aed7..5efe3794b3 100644 --- a/private/topology/mock_topology/mock.go +++ b/private/topology/mock_topology/mock.go @@ -182,6 +182,21 @@ func (mr *MockTopologyMockRecorder) Multicast(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Multicast", reflect.TypeOf((*MockTopology)(nil).Multicast), arg0) } +// PortRange mocks base method. +func (m *MockTopology) PortRange() (uint16, uint16) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PortRange") + ret0, _ := ret[0].(uint16) + ret1, _ := ret[1].(uint16) + return ret0, ret1 +} + +// PortRange indicates an expected call of PortRange. +func (mr *MockTopologyMockRecorder) PortRange() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortRange", reflect.TypeOf((*MockTopology)(nil).PortRange)) +} + // PublicAddress mocks base method. func (m *MockTopology) PublicAddress(arg0 addr.SVC, arg1 string) *net.UDPAddr { m.ctrl.T.Helper() diff --git a/private/topology/reload.go b/private/topology/reload.go index 74f974681f..1ec5d4b9e1 100644 --- a/private/topology/reload.go +++ b/private/topology/reload.go @@ -148,6 +148,13 @@ func (l *Loader) InterfaceIDs() []uint16 { return ids } +func (l *Loader) PortRange() (uint16, uint16) { + l.mtx.Lock() + defer l.mtx.Unlock() + + return l.topo.PortRange() +} + func (l *Loader) ControlServiceAddresses() []*net.UDPAddr { l.mtx.Lock() defer l.mtx.Unlock() diff --git a/private/topology/testdata/basic.json b/private/topology/testdata/basic.json index 7107b480da..a926651e1c 100644 --- a/private/topology/testdata/basic.json +++ b/private/topology/testdata/basic.json @@ -3,6 +3,7 @@ "timestamp_human": "1975-05-06 01:02:03.000000+0000", "isd_as": "1-ff00:0:311", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [], "border_routers": { "br1-ff00:0:311-1": { diff --git a/private/topology/testdata/core.json b/private/topology/testdata/core.json index 312043e2f1..a13778ee3d 100644 --- a/private/topology/testdata/core.json +++ b/private/topology/testdata/core.json @@ -3,6 +3,7 @@ "timestamp_human": "May 6 00:00:00 CET 1975", "isd_as": "6-ff00:0:362", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/private/topology/topology.go b/private/topology/topology.go index b7c6ce443f..0590743209 100644 --- a/private/topology/topology.go +++ b/private/topology/topology.go @@ -23,17 +23,22 @@ import ( "net/netip" "os" "sort" + "strconv" + "strings" "time" "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" jsontopo "github.com/scionproto/scion/private/topology/json" "github.com/scionproto/scion/private/topology/underlay" ) -// EndhostPort is the underlay port that the dispatcher binds to on non-routers. -const EndhostPort = underlay.EndhostPort +const ( + // EndhostPort is the underlay port that SCION binds to on non-routers. + EndhostPort = underlay.EndhostPort +) // ErrAddressNotFound indicates the address was not found. var ErrAddressNotFound = serrors.New("address not found") @@ -56,10 +61,12 @@ type ( // there is again a sorted slice of names of the servers that provide the service. // Additionally, there is a map from those names to TopoAddr structs. RWTopology struct { - Timestamp time.Time - IA addr.IA - IsCore bool - MTU int + Timestamp time.Time + IA addr.IA + IsCore bool + MTU int + DispatchedPortStart uint16 + DispatchedPortEnd uint16 BR map[string]BRInfo IFInfoMap IfInfoMap @@ -201,6 +208,11 @@ func (t *RWTopology) populateMeta(raw *jsontopo.Topology) error { } t.MTU = raw.MTU + t.DispatchedPortStart, t.DispatchedPortEnd, err = validatePortRange(raw.EndhostPortRange) + if err != nil { + return err + } + isCore := false for _, attr := range raw.Attributes { if attr == jsontopo.AttrCore { @@ -212,6 +224,37 @@ func (t *RWTopology) populateMeta(raw *jsontopo.Topology) error { return nil } +func validatePortRange(portRange string) (uint16, uint16, error) { + if portRange == "" || portRange == "-" { + log.Debug("Empty port range defined") + return 0, 0, nil + } + if portRange == "all" || portRange == "ALL" { + log.Debug("\"all\" port range defined") + return uint16(1), uint16(65535), nil + } + ports := strings.Split(portRange, "-") + if len(ports) != 2 { + return 0, 0, serrors.New("invalid format: expected startPort-endPort", "got", portRange) + } + startPort, errStart := strconv.ParseUint(ports[0], 10, 16) + endPort, errEnd := strconv.ParseUint(ports[1], 10, 16) + if errStart != nil || errEnd != nil { + return 0, 0, serrors.New("invalid port numbers", "got", portRange) + } + if startPort < 1 { + return 0, 0, serrors.New("invalid value for start port", "start port", startPort) + } + if endPort < 1 { + return 0, 0, serrors.New("invalid value for end port", "end port", endPort) + } + if startPort > endPort { + return 0, 0, serrors.New("start port is bigger than end port for the SCION port range", + "start port", startPort, "end port", endPort) + } + return uint16(startPort), uint16(endPort), nil +} + func (t *RWTopology) populateBR(raw *jsontopo.Topology) error { for name, rawBr := range raw.BorderRouters { if rawBr.InternalAddr == "" { @@ -372,10 +415,12 @@ func (t *RWTopology) Copy() *RWTopology { return nil } return &RWTopology{ - Timestamp: t.Timestamp, - IA: t.IA, - MTU: t.MTU, - IsCore: t.IsCore, + Timestamp: t.Timestamp, + IA: t.IA, + MTU: t.MTU, + IsCore: t.IsCore, + DispatchedPortStart: t.DispatchedPortStart, + DispatchedPortEnd: t.DispatchedPortEnd, BR: copyBRMap(t.BR), IFInfoMap: t.IFInfoMap.copy(), diff --git a/private/topology/underlay/defs.go b/private/topology/underlay/defs.go index 534c2e0115..a6fa30c23b 100644 --- a/private/topology/underlay/defs.go +++ b/private/topology/underlay/defs.go @@ -39,7 +39,7 @@ const ( ) const ( - // EndhostPort is the underlay port that the dispatcher binds to on non-routers. Subject to + // EndhostPort is the underlay port that SCION binds to on non-routers. Subject to // change during standardisation. EndhostPort = 30041 ) diff --git a/proto/daemon/v1/BUILD.bazel b/proto/daemon/v1/BUILD.bazel index cf258adada..8e53589e34 100644 --- a/proto/daemon/v1/BUILD.bazel +++ b/proto/daemon/v1/BUILD.bazel @@ -9,6 +9,7 @@ proto_library( deps = [ "//proto/drkey/v1:drkey", "@com_google_protobuf//:duration_proto", + "@com_google_protobuf//:empty_proto", "@com_google_protobuf//:timestamp_proto", ], ) diff --git a/proto/daemon/v1/daemon.proto b/proto/daemon/v1/daemon.proto index 3fb8632000..fff158a11a 100644 --- a/proto/daemon/v1/daemon.proto +++ b/proto/daemon/v1/daemon.proto @@ -20,6 +20,7 @@ package proto.daemon.v1; import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; +import "google/protobuf/empty.proto"; import "proto/drkey/v1/drkey.proto"; service DaemonService { @@ -35,6 +36,8 @@ service DaemonService { rpc Services(ServicesRequest) returns (ServicesResponse) {} // Inform the SCION Daemon of a revocation. rpc NotifyInterfaceDown(NotifyInterfaceDownRequest) returns (NotifyInterfaceDownResponse) {} + // Returns the endhost portRange defined in the local AS. + rpc PortRange(google.protobuf.Empty) returns (PortRangeResponse) {} // DRKeyASHost returns a key that matches the request. rpc DRKeyASHost (DRKeyASHostRequest) returns (DRKeyASHostResponse) {} // DRKeyHostAS returns a key that matches the request. @@ -200,6 +203,13 @@ message NotifyInterfaceDownRequest { message NotifyInterfaceDownResponse {}; +message PortRangeResponse { + // The lowest port in the SCION/UDP dispatched port range. + uint32 dispatched_port_start = 1; + // The highest port in the SCION/UDP dispatched port range. + uint32 dispatched_port_end = 2; +} + message DRKeyHostASRequest{ // Point in time where requested key is valid. google.protobuf.Timestamp val_time = 1; diff --git a/router/cmd/router/main.go b/router/cmd/router/main.go index 835b066f12..6a43e58796 100644 --- a/router/cmd/router/main.go +++ b/router/cmd/router/main.go @@ -56,14 +56,17 @@ func realMain(ctx context.Context) error { } g, errCtx := errgroup.WithContext(ctx) metrics := router.NewMetrics() + dp := &router.Connector{ DataPlane: router.DataPlane{ Metrics: metrics, ExperimentalSCMPAuthentication: globalCfg.Features.ExperimentalSCMPAuthentication, }, - ReceiveBufferSize: globalCfg.Router.ReceiveBufferSize, - SendBufferSize: globalCfg.Router.SendBufferSize, - BFD: globalCfg.Router.BFD, + ReceiveBufferSize: globalCfg.Router.ReceiveBufferSize, + SendBufferSize: globalCfg.Router.SendBufferSize, + BFD: globalCfg.Router.BFD, + DispatchedPortStart: globalCfg.Router.DispatchedPortStart, + DispatchedPortEnd: globalCfg.Router.DispatchedPortEnd, } iaCtx := &control.IACtx{ Config: controlConfig, diff --git a/router/config/config.go b/router/config/config.go index 01a0e8291e..eeb0308fa1 100644 --- a/router/config/config.go +++ b/router/config/config.go @@ -48,6 +48,12 @@ type RouterConfig struct { NumSlowPathProcessors int `toml:"num_slow_processors,omitempty"` BatchSize int `toml:"batch_size,omitempty"` BFD BFD `toml:"bfd,omitempty"` + // TODO: These two values were introduced to override the port range for + // configured router in the context of acceptance tests. However, this + // introduces two sources for the port configuration. We should remove this + // and adapt the acceptance tests. + DispatchedPortStart *int `toml:"dispatched_port_start,omitempty"` + DispatchedPortEnd *int `toml:"dispatched_port_end,omitempty"` } // BFD configuration. Unfortunately cannot be shared with topology.BFD @@ -79,7 +85,27 @@ func (cfg *RouterConfig) Validate() error { if cfg.NumSlowPathProcessors < 1 { return serrors.New("Provided router config is invalid. NumSlowPathProcessors < 1") } - + if cfg.DispatchedPortStart != nil { + if cfg.DispatchedPortEnd == nil { + return serrors.New("provided router config is invalid. " + + "EndHostEndPort is nil; EndHostStartPort isn't") + } + if *cfg.DispatchedPortStart < 0 { + return serrors.New("provided router config is invalid. EndHostStartPort < 0") + } + if *cfg.DispatchedPortEnd >= (1 << 16) { + return serrors.New("provided router config is invalid. EndHostEndPort > 2**16 -1") + } + if *cfg.DispatchedPortStart > *cfg.DispatchedPortEnd { + return serrors.New("provided router config is invalid. " + + "EndHostStartPort > DispatchedPortEnd") + } + } else { + if cfg.DispatchedPortEnd != nil { + return serrors.New("provided router config is invalid. " + + "EndHostStartPort is nil; EndHostEndPort isn't") + } + } return nil } diff --git a/router/connector.go b/router/connector.go index 78adffb0cf..e3d205287f 100644 --- a/router/connector.go +++ b/router/connector.go @@ -23,7 +23,6 @@ import ( "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/private/topology" "github.com/scionproto/scion/private/underlay/conn" "github.com/scionproto/scion/router/config" "github.com/scionproto/scion/router/control" @@ -40,9 +39,11 @@ type Connector struct { externalInterfaces map[uint16]control.ExternalInterface siblingInterfaces map[uint16]control.SiblingInterface - ReceiveBufferSize int - SendBufferSize int - BFD config.BFD + ReceiveBufferSize int + SendBufferSize int + BFD config.BFD + DispatchedPortStart *int + DispatchedPortEnd *int } var errMultiIA = serrors.New("different IA not allowed") @@ -141,25 +142,25 @@ func (c *Connector) AddExternalInterface(localIfID common.IFIDType, link control } // AddSvc adds the service address for the given ISD-AS. -func (c *Connector) AddSvc(ia addr.IA, svc addr.SVC, ip net.IP) error { +func (c *Connector) AddSvc(ia addr.IA, svc addr.SVC, a *net.UDPAddr) error { c.mtx.Lock() defer c.mtx.Unlock() - log.Debug("Adding service", "isd_as", ia, "svc", svc, "ip", ip) + log.Debug("Adding service", "isd_as", ia, "svc", svc, "address", a) if !c.ia.Equal(ia) { return serrors.WithCtx(errMultiIA, "current", c.ia, "new", ia) } - return c.DataPlane.AddSvc(svc, &net.UDPAddr{IP: ip, Port: topology.EndhostPort}) + return c.DataPlane.AddSvc(svc, a) } // DelSvc deletes the service entry for the given ISD-AS and IP pair. -func (c *Connector) DelSvc(ia addr.IA, svc addr.SVC, ip net.IP) error { +func (c *Connector) DelSvc(ia addr.IA, svc addr.SVC, a *net.UDPAddr) error { c.mtx.Lock() defer c.mtx.Unlock() - log.Debug("Deleting service", "isd_as", ia, "svc", svc, "ip", ip) + log.Debug("Deleting service", "isd_as", ia, "svc", svc, "address", a) if !c.ia.Equal(ia) { return serrors.WithCtx(errMultiIA, "current", c.ia, "new", ia) } - return c.DataPlane.DelSvc(svc, &net.UDPAddr{IP: ip, Port: topology.EndhostPort}) + return c.DataPlane.DelSvc(svc, a) } // SetKey sets the key for the given ISD-AS at the given index. @@ -232,3 +233,16 @@ func (c *Connector) applyBFDDefaults(cfg control.BFD) control.BFD { } return cfg } + +func (c *Connector) SetPortRange(start, end uint16) { + c.mtx.Lock() + defer c.mtx.Unlock() + if c.DispatchedPortStart != nil { + start = uint16(*c.DispatchedPortStart) + } + if c.DispatchedPortEnd != nil { + end = uint16(*c.DispatchedPortEnd) + } + log.Debug("Endhost port range configuration", "startPort", start, "endPort", end) + c.DataPlane.SetPortRange(start, end) +} diff --git a/router/control/conf.go b/router/control/conf.go index 46be29aff0..7c4ecd8bc3 100644 --- a/router/control/conf.go +++ b/router/control/conf.go @@ -34,9 +34,10 @@ type Dataplane interface { CreateIACtx(ia addr.IA) error AddInternalInterface(ia addr.IA, local netip.AddrPort) error AddExternalInterface(localIfID common.IFIDType, info LinkInfo, owned bool) error - AddSvc(ia addr.IA, svc addr.SVC, ip net.IP) error - DelSvc(ia addr.IA, svc addr.SVC, ip net.IP) error + AddSvc(ia addr.IA, svc addr.SVC, a *net.UDPAddr) error + DelSvc(ia addr.IA, svc addr.SVC, a *net.UDPAddr) error SetKey(ia addr.IA, index int, key []byte) error + SetPortRange(start, end uint16) } // BFD is the configuration for the BFD sessions. @@ -128,6 +129,7 @@ func ConfigDataplane(dp Dataplane, cfg *Config) error { return err } } + // Add internal interfaces if cfg.BR != nil { if cfg.BR.InternalAddr != (netip.AddrPort{}) { @@ -144,6 +146,8 @@ func ConfigDataplane(dp Dataplane, cfg *Config) error { if err := confServices(dp, cfg); err != nil { return err } + // Set Endhost port range + dp.SetPortRange(cfg.Topo.PortRange()) return nil } @@ -222,7 +226,7 @@ func confServices(dp Dataplane, cfg *Config) error { return nil } for _, svc := range svcTypes { - addrs, err := cfg.Topo.UnderlayMulticast(svc) + addrs, err := cfg.Topo.Multicast(svc) if err != nil { // XXX assumption is that any error means there are no addresses for the SVC type continue @@ -232,7 +236,7 @@ func confServices(dp Dataplane, cfg *Config) error { return addrs[i].IP.String() < addrs[j].IP.String() }) for _, a := range addrs { - if err := dp.AddSvc(cfg.IA, svc, a.IP); err != nil { + if err := dp.AddSvc(cfg.IA, svc, a); err != nil { return err } } diff --git a/router/control/testdata/topology.json b/router/control/testdata/topology.json index 91e0399cb7..2d3a1739e7 100644 --- a/router/control/testdata/topology.json +++ b/router/control/testdata/topology.json @@ -1,6 +1,7 @@ { "isd_as": "1-ff00:0:110", "mtu": 1472, + "dispatched_ports": "1024-65535", "attributes": [ "core" ], diff --git a/router/dataplane.go b/router/dataplane.go index ff387ce187..76a92e4349 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -20,6 +20,7 @@ import ( "context" "crypto/rand" "crypto/subtle" + "encoding/binary" "errors" "fmt" "hash" @@ -90,22 +91,24 @@ type BatchConn interface { // from multiple sockets, performs routing, and sends them to their destinations // (after updating the path, if that is needed). type DataPlane struct { - interfaces map[uint16]BatchConn - external map[uint16]BatchConn - linkTypes map[uint16]topology.LinkType - neighborIAs map[uint16]addr.IA - peerInterfaces map[uint16]uint16 - internal BatchConn - internalIP netip.Addr - internalNextHops map[uint16]*net.UDPAddr - svc *services - macFactory func() hash.Hash - bfdSessions map[uint16]bfdSession - localIA addr.IA - mtx sync.Mutex - running bool - Metrics *Metrics - forwardingMetrics map[uint16]interfaceMetrics + interfaces map[uint16]BatchConn + external map[uint16]BatchConn + linkTypes map[uint16]topology.LinkType + neighborIAs map[uint16]addr.IA + peerInterfaces map[uint16]uint16 + internal BatchConn + internalIP netip.Addr + internalNextHops map[uint16]*net.UDPAddr + svc *services + macFactory func() hash.Hash + bfdSessions map[uint16]bfdSession + localIA addr.IA + mtx sync.Mutex + running bool + Metrics *Metrics + forwardingMetrics map[uint16]interfaceMetrics + dispatchedPortStart uint16 + dispatchedPortEnd uint16 ExperimentalSCMPAuthentication bool @@ -196,6 +199,11 @@ func (d *DataPlane) SetKey(key []byte) error { return nil } +func (d *DataPlane) SetPortRange(start, end uint16) { + d.dispatchedPortStart = start + d.dispatchedPortEnd = end +} + // AddInternalInterface sets the interface the data-plane will use to // send/receive traffic in the local AS. This can only be called once; future // calls will return an error. This can only be called on a not yet running @@ -1570,7 +1578,7 @@ func (p *scionPacketProcessor) verifyCurrentMAC() (processResult, error) { } func (p *scionPacketProcessor) resolveInbound() (*net.UDPAddr, processResult, error) { - a, err := p.d.resolveLocalDst(p.scionLayer) + a, err := p.d.resolveLocalDst(p.scionLayer, p.lastLayer) switch { case errors.Is(err, noSVCBackend): log.Debug("SCMP: no SVC backend") @@ -1966,14 +1974,18 @@ func (p *scionPacketProcessor) processOHP() (processResult, error) { if err := updateSCIONLayer(p.rawPkt, s, p.buffer); err != nil { return processResult{}, err } - a, err := p.d.resolveLocalDst(s) + a, err := p.d.resolveLocalDst(s, p.lastLayer) if err != nil { return processResult{}, err } return processResult{OutAddr: a, OutPkt: p.rawPkt}, nil } -func (d *DataPlane) resolveLocalDst(s slayers.SCION) (*net.UDPAddr, error) { +func (d *DataPlane) resolveLocalDst( + s slayers.SCION, + lastLayer gopacket.DecodingLayer, +) (*net.UDPAddr, error) { + dst, err := s.DstAddr() if err != nil { // TODO parameter problem. @@ -1987,16 +1999,165 @@ func (d *DataPlane) resolveLocalDst(s slayers.SCION) (*net.UDPAddr, error) { if !ok { return nil, noSVCBackend } + // if SVC address is outside the configured port range we send to the fix + // port. + if uint16(a.Port) < d.dispatchedPortStart || uint16(a.Port) > d.dispatchedPortEnd { + a.Port = topology.EndhostPort + } return a, nil case addr.HostTypeIP: - return addEndhostPort(dst.IP().AsSlice()), nil + // Parse UPD port and rewrite underlay IP/UDP port + return d.addEndhostPort(lastLayer, dst.IP().AsSlice()) default: panic("unexpected address type returned from DstAddr") } } -func addEndhostPort(dst net.IP) *net.UDPAddr { - return &net.UDPAddr{IP: dst, Port: topology.EndhostPort} +func (d *DataPlane) addEndhostPort( + lastLayer gopacket.DecodingLayer, + dst []byte, +) (*net.UDPAddr, error) { + + // Parse UPD port and rewrite underlay IP/UDP port + l4Type := nextHdr(lastLayer) + switch l4Type { + case slayers.L4UDP: + if len(lastLayer.LayerPayload()) < 8 { + // TODO(JordiSubira): Treat this as a parameter problem + return nil, serrors.New("SCION/UDP header len too small", "legth", + len(lastLayer.LayerPayload())) + } + port := binary.BigEndian.Uint16(lastLayer.LayerPayload()[2:]) + if port < d.dispatchedPortStart || port > d.dispatchedPortEnd { + port = topology.EndhostPort + } + return &net.UDPAddr{IP: dst, Port: int(port)}, nil + case slayers.L4SCMP: + var scmpLayer slayers.SCMP + err := scmpLayer.DecodeFromBytes(lastLayer.LayerPayload(), gopacket.NilDecodeFeedback) + if err != nil { + // TODO(JordiSubira): Treat this as a parameter problem. + return nil, serrors.WrapStr("decoding SCMP layer for extracting endhost dst port", err) + } + port, err := getDstPortSCMP(&scmpLayer) + if err != nil { + // TODO(JordiSubira): Treat this as a parameter problem. + return nil, serrors.WrapStr("getting dst port from SCMP message", err) + } + // if the SCMP dst port is outside the range, we send it to the EndhostPort + if port < d.dispatchedPortStart || port > d.dispatchedPortEnd { + port = topology.EndhostPort + } + return &net.UDPAddr{IP: dst, Port: int(port)}, nil + default: + log.Debug("msg", "protocol", l4Type) + return &net.UDPAddr{IP: dst, Port: topology.EndhostPort}, nil + } +} + +func getDstPortSCMP(scmp *slayers.SCMP) (uint16, error) { + // XXX(JordiSubira): This implementation is far too slow for the dataplane. + // We should reimplement this with fewer helpers and memory allocations, since + // our sole goal is to parse the L4 port or identifier in the offending packets. + if scmp.TypeCode.Type() == slayers.SCMPTypeEchoRequest || + scmp.TypeCode.Type() == slayers.SCMPTypeTracerouteRequest { + return topology.EndhostPort, nil + } + if scmp.TypeCode.Type() == slayers.SCMPTypeEchoReply { + var scmpEcho slayers.SCMPEcho + err := scmpEcho.DecodeFromBytes(scmp.Payload, gopacket.NilDecodeFeedback) + if err != nil { + return 0, err + } + return scmpEcho.Identifier, nil + } + if scmp.TypeCode.Type() == slayers.SCMPTypeTracerouteReply { + var scmpTraceroute slayers.SCMPTraceroute + err := scmpTraceroute.DecodeFromBytes(scmp.Payload, gopacket.NilDecodeFeedback) + if err != nil { + return 0, err + } + return scmpTraceroute.Identifier, nil + } + + // Drop unknown SCMP error messages. + if scmp.NextLayerType() == gopacket.LayerTypePayload { + return 0, serrors.New("unsupported SCMP error message", + "type", scmp.TypeCode.Type()) + } + l, err := decodeSCMP(scmp) + if err != nil { + return 0, err + } + if len(l) != 2 { + return 0, serrors.New("SCMP error message without payload") + } + gpkt := gopacket.NewPacket(*l[1].(*gopacket.Payload), slayers.LayerTypeSCION, + gopacket.DecodeOptions{ + NoCopy: true, + }, + ) + + // If the offending packet was UDP/SCION, use the source port to deliver. + if udp := gpkt.Layer(slayers.LayerTypeSCIONUDP); udp != nil { + port := udp.(*slayers.UDP).SrcPort + // XXX(roosd): We assume that the zero value means the UDP header is + // truncated. This flags packets of misbehaving senders as truncated, if + // they set the source port to 0. But there is no harm, since those + // packets are destined to be dropped anyway. + if port == 0 { + return 0, serrors.New("SCMP error with truncated UDP header") + } + return port, nil + } + + // If the offending packet was SCMP/SCION, and it is an echo or traceroute, + // use the Identifier to deliver. In all other cases, the message is dropped. + if scmp := gpkt.Layer(slayers.LayerTypeSCMP); scmp != nil { + + tc := scmp.(*slayers.SCMP).TypeCode + // SCMP Error messages in response to an SCMP error message are not allowed. + if !tc.InfoMsg() { + return 0, serrors.New("SCMP error message in response to SCMP error message", + "type", tc.Type()) + } + // We only support echo and traceroute requests. + t := tc.Type() + if t != slayers.SCMPTypeEchoRequest && t != slayers.SCMPTypeTracerouteRequest { + return 0, serrors.New("unsupported SCMP info message", "type", t) + } + + var port uint16 + // Extract the port from the echo or traceroute ID field. + if echo := gpkt.Layer(slayers.LayerTypeSCMPEcho); echo != nil { + port = echo.(*slayers.SCMPEcho).Identifier + } else if tr := gpkt.Layer(slayers.LayerTypeSCMPTraceroute); tr != nil { + port = tr.(*slayers.SCMPTraceroute).Identifier + } else { + return 0, serrors.New("SCMP error with truncated payload") + } + return port, nil + } + return 0, serrors.New("unknown SCION SCMP content") +} + +// decodeSCMP decodes the SCMP payload. WARNING: Decoding is done with NoCopy set. +func decodeSCMP(scmp *slayers.SCMP) ([]gopacket.SerializableLayer, error) { + gpkt := gopacket.NewPacket(scmp.Payload, scmp.NextLayerType(), + gopacket.DecodeOptions{NoCopy: true}) + layers := gpkt.Layers() + if len(layers) == 0 || len(layers) > 2 { + return nil, serrors.New("invalid number of SCMP layers", "count", len(layers)) + } + ret := make([]gopacket.SerializableLayer, len(layers)) + for i, l := range layers { + s, ok := l.(gopacket.SerializableLayer) + if !ok { + return nil, serrors.New("invalid SCMP layer, not serializable", "index", i) + } + ret[i] = s + } + return ret, nil } // TODO(matzf) this function is now only used to update the OneHop-path. diff --git a/router/dataplane_internal_test.go b/router/dataplane_internal_test.go index 9fa22fb4a2..7b9914e7f7 100644 --- a/router/dataplane_internal_test.go +++ b/router/dataplane_internal_test.go @@ -42,7 +42,9 @@ import ( "github.com/scionproto/scion/router/mock_router" ) -var testKey = []byte("testkey_xxxxxxxx") +var ( + testKey = []byte("testkey_xxxxxxxx") +) // TestReceiver sets up a mocked batchConn, starts the receiver that reads from // this batchConn and forwards it to the processing routines channels. We verify @@ -444,8 +446,7 @@ func TestSlowPathProcessing(t *testing.T) { nil, mock_router.NewMockBatchConn(ctrl), fakeInternalNextHops, map[addr.SVC][]*net.UDPAddr{}, - xtest.MustParseIA("1-ff00:0:110"), - nil, testKey) + xtest.MustParseIA("1-ff00:0:110"), nil, testKey) }, mockMsg: func() []byte { spkt := prepBaseMsg(t, payload, 0) diff --git a/router/dataplane_test.go b/router/dataplane_test.go index 105a0ef6a8..7750bb1861 100644 --- a/router/dataplane_test.go +++ b/router/dataplane_test.go @@ -52,7 +52,11 @@ import ( "github.com/scionproto/scion/router/mock_router" ) -var metrics = router.GetMetrics() +var ( + metrics = router.GetMetrics() + srcUDPPort = 50001 + dstUDPPort = 50002 +) func TestDataPlaneAddInternalInterface(t *testing.T) { internalIP := netip.MustParseAddr("198.51.100.1") @@ -652,7 +656,7 @@ func TestProcessPkt(t *testing.T) { dpath.HopFields[2].Mac = computeMAC(t, key, dpath.InfoFields[0], dpath.HopFields[2]) ret := toMsg(t, spkt, dpath) if afterProcessing { - ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: topology.EndhostPort} + ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: dstUDPPort} ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil } return ret @@ -1247,7 +1251,7 @@ func TestProcessPkt(t *testing.T) { addr.SvcCS: { &net.UDPAddr{ IP: net.ParseIP("10.0.200.200").To4(), - Port: topology.EndhostPort, + Port: dstUDPPort, }, }, }, @@ -1267,7 +1271,7 @@ func TestProcessPkt(t *testing.T) { ret := toMsg(t, spkt, dpath) if afterProcessing { ret.Addr = &net.UDPAddr{IP: net.ParseIP("10.0.200.200").To4(), - Port: topology.EndhostPort} + Port: dstUDPPort} ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil } return ret @@ -1285,7 +1289,7 @@ func TestProcessPkt(t *testing.T) { map[addr.SVC][]*net.UDPAddr{ addr.SvcCS: {&net.UDPAddr{ IP: net.ParseIP("172.0.2.10"), - Port: topology.EndhostPort, + Port: dstUDPPort, }}, }, xtest.MustParseIA("1-ff00:0:110"), @@ -1330,7 +1334,7 @@ func TestProcessPkt(t *testing.T) { ret := toMsg(t, spkt, dpath) ret.Addr = &net.UDPAddr{ IP: net.ParseIP("172.0.2.10"), - Port: topology.EndhostPort, + Port: dstUDPPort, } ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil return ret @@ -1390,7 +1394,7 @@ func TestProcessPkt(t *testing.T) { map[addr.SVC][]*net.UDPAddr{ addr.SvcCS: {&net.UDPAddr{ IP: net.ParseIP("172.0.2.10"), - Port: topology.EndhostPort, + Port: dstUDPPort, }}, }, xtest.MustParseIA("1-ff00:0:110"), nil, key) @@ -1596,9 +1600,13 @@ func toMsg(t *testing.T, spkt *slayers.SCION, dpath path.Path) *ipv4.Message { ret := &ipv4.Message{} spkt.Path = dpath buffer := gopacket.NewSerializeBuffer() + scionudpLayer := &slayers.UDP{} + scionudpLayer.SrcPort = uint16(srcUDPPort) + scionudpLayer.DstPort = uint16(dstUDPPort) + scionudpLayer.SetNetworkLayerForChecksum(spkt) payload := []byte("actualpayloadbytes") err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{FixLengths: true}, - spkt, gopacket.Payload(payload)) + spkt, scionudpLayer, gopacket.Payload(payload)) require.NoError(t, err) raw := buffer.Bytes() ret.Buffers = make([][]byte, 1) @@ -1619,7 +1627,7 @@ func prepBaseMsg(now time.Time) (*slayers.SCION, *scion.Decoded) { DstIA: xtest.MustParseIA("4-ff00:0:411"), SrcIA: xtest.MustParseIA("2-ff00:0:222"), Path: &scion.Raw{}, - PayloadLen: 18, + PayloadLen: 26, // scionudpLayer + len("actualpayloadbytes") } dpath := &scion.Decoded{ @@ -1697,7 +1705,7 @@ func toIP(t *testing.T, spkt *slayers.SCION, path path.Path, afterProcessing boo require.NoError(t, spkt.SetDstAddr(dst)) ret := toMsg(t, spkt, path) if afterProcessing { - ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: topology.EndhostPort} + ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: dstUDPPort} ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil } return ret diff --git a/router/export_test.go b/router/export_test.go index f8134b4a3d..96faeed914 100644 --- a/router/export_test.go +++ b/router/export_test.go @@ -25,6 +25,11 @@ import ( "github.com/scionproto/scion/private/topology" ) +var ( + dispatchedPortStart = 1024 + dispatchedPortEnd = 1<<16 - 1 +) + var metrics = NewMetrics() func GetMetrics() *Metrics { @@ -50,15 +55,17 @@ func NewDP( key []byte) *DataPlane { dp := &DataPlane{ - localIA: local, - external: external, - linkTypes: linkTypes, - neighborIAs: neighbors, - internalNextHops: internalNextHops, - svc: &services{m: svc}, - internal: internal, - internalIP: netip.MustParseAddr("198.51.100.1"), - Metrics: metrics, + localIA: local, + external: external, + linkTypes: linkTypes, + neighborIAs: neighbors, + internalNextHops: internalNextHops, + dispatchedPortStart: uint16(dispatchedPortStart), + dispatchedPortEnd: uint16(dispatchedPortEnd), + svc: &services{m: svc}, + internal: internal, + internalIP: netip.MustParseAddr("198.51.100.1"), + Metrics: metrics, } if err := dp.SetKey(key); err != nil { panic(err) diff --git a/scion-pki/certs/BUILD.bazel b/scion-pki/certs/BUILD.bazel index 21de49c53f..82aaab42ac 100644 --- a/scion-pki/certs/BUILD.bazel +++ b/scion-pki/certs/BUILD.bazel @@ -32,7 +32,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/addrutil:go_default_library", "//pkg/snet/squic:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/app:go_default_library", "//private/app/appnet:go_default_library", "//private/app/command:go_default_library", diff --git a/scion-pki/certs/renew.go b/scion-pki/certs/renew.go index 4bb546a182..bb7f5449e8 100644 --- a/scion-pki/certs/renew.go +++ b/scion-pki/certs/renew.go @@ -45,7 +45,6 @@ import ( "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/addrutil" "github.com/scionproto/scion/pkg/snet/squic" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app" infraenv "github.com/scionproto/scion/private/app/appnet" "github.com/scionproto/scion/private/app/command" @@ -264,11 +263,9 @@ The template is expressed in JSON. A valid example:: return err } daemonAddr := envFlags.Daemon() - dispatcher := envFlags.Dispatcher() localIP := net.IP(envFlags.Local().AsSlice()) log.Debug("Resolved SCION environment flags", "daemon", daemonAddr, - "dispatcher", dispatcher, "local", localIP, ) @@ -420,12 +417,11 @@ The template is expressed in JSON. A valid example:: } r := renewer{ - LocalIA: info.IA, - LocalIP: localIP, - Daemon: sd, - Disatcher: dispatcher, - Timeout: flags.timeout, - StdErr: cmd.ErrOrStderr(), + LocalIA: info.IA, + LocalIP: localIP, + Daemon: sd, + Timeout: flags.timeout, + StdErr: cmd.ErrOrStderr(), PathOptions: func() []path.Option { pathOpts := []path.Option{ path.WithInteractive(flags.interactive), @@ -435,9 +431,8 @@ The template is expressed in JSON. A valid example:: } if !flags.noProbe { pathOpts = append(pathOpts, path.WithProbing(&path.ProbeConfig{ - LocalIA: info.IA, - LocalIP: localIP, - Dispatcher: dispatcher, + LocalIA: info.IA, + LocalIP: localIP, })) } return pathOpts @@ -742,19 +737,16 @@ func (r *renewer) requestRemote( } sn := &snet.SCIONNetwork{ - LocalIA: local.IA, - Dispatcher: &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(r.Disatcher), - SCMPHandler: snet.SCMPPropagationStopper{ - Handler: snet.DefaultSCMPHandler{ - RevocationHandler: daemon.RevHandler{Connector: r.Daemon}, - }, - Log: log.FromCtx(ctx).Debug, + Topology: r.Daemon, + SCMPHandler: snet.SCMPPropagationStopper{ + Handler: snet.DefaultSCMPHandler{ + RevocationHandler: daemon.RevHandler{Connector: r.Daemon}, }, + Log: log.FromCtx(ctx).Debug, }, } - conn, err := sn.Listen(ctx, "udp", local.Host, addr.SvcNone) + conn, err := sn.Listen(ctx, "udp", local.Host) if err != nil { return nil, serrors.WrapStr("dialing", err) } @@ -767,9 +759,9 @@ func (r *renewer) requestRemote( }, SVCRouter: svcRouter{Connector: r.Daemon}, Resolver: &svc.Resolver{ - LocalIA: local.IA, - ConnFactory: sn.Dispatcher, - LocalIP: local.Host.IP, + LocalIA: local.IA, + Network: sn, + LocalIP: local.Host.IP, }, SVCResolutionFraction: 1, }, diff --git a/scion.sh b/scion.sh index 0f4851be1e..2d92c5d14f 100755 --- a/scion.sh +++ b/scion.sh @@ -24,9 +24,6 @@ cmd_topology() { echo "Create topology, configuration, and execution files." tools/topogen.py "$@" - if is_docker_be; then - ./tools/quiet ./tools/dc run utils_chowner - fi } cmd_topodot() { @@ -90,22 +87,10 @@ cmd_mstart() { run_setup() { tools/set_ipv6_addr.py -a - # Ensure base dir for dispatcher socket exists; on ubuntu this symbolic link to /dev/shm always exists. - if [ ! -d /run/shm/ ]; then - sudo ln -s /dev/shm /run/shm; - fi - # Create dispatcher dir or change owner - local disp_dir="/run/shm/dispatcher" - [ -d "$disp_dir" ] || mkdir "$disp_dir" - [ $(stat -c "%U" "$disp_dir") == "$LOGNAME" ] || { sudo -p "Fixing ownership of $disp_dir - [sudo] password for %p: " chown $LOGNAME: "$disp_dir"; } } run_teardown() { tools/set_ipv6_addr.py -d - local disp_dir="/run/shm/dispatcher" - if [ -e "$disp_dir" ]; then - find "$disp_dir" -xdev -mindepth 1 -print0 | xargs -r0 rm -v - fi } stop_scion() { diff --git a/scion/cmd/scion/BUILD.bazel b/scion/cmd/scion/BUILD.bazel index 4e60f134cb..db17255736 100644 --- a/scion/cmd/scion/BUILD.bazel +++ b/scion/cmd/scion/BUILD.bazel @@ -24,7 +24,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/addrutil:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/app:go_default_library", "//private/app/command:go_default_library", "//private/app/flag:go_default_library", diff --git a/scion/cmd/scion/ping.go b/scion/cmd/scion/ping.go index df6bf02ac1..d5a7ec3bf1 100644 --- a/scion/cmd/scion/ping.go +++ b/scion/cmd/scion/ping.go @@ -33,7 +33,6 @@ import ( "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/addrutil" snetpath "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app" "github.com/scionproto/scion/private/app/flag" "github.com/scionproto/scion/private/app/path" @@ -131,11 +130,9 @@ On other errors, ping will exit with code 2. return err } daemonAddr := envFlags.Daemon() - dispatcher := envFlags.Dispatcher() localIP := net.IP(envFlags.Local().AsSlice()) log.Debug("Resolved SCION environment flags", "daemon", daemonAddr, - "dispatcher", dispatcher, "local", localIP, ) @@ -167,9 +164,8 @@ On other errors, ping will exit with code 2. } if flags.healthyOnly { opts = append(opts, path.WithProbing(&path.ProbeConfig{ - LocalIA: info.IA, - LocalIP: localIP, - Dispatcher: dispatcher, + LocalIA: info.IA, + LocalIP: localIP, })) } path, err := path.Choose(traceCtx, sd, remote.IA, opts...) @@ -265,7 +261,7 @@ On other errors, ping will exit with code 2. } stats, err := ping.Run(ctx, ping.Config{ - Dispatcher: reliable.NewDispatcher(dispatcher), + Topology: sd, Attempts: count, Interval: flags.interval, Timeout: flags.timeout, diff --git a/scion/cmd/scion/showpaths.go b/scion/cmd/scion/showpaths.go index b0cd4480c9..55141ea5a1 100644 --- a/scion/cmd/scion/showpaths.go +++ b/scion/cmd/scion/showpaths.go @@ -99,11 +99,9 @@ On other errors, showpaths will exit with code 2. } flags.cfg.Daemon = envFlags.Daemon() - flags.cfg.Dispatcher = envFlags.Dispatcher() flags.cfg.Local = net.IP(envFlags.Local().AsSlice()) log.Debug("Resolved SCION environment flags", "daemon", flags.cfg.Daemon, - "dispatcher", flags.cfg.Dispatcher, "local", flags.cfg.Local, ) diff --git a/scion/cmd/scion/traceroute.go b/scion/cmd/scion/traceroute.go index 0bdf6a5233..8520d6a368 100644 --- a/scion/cmd/scion/traceroute.go +++ b/scion/cmd/scion/traceroute.go @@ -33,7 +33,6 @@ import ( "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/addrutil" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/app" "github.com/scionproto/scion/private/app/flag" "github.com/scionproto/scion/private/app/path" @@ -107,11 +106,9 @@ On other errors, traceroute will exit with code 2. return err } daemonAddr := envFlags.Daemon() - dispatcher := envFlags.Dispatcher() localIP := net.IP(envFlags.Local().AsSlice()) log.Debug("Resolved SCION environment flags", "daemon", daemonAddr, - "dispatcher", dispatcher, "local", localIP, ) @@ -184,7 +181,7 @@ On other errors, traceroute will exit with code 2. var stats traceroute.Stats var updates []traceroute.Update cfg := traceroute.Config{ - Dispatcher: reliable.NewDispatcher(dispatcher), + Topology: sd, Remote: remote, MTU: path.Metadata().MTU, Local: local, diff --git a/scion/ping/BUILD.bazel b/scion/ping/BUILD.bazel index 964b11c68e..a828aadeea 100644 --- a/scion/ping/BUILD.bazel +++ b/scion/ping/BUILD.bazel @@ -15,7 +15,6 @@ go_library( "//pkg/private/serrors:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/topology/underlay:go_default_library", ], ) diff --git a/scion/ping/ping.go b/scion/ping/ping.go index 8fc46e9b3c..af0a983d1a 100644 --- a/scion/ping/ping.go +++ b/scion/ping/ping.go @@ -22,12 +22,10 @@ import ( "sync" "time" - "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/topology/underlay" ) @@ -74,9 +72,12 @@ func (s State) String() string { // Config configures the ping run. type Config struct { - Dispatcher reliable.Dispatcher - Local *snet.UDPAddr - Remote *snet.UDPAddr + Local *snet.UDPAddr + Remote *snet.UDPAddr + + // Topology is the helper class to get control-plane information for the + // local AS. + Topology snet.Topology // Attempts is the number of pings to send. Attempts uint16 @@ -103,22 +104,25 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { } replies := make(chan reply, 10) - - id := snet.RandomSCMPIdentifer() - svc := snet.DefaultPacketDispatcherService{ - Dispatcher: cfg.Dispatcher, - SCMPHandler: scmpHandler{ - id: id, - replies: replies, - }, + scmpHandler := &scmpHandler{ + replies: replies, + } + sn := &snet.SCIONNetwork{ + SCMPHandler: scmpHandler, + Topology: cfg.Topology, } - conn, port, err := svc.Register(ctx, cfg.Local.IA, cfg.Local.Host, addr.SvcNone) + conn, err := sn.OpenRaw(ctx, cfg.Local.Host) if err != nil { return Stats{}, err } local := cfg.Local.Copy() - local.Host.Port = int(port) + local.Host = conn.LocalAddr().(*net.UDPAddr) + + // we set the identifier on the handler to the same value as + // the udp port + id := local.Host.Port + scmpHandler.SetId(id) // we need to have at least 8 bytes to store the request time in the // payload. @@ -131,7 +135,7 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { timeout: cfg.Timeout, pldSize: cfg.PayloadSize, pld: make([]byte, cfg.PayloadSize), - id: id, + id: uint16(id), conn: conn, local: local, replies: replies, @@ -320,6 +324,10 @@ func (h scmpHandler) Handle(pkt *snet.Packet) error { return nil } +func (h *scmpHandler) SetId(id int) { + h.id = uint16(id) +} + func (h scmpHandler) handle(pkt *snet.Packet) (snet.SCMPEchoReply, error) { if pkt.Payload == nil { return snet.SCMPEchoReply{}, serrors.New("no v2 payload found") diff --git a/scion/showpaths/config.go b/scion/showpaths/config.go index bae04756f1..dcc4bdcad1 100644 --- a/scion/showpaths/config.go +++ b/scion/showpaths/config.go @@ -38,9 +38,6 @@ type Config struct { // Sequence is a string of space separated Hop Predicates that is used for // filtering. Sequence string - // Dispatcher is the path to the dispatcher socket. Leaving this empty uses - // the default dispatcher socket value. - Dispatcher string // Epic filters paths for which EPIC is not available, and when probing, the // EPIC path type header is used. Epic bool diff --git a/scion/showpaths/showpaths.go b/scion/showpaths/showpaths.go index 2e950e0b69..85b6c0b833 100644 --- a/scion/showpaths/showpaths.go +++ b/scion/showpaths/showpaths.go @@ -352,11 +352,10 @@ func Run(ctx context.Context, dst addr.IA, cfg Config) (*Result, error) { if !cfg.NoProbe { p := pathprobe.FilterEmptyPaths(paths) statuses, err = pathprobe.Prober{ - DstIA: dst, - LocalIA: localIA, - LocalIP: cfg.Local, - ID: snet.RandomSCMPIdentifer(), - Dispatcher: cfg.Dispatcher, + DstIA: dst, + LocalIA: localIA, + LocalIP: cfg.Local, + Topology: sdConn, }.GetStatuses(ctx, p, pathprobe.WithEPIC(cfg.Epic)) if err != nil { return nil, serrors.WrapStr("getting statuses", err) diff --git a/scion/traceroute/BUILD.bazel b/scion/traceroute/BUILD.bazel index b58bd9fe4a..0fda29f297 100644 --- a/scion/traceroute/BUILD.bazel +++ b/scion/traceroute/BUILD.bazel @@ -13,6 +13,5 @@ go_library( "//pkg/slayers/path/scion:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", ], ) diff --git a/scion/traceroute/traceroute.go b/scion/traceroute/traceroute.go index 2893ab983d..bcecbb9e04 100644 --- a/scion/traceroute/traceroute.go +++ b/scion/traceroute/traceroute.go @@ -28,7 +28,6 @@ import ( "github.com/scionproto/scion/pkg/slayers/path/scion" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" ) // Update contains the information for a single hop. @@ -56,8 +55,8 @@ type Stats struct { // Config configures the traceroute run. type Config struct { - Dispatcher reliable.Dispatcher Local *snet.UDPAddr + Topology snet.Topology MTU uint16 PathEntry snet.Path PayloadSize uint @@ -99,18 +98,17 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { if _, isEmpty := cfg.PathEntry.Dataplane().(path.Empty); isEmpty { return Stats{}, serrors.New("empty path is not allowed for traceroute") } - id := snet.RandomSCMPIdentifer() replies := make(chan reply, 10) - dispatcher := snet.DefaultPacketDispatcherService{ - Dispatcher: cfg.Dispatcher, + sn := &snet.SCIONNetwork{ SCMPHandler: scmpHandler{replies: replies}, + Topology: cfg.Topology, } - conn, port, err := dispatcher.Register(ctx, cfg.Local.IA, cfg.Local.Host, addr.SvcNone) + conn, err := sn.OpenRaw(ctx, cfg.Local.Host) if err != nil { return Stats{}, err } local := cfg.Local.Copy() - local.Host.Port = int(port) + local.Host = conn.LocalAddr().(*net.UDPAddr) t := tracerouter{ probesPerHop: cfg.ProbesPerHop, timeout: cfg.Timeout, @@ -120,7 +118,7 @@ func Run(ctx context.Context, cfg Config) (Stats, error) { replies: replies, errHandler: cfg.ErrHandler, updateHandler: cfg.UpdateHandler, - id: id, + id: uint16(conn.LocalAddr().(*net.UDPAddr).Port), path: cfg.PathEntry, epic: cfg.EPIC, } diff --git a/tools/braccept/cases/child_to_internal.go b/tools/braccept/cases/child_to_internal.go index 88d5fcdcb9..58b5c1c0c2 100644 --- a/tools/braccept/cases/child_to_internal.go +++ b/tools/braccept/cases/child_to_internal.go @@ -33,7 +33,11 @@ import ( ) // ChildToInternalHost tests traffic from a child to an AS host. -func ChildToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { +func ChildToInternalHost( + artifactsDir string, + mac hash.Hash, +) runner.Case { + const endhostPort = 21000 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -106,7 +110,7 @@ func ChildToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { scionudp := &slayers.UDP{} scionudp.SrcPort = 2345 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(endhostPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -127,8 +131,7 @@ func ChildToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { // IP4: Src=192.168.0.11 Dst=192.168.0.51 Checksum=0 ip.SrcIP = net.IP{192, 168, 0, 11} ip.DstIP = net.IP{192, 168, 0, 51} - // UDP: Src=30001 Dst=30041 - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(scionudp.DstPort) sp.InfoFields[0].UpdateSegID(sp.HopFields[1].Mac) if err := gopacket.SerializeLayers(want, options, @@ -149,7 +152,11 @@ func ChildToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { // ChildToInternalHostShortcut tests traffic from a child to an AS host with a // short-cut path. I.e., a path where only a partial path segment is used. -func ChildToInternalHostShortcut(artifactsDir string, mac hash.Hash) runner.Case { +func ChildToInternalHostShortcut( + artifactsDir string, + mac hash.Hash, +) runner.Case { + const endhostPort = 21000 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -213,7 +220,7 @@ func ChildToInternalHostShortcut(artifactsDir string, mac hash.Hash) runner.Case scionudp := &slayers.UDP{} scionudp.SrcPort = 2345 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(endhostPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -234,8 +241,7 @@ func ChildToInternalHostShortcut(artifactsDir string, mac hash.Hash) runner.Case // IP4: Src=192.168.0.11 Dst=192.168.0.51 Checksum=0 ip.SrcIP = net.IP{192, 168, 0, 11} ip.DstIP = net.IP{192, 168, 0, 51} - // UDP: Src=30001 Dst=30041 - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(endhostPort) sp.InfoFields[0].UpdateSegID(sp.HopFields[1].Mac) if err := gopacket.SerializeLayers(want, options, diff --git a/tools/braccept/cases/onehop.go b/tools/braccept/cases/onehop.go index 56890a6139..be28bb0f3b 100644 --- a/tools/braccept/cases/onehop.go +++ b/tools/braccept/cases/onehop.go @@ -34,7 +34,11 @@ import ( ) // IncomingOneHop tests one-hop being sent from the remote AS to the local AS. -func IncomingOneHop(artifactsDir string, mac hash.Hash) runner.Case { +func IncomingOneHop( + artifactsDir string, + mac hash.Hash, +) runner.Case { + const endhostPort = 21000 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -88,7 +92,7 @@ func IncomingOneHop(artifactsDir string, mac hash.Hash) runner.Case { scionudp := &slayers.UDP{} scionudp.SrcPort = 2345 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(endhostPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -107,7 +111,7 @@ func IncomingOneHop(artifactsDir string, mac hash.Hash) runner.Case { ethernet.DstMAC = net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0xbe, 0xef} ip.SrcIP = net.IP{192, 168, 0, 11} ip.DstIP = net.IP{192, 168, 0, 71} - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(endhostPort) // Second hop in OHP should have been set by BR. ohp.SecondHop.ConsIngress = 131 ohp.SecondHop.Mac = path.MAC(mac, ohp.Info, ohp.SecondHop, nil) diff --git a/tools/braccept/cases/parent_to_internal.go b/tools/braccept/cases/parent_to_internal.go index cdc77ac649..2892915dc9 100644 --- a/tools/braccept/cases/parent_to_internal.go +++ b/tools/braccept/cases/parent_to_internal.go @@ -33,7 +33,11 @@ import ( ) // ParentToInternalHost test traffic from a parent to an AS host. -func ParentToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { +func ParentToInternalHost( + artifactsDir string, + mac hash.Hash, +) runner.Case { + const endhostPort = 21000 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -101,7 +105,7 @@ func ParentToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { scionudp := &slayers.UDP{} scionudp.SrcPort = 2354 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(endhostPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -120,7 +124,7 @@ func ParentToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { ethernet.DstMAC = net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0xbe, 0xef} ip.SrcIP = net.IP{192, 168, 0, 11} ip.DstIP = net.IP{192, 168, 0, 51} - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(scionudp.DstPort) if err := gopacket.SerializeLayers(want, options, ethernet, ip, udp, scionL, scionudp, gopacket.Payload(payload), @@ -140,7 +144,11 @@ func ParentToInternalHost(artifactsDir string, mac hash.Hash) runner.Case { // ParentToInternalHostMultiSegment test traffic from a parent to an AS host // where two path segments are involved. -func ParentToInternalHostMultiSegment(artifactsDir string, mac hash.Hash) runner.Case { +func ParentToInternalHostMultiSegment( + artifactsDir string, + mac hash.Hash, +) runner.Case { + const endhostPort = 21000 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -215,7 +223,7 @@ func ParentToInternalHostMultiSegment(artifactsDir string, mac hash.Hash) runner scionudp := &slayers.UDP{} scionudp.SrcPort = 2354 - scionudp.DstPort = 53 + scionudp.DstPort = uint16(endhostPort) scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -234,7 +242,7 @@ func ParentToInternalHostMultiSegment(artifactsDir string, mac hash.Hash) runner ethernet.DstMAC = net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0xbe, 0xef} ip.SrcIP = net.IP{192, 168, 0, 11} ip.DstIP = net.IP{192, 168, 0, 51} - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(endhostPort) if err := gopacket.SerializeLayers(want, options, ethernet, ip, udp, scionL, scionudp, gopacket.Payload(payload), diff --git a/tools/braccept/cases/svc.go b/tools/braccept/cases/svc.go index 220522a031..b3adfea429 100644 --- a/tools/braccept/cases/svc.go +++ b/tools/braccept/cases/svc.go @@ -34,6 +34,7 @@ import ( // SVC tests resolution of SVC addresses. func SVC(artifactsDir string, mac hash.Hash) runner.Case { + const csPort = 20007 options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -103,7 +104,7 @@ func SVC(artifactsDir string, mac hash.Hash) runner.Case { } scionudp := &slayers.UDP{} scionudp.SrcPort = 2345 - scionudp.DstPort = 53 + scionudp.DstPort = 6789 scionudp.SetNetworkLayerForChecksum(scionL) payload := []byte("actualpayloadbytes") @@ -125,8 +126,7 @@ func SVC(artifactsDir string, mac hash.Hash) runner.Case { ip.SrcIP = net.IP{192, 168, 0, 11} // CS address from the topology file. ip.DstIP = net.IP{192, 168, 0, 71} - // UDP: Src=30001 Dst=30041 - udp.SrcPort, udp.DstPort = 30001, 30041 + udp.SrcPort, udp.DstPort = 30001, layers.UDPPort(csPort) sp.InfoFields[0].UpdateSegID(sp.HopFields[1].Mac) if err := gopacket.SerializeLayers(want, options, diff --git a/tools/braccept/main.go b/tools/braccept/main.go index dbc8a37172..cddeed995c 100644 --- a/tools/braccept/main.go +++ b/tools/braccept/main.go @@ -167,7 +167,7 @@ func loadKey(artifactsDir string) (hash.Hash, error) { // registerScionPorts registers the following UDP ports in gopacket such as SCION is the // next layer. In other words, map the following ports to expect SCION as the payload. func registerScionPorts() { - layers.RegisterUDPPortLayerType(layers.UDPPort(30041), slayers.LayerTypeSCION) + layers.RegisterUDPPortLayerType(layers.UDPPort(53), slayers.LayerTypeSCION) for i := 30000; i < 30010; i++ { layers.RegisterUDPPortLayerType(layers.UDPPort(i), slayers.LayerTypeSCION) } diff --git a/tools/end2end/BUILD.bazel b/tools/end2end/BUILD.bazel index 5007b8ff4b..6e44df874a 100644 --- a/tools/end2end/BUILD.bazel +++ b/tools/end2end/BUILD.bazel @@ -16,8 +16,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/metrics:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", - "//private/topology:go_default_library", "//private/tracing:go_default_library", "//tools/integration:go_default_library", "//tools/integration/integrationlib:go_default_library", diff --git a/tools/end2end/main.go b/tools/end2end/main.go index 8dff7dad6a..d16578140d 100644 --- a/tools/end2end/main.go +++ b/tools/end2end/main.go @@ -29,7 +29,6 @@ import ( "flag" "fmt" "net" - "net/netip" "os" "time" @@ -45,8 +44,6 @@ import ( "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/metrics" snetpath "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" - "github.com/scionproto/scion/private/topology" "github.com/scionproto/scion/private/tracing" libint "github.com/scionproto/scion/tools/integration" integration "github.com/scionproto/scion/tools/integration/integrationlib" @@ -137,27 +134,26 @@ func (s server) run() { sdConn := integration.SDConn() defer sdConn.Close() - connFactory := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), + sn := &snet.SCIONNetwork{ SCMPHandler: snet.DefaultSCMPHandler{ RevocationHandler: daemon.RevHandler{Connector: sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + PacketConnMetrics: scionPacketConnMetrics, + Topology: sdConn, } - conn, port, err := connFactory.Register(context.Background(), integration.Local.IA, - integration.Local.Host, addr.SvcNone) + conn, err := sn.Listen(context.Background(), "udp", integration.Local.Host) if err != nil { integration.LogFatal("Error listening", "err", err) } defer conn.Close() + localAddr := conn.LocalAddr().(*snet.UDPAddr) if len(os.Getenv(libint.GoIntegrationEnv)) > 0 { // Needed for integration test ready signal. - fmt.Printf("Port=%d\n", port) + fmt.Printf("Port=%d\n", localAddr.Host.Port) fmt.Printf("%s%s\n\n", libint.ReadySignal, integration.Local.IA) } - log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host, port)) - + log.Info("Listening", "local", fmt.Sprintf("%v:%d", localAddr.Host.IP, localAddr.Host.Port)) // Receive ping message for { if err := s.handlePing(conn); err != nil { @@ -166,26 +162,17 @@ func (s server) run() { } } -func (s server) handlePing(conn snet.PacketConn) error { - var p snet.Packet - var ov net.UDPAddr - if err := readFrom(conn, &p, &ov); err != nil { +func (s server) handlePing(conn *snet.Conn) error { + rawPld := make([]byte, common.MaxMTU) + n, clientAddr, err := readFrom(conn, rawPld) + if err != nil { return serrors.WrapStr("reading packet", err) } - udp, ok := p.Payload.(snet.UDPPayload) - if !ok { - return serrors.New("unexpected payload received", - "source", p.Source, - "destination", p.Destination, - "type", common.TypeOf(p.Payload), - ) - } + var pld Ping - if err := json.Unmarshal(udp.Payload, &pld); err != nil { + if err := json.Unmarshal(rawPld[:n], &pld); err != nil { return serrors.New("invalid payload contents", - "source", p.Source, - "destination", p.Destination, - "data", string(udp.Payload), + "data", string(rawPld), ) } @@ -206,17 +193,16 @@ func (s server) handlePing(conn snet.PacketConn) error { tracing.Error(span, err) return err } - + clientUDPAddr := clientAddr.(*snet.UDPAddr) if pld.Message != ping || !pld.Server.Equal(integration.Local.IA) { return withTag(serrors.New("unexpected data in payload", - "source", p.Source, - "destination", p.Destination, + "remote", clientUDPAddr, "data", pld, )) } - log.Info(fmt.Sprintf("Ping received from %s, sending pong.", p.Source)) + log.Info(fmt.Sprintf("Ping received from %v, sending pong.", clientUDPAddr)) raw, err := json.Marshal(Pong{ - Client: p.Source.IA, + Client: clientUDPAddr.IA, Server: integration.Local.IA, Message: pong, Trace: pld.Trace, @@ -224,36 +210,19 @@ func (s server) handlePing(conn snet.PacketConn) error { if err != nil { return withTag(serrors.WrapStr("packing pong", err)) } - - p.Destination, p.Source = p.Source, p.Destination - p.Payload = snet.UDPPayload{ - DstPort: udp.SrcPort, - SrcPort: udp.DstPort, - Payload: raw, - } - // reverse path - rpath, ok := p.Path.(snet.RawPath) - if !ok { - return serrors.New("unexpected path", "type", common.TypeOf(p.Path)) - } - replypather := snet.DefaultReplyPather{} - replyPath, err := replypather.ReplyPath(rpath) - if err != nil { - return serrors.WrapStr("creating reply path", err) - } - p.Path = replyPath // Send pong - if err := conn.WriteTo(&p, &ov); err != nil { + if _, err := conn.WriteTo(raw, clientUDPAddr); err != nil { return withTag(serrors.WrapStr("sending reply", err)) } - log.Info("Sent pong to", "client", p.Destination) + log.Info("Sent pong to", "client", clientUDPAddr) return nil } type client struct { - conn snet.PacketConn - port uint16 - sdConn daemon.Connector + network *snet.SCIONNetwork + conn *snet.Conn + sdConn daemon.Connector + errorPaths map[snet.PathFingerprint]struct{} } @@ -262,25 +231,20 @@ func (c *client) run() int { log.Info("Starting", "pair", pair) defer log.Info("Finished", "pair", pair) defer integration.Done(integration.Local.IA, remote.IA) - connFactory := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), + c.sdConn = integration.SDConn() + defer c.sdConn.Close() + c.network = &snet.SCIONNetwork{ SCMPHandler: snet.DefaultSCMPHandler{ - RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()}, + RevocationHandler: daemon.RevHandler{Connector: c.sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + PacketConnMetrics: scionPacketConnMetrics, + Topology: c.sdConn, } - - var err error - c.conn, c.port, err = connFactory.Register(context.Background(), integration.Local.IA, - integration.Local.Host, addr.SvcNone) - if err != nil { - integration.LogFatal("Unable to listen", "err", err) - } - log.Info("Send on", "local", - fmt.Sprintf("%v,[%v]:%d", integration.Local.IA, integration.Local.Host.IP, c.port)) - c.sdConn = integration.SDConn() - defer c.sdConn.Close() + log.Info("Send", "local", + fmt.Sprintf("%v,[%v] -> %v,[%v]", + integration.Local.IA, integration.Local.Host, + remote.IA, remote.Host)) c.errorPaths = make(map[snet.PathFingerprint]struct{}) return integration.AttemptRepeatedly("End2End", c.attemptRequest) } @@ -310,10 +274,12 @@ func (c *client) attemptRequest(n int) bool { } // Send ping - if err := c.ping(ctx, n, path); err != nil { + close, err := c.ping(ctx, n, path) + if err != nil { logger.Error("Could not send packet", "err", withTag(err)) return false } + defer close() // Receive pong if err := c.pong(ctx); err != nil { logger.Error("Error receiving pong", "err", withTag(err)) @@ -325,56 +291,33 @@ func (c *client) attemptRequest(n int) bool { return true } -func (c *client) ping(ctx context.Context, n int, path snet.Path) error { +func (c *client) ping(ctx context.Context, n int, path snet.Path) (func(), error) { rawPing, err := json.Marshal(Ping{ Server: remote.IA, Message: ping, Trace: tracing.IDFromCtx(ctx), }) if err != nil { - return serrors.WrapStr("packing ping", err) + return nil, serrors.WrapStr("packing ping", err) } - if err := c.conn.SetWriteDeadline(getDeadline(ctx)); err != nil { - return serrors.WrapStr("setting write deadline", err) + log.FromCtx(ctx).Info("Dialing", "remote", remote) + c.conn, err = c.network.Dial(ctx, "udp", integration.Local.Host, &remote) + if err != nil { + return nil, serrors.WrapStr("dialing conn", err) } - if remote.NextHop == nil { - remote.NextHop = &net.UDPAddr{ - IP: remote.Host.IP, - Port: topology.EndhostPort, - } + if err := c.conn.SetWriteDeadline(getDeadline(ctx)); err != nil { + return nil, serrors.WrapStr("setting write deadline", err) } - - remoteHostIP, ok := netip.AddrFromSlice(remote.Host.IP) - if !ok { - return serrors.New("invalid remote host IP", "ip", remote.Host.IP) + log.Info("sending ping", "attempt", n, "remote", c.conn.RemoteAddr()) + if _, err := c.conn.Write(rawPing); err != nil { + return nil, err } - localHostIP, ok := netip.AddrFromSlice(integration.Local.Host.IP) - if !ok { - return serrors.New("invalid local host IP", "ip", integration.Local.Host.IP) - } - pkt := &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Destination: snet.SCIONAddress{ - IA: remote.IA, - Host: addr.HostIP(remoteHostIP), - }, - Source: snet.SCIONAddress{ - IA: integration.Local.IA, - Host: addr.HostIP(localHostIP), - }, - Path: remote.Path, - Payload: snet.UDPPayload{ - SrcPort: c.port, - DstPort: uint16(remote.Host.Port), - Payload: rawPing, - }, - }, - } - log.Info("sending ping", "attempt", n, "path", path) - if err := c.conn.WriteTo(pkt, remote.NextHop); err != nil { - return err + closer := func() { + if err := c.conn.Close(); err != nil { + log.Error("Unable to close connection", "err", err) + } } - return nil + return closer, nil } func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) { @@ -436,20 +379,15 @@ func (c *client) pong(ctx context.Context) error { if err := c.conn.SetReadDeadline(getDeadline(ctx)); err != nil { return serrors.WrapStr("setting read deadline", err) } - var p snet.Packet - var ov net.UDPAddr - if err := readFrom(c.conn, &p, &ov); err != nil { + rawPld := make([]byte, common.MaxMTU) + n, serverAddr, err := readFrom(c.conn, rawPld) + if err != nil { return serrors.WrapStr("reading packet", err) } - udp, ok := p.Payload.(snet.UDPPayload) - if !ok { - return serrors.New("unexpected payload received", "type", common.TypeOf(p.Payload)) - } - var pld Pong - if err := json.Unmarshal(udp.Payload, &pld); err != nil { - return serrors.WrapStr("unpacking pong", err, "data", string(udp.Payload)) + if err := json.Unmarshal(rawPld[:n], &pld); err != nil { + return serrors.WrapStr("unpacking pong", err, "data", string(rawPld)) } expected := Pong{ @@ -460,7 +398,7 @@ func (c *client) pong(ctx context.Context) error { if pld.Client != expected.Client || pld.Server != expected.Server || pld.Message != pong { return serrors.New("unexpected contents received", "data", pld, "expected", expected) } - log.Info("Received pong", "server", p.Source) + log.Info("Received pong", "server", serverAddr) return nil } @@ -472,14 +410,14 @@ func getDeadline(ctx context.Context) time.Time { return dl } -func readFrom(conn snet.PacketConn, pkt *snet.Packet, ov *net.UDPAddr) error { - err := conn.ReadFrom(pkt, ov) +func readFrom(conn *snet.Conn, pld []byte) (int, net.Addr, error) { + n, remoteAddr, err := conn.ReadFrom(pld) // Attach more context to error var opErr *snet.OpError if !(errors.As(err, &opErr) && opErr.RevInfo() != nil) { - return err + return n, remoteAddr, err } - return serrors.WithCtx(err, + return n, remoteAddr, serrors.WithCtx(err, "isd_as", opErr.RevInfo().IA(), "interface", opErr.RevInfo().IfID, ) diff --git a/tools/end2end_integration/main.go b/tools/end2end_integration/main.go index 43efd2e093..5b4ff4cdf2 100644 --- a/tools/end2end_integration/main.go +++ b/tools/end2end_integration/main.go @@ -320,7 +320,7 @@ func clientTemplate(progressSock string) integration.Cmd { // remote[ISD/AS] is specified, h2:h2 and h1:h1. Not all combinations yield something useful... // caveat emptor. func getPairs() ([]integration.IAPair, error) { - pairs := integration.IAPairs(integration.DispAddr) + pairs := integration.IAPairs(integration.CSAddr) if subset == "all" { return pairs, nil } diff --git a/tools/end2endblast/BUILD.bazel b/tools/end2endblast/BUILD.bazel index e89d3c45cc..9a691e0751 100644 --- a/tools/end2endblast/BUILD.bazel +++ b/tools/end2endblast/BUILD.bazel @@ -16,7 +16,6 @@ go_library( "//pkg/snet:go_default_library", "//pkg/snet/metrics:go_default_library", "//pkg/snet/path:go_default_library", - "//pkg/sock/reliable:go_default_library", "//private/topology:go_default_library", "//tools/integration:go_default_library", "//tools/integration/integrationlib:go_default_library", diff --git a/tools/end2endblast/main.go b/tools/end2endblast/main.go index 74d04ef854..585eeeda44 100644 --- a/tools/end2endblast/main.go +++ b/tools/end2endblast/main.go @@ -40,7 +40,6 @@ import ( "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/metrics" snetpath "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/pkg/sock/reliable" "github.com/scionproto/scion/private/topology" libint "github.com/scionproto/scion/tools/integration" integration "github.com/scionproto/scion/tools/integration/integrationlib" @@ -124,28 +123,26 @@ func (s *server) run() { sdConn := integration.SDConn() defer sdConn.Close() - connFactory := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), + sn := &snet.SCIONNetwork{ SCMPHandler: snet.DefaultSCMPHandler{ RevocationHandler: daemon.RevHandler{Connector: sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + PacketConnMetrics: scionPacketConnMetrics, + Topology: sdConn, } - - conn, port, err := connFactory.Register(context.Background(), integration.Local.IA, - integration.Local.Host, addr.SvcNone) + conn, err := sn.OpenRaw(context.Background(), integration.Local.Host) if err != nil { integration.LogFatal("Error listening", "err", err) } defer conn.Close() + localAddr := conn.LocalAddr().(*net.UDPAddr) if len(os.Getenv(libint.GoIntegrationEnv)) > 0 { // Needed for integration test ready signal. - fmt.Printf("Port=%d\n", port) + fmt.Printf("Port=%d\n", localAddr.Port) fmt.Printf("%s%s\n\n", libint.ReadySignal, integration.Local.IA) } - - log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host, port)) + log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host.IP, localAddr.Port)) // Receive ping message for { @@ -239,23 +236,23 @@ func (c *client) run() int { log.Info("Starting", "pair", pair) defer log.Info("Finished", "pair", pair) defer integration.Done(integration.Local.IA, remote.IA) - connFactory := &snet.DefaultPacketDispatcherService{ - Dispatcher: reliable.NewDispatcher(""), + sn := &snet.SCIONNetwork{ SCMPHandler: snet.DefaultSCMPHandler{ - RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()}, + RevocationHandler: daemon.RevHandler{Connector: c.sdConn}, SCMPErrors: scmpErrorsCounter, }, - SCIONPacketConnMetrics: scionPacketConnMetrics, + PacketConnMetrics: scionPacketConnMetrics, + Topology: c.sdConn, } var err error - c.conn, c.port, err = connFactory.Register(context.Background(), integration.Local.IA, - integration.Local.Host, addr.SvcNone) + c.conn, err = sn.OpenRaw(context.Background(), integration.Local.Host) if err != nil { integration.LogFatal("Unable to listen", "err", err) } + port := c.conn.LocalAddr().(*net.UDPAddr).Port log.Info("Send on", "local", - fmt.Sprintf("%v,[%v]:%d", integration.Local.IA, integration.Local.Host.IP, c.port)) + fmt.Sprintf("%v,[%v]:%d", integration.Local.IA, integration.Local.Host.IP, port)) c.sdConn = integration.SDConn() defer c.sdConn.Close() diff --git a/tools/integration/integration.go b/tools/integration/integration.go index 991b708e72..518bef3de7 100644 --- a/tools/integration/integration.go +++ b/tools/integration/integration.go @@ -218,12 +218,8 @@ func generateAllSrcDst(hostAddr HostAddr, unique bool) []IAPair { type HostAddr func(ia addr.IA) *snet.UDPAddr -// DispAddr reads the CS host Addr from the topology for the specified IA. In general this -// could be the IP of any service (PS/BS/CS) in that IA because they share the same dispatcher in -// the dockerized topology. -// The host IP is used as client or server address in the tests because the testing container is -// connecting to the dispatcher of the services. -var DispAddr HostAddr = func(ia addr.IA) *snet.UDPAddr { +// CSAddr reads the CS host Addr from the topology for the specified IA. +var CSAddr HostAddr = func(ia addr.IA) *snet.UDPAddr { if a := loadAddr(ia); a != nil { return a } diff --git a/tools/integration/integrationlib/common.go b/tools/integration/integrationlib/common.go index b215a97ae3..f9b2388a3b 100644 --- a/tools/integration/integrationlib/common.go +++ b/tools/integration/integrationlib/common.go @@ -70,6 +70,8 @@ func addFlags() error { if err != nil { return serrors.WrapStr("reading scion environment", err) } + // TODO(JordiSubira): Make this flag optional and consider the same case as Unspecified + // if it isn't explicitly set. flag.Var(&Local, "local", "(Mandatory) address to listen on") flag.StringVar(&Mode, "mode", ModeClient, "Run in "+ModeClient+" or "+ModeServer+" mode") flag.StringVar(&Progress, "progress", "", "Socket to write progress to") diff --git a/tools/scion_integration/main.go b/tools/scion_integration/main.go index 8964b6c416..5f993754f0 100644 --- a/tools/scion_integration/main.go +++ b/tools/scion_integration/main.go @@ -110,7 +110,7 @@ func realMain() int { } log.Info(fmt.Sprintf("Run scion %s tests:", tc.Name)) in := integration.NewBinaryIntegration(tc.Name, integration.WrapperCmd, tc.Args, nil) - pairs := tc.Pairs(integration.DispAddr) + pairs := tc.Pairs(integration.CSAddr) err := integration.RunUnaryTests(in, pairs, integration.DefaultRunTimeout, tc.OutputCheck) if err != nil { log.Error(fmt.Sprintf("Error during scion %s tests", tc.Name), "err", err) diff --git a/tools/topology/config.py b/tools/topology/config.py index aa70bfbb2e..fffff91be5 100644 --- a/tools/topology/config.py +++ b/tools/topology/config.py @@ -31,6 +31,7 @@ DEFAULT_MTU, DEFAULT6_NETWORK, NETWORKS_FILE, + DEFAULT_DISPATCHED_PORTS, ) from topology.scion_addr import ISD_AS from topology.util import write_file @@ -85,6 +86,7 @@ def _read_defaults(self, network): self.subnet_gen4 = SubnetGenerator(DEFAULT_NETWORK, self.args.docker) self.subnet_gen6 = SubnetGenerator(DEFAULT6_NETWORK, self.args.docker) self.default_mtu = defaults.get("mtu", DEFAULT_MTU) + self.dispatched_ports = defaults.get("dispatched_ports", DEFAULT_DISPATCHED_PORTS) def generate_all(self): """ @@ -139,7 +141,8 @@ def _generate_topology(self): def _topo_args(self): return TopoGenArgs(self.args, self.topo_config, self.subnet_gen4, - self.subnet_gen6, self.default_mtu) + self.subnet_gen6, self.default_mtu, + self.dispatched_ports) def _generate_supervisor(self, topo_dicts): args = self._supervisor_args(topo_dicts) diff --git a/tools/topology/defines.py b/tools/topology/defines.py index bef592788c..f5c9b3bdf2 100644 --- a/tools/topology/defines.py +++ b/tools/topology/defines.py @@ -33,6 +33,8 @@ #: Default SCION router UDP port. SCION_ROUTER_PORT = 50000 +DEFAULT_DISPATCHED_PORTS = "31000-32767" + #: Default MTU - assumes overlay is ipv4+udp DEFAULT_MTU = 1500 - 20 - 8 #: IPv6 min value diff --git a/tools/topology/docker.py b/tools/topology/docker.py index 00a69962b3..ba59b9db58 100644 --- a/tools/topology/docker.py +++ b/tools/topology/docker.py @@ -175,7 +175,6 @@ def _control_service_conf(self, topo_id, topo, base): 'volumes': [ self._cache_vol(), '%s:/etc/scion:ro' % base, - self._disp_vol(k), ], 'command': ['--config', '/etc/scion/%s.toml' % k] } @@ -189,14 +188,10 @@ def _dispatcher_conf(self, topo_id, topo, base): 'networks': {}, 'user': self.user, 'volumes': [], - 'depends_on': { - 'utils_chowner': { - 'condition': 'service_started' - }, - }, } - keys = (list(topo.get("control_service", {})) + - ["tester_%s" % topo_id.file_fmt()]) + keys = list(topo.get("control_service", {})) + if topo.get("test_dispatcher"): + keys.append("tester_%s" % topo_id.file_fmt()) for disp_id in keys: entry = copy.deepcopy(base_entry) net_key = disp_id @@ -208,7 +203,6 @@ def _dispatcher_conf(self, topo_id, topo, base): entry['networks'][self.bridges[net['net']]] = { '%s_address' % ipv: ip } - entry['volumes'].append(self._disp_vol(disp_id)) conf = '%s:/etc/scion:rw' % base entry['volumes'].append(conf) entry['command'] = [ @@ -216,8 +210,6 @@ def _dispatcher_conf(self, topo_id, topo, base): ] self.dc_conf['services']['disp_%s' % disp_id] = entry - self.dc_conf['volumes'][self._disp_vol(disp_id).split(':') - [0]] = None def _sciond_conf(self, topo_id, base): name = sciond_name(topo_id) @@ -235,7 +227,6 @@ def _sciond_conf(self, topo_id, base): 'user': self.user, 'volumes': [ - self._disp_vol(disp_id), self._cache_vol(), '%s:/etc/scion:ro' % base ], @@ -248,8 +239,5 @@ def _sciond_conf(self, topo_id, base): } self.dc_conf['services'][name] = entry - def _disp_vol(self, disp_id): - return 'vol_disp_%s:/run/shm/dispatcher:rw' % disp_id - def _cache_vol(self): return self.output_base + '/gen-cache:/share/cache:rw' diff --git a/tools/topology/docker_utils.py b/tools/topology/docker_utils.py index 90d4041f51..6bd9be0b21 100644 --- a/tools/topology/docker_utils.py +++ b/tools/topology/docker_utils.py @@ -49,49 +49,40 @@ def __init__(self, args): self.output_base = os.environ.get('SCION_OUTPUT_BASE', os.getcwd()) def generate(self): - self._utils_conf() for topo_id in self.args.topo_dicts: self._test_conf(topo_id) if self.args.sig: self._sig_testing_conf() return self.dc_conf - def _utils_conf(self): - entry_chown = { - 'image': 'busybox', - 'network_mode': 'none', - 'volumes': [ - '/etc/passwd:/etc/passwd:ro', - '/etc/group:/etc/group:ro' - ], - 'command': 'chown -R ' + self.user + ' /mnt/volumes' - } - for volume in self.dc_conf['volumes']: - entry_chown['volumes'].append('%s:/mnt/volumes/%s' % (volume, volume)) - self.dc_conf['services']['utils_chowner'] = entry_chown - def _test_conf(self, topo_id): cntr_base = '/share' name = 'tester_%s' % topo_id.file_fmt() entry = { 'image': docker_image(self.args, 'tester'), - 'depends_on': ['disp_%s' % name], 'privileged': True, 'entrypoint': 'sh tester.sh', 'environment': {}, # 'user': self.user, 'volumes': [ - 'vol_disp_%s:/run/shm/dispatcher:rw' % name, self.output_base + '/logs:' + cntr_base + '/logs:rw', self.output_base + '/gen:' + cntr_base + '/gen:rw', self.output_base + '/gen-certs:' + cntr_base + '/gen-certs:rw' ], - 'network_mode': 'service:disp_%s' % name, } net = self.args.networks[name][0] ipv = 'ipv4' if ipv not in net: ipv = 'ipv6' + ip = str(net[ipv]) + if 'disp_%s' % name in self.dc_conf['services']: + entry['depends_on'] = ['disp_%s' % name] + entry.update({'network_mode': 'service:disp_%s' % name}) + else: + entry['networks'] = {} + entry['networks'][self.args.bridges[net['net']]] = { + '%s_address' % ipv: ip + } disp_net = self.args.networks[name][0] entry['environment']['SCION_LOCAL_ADDR'] = str(disp_net[ipv]) sciond_net = self.args.networks['sd%s' % topo_id.file_fmt()][0] diff --git a/tools/topology/go.py b/tools/topology/go.py index dcc307e2a2..72a1902097 100644 --- a/tools/topology/go.py +++ b/tools/topology/go.py @@ -113,7 +113,6 @@ def _build_control_service_conf(self, topo_id, ia, base, name, infra_elem, ca): 'general': { 'id': name, 'config_dir': config_dir, - 'reconnect_to_dispatcher': True, }, 'log': self._log_entry(name), 'trust_db': { @@ -148,7 +147,6 @@ def _build_sciond_conf(self, topo_id, ia, base): 'general': { 'id': name, 'config_dir': config_dir, - 'reconnect_to_dispatcher': True, }, 'log': self._log_entry(name), 'trust_db': { @@ -177,13 +175,13 @@ def generate_disp(self): else: elem_dir = os.path.join(self.args.output_dir, "dispatcher") config_file_path = os.path.join(elem_dir, DISP_CONFIG_NAME) - write_file(config_file_path, toml.dumps(self._build_disp_conf("dispatcher"))) + write_file(config_file_path, toml.dumps(self._build_disp_conf( + "dispatcher"))) def _gen_disp_docker(self): for topo_id, topo in self.args.topo_dicts.items(): base = topo_id.base_dir(self.args.output_dir) elem_ids = ['sig_%s' % topo_id.file_fmt()] + \ - list(topo.get("border_routers", {})) + \ list(topo.get("control_service", {})) + \ ['tester_%s' % topo_id.file_fmt()] for k in elem_ids: @@ -196,9 +194,11 @@ def _build_disp_conf(self, name, topo_id=None): self.args.networks, DISP_PROM_PORT, name) api_addr = prom_addr_dispatcher(self.args.docker, topo_id, self.args.networks, DISP_PROM_PORT+700, name) - return { + srv_addresses = self._build_srv_addresses(self.args.docker, name, topo_id) + tomlDict = { 'dispatcher': { 'id': name, + 'local_udp_forwarding': True, }, 'log': self._log_entry(name), 'metrics': { @@ -209,6 +209,26 @@ def _build_disp_conf(self, name, topo_id=None): 'addr': api_addr, }, } + if len(srv_addresses) > 1: + tomlDict["dispatcher"]["service_addresses"] = srv_addresses + return tomlDict + + def _build_srv_addresses(self, docker, name, topo_id): + srv_addresses = dict() + if docker: + if name.startswith("disp_cs"): + topo = self.args.topo_dicts.get(topo_id) + cs_addresses = list(topo.get("control_service", {}).values()) + srv_addresses[str(topo_id)+",CS"] = cs_addresses[0]["addr"] + ds_addresses = list(topo.get("discovery_service", {}).values()) + srv_addresses[str(topo_id)+",DS"] = ds_addresses[0]["addr"] + else: + for topo_id, topo in self.args.topo_dicts.items(): + cs_addresses = list(topo.get("control_service", {}).values()) + srv_addresses[str(topo_id)+",CS"] = cs_addresses[0]["addr"] + ds_addresses = list(topo.get("discovery_service", {}).values()) + srv_addresses[str(topo_id)+",DS"] = ds_addresses[0]["addr"] + return srv_addresses def _tracing_entry(self): docker_ip = docker_host(self.args.docker) diff --git a/tools/topology/net.py b/tools/topology/net.py index 7968301f7f..7ac4aa4135 100644 --- a/tools/topology/net.py +++ b/tools/topology/net.py @@ -176,8 +176,11 @@ def _exclude_net(self, alloc, net): class PortGenerator(object): + # XXX(JordiSubira): We keep this in the default range. If the configured range, + # doesn't include the 31000-32767 range, the services will be able to operate + # with the shim dispatcher. def __init__(self): - self.iter = iter(range(31000, 35000)) + self.iter = iter(range(31000, 32767)) self._ports = defaultdict(lambda: next(self.iter)) def register(self, id_: str) -> int: diff --git a/tools/topology/sig.py b/tools/topology/sig.py index 8a965aa997..cec64fc57d 100644 --- a/tools/topology/sig.py +++ b/tools/topology/sig.py @@ -71,16 +71,10 @@ def _dispatcher_conf(self, topo_id, base): # Create dispatcher config entry = { 'image': docker_image(self.args, 'dispatcher'), - 'depends_on': { - 'utils_chowner': { - 'condition': 'service_started' - }, - }, 'user': self.user, 'networks': {}, 'volumes': [ - self._disp_vol(topo_id), '%s:/etc/scion:rw' % base, ], 'command': @@ -97,8 +91,6 @@ def _dispatcher_conf(self, topo_id, base): } self.dc_conf['services']['disp_sig_%s' % topo_id.file_fmt()] = entry - vol_name = 'vol_disp_sig_%s' % topo_id.file_fmt() - self.dc_conf['volumes'][vol_name] = None def _sig_dc_conf(self, topo_id, base): setup_name = 'sig_setup_%s' % topo_id.file_fmt() @@ -122,7 +114,6 @@ def _sig_dc_conf(self, topo_id, base): }, 'cap_add': ['NET_ADMIN'], 'volumes': [ - self._disp_vol(topo_id), '/dev/net/tun:/dev/net/tun', '%s:/etc/scion' % base, ], @@ -183,6 +174,3 @@ def _sig_toml(self, topo_id, topo): path = os.path.join(topo_id.base_dir(self.args.output_dir), SIG_CONFIG_NAME) write_file(path, toml.dumps(sig_conf)) - - def _disp_vol(self, topo_id): - return 'vol_disp_sig_%s:/run/shm/dispatcher:rw' % topo_id.file_fmt() diff --git a/tools/topology/topo.py b/tools/topology/topo.py index c67663a498..9fb39e7ce0 100644 --- a/tools/topology/topo.py +++ b/tools/topology/topo.py @@ -66,7 +66,8 @@ def __init__(self, topo_config, subnet_gen4: SubnetGenerator, subnet_gen6: SubnetGenerator, - default_mtu: int): + default_mtu: int, + dispatched_ports: str): """ :param ArgsBase args: Contains the passed command line arguments. :param dict topo_config: The parsed topology config. @@ -81,6 +82,7 @@ def __init__(self, ADDR_TYPE_6: subnet_gen6, } self.default_mtu = default_mtu + self.dispatched_ports = dispatched_ports self.port_gen = PortGenerator() @@ -242,6 +244,16 @@ def _generate_as_topo(self, topo_id, as_conf): 'attributes': attributes, 'isd_as': str(topo_id), 'mtu': mtu, + # XXX(JordiSubira): This key is used internally later on, to decide + # whether to create a dispatcher container collocated with the tester + # container. + # + # Correcter/nicer would be to pass the ConfigGenerator.topo_config + # via the DockerGenArgs to DockerGenerator and check the test_dispatcher + # flag for the individual AS in DockerGenerator.generate before the call + # to self._gen_topo + 'test_dispatcher': as_conf.get('test_dispatcher', True), + 'dispatched_ports': as_conf.get('dispatched_ports', self.args.dispatched_ports), } for i in SCION_SERVICE_NAMES: self.topo_dicts[topo_id][i] = {}