From 66ef8b7a8ecc720c29b86113b20a1bc2f8ab4a4c Mon Sep 17 00:00:00 2001 From: JordiSubira Date: Tue, 9 May 2023 14:38:53 +0200 Subject: [PATCH] remove dispatcher and reliable: - still integration test failing due to lack of support for SCMP handling and more --- .golangcilint.yml | 5 +- BUILD.bazel | 1 - acceptance/common/topogen.bzl | 1 - acceptance/hidden_paths/test.py | 16 +- acceptance/sig_short_exp_time/BUILD.bazel | 24 - .../sig_short_exp_time/docker-compose.yml | 40 +- acceptance/sig_short_exp_time/test | 8 +- .../1-ff00_0_110/dispatcher/disp.toml | 3 - .../1-ff00_0_111/dispatcher/disp.toml | 3 - acceptance/topo_cs_reload/BUILD.bazel | 12 - acceptance/topo_cs_reload/docker-compose.yml | 17 +- acceptance/topo_cs_reload/reload_test.go | 2 - acceptance/topo_cs_reload/testdata/cs.toml | 1 - acceptance/topo_cs_reload/testdata/disp.toml | 5 - acceptance/topo_cs_reload/testdata/sd.toml | 1 - acceptance/topo_daemon_reload/BUILD.bazel | 12 - .../topo_daemon_reload/docker-compose.yml | 14 +- acceptance/topo_daemon_reload/reload_test.go | 6 +- .../topo_daemon_reload/testdata/disp.toml | 5 - .../topo_daemon_reload/testdata/sd.toml | 1 - acceptance/trc_update/test.py | 2 +- demo/drkey/test.py | 23 +- dispatcher/BUILD.bazel | 45 - dispatcher/cmd/dispatcher/BUILD.bazel | 45 - dispatcher/config/BUILD.bazel | 36 - dispatcher/config/config.go | 115 --- dispatcher/config/config_test.go | 59 -- dispatcher/config/sample.go | 35 - dispatcher/internal/metrics/BUILD.bazel | 12 - dispatcher/internal/metrics/metrics.go | 225 ---- dispatcher/internal/registration/BUILD.bazel | 43 - dispatcher/internal/registration/errors.go | 29 - .../internal/registration/generators_test.go | 59 -- dispatcher/internal/registration/iatable.go | 213 ---- dispatcher/internal/registration/portlist.go | 87 -- .../internal/registration/portlist_test.go | 67 -- .../internal/registration/scmp_table.go | 50 - 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 | 33 - dispatcher/internal/respool/buffer.go | 42 - dispatcher/internal/respool/packet.go | 184 ---- dispatcher/internal/respool/packet_test.go | 137 --- dispatcher/mgmtapi/api.go | 46 - dispatcher/mgmtapi/dummy.html | 0 dispatcher/mgmtapi/spec.gen.go | 104 -- dispatcher/network/BUILD.bazel | 20 - dispatcher/network/app_socket.go | 223 ---- dispatcher/network/dispatcher.go | 64 -- dispatcher/table.go | 71 -- dispatcher/underlay.go | 410 -------- dispatcher/underlay_test.go | 957 ------------------ docker/BUILD.bazel | 11 - gateway/gateway.go | 10 +- gateway/pathhealth/BUILD.bazel | 1 + gateway/pathhealth/pathwatcher.go | 33 +- gateway/pathhealth/remotewatcher.go | 2 +- pkg/slayers/scion.go | 2 +- pkg/sock/reliable/BUILD.bazel | 41 - pkg/sock/reliable/errors.go | 54 - pkg/sock/reliable/errors_test.go | 75 -- pkg/sock/reliable/frame.go | 147 --- 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 | 118 --- 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 | 60 -- private/app/env/env_test.go | 21 +- scion.sh | 12 - spec/BUILD.bazel | 10 - tools/end2end_integration/main.go | 2 +- tools/integration/integration.go | 8 +- tools/scion_integration/main.go | 2 +- tools/topology/common.py | 21 +- tools/topology/config.py | 1 - tools/topology/docker.py | 48 +- tools/topology/docker_utils.py | 10 +- tools/topology/go.py | 45 - tools/topology/prometheus.py | 22 - tools/topology/sig.py | 73 +- tools/topology/supervisor.py | 18 - tools/topology/topo.py | 3 +- 110 files changed, 121 insertions(+), 9019 deletions(-) delete mode 100644 acceptance/topo_cs_reload/testdata/disp.toml delete mode 100644 acceptance/topo_daemon_reload/testdata/disp.toml delete mode 100644 dispatcher/BUILD.bazel delete mode 100644 dispatcher/cmd/dispatcher/BUILD.bazel delete mode 100644 dispatcher/config/BUILD.bazel delete mode 100644 dispatcher/config/config.go delete mode 100644 dispatcher/config/config_test.go delete mode 100644 dispatcher/config/sample.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/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/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/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/mgmtapi/api.go delete mode 100644 dispatcher/mgmtapi/dummy.html delete mode 100644 dispatcher/mgmtapi/spec.gen.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/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 3bbbf5eaa3..7e3a979dbf 100644 --- a/.golangcilint.yml +++ b/.golangcilint.yml @@ -80,7 +80,4 @@ issues: ^control/colibri/reservation/reservationdbtest/reservationdbtest.go$|\ ^control/colibri/reservation/segment/reservation_test.go$|\ ^control/colibri/reservation/sqlite/db_test.go$|\ - ^control/colibri/reservationstore/store.go$|\ - ^pkg/sock/reliable/reconnect/conn_io_test.go$|\ - ^pkg/sock/reliable/reconnect/network_test.go$|\ - ^pkg/sock/reliable/reconnect/reconnecter_test.go$" + ^control/colibri/reservationstore/store.go$|\" diff --git a/BUILD.bazel b/BUILD.bazel index f48ed70cd8..b806f43a4c 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -58,7 +58,6 @@ pkg_tar( srcs = [ "//control/cmd/control", "//daemon/cmd/daemon", - "//dispatcher/cmd/dispatcher", "//gateway/cmd/gateway", "//router/cmd/router", "//scion-pki/cmd/scion-pki", diff --git a/acceptance/common/topogen.bzl b/acceptance/common/topogen.bzl index 675cee50ad..5e77e86e73 100644 --- a/acceptance/common/topogen.bzl +++ b/acceptance/common/topogen.bzl @@ -110,7 +110,6 @@ def container_loaders(tester, gateway): images = { "control:latest": "//docker:control", "daemon:latest": "//docker:daemon", - "dispatcher:latest": "//docker:dispatcher", "tester:latest": tester, "posix-router:latest": "//docker:posix_router", } diff --git a/acceptance/hidden_paths/test.py b/acceptance/hidden_paths/test.py index 2d62c8c50e..de4bc7e870 100755 --- a/acceptance/hidden_paths/test.py +++ b/acceptance/hidden_paths/test.py @@ -48,6 +48,20 @@ class Test(base.TestTopogen): http_server_port = 9099 + _testers = { + "2": "tester_1-ff00_0_2", + "3": "tester_1-ff00_0_3", + "4": "tester_1-ff00_0_4", + "5": "tester_1-ff00_0_5", + } + _ases = { + "2": "1-ff00:0:2", + "3": "1-ff00:0:3", + "4": "1-ff00:0:4", + "5": "1-ff00:0:5", + } + + def setup_prepare(self): super().setup_prepare() @@ -114,7 +128,7 @@ def setup_start(self): server_thread.start() super().setup_start() - time.sleep(4) # Give applications time to download configurations + time.sleep(10) # Give applications time to download configurations server.shutdown() diff --git a/acceptance/sig_short_exp_time/BUILD.bazel b/acceptance/sig_short_exp_time/BUILD.bazel index 854ba9f37c..4da04a6619 100644 --- a/acceptance/sig_short_exp_time/BUILD.bazel +++ b/acceptance/sig_short_exp_time/BUILD.bazel @@ -6,8 +6,6 @@ sh_test( srcs = ["test"], data = [ "docker-compose.yml", - ":dispatcher1.tar", - ":dispatcher2.tar", ":sig1.tar", ":sig2.tar", ":udpproxy.tar", @@ -24,28 +22,6 @@ container_image( base = "//tools/udpproxy", ) -container_image( - name = "dispatcher1", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - files = ["testdata/1-ff00_0_110/dispatcher/disp.toml"], -) - -container_image( - name = "dispatcher2", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - files = ["testdata/1-ff00_0_111/dispatcher/disp.toml"], -) - container_image( name = "sig1", base = "//docker:posix_gateway", diff --git a/acceptance/sig_short_exp_time/docker-compose.yml b/acceptance/sig_short_exp_time/docker-compose.yml index 65fc288fd1..fedda02b3d 100644 --- a/acceptance/sig_short_exp_time/docker-compose.yml +++ b/acceptance/sig_short_exp_time/docker-compose.yml @@ -42,58 +42,42 @@ services: ipv4_address: 242.254.100.4 bridge2: ipv4_address: 242.254.200.4 - dispatcher1: - container_name: dispatcher1 - image: bazel/acceptance/sig_short_exp_time:dispatcher1 - networks: - bridge1: - ipv4_address: 242.254.100.2 - volumes: - - vol_scion_disp_sig1-ff00_0_110:/run/shm/dispatcher:rw - dispatcher2: - container_name: dispatcher2 - image: bazel/acceptance/sig_short_exp_time:dispatcher2 - networks: - bridge2: - ipv4_address: 242.254.200.2 - volumes: - - vol_scion_disp_sig1-ff00_0_111:/run/shm/dispatcher:rw sig1: cap_add: - NET_ADMIN container_name: sig1 - depends_on: - - dispatcher1 image: bazel/acceptance/sig_short_exp_time:sig1 - network_mode: service:dispatcher1 + networks: + bridge1: + ipv4_address: 242.254.100.2 privileged: true volumes: - - vol_scion_disp_sig1-ff00_0_110:/run/shm/dispatcher:rw - /dev/net/tun:/dev/net/tun sig2: cap_add: - NET_ADMIN container_name: sig2 - depends_on: - - dispatcher2 image: bazel/acceptance/sig_short_exp_time:sig2 - network_mode: service:dispatcher2 + networks: + bridge2: + ipv4_address: 242.254.200.2 privileged: true volumes: - - vol_scion_disp_sig1-ff00_0_111:/run/shm/dispatcher:rw - /dev/net/tun:/dev/net/tun tester1: container_name: tester1 image: alpine - network_mode: service:dispatcher1 + networks: + bridge1: + ipv4_address: 242.254.100.10 privileged: true tester2: container_name: tester2 image: alpine - network_mode: service:dispatcher2 + networks: + bridge2: + ipv4_address: 242.254.200.10 privileged: true version: '2.4' volumes: vol_logs: null - vol_scion_disp_sig1-ff00_0_110: null - vol_scion_disp_sig1-ff00_0_111: null diff --git a/acceptance/sig_short_exp_time/test b/acceptance/sig_short_exp_time/test index 1908cde8dc..7d0f5e67ab 100755 --- a/acceptance/sig_short_exp_time/test +++ b/acceptance/sig_short_exp_time/test @@ -44,18 +44,16 @@ # | | # | +---------------------------------------------+ | # +---+ 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 # Register with the docker daemon the docker images bazel created docker image load -i acceptance/sig_short_exp_time/udpproxy.tar - docker image load -i acceptance/sig_short_exp_time/dispatcher1.tar - docker image load -i acceptance/sig_short_exp_time/dispatcher2.tar docker image load -i acceptance/sig_short_exp_time/sig1.tar docker image load -i acceptance/sig_short_exp_time/sig2.tar - docker-compose -f acceptance/sig_short_exp_time/docker-compose.yml up -d dispatcher1 dispatcher2 sig1 sig2 patha pathb + docker-compose -f acceptance/sig_short_exp_time/docker-compose.yml up -d sig1 sig2 patha pathb # Set up forward route on network stack 1 and 2 through the sig tunnel # device. The route is a property of the network stack, and persists after @@ -103,7 +101,7 @@ RC=$? OUTPUT_DIR=$TEST_UNDECLARED_OUTPUTS_DIR mkdir -p $OUTPUT_DIR/logs -for CNTR in sig1 sig2 dispatcher1 dispatcher2; do +for CNTR in sig1 sig2; do docker-compose -f acceptance/sig_short_exp_time/docker-compose.yml logs "$CNTR" > "$OUTPUT_DIR/logs/$CNTR.log" done diff --git a/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/dispatcher/disp.toml b/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/dispatcher/disp.toml index f02dc620a8..2a93d6d8b2 100644 --- a/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/dispatcher/disp.toml +++ b/acceptance/sig_short_exp_time/testdata/1-ff00_0_110/dispatcher/disp.toml @@ -1,5 +1,2 @@ -[dispatcher] -id = "disp_1-ff00_0_110" - [log.console] level = "debug" diff --git a/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/dispatcher/disp.toml b/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/dispatcher/disp.toml index 1054942819..2a93d6d8b2 100644 --- a/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/dispatcher/disp.toml +++ b/acceptance/sig_short_exp_time/testdata/1-ff00_0_111/dispatcher/disp.toml @@ -1,5 +1,2 @@ -[dispatcher] -id = "disp_1-ff00_0_111" - [log.console] level = "debug" diff --git a/acceptance/topo_cs_reload/BUILD.bazel b/acceptance/topo_cs_reload/BUILD.bazel index a43914ba5d..0327bf788c 100644 --- a/acceptance/topo_cs_reload/BUILD.bazel +++ b/acceptance/topo_cs_reload/BUILD.bazel @@ -14,7 +14,6 @@ go_test( "docker-compose.yml", "testdata/topology_reload.json", ":control.tar", - ":dispatcher.tar", ":invalid_changed_ip", ":invalid_changed_port", ":testdata/gen_crypto.sh", @@ -36,17 +35,6 @@ go_test( ], ) -container_image( - name = "dispatcher", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - files = ["testdata/disp.toml"], -) - container_image( name = "control", base = "//docker:control", diff --git a/acceptance/topo_cs_reload/docker-compose.yml b/acceptance/topo_cs_reload/docker-compose.yml index 00591e1404..d2d4fac7d5 100644 --- a/acceptance/topo_cs_reload/docker-compose.yml +++ b/acceptance/topo_cs_reload/docker-compose.yml @@ -7,25 +7,14 @@ networks: config: - subnet: 242.253.100.0/24 services: - topo_cs_reload_dispatcher: - container_name: topo_cs_reload_dispatcher - image: bazel/acceptance/topo_cs_reload:dispatcher - networks: - bridge1: - ipv4_address: 242.253.100.2 - volumes: - - vol_topo_cs_reload_disp:/run/shm/dispatcher:rw topo_cs_reload_control_srv: container_name: topo_cs_reload_control_srv image: bazel/acceptance/topo_cs_reload:control - depends_on: - - topo_cs_reload_dispatcher volumes: - - vol_topo_cs_reload_disp:/run/shm/dispatcher:ro - "${TOPO_CS_RELOAD_CONFIG_DIR}/certs:/certs:ro" - "${TOPO_CS_RELOAD_CONFIG_DIR}/keys:/keys:ro" - "${TOPO_CS_RELOAD_CONFIG_DIR}/crypto:/crypto:ro" - network_mode: service:topo_cs_reload_dispatcher + 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 55e30c924d..cb5ecbe968 100644 --- a/acceptance/topo_cs_reload/reload_test.go +++ b/acceptance/topo_cs_reload/reload_test.go @@ -100,7 +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") s.mustExec(t, "docker", "image", "load", "-i", "control.tar") // now start the docker containers s.mustExec(t, "docker-compose", "-f", "docker-compose.yml", "up", "-d") @@ -119,7 +118,6 @@ func (s testState) teardownTest(t *testing.T) { require.NoError(t, os.MkdirAll(fmt.Sprintf("%s/logs", outdir), os.ModePerm|os.ModeDir)) // collect logs for service, file := range map[string]string{ - "topo_cs_reload_dispatcher": "disp.log", "topo_cs_reload_control_srv": "control.log", } { cmd := exec.Command("docker-compose", "-f", "docker-compose.yml", "logs", "--no-color", 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_daemon_reload/BUILD.bazel b/acceptance/topo_daemon_reload/BUILD.bazel index fe25b5841a..516d79224c 100644 --- a/acceptance/topo_daemon_reload/BUILD.bazel +++ b/acceptance/topo_daemon_reload/BUILD.bazel @@ -7,7 +7,6 @@ go_test( data = [ "testdata/topology_reload.json", ":daemon.tar", - ":dispatcher.tar", ":docker-compose.yml", "//acceptance/topo_common:invalid_reloads", "//acceptance/topo_common:topology", @@ -23,17 +22,6 @@ go_test( ], ) -container_image( - name = "dispatcher", - base = "//docker:dispatcher", - cmd = [ - "--config", - "/disp.toml", - ], - entrypoint = ["/app/dispatcher"], - files = ["testdata/disp.toml"], -) - container_image( name = "daemon", base = "//docker:daemon", diff --git a/acceptance/topo_daemon_reload/docker-compose.yml b/acceptance/topo_daemon_reload/docker-compose.yml index 2335a92fab..14074f14c3 100644 --- a/acceptance/topo_daemon_reload/docker-compose.yml +++ b/acceptance/topo_daemon_reload/docker-compose.yml @@ -7,22 +7,14 @@ networks: config: - subnet: 242.254.100.0/24 services: - topo_daemon_reload_dispatcher: - container_name: topo_daemon_reload_dispatcher - image: bazel/acceptance/topo_daemon_reload:dispatcher - networks: - bridge1: - ipv4_address: 242.254.100.2 - volumes: - - vol_topo_daemon_reload_disp:/run/shm/dispatcher:rw topo_daemon_reload_daemon: container_name: topo_daemon_reload_daemon image: bazel/acceptance/topo_daemon_reload:daemon + networks: + bridge1: + ipv4_address: 242.254.100.2 volumes: - - vol_topo_daemon_reload_disp:/run/shm/dispatcher:ro - vol_topo_daemon_reload_certs:/certs:ro - network_mode: service:topo_daemon_reload_dispatcher 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 7a39ce2320..038bab01fd 100644 --- a/acceptance/topo_daemon_reload/reload_test.go +++ b/acceptance/topo_daemon_reload/reload_test.go @@ -63,11 +63,10 @@ func TestSDTopoReload(t *testing.T) { func setupTest(t *testing.T) { // first load the docker images from bazel into the docker deamon, the // tars are in the same folder as this test runs in bazel. - mustExec(t, "docker", "image", "load", "-i", "dispatcher.tar") mustExec(t, "docker", "image", "load", "-i", "daemon.tar") // now start the docker containers mustExec(t, "docker-compose", "-f", "docker-compose.yml", "up", - "-d", "topo_daemon_reload_dispatcher", "topo_daemon_reload_daemon") + "-d", "topo_daemon_reload_daemon") // wait a bit to make sure the containers are ready. time.Sleep(time.Second / 2) t.Log("Test setup done") @@ -82,8 +81,7 @@ func teardownTest(t *testing.T) { require.NoError(t, os.MkdirAll(fmt.Sprintf("%s/logs", outdir), os.ModePerm|os.ModeDir)) // collect logs for service, file := range map[string]string{ - "topo_daemon_reload_dispatcher": "disp.log", - "topo_daemon_reload_daemon": "daemon.log", + "topo_daemon_reload_daemon": "daemon.log", } { cmd := exec.Command("docker-compose", "-f", "docker-compose.yml", "logs", "--no-color", service) 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/trc_update/test.py b/acceptance/trc_update/test.py index eed759f090..6144930bda 100755 --- a/acceptance/trc_update/test.py +++ b/acceptance/trc_update/test.py @@ -84,7 +84,7 @@ def _run(self): for cs in cs_services: self.dc.start_container(cs) - time.sleep(5) + time.sleep(20) logger.info('==> Check connectivity') end2end("-d", "-outDir", artifacts) diff --git a/demo/drkey/test.py b/demo/drkey/test.py index 19f08d40ce..446829015d 100644 --- a/demo/drkey/test.py +++ b/demo/drkey/test.py @@ -68,12 +68,11 @@ def setup_prepare(self): } }, [conf_dir / "sd.toml"]) - # Enable delegation for tester host on the fast side (server side), i.e. - # allow the tester host to directly request the secret value from which - # keys can be derived locally for any host. - tester_ip = self._container_ip("scion_disp_tester_%s" % self.server_isd_as.file_fmt()) - cs_config = self._conf_dir(self.server_isd_as) // "cs*-1.toml" - scion.update_toml({"drkey.delegation.scmp": [tester_ip]}, cs_config) + # Enable delegation for demo "server", i.e. allow server to + # access the base secret value from which keys can be derived locally. + server_ip = self._server_ip(self.server_isd_as) + server_cs_config = self._conf_dir(self.server_isd_as) // "cs*-1.toml" + scion.update_toml({"drkey.delegation.scmp": [server_ip]}, server_cs_config) def _run(self): time.sleep(10) # wait until CSes are all up and running @@ -130,10 +129,14 @@ def _run(self): raise AssertionError("Key derived by server does not match key derived by client!", server_key, client_key) - def _endhost_ip(self, isd_as: ISD_AS) -> str: - """ Determine the IP used for the end host (client or server) in the given ISD-AS """ - # The address must be the daemon IP (as it makes requests to the control - # service on behalf of the end host application). + def _server_ip(self, isd_as: ISD_AS) -> str: + """ Determine the IP used for the "server" in the given ISD-AS """ + return self._container_ip("tester_%s" % isd_as.file_fmt()) + + def _client_ip(self, isd_as: ISD_AS) -> str: + """ Determine the IP used for the "client" in the given ISD-AS """ + # The client's address must be the daemon (as this makes requests to the CS on behalf of the + # application). return self._container_ip("scion_sd%s" % isd_as.file_fmt()) def _container_ip(self, container: str) -> str: diff --git a/dispatcher/BUILD.bazel b/dispatcher/BUILD.bazel deleted file mode 100644 index 8538e90bdd..0000000000 --- a/dispatcher/BUILD.bazel +++ /dev/null @@ -1,45 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "dispatcher.go", - "table.go", - "underlay.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", - "//pkg/private/serrors:go_default_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", - ], -) - -go_test( - name = "go_default_test", - srcs = ["underlay_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", - "@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 deleted file mode 100644 index f7a8f876c0..0000000000 --- a/dispatcher/cmd/dispatcher/BUILD.bazel +++ /dev/null @@ -1,45 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") -load("//:scion.bzl", "scion_go_binary") - -go_library( - name = "go_default_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", - ], -) - -scion_go_binary( - name = "dispatcher", - 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/config/BUILD.bazel b/dispatcher/config/BUILD.bazel deleted file mode 100644 index fe9bc729bf..0000000000 --- a/dispatcher/config/BUILD.bazel +++ /dev/null @@ -1,36 +0,0 @@ -load("//tools/lint:go.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "config.go", - "sample.go", - ], - importpath = "github.com/scionproto/scion/dispatcher/config", - visibility = ["//visibility:public"], - deps = [ - "//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", - ], -) - -go_test( - name = "go_default_test", - srcs = ["config_test.go"], - 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//:go_default_library", - "@com_github_stretchr_testify//assert:go_default_library", - ], -) diff --git a/dispatcher/config/config.go b/dispatcher/config/config.go deleted file mode 100644 index 1fad7d9b09..0000000000 --- a/dispatcher/config/config.go +++ /dev/null @@ -1,115 +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 config contains the configuration of the SCION dispatcher. -package config - -import ( - "fmt" - "io" - - "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) - -type Config struct { - Features env.Features `toml:"features,omitempty"` - Logging log.Config `toml:"log,omitempty"` - Metrics env.Metrics `toml:"metrics,omitempty"` - API api.Config `toml:"api,omitempty"` - 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, - &cfg.Logging, - &cfg.Metrics, - &cfg.API, - &cfg.Dispatcher, - ) -} - -func (cfg *Config) Validate() error { - return config.ValidateAll( - &cfg.Features, - &cfg.Logging, - &cfg.Metrics, - &cfg.API, - &cfg.Dispatcher, - ) -} - -func (cfg *Config) Sample(dst io.Writer, path config.Path, _ config.CtxMap) { - config.WriteSample(dst, path, config.CtxMap{config.ID: idSample}, - &cfg.Features, - &cfg.Logging, - &cfg.Metrics, - &cfg.API, - &cfg.Dispatcher, - ) -} - -func (cfg *Config) ConfigName() string { - return "dispatcher_config" -} diff --git a/dispatcher/config/config_test.go b/dispatcher/config/config_test.go deleted file mode 100644 index bc1b1621e3..0000000000 --- a/dispatcher/config/config_test.go +++ /dev/null @@ -1,59 +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 config - -import ( - "bytes" - "testing" - - "github.com/pelletier/go-toml" - "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) { - var sample bytes.Buffer - var cfg Config - cfg.Sample(&sample, nil, nil) - - InitTestConfig(&cfg) - err := toml.NewDecoder(bytes.NewReader(sample.Bytes())).Strict(true).Decode(&cfg) - assert.NoError(t, err) - CheckTestConfig(t, &cfg, idSample) -} - -func InitTestConfig(cfg *Config) { - apitest.InitConfig(&cfg.API) - envtest.InitTest(nil, &cfg.Metrics, nil, nil) - logtest.InitTestLogging(&cfg.Logging) - cfg.Dispatcher.DeleteSocket = true -} - -func CheckTestConfig(t *testing.T, cfg *Config, id string) { - apitest.CheckConfig(t, &cfg.API) - 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) -} diff --git a/dispatcher/config/sample.go b/dispatcher/config/sample.go deleted file mode 100644 index e105927c6e..0000000000 --- a/dispatcher/config/sample.go +++ /dev/null @@ -1,35 +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 config - -const idSample = "dispatcher" - -const dispSample = ` -# ID of the Dispatcher. (required) -id = "%s" - -# The local API socket. (default /run/shm/dispatcher/default.sock) -application_socket = "/run/shm/dispatcher/default.sock" - -# File permissions of the ApplicationSocket socket file, in octal. (default "0770") -socket_file_mode = "0770" - -# The native port opened by the dispatcher. (default 30041) -underlay_port = 30041 - -# Remove the socket file (if it exists) on start. (default false) -delete_socket = false -` diff --git a/dispatcher/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/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 35eeb8a6bf..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.HostSVC { - 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 a19c2a937f..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.HostSVC - // 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.HostSVC, - 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.HostSVC, 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.HostSVC, - 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.HostSVC, 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.HostSVC - // 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.HostSVC { - 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/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/svctable.go b/dispatcher/internal/registration/svctable.go deleted file mode 100644 index 6aacfe0c1e..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.HostSVC, 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.HostSVC, ip net.IP) []interface{} - String() string -} - -func NewSVCTable() SVCTable { - return newSvcTable() -} - -var _ SVCTable = (*svcTable)(nil) - -type svcTable struct { - m map[addr.HostSVC]unicastIpTable -} - -func newSvcTable() *svcTable { - return &svcTable{ - m: make(map[addr.HostSVC]unicastIpTable), - } -} - -func (t *svcTable) Register(svc addr.HostSVC, 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.HostSVC, 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.HostSVC, 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.HostSVC) []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.HostSVC, 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.HostSVC, ip net.IP, port *ring.Ring) func() { - return func() { - t.doCleanup(svc, ip, port) - } -} - -func (t *svcTable) doCleanup(svc addr.HostSVC, 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 240a7ebe1c..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.HostSVC - 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.HostSVC - 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.HostSVC - 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.HostSVC - 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 451d14518b..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.HostSVC, - 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, public.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.HostSVC, 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.HostSVC, 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 5d717035b2..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.HostSVC - af assert.ErrorAssertionFunc - }{ - "no public address fails": { - a: nil, - svc: addr.SvcNone, - af: assert.Error, - }, - "zero public IPv4 address succeeds": { - a: &net.UDPAddr{IP: net.IPv4zero, Port: 80}, - svc: addr.SvcNone, - af: assert.NoError, - }, - "zero public IPv6 address succeeds": { - a: &net.UDPAddr{IP: net.IPv6zero, Port: 80}, - svc: addr.SvcNone, - af: assert.NoError, - }, - "public address with port, no bind, no svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 5, 1}, Port: 8080}, - af: assert.NoError, - }, - "public address without port, no bind, no svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 9, 1}}, - svc: addr.SvcNone, - af: assert.NoError, - }, - "public address, bind, no svc fails": { - a: &net.UDPAddr{IP: net.IP{192, 0, 20, 1}, Port: 8880}, - b: net.IP{10, 2, 3, 4}, - svc: addr.SvcNone, - af: assert.Error, - }, - - "public address, no bind, svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 22, 1}, Port: 8889}, - svc: addr.SvcCS, - af: assert.NoError, - }, - - "zero bind IPv4 address fails": { - a: &net.UDPAddr{IP: net.IP{192, 0, 23, 1}, Port: 8888}, - b: net.IPv4zero, - svc: addr.SvcCS, - af: assert.Error, - }, - - "zero bind IPv6 address fails": { - a: &net.UDPAddr{IP: net.IP{192, 0, 23, 1}, Port: 8888}, - b: net.IPv6zero, - svc: addr.SvcCS, - af: assert.Error, - }, - "public address, bind, svc succeeds": { - a: &net.UDPAddr{IP: net.IP{192, 0, 23, 1}, Port: 8888}, - b: net.IP{10, 2, 3, 4}, - svc: addr.SvcCS, - af: assert.NoError, - }, - } - - for n, tc := range tests { - t.Run(n, func(t *testing.T) { - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - ref, err := table.Register(tc.a, tc.b, tc.svc, dummyValue) - tc.af(t, err) - if err != nil { - assert.Nil(t, ref) - return - } - assert.NotNil(t, ref) - }) - } - - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - -} - -func TestRegisterOnlyPublic(t *testing.T) { - - t.Run("Free reference, size is 0", func(t *testing.T) { - t.Log("Given a table with a public address registration") - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - public := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - ref, err := table.Register(public, nil, addr.SvcNone, dummyValue) - require.NoError(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - assert.NotNil(t, ref) - - t.Log("Lookup is successful") - retValue, ok := table.LookupPublic(public) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - ref.Free() - assert.Equal(t, table.Size(), 0, "size is 0") - assert.Panics(t, ref.Free, "Free same reference again, panic") - retValue, ok = table.LookupPublic(public) - assert.False(t, ok, "lookup should fail") - assert.Nil(t, retValue) - }) - - t.Run("Register", func(t *testing.T) { - t.Log("Given a table with a public address registration") - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "initial size is 0") - public := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - ref, err := table.Register(public, nil, addr.SvcNone, dummyValue) - require.NoError(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - assert.NotNil(t, ref) - - t.Log("Lookup is successful") - retValue, ok := table.LookupPublic(public) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - t.Log("Register same address returns error") - ref, err = table.Register(public, nil, addr.SvcNone, dummyValue) - assert.Error(t, err) - assert.Nil(t, ref) - - t.Log("Register 0.0.0.0, error due to overlap") - public = &net.UDPAddr{IP: net.IPv4zero, Port: 80} - ref, err = table.Register(public, nil, addr.SvcNone, dummyValue) - assert.Error(t, err) - assert.Nil(t, ref) - - t.Log("Register ::, success") - public = &net.UDPAddr{IP: net.IPv6zero, Port: 80} - ref, err = table.Register(public, nil, addr.SvcNone, dummyValue) - assert.NoError(t, err) - assert.NotNil(t, ref) - }) -} - -func TestRegisterPublicAndSVC(t *testing.T) { - table := NewTable(minPort, maxPort) - assert.Equal(t, table.Size(), 0, "size is 0") - - t.Log("Given a table with a public address registration") - p := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - _, err := table.Register(p, nil, addr.SvcCS, dummyValue) - require.NoError(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - - t.Log("Public lookup is successful") - retValue, ok := table.LookupPublic(p) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - t.Log("SVC lookup is successful (bind inherits from public)") - retValues := table.LookupService(addr.SvcCS, p.IP) - assert.Equal(t, retValues, []interface{}{dummyValue}) -} - -func TestRegisterWithBind(t *testing.T) { - table := NewTable(minPort, maxPort) - - t.Log("Given a table with a bind address registration") - p := &net.UDPAddr{IP: net.IP{192, 0, 2, 1}, Port: 80} - bind := net.IP{10, 2, 3, 4} - ref, err := table.Register(p, bind, addr.SvcCS, dummyValue) - require.NoError(t, err) - assert.NotNil(t, ref) - assert.Equal(t, table.Size(), 1, "size is 1") - - t.Log("Public lookup is successful") - retValue, ok := table.LookupPublic(p) - assert.True(t, ok) - assert.Equal(t, retValue, dummyValue) - - t.Log("SVC lookup is successful") - retValues := table.LookupService(addr.SvcCS, bind) - assert.Equal(t, retValues, []interface{}{dummyValue}) - - t.Log("Bind lookup on different svc fails") - retValues = table.LookupService(addr.SvcDS, bind) - assert.Empty(t, retValues) - - t.Log("Colliding binds returns error, and public port is released") - otherPublic := &net.UDPAddr{IP: net.IP{192, 0, 2, 2}, Port: 80} - _, err = table.Register(otherPublic, bind, addr.SvcCS, dummyValue) - assert.Error(t, err) - assert.Equal(t, table.Size(), 1, "size is 1") - _, err = table.Register(otherPublic, nil, addr.SvcNone, dummyValue) - assert.NoError(t, err) - - t.Log("Freeing the entry allows for reregistration") - ref.Free() - _, err = table.Register(p, bind, addr.SvcCS, dummyValue) - assert.NoError(t, err) -} diff --git a/dispatcher/internal/registration/udptable.go b/dispatcher/internal/registration/udptable.go deleted file mode 100644 index 0abdc97f16..0000000000 --- a/dispatcher/internal/registration/udptable.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "fmt" - "net" - - "github.com/scionproto/scion/pkg/private/serrors" -) - -// UDPPortTable stores port allocations for UDP/IPv4 and UDP/IPv6 sockets. -// -// Additionally, it allocates ports dynamically if the requested port is 0. -type UDPPortTable struct { - v4PortTable map[int]IPTable - v6PortTable map[int]IPTable - allocator *UDPPortAllocator -} - -func NewUDPPortTable(minPort, maxPort int) *UDPPortTable { - return NewUDPPortTableFromMap(minPort, maxPort, make(map[int]IPTable), make(map[int]IPTable)) -} - -func NewUDPPortTableFromMap(minPort, maxPort int, v4, v6 map[int]IPTable) *UDPPortTable { - return &UDPPortTable{ - v4PortTable: v4, - v6PortTable: v6, - allocator: NewUDPPortAllocator(minPort, maxPort), - } -} - -func (t *UDPPortTable) Lookup(address *net.UDPAddr) (interface{}, bool) { - if address.IP.IsUnspecified() { - return nil, false - } - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if !ok { - return nil, false - } - return ipTable.Route(address.IP) -} - -func (t *UDPPortTable) getPortTableByIP(ip net.IP) map[int]IPTable { - if ip.To4() != nil { - return t.v4PortTable - } - return t.v6PortTable -} - -func (t *UDPPortTable) overlapsWith(address *net.UDPAddr) bool { - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if !ok { - return false - } - return ipTable.OverlapsWith(address.IP) -} - -// Insert adds address into the allocation table. It will return an error if an -// entry overlaps, or if the value is nil. -func (t *UDPPortTable) Insert(address *net.UDPAddr, value interface{}) (*net.UDPAddr, error) { - if t.overlapsWith(address) { - return nil, serrors.WithCtx(ErrOverlappingAddress, "address", address) - } - if value == nil { - return nil, ErrNoValue - } - address = copyUDPAddr(address) - newAddress, err := t.computeAddressWithPort(address) - if err != nil { - return nil, err - } - t.insertUDPAddress(newAddress, value) - return newAddress, nil -} - -func (t *UDPPortTable) computeAddressWithPort(address *net.UDPAddr) (*net.UDPAddr, error) { - var err error - if address.Port == 0 { - address.Port, err = t.allocator.Allocate(address.IP, t) - } - return address, err -} - -func (t *UDPPortTable) insertUDPAddress(address *net.UDPAddr, value interface{}) { - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if !ok { - ipTable = make(IPTable) - portTable[address.Port] = ipTable - } - ipTable[address.IP.String()] = value -} - -func copyUDPAddr(address *net.UDPAddr) *net.UDPAddr { - return &net.UDPAddr{ - IP: copyIPAddr(address.IP), - Port: address.Port, - Zone: address.Zone, - } -} - -func copyIPAddr(ip net.IP) net.IP { - c := make(net.IP, len(ip)) - copy(c, ip) - return c -} - -func (t *UDPPortTable) Remove(address *net.UDPAddr) { - portTable := t.getPortTableByIP(address.IP) - ipTable, ok := portTable[address.Port] - if ok { - delete(ipTable, address.IP.String()) - if len(ipTable) == 0 { - delete(portTable, address.Port) - } - } -} - -// IPTable maps string representations of IP addresses to arbitrary values. -type IPTable map[string]interface{} - -// OverlapsWith returns true if ip overlaps with any entry in t. For example, -// 0.0.0.0 would overlap with any other IPv4 address. -func (t IPTable) OverlapsWith(ip net.IP) bool { - if ip.IsUnspecified() && len(t) > 0 { - return true - } - _, ok := t.Route(ip) - return ok -} - -// Route returns the object associated with destination ip. -// -// This can either be an entry matching argument ip exactly, or a zero IP -// address. -func (t IPTable) Route(ip net.IP) (interface{}, bool) { - if v, ok := t[getZeroString(ip)]; ok { - return v, true - } - if v, ok := t[ip.String()]; ok { - return v, true - } - return nil, false -} - -func getZeroString(ip net.IP) string { - if ip.To4() != nil { - return "0.0.0.0" - } else { - return "::" - } -} - -// UDPPortAllocator attempts to find a free port between a min port and a max port in -// an allocation table. Attempts wrap around when they reach max port. -// -// If no port is available, the allocation function panics. -type UDPPortAllocator struct { - minPort int - maxPort int - nextPort int -} - -// NewUDPPortAllocator returns an allocation. The function panics if min > max, or if -// min or max is not a valid port number. -func NewUDPPortAllocator(min, max int) *UDPPortAllocator { - if min <= 0 { - panic(fmt.Sprintf("bad min port value %d", min)) - } - if min > max { - panic(fmt.Sprintf("min port must be less than maxport, but %d > %d", min, max)) - } - if max >= (1 << 16) { - panic(fmt.Sprintf("max port cannot exceed %d (was %d)", (1<<16)-1, max)) - } - return &UDPPortAllocator{ - minPort: min, - maxPort: max, - nextPort: min, - } -} - -// Allocate returns the next available port for the IP address. It will panic -// if it runs out of ports. -func (a *UDPPortAllocator) Allocate(ip net.IP, t *UDPPortTable) (int, error) { - for i := a.minPort; i < a.maxPort+1; i++ { - candidate := &net.UDPAddr{ - IP: ip, - Port: a.nextPort, - } - a.nextPort++ - if a.nextPort == a.maxPort+1 { - a.nextPort = a.minPort - } - if !t.overlapsWith(candidate) { - return candidate.Port, nil - } - } - return 0, ErrNoPorts -} diff --git a/dispatcher/internal/registration/udptable_test.go b/dispatcher/internal/registration/udptable_test.go deleted file mode 100644 index 737d8bfcd4..0000000000 --- a/dispatcher/internal/registration/udptable_test.go +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2018 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package registration - -import ( - "net" - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -var docIPv6AddressStr = "2001:db8::1" -var docIPv6Address = net.ParseIP(docIPv6AddressStr) - -var minPort = 1024 -var maxPort = 65535 - -func testUDPTableWithPorts(v4, v6 map[int]IPTable) *UDPPortTable { - if v4 == nil { - v4 = map[int]IPTable{} - } - if v6 == nil { - v6 = map[int]IPTable{} - } - return NewUDPPortTableFromMap(minPort, maxPort, v4, v6) -} - -func TestUDPPortTableLookup(t *testing.T) { - value := "test value" - Convey("", t, func() { - Convey("Given a non-zero IPv4 address", func() { - address := &net.UDPAddr{IP: net.IP{10, 1, 2, 3}, Port: 10080} - Convey("Lookup on an empty table returns nil", func() { - table := NewUDPPortTable(minPort, maxPort) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - Convey("Lookup on table with non-matching entries returns nil", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 10080: {"10.4.5.6": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - Convey("Lookup on table with exact match returns value", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 10080: {"10.1.2.3": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with matching 0.0.0.0 entry returns value", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 10080: {"0.0.0.0": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with non-matching 0.0.0.0 entry returns nil", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 80: {"0.0.0.0": value}}, nil) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - }) - Convey("Lookup fails for zero IPv4 address", func() { - table := NewUDPPortTable(minPort, maxPort) - address := &net.UDPAddr{IP: net.IPv4zero, Port: 10080} - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - Convey("Given an IPv6 address", func() { - address := &net.UDPAddr{IP: docIPv6Address, Port: 10080} - Convey("Lookup on table with matching entry returns value", func() { - table := testUDPTableWithPorts(nil, map[int]IPTable{ - 10080: {docIPv6AddressStr: value}, - }) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with matching :: entry returns value", func() { - table := testUDPTableWithPorts(nil, map[int]IPTable{ - 10080: {"::": value}, - }) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldEqual, value) - SoMsg("ok", ok, ShouldBeTrue) - }) - Convey("Lookup on table with non-matching :: entry returns nil", func() { - table := testUDPTableWithPorts(nil, map[int]IPTable{ - 80: {"::": value}, - }) - retValue, ok := table.Lookup(address) - SoMsg("value", retValue, ShouldBeNil) - SoMsg("ok", ok, ShouldBeFalse) - }) - }) - }) -} - -func TestUDPPortTableInsert(t *testing.T) { - value := "Test value" - Convey("", t, func() { - Convey("Given an empty table", func() { - table := NewUDPPortTable(minPort, maxPort) - Convey("Inserting an IPv4 address with a port returns a copy of the same address", - func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(address) - SoMsg("err", err, ShouldBeNil) - SoMsg("address content", retAddress, ShouldResemble, address) - SoMsg("address not same object", retAddress, ShouldNotEqual, address) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an IPv4 address with a 0 port returns an allocated port", - func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}} - expectedAddress := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 1024} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(expectedAddress) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, expectedAddress) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an IPv6 address with a port returns a copy of the same address", - func() { - address := &net.UDPAddr{IP: docIPv6Address, Port: 10080} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(address) - SoMsg("err", err, ShouldBeNil) - SoMsg("address content", retAddress, ShouldResemble, address) - SoMsg("address not same object", retAddress, ShouldNotEqual, address) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an IPv6 address with a 0 port returns an allocated port", - func() { - address := &net.UDPAddr{IP: docIPv6Address} - expectedAddress := &net.UDPAddr{IP: docIPv6Address, Port: 1024} - retAddress, err := table.Insert(address, value) - _, lookupOk := table.Lookup(expectedAddress) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, expectedAddress) - SoMsg("lookup ok", lookupOk, ShouldBeTrue) - }) - Convey("Inserting an address without a value is not permitted", func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 10080} - retAddress, err := table.Insert(address, nil) - _, lookupOk := table.Lookup(address) - SoMsg("err", err, ShouldNotBeNil) - SoMsg("address", retAddress, ShouldBeNil) - SoMsg("lookup ok", lookupOk, ShouldBeFalse) - }) - Convey("Inserting a zero IPv4 address is permitted", func() { - address := &net.UDPAddr{IP: net.IPv4zero, Port: 10080} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, address) - }) - Convey("Inserting a zero IPv6 address is permitted", func() { - address := &net.UDPAddr{IP: net.IPv6zero, Port: 10080} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, address) - }) - }) - Convey("Given a table with a zero address", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 1024: {"0.0.0.0": value}}, nil) - Convey("A colliding allocation will return an error", func() { - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 1024} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldNotBeNil) - SoMsg("address", retAddress, ShouldBeNil) - }) - }) - Convey("Given a table with a non-zero address", func() { - table := testUDPTableWithPorts(map[int]IPTable{ - 1024: {"10.0.0.0": value}}, nil) - Convey("Inserting zero IPv4 address on the same port fails", func() { - address := &net.UDPAddr{IP: net.IPv4zero, Port: 1024} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldNotBeNil) - SoMsg("address", retAddress, ShouldBeNil) - }) - Convey("Inserting zero IPv6 address on the same port succeeds", func() { - address := &net.UDPAddr{IP: net.IPv6zero, Port: 1024} - retAddress, err := table.Insert(address, value) - SoMsg("err", err, ShouldBeNil) - SoMsg("address", retAddress, ShouldResemble, address) - }) - }) - }) -} - -func TestUDPPortTableRemove(t *testing.T) { - value := "test value" - Convey("", t, func() { - Convey("", func() { - table := testUDPTableWithPorts( - map[int]IPTable{ - 10080: {"10.2.3.4": value}, - 10081: {"0.0.0.0": value}}, - map[int]IPTable{ - 10082: {docIPv6AddressStr: value}, - 10083: {"::": value}}, - ) - - Convey("Remove non-zero addresses", func() { - addrs := []*net.UDPAddr{ - {IP: net.IP{10, 2, 3, 4}, Port: 10080}, - {IP: docIPv6Address, Port: 10082}, - } - for _, address := range addrs { - _, lookupOk := table.Lookup(address) - SoMsg("lookup succeeds before removing", lookupOk, ShouldBeTrue) - table.Remove(address) - _, lookupOk = table.Lookup(address) - SoMsg("lookup fails after removing", lookupOk, ShouldBeFalse) - } - }) - Convey("Remove zero address", func() { - addrs := map[*net.UDPAddr]net.IP{ - {IP: net.IPv4zero, Port: 10081}: {10, 1, 2, 3}, - {IP: net.IPv6zero, Port: 10083}: docIPv6Address, - } - - for address, lookupAddress := range addrs { - _, lookupOk := table.Lookup(&net.UDPAddr{IP: lookupAddress, Port: address.Port}) - SoMsg("lookup succeeds before removing", lookupOk, ShouldBeTrue) - table.Remove(address) - _, lookupOk = table.Lookup(&net.UDPAddr{IP: lookupAddress, Port: address.Port}) - SoMsg("lookup fails after removing", lookupOk, ShouldBeFalse) - } - }) - }) - Convey("Removing non-existent entry does not panic", func() { - table := NewUDPPortTable(minPort, maxPort) - address := &net.UDPAddr{IP: net.IP{10, 2, 3, 4}, Port: 666} - So(func() { table.Remove(address) }, ShouldNotPanic) - }) - }) -} - -func TestUDPPortAllocator(t *testing.T) { - address := net.IP{10, 2, 3, 4} - value := "test value" - Convey("", t, func() { - Convey("Constructing an allocator with minport > maxport will panic", func() { - So(func() { NewUDPPortAllocator(10, 4) }, ShouldPanic) - }) - Convey("Constructing an allocator with a negative minport will panic", func() { - So(func() { NewUDPPortAllocator(-4, 4) }, ShouldPanic) - }) - Convey("Constructing an allocator with a minport of 0 will panic", func() { - So(func() { NewUDPPortAllocator(0, 4) }, ShouldPanic) - }) - Convey("Constructing an allocator with maxport > 65535 wil panic", func() { - So(func() { NewUDPPortAllocator(1, 65536) }, ShouldPanic) - }) - Convey("Given an allocator", func() { - allocator := NewUDPPortAllocator(1000, 1500) - table := NewUDPPortTable(minPort, maxPort) - Convey("if table is empty, first allocation gives min port", func() { - port, err := allocator.Allocate(address, table) - SoMsg("port", port, ShouldEqual, 1000) - SoMsg("err", err, ShouldBeNil) - }) - Convey("if table contains used first port, first allocation gives next port", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 1000: {"10.2.3.4": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 1001) - SoMsg("err", err, ShouldBeNil) - }) - Convey("if wildcard bind uses first port, first allocation gives next port", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 1000: {"0.0.0.0": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 1001) - SoMsg("err", err, ShouldBeNil) - }) - }) - Convey("Given an allocator with few ports", func() { - allocator := NewUDPPortAllocator(1, 3) - Convey("if all ports are taken except max, max is chosen", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 1: {"0.0.0.0": value}, - 2: {"0.0.0.0": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 3) - SoMsg("err", err, ShouldBeNil) - Convey("if first port is available, it is chosen after wrapping", func() { - port, err := allocator.Allocate(address, testUDPTableWithPorts( - map[int]IPTable{ - 2: {"0.0.0.0": value}, - 3: {"0.0.0.0": value}, - }, nil, - )) - SoMsg("port", port, ShouldEqual, 1) - SoMsg("err", err, ShouldBeNil) - }) - }) - Convey("if all ports are taken, error", func() { - table := testUDPTableWithPorts( - map[int]IPTable{ - 1: {"0.0.0.0": value}, - 2: {"0.0.0.0": value}, - 3: {"0.0.0.0": value}, - }, nil) - port, err := allocator.Allocate(address, table) - SoMsg("port", port, ShouldEqual, 0) - SoMsg("err", err, ShouldNotBeNil) - }) - }) - Convey("Given an allocator with IPv6 data", func() { - v6address := net.ParseIP(docIPv6AddressStr) - allocator := NewUDPPortAllocator(1000, 1500) - table := testUDPTableWithPorts(nil, - map[int]IPTable{ - 1000: {docIPv6AddressStr: value}, - }) - Convey("allocation skips ports correctly for IPv6", func() { - port, err := allocator.Allocate(v6address, table) - SoMsg("port", port, ShouldEqual, 1001) - SoMsg("err", err, ShouldBeNil) - }) - }) - }) -} diff --git a/dispatcher/internal/respool/BUILD.bazel b/dispatcher/internal/respool/BUILD.bazel deleted file mode 100644 index ba4207764d..0000000000 --- a/dispatcher/internal/respool/BUILD.bazel +++ /dev/null @@ -1,33 +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/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 c2843d7ead..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 ( - "net" - "testing" - - "github.com/google/gopacket" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "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(&net.IPAddr{IP: net.IP{127, 0, 0, 1}})) - require.NoError(t, scion.SetDstAddr(&net.IPAddr{IP: net.IP{127, 0, 0, 2}})) - return scion -} diff --git a/dispatcher/mgmtapi/api.go b/dispatcher/mgmtapi/api.go deleted file mode 100644 index 28327f2a8d..0000000000 --- a/dispatcher/mgmtapi/api.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2021 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 mgmtapi - -import ( - "net/http" -) - -// Server implements the Dispatcher Service API. -type Server struct { - Config http.HandlerFunc - Info http.HandlerFunc - LogLevel http.HandlerFunc -} - -// GetConfig is an indirection to the http handler. -func (s *Server) GetConfig(w http.ResponseWriter, r *http.Request) { - s.Config(w, r) -} - -// GetInfo is an indirection to the http handler. -func (s *Server) GetInfo(w http.ResponseWriter, r *http.Request) { - s.Info(w, r) -} - -// GetLogLevel is an indirection to the http handler. -func (s *Server) GetLogLevel(w http.ResponseWriter, r *http.Request) { - s.LogLevel(w, r) -} - -// SetLogLevel is an indirection to the http handler. -func (s *Server) SetLogLevel(w http.ResponseWriter, r *http.Request) { - s.LogLevel(w, r) -} diff --git a/dispatcher/mgmtapi/dummy.html b/dispatcher/mgmtapi/dummy.html deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dispatcher/mgmtapi/spec.gen.go b/dispatcher/mgmtapi/spec.gen.go deleted file mode 100644 index 1beab4eb20..0000000000 --- a/dispatcher/mgmtapi/spec.gen.go +++ /dev/null @@ -1,104 +0,0 @@ -// Package mgmtapi provides primitives to interact with the openapi HTTP API. -// -// Code generated by unknown module path version unknown version DO NOT EDIT. -package mgmtapi - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "fmt" - "net/url" - "path" - "strings" - - "github.com/getkin/kin-openapi/openapi3" -) - -// Base64 encoded, gzipped, json marshaled Swagger object -var swaggerSpec = []string{ - - "H4sIAAAAAAAC/9xVTW8bIRD9K2ja48rrfJz21qRVZMlporq3yAfMjtdELNCBtWJZ+9+rAduJ14lS9RCp", - "vcEAb948HsMWlGu9s2hjgGoLhME7GzBNrmT9A391GCLPlLMRbRpK741WMmpny8fgLMeCWmErefSZcAkV", - "fCqfocu8GspZlLaWVH8jcgR93xdQY1CkPYNBxTkF7ZLy6u4g405dM8U1Gh57ch4p6kzU7MPHWFPXNNo2", - "Ii8XgLZroXqAGhddAwVou3QcTlzmBeCTbL1BqPYrceN5FiJp2yQ6TE0T1gyTYeeHbW7xiCpCX8BxlSd0", - "cR8+ppt2ixZDkA2+m/3AepCd9yX6J/gzpLVWKG6llQ22aKP4cj8RS0cirlDMrid338VXHbyMaoXEDHRM", - "cjwHB4ehgDVSyPjj0dlozOU7j1Z6DRVcjMajcyjAy7hKpZfK2aVueNhg8hILk5w0qaGCG4zXeUdx7MXz", - "8XhgwohPsfRG6oH9hrKdWGzWKYUhLDsj7vbJmfZlTvGafQ9UyhdvItmza1tJG6jgnrSNISn58+52KnKh", - "XYYXS21wxIrKJvDtKde2zsKcMcr9db2lyCS78d/S40oGrQSXRm3WwMsGhVy4LiaVmDo5I8LOlp4c03hT", - "JeOa8vDQ35Lq0CPelevve9ghx4dpeYNRmEEzO9GoAN+9IspsIErCv3L15kP02Lfgl/lzB4vUYf9f3dLs", - "T24pHUHirgnVwxY6MlDBKkZfleV25ULsq613FPtSel2uz7jFStJyYbJGvCX39qXsTIQKjFPSpDB7wNFg", - "+WJ8eXnGKswPdIY/w3Vil34DfPIuYC0Wm92HsHue6V1a2XIr2RXTz/vfAQAA//9Se+9IPwgAAA==", -} - -// GetSwagger returns the content of the embedded swagger specification file -// or error if failed to decode -func decodeSpec() ([]byte, error) { - zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) - if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %s", err) - } - zr, err := gzip.NewReader(bytes.NewReader(zipped)) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) - } - var buf bytes.Buffer - _, err = buf.ReadFrom(zr) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %s", err) - } - - return buf.Bytes(), nil -} - -var rawSpec = decodeSpecCached() - -// a naive cached of a decoded swagger spec -func decodeSpecCached() func() ([]byte, error) { - data, err := decodeSpec() - return func() ([]byte, error) { - return data, err - } -} - -// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. -func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - var res = make(map[string]func() ([]byte, error)) - if len(pathToFile) > 0 { - res[pathToFile] = rawSpec - } - - return res -} - -// GetSwagger returns the Swagger specification corresponding to the generated code -// in this file. The external references of Swagger specification are resolved. -// The logic of resolving external references is tightly connected to "import-mapping" feature. -// Externally referenced files must be embedded in the corresponding golang packages. -// Urls can be supported but this task was out of the scope. -func GetSwagger() (swagger *openapi3.T, err error) { - var resolvePath = PathToRawSpec("") - - loader := openapi3.NewLoader() - loader.IsExternalRefsAllowed = true - loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - var pathToFile = url.String() - pathToFile = path.Clean(pathToFile) - getSpec, ok := resolvePath[pathToFile] - if !ok { - err1 := fmt.Errorf("path not found: %s", pathToFile) - return nil, err1 - } - return getSpec() - } - var specData []byte - specData, err = rawSpec() - if err != nil { - return - } - swagger, err = loader.LoadFromData(specData) - if err != nil { - return - } - return -} 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 300cf7f190..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.HostSVC) { - - 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 e530602202..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.HostSVC, 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 d42b14dc17..0000000000 --- a/dispatcher/underlay.go +++ /dev/null @@ -1,410 +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 d := dst.(type) { - case *net.IPAddr: - return UDPDestination{ - IA: pkt.SCION.DstIA, - Public: &net.UDPAddr{ - IP: d.IP, - Port: int(pkt.UDP.DstPort), - }, - }, nil - case addr.HostSVC: - return SVCDestination{ - IA: pkt.SCION.DstIA, - Svc: d, - }, 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 - } - ipAddr, ok := dst.(*net.IPAddr) - if !ok { - return nil, serrors.WithCtx(ErrUnsupportedDestination, "type", common.TypeOf(dst)) - } - return UDPDestination{ - IA: pkt.SCION.DstIA, - Public: &net.UDPAddr{ - IP: ipAddr.IP, - 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.HostSVC -} - -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 bc08c21095..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(&net.IPAddr{IP: net.IP{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.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(&net.IPAddr{IP: net.IP{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(&net.IPAddr{IP: net.IP{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(&net.IPAddr{IP: net.IP{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(&net.IPAddr{IP: net.IP{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(&net.IPAddr{IP: net.IP{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(&net.IPAddr{IP: net.IP{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(&net.IPAddr{IP: net.IP{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(&net.IPAddr{IP: net.IP{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(&net.IPAddr{IP: net.IP{127, 0, 0, 1}})) - require.NoError(t, pkt.SCION.SetDstAddr(&net.IPAddr{IP: net.IP{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(&net.IPAddr{IP: net.IP{127, 0, 0, 2}})) - require.NoError(t, expected.SetDstAddr(&net.IPAddr{IP: net.IP{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(&net.IPAddr{IP: net.IP{192, 168, 0, 1}})) - require.NoError(t, scion.SetDstAddr(&net.IPAddr{IP: net.IP{192, 168, 0, 2}})) - return scion -} diff --git a/docker/BUILD.bazel b/docker/BUILD.bazel index 9744290598..3f01c7ef89 100644 --- a/docker/BUILD.bazel +++ b/docker/BUILD.bazel @@ -7,7 +7,6 @@ container_bundle( images = { "control:latest": ":control", "daemon:latest": ":daemon", - "dispatcher:latest": ":dispatcher", "posix-gateway:latest": ":posix_gateway", "posix-router:latest": "posix_router", }, @@ -46,16 +45,6 @@ scion_app_images( entrypoint = ["/app/control"], ) -scion_app_images( - name = "dispatcher", - src = "//dispatcher/cmd/dispatcher", - cmd = [ - "--config", - "/share/conf/disp.toml", - ], - entrypoint = ["/app/dispatcher"], -) - scion_app_images( name = "daemon", src = "//daemon/cmd/daemon", diff --git a/gateway/gateway.go b/gateway/gateway.go index 4f9fa38dcf..84ab960c1c 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -320,13 +320,9 @@ 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{ - LocalIA: localIA, - LocalIP: g.PathMonitorIP, - }, + LocalIA: localIA, + LocalIP: g.PathMonitorIP, + RevocationHandler: revocationHandler, ProbeInterval: 0, // using default for now ProbesSent: probesSent, ProbesReceived: probesReceived, diff --git a/gateway/pathhealth/BUILD.bazel b/gateway/pathhealth/BUILD.bazel index 2bf91721df..927577b70d 100644 --- a/gateway/pathhealth/BUILD.bazel +++ b/gateway/pathhealth/BUILD.bazel @@ -23,6 +23,7 @@ go_library( "//pkg/slayers/path/scion:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", + "//private/topology:go_default_library", ], ) diff --git a/gateway/pathhealth/pathwatcher.go b/gateway/pathhealth/pathwatcher.go index 1888bfe827..fa27d64558 100644 --- a/gateway/pathhealth/pathwatcher.go +++ b/gateway/pathhealth/pathwatcher.go @@ -29,6 +29,7 @@ import ( "github.com/scionproto/scion/pkg/slayers/path/scion" "github.com/scionproto/scion/pkg/snet" snetpath "github.com/scionproto/scion/pkg/snet/path" + "github.com/scionproto/scion/private/topology" ) const ( @@ -51,7 +52,6 @@ type DefaultPathWatcherFactory struct { // 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 @@ -77,10 +77,6 @@ func (f *DefaultPathWatcherFactory) New( 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, @@ -90,21 +86,24 @@ func (f *DefaultPathWatcherFactory) New( } return create(remote) } + nc, err := (&snet.DefaultConnector{ + SCMPHandler: scmpHandler{ + wrappedHandler: snet.DefaultSCMPHandler{ + RevocationHandler: f.RevocationHandler, + SCMPErrors: f.SCMPErrors, + }, + pkts: pktChan, + }, + Metrics: f.SCIONPacketConnMetrics, + }).OpenUDP(&net.UDPAddr{IP: f.LocalIP, Port: topology.EndhostPort}) + if err != nil { + return nil, serrors.WrapStr("creating connection for probing", err) + } return &pathWatcher{ remote: remote, probeInterval: f.ProbeInterval, - conn: &snet.SCIONPacketConn{ - Conn: nc, - SCMPHandler: scmpHandler{ - wrappedHandler: snet.DefaultSCMPHandler{ - RevocationHandler: f.RevocationHandler, - SCMPErrors: f.SCMPErrors, - }, - pkts: pktChan, - }, - Metrics: f.SCIONPacketConnMetrics, - }, - id: id, + conn: nc, + id: id, localAddr: snet.SCIONAddress{ IA: f.LocalIA, Host: addr.HostFromIP(f.LocalIP), diff --git a/gateway/pathhealth/remotewatcher.go b/gateway/pathhealth/remotewatcher.go index c503aae7eb..5863852268 100644 --- a/gateway/pathhealth/remotewatcher.go +++ b/gateway/pathhealth/remotewatcher.go @@ -229,7 +229,7 @@ func (w *remoteWatcher) updatePaths(ctx context.Context) { } pathW, err := w.pathWatcherFactory.New(ctx, w.remote, path, id) 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) diff --git a/pkg/slayers/scion.go b/pkg/slayers/scion.go index 5bf3df2176..bcc131649a 100644 --- a/pkg/slayers/scion.go +++ b/pkg/slayers/scion.go @@ -374,7 +374,7 @@ func (s *SCION) SetDstAddr(dst net.Addr) error { return err } -// SetSrcAddr sets the source address and updates the DstAddrType field accordingly. +// SetSrcAddr sets the source address and updates the SrcAddrType field accordingly. // SetSrcAddr takes ownership of src and callers should not write to it after calling SetSrcAddr. // Changes to src might leave the layer in an inconsistent state. func (s *SCION) SetSrcAddr(src net.Addr) error { 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 f0b64f8659..0000000000 --- a/pkg/sock/reliable/frame.go +++ /dev/null @@ -1,147 +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/addr" - "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 := addr.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 := addr.HostAddrType(f.AddressType) - if t == addr.HostTypeIPv4 || t == addr.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 20893aa72c..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.HostSVC) (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 90e2c1c65c..0000000000 --- a/pkg/sock/reliable/packetizer.go +++ /dev/null @@ -1,118 +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/addr" - "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(addr.HostAddrType(rcvdAddrType)) - portLength := getPortLength(addr.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 91f0df5110..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.HostSVC) (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.HostSVC) *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 e7475825a6..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.HostSVC -} - -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.HostSVC(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(addr.HostAddrType(l.AddressType)) { - return ErrBadAddressType - } - addressLength := getAddressLength(addr.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 c70afac0b5..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.HostSVC) (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.HostSVC) (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.HostSVC) (*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.HostSVC) (*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 f79dc256cd..0000000000 --- a/pkg/sock/reliable/util.go +++ /dev/null @@ -1,60 +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" - - "github.com/scionproto/scion/pkg/addr" -) - -func getAddressType(address *net.UDPAddr) addr.HostAddrType { - if address == nil || address.IP == nil { - return addr.HostTypeNone - } - return getIPAddressType(address.IP) -} - -func getIPAddressType(ip net.IP) addr.HostAddrType { - if ip.To4() != nil { - return addr.HostTypeIPv4 - } - return addr.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 addr.HostAddrType) bool { - return t == addr.HostTypeNone || t == addr.HostTypeIPv4 || t == addr.HostTypeIPv6 -} - -func getAddressLength(t addr.HostAddrType) int { - n, _ := addr.HostLen(t) - return int(n) -} - -func getPortLength(t addr.HostAddrType) int { - if t == addr.HostTypeIPv4 || t == addr.HostTypeIPv6 { - return 2 - } - return 0 -} 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/scion.sh b/scion.sh index 371f410274..d7ec2a6fdd 100755 --- a/scion.sh +++ b/scion.sh @@ -88,22 +88,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 } cmd_stop() { diff --git a/spec/BUILD.bazel b/spec/BUILD.bazel index 49b6a8e3bf..84016cd14d 100644 --- a/spec/BUILD.bazel +++ b/spec/BUILD.bazel @@ -27,16 +27,6 @@ openapi_bundle( visibility = ["//visibility:public"], ) -openapi_bundle( - name = "dispatcher", - srcs = [ - "//spec/common:base.yml", - "//spec/common:process.yml", - ], - entrypoint = "//spec/dispatcher:spec.yml", - visibility = ["//visibility:public"], -) - openapi_bundle( name = "daemon", srcs = [ diff --git a/tools/end2end_integration/main.go b/tools/end2end_integration/main.go index 757d252381..992bc9dda8 100644 --- a/tools/end2end_integration/main.go +++ b/tools/end2end_integration/main.go @@ -296,7 +296,7 @@ func clientTemplate(progressSock string) integration.Cmd { // getPairs returns the pairs to test according to the specified subset. 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/integration/integration.go b/tools/integration/integration.go index 991b708e72..518bef3de7 100644 --- a/tools/integration/integration.go +++ b/tools/integration/integration.go @@ -218,12 +218,8 @@ func generateAllSrcDst(hostAddr HostAddr, unique bool) []IAPair { type HostAddr func(ia addr.IA) *snet.UDPAddr -// DispAddr reads the CS host Addr from the topology for the specified IA. In general this -// could be the IP of any service (PS/BS/CS) in that IA because they share the same dispatcher in -// the dockerized topology. -// The host IP is used as client or server address in the tests because the testing container is -// connecting to the dispatcher of the services. -var DispAddr HostAddr = func(ia addr.IA) *snet.UDPAddr { +// CSAddr reads the CS host Addr from the topology for the specified IA. +var CSAddr HostAddr = func(ia addr.IA) *snet.UDPAddr { if a := loadAddr(ia); a != nil { return a } diff --git a/tools/scion_integration/main.go b/tools/scion_integration/main.go index 8964b6c416..5f993754f0 100644 --- a/tools/scion_integration/main.go +++ b/tools/scion_integration/main.go @@ -110,7 +110,7 @@ func realMain() int { } log.Info(fmt.Sprintf("Run scion %s tests:", tc.Name)) in := integration.NewBinaryIntegration(tc.Name, integration.WrapperCmd, tc.Args, nil) - pairs := tc.Pairs(integration.DispAddr) + pairs := tc.Pairs(integration.CSAddr) err := integration.RunUnaryTests(in, pairs, integration.DefaultRunTimeout, tc.OutputCheck) if err != nil { log.Error(fmt.Sprintf("Error during scion %s tests", tc.Name), "err", err) diff --git a/tools/topology/common.py b/tools/topology/common.py index b12326da03..51abcae320 100644 --- a/tools/topology/common.py +++ b/tools/topology/common.py @@ -39,7 +39,6 @@ PS_CONFIG_NAME = 'ps.toml' CO_CONFIG_NAME = 'co.toml' SD_CONFIG_NAME = 'sd.toml' -DISP_CONFIG_NAME = 'disp.toml' SIG_CONFIG_NAME = 'sig.toml' SD_API_PORT = 30255 @@ -128,24 +127,6 @@ def sciond_ip(docker, topo_id, networks: Mapping[IPNetwork, return None -def prom_addr_dispatcher(docker, topo_id, - networks: Mapping[IPNetwork, - NetworkDescription], port, name): - if not docker: - return "[127.0.0.1]:%s" % port - target_name = '' - if name.startswith('disp_br'): - target_name = 'br%s%s_internal' % (topo_id.file_fmt(), name[-2:]) - elif name.startswith('disp_sig'): - target_name = 'sig%s' % topo_id.file_fmt() - else: - target_name = 'disp%s' % topo_id.file_fmt() - for net_desc in networks.values(): - if target_name in net_desc.ip_net: - return '[%s]:%s' % (net_desc.ip_net[target_name].ip, port) - return None - - def docker_image(args, image): if args.docker_registry: image = '%s/%s' % (args.docker_registry, image) @@ -175,7 +156,7 @@ def remote_nets(networks, topo_id): """ rem_nets = [] for key in networks: - if 'sig' in key and topo_id.file_fmt() not in key: + if 'sig_setup' in key and topo_id.file_fmt() not in key: rem_nets.append(str(networks[key][0]['net'])) return ','.join(rem_nets) diff --git a/tools/topology/config.py b/tools/topology/config.py index 948ed97f80..57e05d741c 100644 --- a/tools/topology/config.py +++ b/tools/topology/config.py @@ -131,7 +131,6 @@ def _generate_go(self, topo_dicts): go_gen.generate_sciond() go_gen.generate_control_service() go_gen.generate_co() - go_gen.generate_disp() def _go_args(self, topo_dicts): return GoGenArgs(self.args, self.topo_config, topo_dicts, self.networks) diff --git a/tools/topology/docker.py b/tools/topology/docker.py index b0594925e4..ccb114ef78 100644 --- a/tools/topology/docker.py +++ b/tools/topology/docker.py @@ -87,7 +87,6 @@ def _sig_args(self): self.elem_networks) def _gen_topo(self, topo_id, topo, base): - self._dispatcher_conf(topo_id, topo, base) self._br_conf(topo_id, topo, base) self._control_service_conf(topo_id, topo, base) self._sciond_conf(topo_id, base) @@ -174,46 +173,23 @@ def _br_conf(self, topo_id, topo, base): def _control_service_conf(self, topo_id, topo, base): for k in topo.get("control_service", {}).keys(): + entry = { 'image': docker_image(self.args, 'control'), 'container_name': self.prefix + k, - 'depends_on': ['scion_disp_%s' % k], - 'network_mode': - 'service:scion_disp_%s' % k, + 'networks': {}, 'user': self.user, 'volumes': [ self._cache_vol(), self._certs_vol(), '%s:/share/conf:ro' % base, - self._disp_vol(k), ], 'command': ['--config', '/share/conf/%s.toml' % k] } - self.dc_conf['services']['scion_%s' % k] = entry - - def _dispatcher_conf(self, topo_id, topo, base): - image = 'dispatcher' - base_entry = { - 'extra_hosts': ['jaeger:%s' % docker_host(self.args.docker)], - 'image': docker_image(self.args, image), - 'networks': {}, - 'user': self.user, - 'volumes': [], - 'depends_on': { - 'utils_chowner': { - 'condition': 'service_started' - }, - }, - } - keys = (list(topo.get("control_service", {})) + - ["tester_%s" % topo_id.file_fmt()]) - for disp_id in keys: - entry = copy.deepcopy(base_entry) - net_key = disp_id - net = self.elem_networks[net_key][0] + net = self.elem_networks[k][0] ipv = 'ipv4' if ipv not in net: ipv = 'ipv6' @@ -221,17 +197,7 @@ def _dispatcher_conf(self, topo_id, topo, base): entry['networks'][self.bridges[net['net']]] = { '%s_address' % ipv: ip } - entry['container_name'] = '%sdisp_%s' % (self.prefix, disp_id) - entry['volumes'].append(self._disp_vol(disp_id)) - conf = '%s:/share/conf:rw' % base - entry['volumes'].append(conf) - entry['command'] = [ - '--config', '/share/conf/disp_%s.toml' % disp_id - ] - - self.dc_conf['services']['scion_disp_%s' % disp_id] = entry - self.dc_conf['volumes'][self._disp_vol(disp_id).split(':') - [0]] = None + self.dc_conf['services']['scion_%s' % k] = entry def _sciond_conf(self, topo_id, base): name = sciond_svc_name(topo_id) @@ -240,18 +206,15 @@ def _sciond_conf(self, topo_id, base): if ipv not in net: ipv = 'ipv6' ip = str(net[ipv]) - disp_id = 'cs%s-1' % topo_id.file_fmt() entry = { 'extra_hosts': ['jaeger:%s' % docker_host(self.args.docker)], 'image': docker_image(self.args, 'daemon'), 'container_name': '%ssd%s' % (self.prefix, topo_id.file_fmt()), - 'depends_on': ['scion_disp_%s' % disp_id], 'user': self.user, 'volumes': [ - self._disp_vol(disp_id), self._cache_vol(), self._certs_vol(), '%s:/share/conf:ro' % base @@ -265,9 +228,6 @@ def _sciond_conf(self, topo_id, base): } self.dc_conf['services'][name] = entry - def _disp_vol(self, disp_id): - return 'vol_%sdisp_%s:/run/shm/dispatcher:rw' % (self.prefix, 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 fc15124bd7..e9efc0babd 100644 --- a/tools/topology/docker_utils.py +++ b/tools/topology/docker_utils.py @@ -76,25 +76,25 @@ def _test_conf(self, topo_id): entry = { 'image': docker_image(self.args, 'tester'), 'container_name': 'tester_%s' % topo_id.file_fmt(), - 'depends_on': ['scion_disp_%s' % name], 'privileged': True, 'entrypoint': 'sh tester.sh', 'environment': {}, # 'user': self.user, 'volumes': [ - 'vol_scion_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:scion_disp_%s' % name, + 'networks': {}, } net = self.args.networks[name][0] ipv = 'ipv4' if ipv not in net: ipv = 'ipv6' - disp_net = self.args.networks[name][0] - entry['environment']['SCION_LOCAL_ADDR'] = str(disp_net[ipv]) + entry['networks'][self.args.bridges[net['net']]] = { + '%s_address' % ipv: str(net[ipv]) + } + entry['environment']['SCION_LOCAL_ADDR'] = str(net[ipv]) sciond_net = self.args.networks['sd%s' % topo_id.file_fmt()][0] if ipv == 'ipv4': entry['environment']['SCION_DAEMON'] = '%s:30255' % sciond_net[ipv] diff --git a/tools/topology/go.py b/tools/topology/go.py index 85e4c63718..10b960e1bb 100644 --- a/tools/topology/go.py +++ b/tools/topology/go.py @@ -26,10 +26,8 @@ from topology.util import write_file from topology.common import ( ArgsBase, - DISP_CONFIG_NAME, docker_host, prom_addr, - prom_addr_dispatcher, sciond_ip, sciond_name, translate_features, @@ -44,7 +42,6 @@ CS_PROM_PORT, DEFAULT_BR_PROM_PORT, SCIOND_PROM_PORT, - DISP_PROM_PORT, CO_PROM_PORT, ) @@ -119,7 +116,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': { @@ -163,7 +159,6 @@ def _build_co_conf(self, topo_id, ia, base, name, infra_elem): 'general': { 'ID': name, 'ConfigDir': config_dir, - 'ReconnectToDispatcher': True, }, 'log': self._log_entry(name), 'trust_db': { @@ -244,7 +239,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': { @@ -267,45 +261,6 @@ def _build_sciond_conf(self, topo_id, ia, base): } return raw_entry - def generate_disp(self): - if self.args.docker: - self._gen_disp_docker() - 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"))) - - 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: - disp_id = 'disp_%s' % k - disp_conf = self._build_disp_conf(disp_id, topo_id) - write_file(os.path.join(base, '%s.toml' % disp_id), toml.dumps(disp_conf)) - - def _build_disp_conf(self, name, topo_id=None): - prometheus_addr = prom_addr_dispatcher(self.args.docker, topo_id, - 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 { - 'dispatcher': { - 'id': name, - }, - 'log': self._log_entry(name), - 'metrics': { - 'prometheus': prometheus_addr, - }, - 'features': translate_features(self.args.features), - 'api': { - 'addr': api_addr, - }, - } - def _tracing_entry(self): docker_ip = docker_host(self.args.docker) entry = { diff --git a/tools/topology/prometheus.py b/tools/topology/prometheus.py index 94cc932fff..4b3929e0fa 100644 --- a/tools/topology/prometheus.py +++ b/tools/topology/prometheus.py @@ -30,7 +30,6 @@ from topology.common import ( ArgsTopoDicts, prom_addr, - prom_addr_dispatcher, sciond_ip, ) from topology.net import ( @@ -42,7 +41,6 @@ SCIOND_PROM_PORT = 30455 SIG_PROM_PORT = 30456 CO_PROM_PORT = 30457 -DISP_PROM_PORT = 30441 DEFAULT_BR_PROM_PORT = 30442 PROM_DC_FILE = "prom-dc.yml" @@ -60,13 +58,11 @@ class PrometheusGenerator(object): "BorderRouters": "br.yml", "ControlService": "cs.yml", "Sciond": "sd.yml", - "Dispatcher": "disp.yml", } JOB_NAMES = { "BorderRouters": "BR", "ControlService": "CS", "Sciond": "SD", - "Dispatcher": "dispatcher", } def __init__(self, args): @@ -86,19 +82,12 @@ def generate(self): for elem_id, elem in as_topo["control_service"].items(): a = prom_addr(elem["addr"], CS_PROM_PORT) ele_dict["ControlService"].append(a) - if self.args.docker: - host_dispatcher = prom_addr_dispatcher(self.args.docker, topo_id, - self.args.networks, DISP_PROM_PORT, "") - br_dispatcher = prom_addr_dispatcher(self.args.docker, topo_id, - self.args.networks, DISP_PROM_PORT, "br") - ele_dict["Dispatcher"] = [host_dispatcher, br_dispatcher] sd_prom_addr = '[%s]:%d' % (sciond_ip(self.args.docker, topo_id, self.args.networks), SCIOND_PROM_PORT) ele_dict["Sciond"].append(sd_prom_addr) config_dict[topo_id] = ele_dict self._write_config_files(config_dict) self._write_dc_file() - self._write_disp_file() def _write_config_files(self, config_dict): targets_paths = defaultdict(list) @@ -112,8 +101,6 @@ def _write_config_files(self, config_dict): as_local_targets_path[self.JOB_NAMES[ele_type]] = [local_path] self._write_target_file(base, target_list, ele_type) self._write_config_file(os.path.join(base, PROM_FILE), as_local_targets_path) - if not self.args.docker: - targets_paths["dispatcher"] = [os.path.join("dispatcher", "prometheus", "disp.yml")] self._write_config_file(os.path.join(self.args.output_dir, PROM_FILE), targets_paths) def _write_config_file(self, config_path, job_dict): @@ -140,15 +127,6 @@ def _write_target_file(self, base_path, target_addrs, ele_type): target_config = [{'targets': target_addrs}] write_file(targets_path, yaml.dump(target_config, default_flow_style=False)) - def _write_disp_file(self): - if self.args.docker: - return - targets_path = os.path.join(self.args.output_dir, "dispatcher", - PrometheusGenerator.PROM_DIR, "disp.yml") - target_config = [{'targets': [prom_addr_dispatcher(False, None, None, - DISP_PROM_PORT, None)]}] - write_file(targets_path, yaml.dump(target_config, default_flow_style=False)) - def _write_dc_file(self): name = 'prometheus' prom_dc = { diff --git a/tools/topology/sig.py b/tools/topology/sig.py index eb7855c0ae..54d6f11bf9 100644 --- a/tools/topology/sig.py +++ b/tools/topology/sig.py @@ -61,66 +61,40 @@ def generate(self): for topo_id, topo in self.args.topo_dicts.items(): base = os.path.join(self.output_base, topo_id.base_dir(self.args.output_dir)) - self._dispatcher_conf(topo_id, base) self._sig_dc_conf(topo_id, base) self._sig_toml(topo_id, topo) self._sig_json(topo_id) return self.dc_conf - def _dispatcher_conf(self, topo_id, base): - # Create dispatcher config - entry = { - 'image': - 'dispatcher', - 'container_name': - 'scion_%sdisp_sig_%s' % (self.prefix, topo_id.file_fmt()), - 'depends_on': { - 'utils_chowner': { - 'condition': 'service_started' - }, - }, - 'user': - self.user, - 'networks': {}, - 'volumes': [ - self._disp_vol(topo_id), - '%s:/share/conf:rw' % base, - ], - 'command': - ['--config', - '/share/conf/disp_sig_%s.toml' % topo_id.file_fmt()], - } - - net = self.args.networks['sig%s' % topo_id.file_fmt()][0] - ipv = 'ipv4' - if ipv not in net: - ipv = 'ipv6' - entry['networks'][self.args.bridges[net['net']]] = { - '%s_address' % ipv: str(net[ipv]) - } - self.dc_conf['services']['scion_disp_sig_%s' % - topo_id.file_fmt()] = entry - vol_name = 'vol_scion_%sdisp_sig_%s' % (self.prefix, - topo_id.file_fmt()) - self.dc_conf['volumes'][vol_name] = None def _sig_dc_conf(self, topo_id, base): - setup_name = 'scion_sig_setup_%s' % topo_id.file_fmt() - disp_id = 'scion_disp_sig_%s' % topo_id.file_fmt() - self.dc_conf['services'][setup_name] = { + setup_name = 'sig_setup%s' % topo_id.file_fmt() + setup_net = self.args.networks[setup_name][0] + ipv_setup = 'ipv4' + if ipv_setup not in setup_net: + ipv_setup = 'ipv6' + entry = { 'image': 'tester:latest', - 'depends_on': [disp_id], + 'container_name': setup_name, 'entrypoint': './sig_setup.sh', + 'networks': {}, 'privileged': True, - 'network_mode': 'service:%s' % disp_id, } - self.dc_conf['services']['scion_sig_%s' % topo_id.file_fmt()] = { + entry['networks'][self.args.bridges[setup_net['net']]] = { + '%s_address' % ipv_setup: str(setup_net[ipv_setup]) + } + self.dc_conf['services'][setup_name] = entry + + sig_net = self.args.networks['sig%s' % topo_id.file_fmt()][0] + ipv_sig = 'ipv4' + if ipv_sig not in sig_net: + ipv_sig = 'ipv6' + entry = { 'image': 'posix-gateway:latest', 'container_name': 'scion_%ssig_%s' % (self.prefix, topo_id.file_fmt()), 'depends_on': [ - disp_id, sciond_svc_name(topo_id), setup_name, ], @@ -133,14 +107,16 @@ def _sig_dc_conf(self, topo_id, base): # but on the RHEL machines in in CI it simply doesn't. Needs to be investigated & fixed. # 'user': self.user, 'volumes': [ - self._disp_vol(topo_id), '/dev/net/tun:/dev/net/tun', '%s:/share/conf' % base, ], - 'network_mode': - 'service:%s' % disp_id, + 'networks': {}, 'command': ['--config', '/share/conf/sig.toml'], } + entry['networks'][self.args.bridges[sig_net['net']]] = { + '%s_address' % ipv_sig: str(sig_net[ipv_sig]) + } + self.dc_conf['services']['scion_sig_%s' % topo_id.file_fmt()] = entry def _sig_json(self, topo_id): sig_cfg = {"ConfigVersion": 1, "ASes": {}} @@ -195,6 +171,3 @@ def _sig_toml(self, topo_id, topo): SIG_CONFIG_NAME) write_file(path, toml.dumps(sig_conf)) - def _disp_vol(self, topo_id): - return 'vol_scion_%sdisp_sig_%s:/run/shm/dispatcher:rw' % ( - self.prefix, topo_id.file_fmt()) diff --git a/tools/topology/supervisor.py b/tools/topology/supervisor.py index cefc0cb73d..18d1c80443 100644 --- a/tools/topology/supervisor.py +++ b/tools/topology/supervisor.py @@ -26,7 +26,6 @@ from topology.util import write_file from topology.common import ( ArgsTopoDicts, - DISP_CONFIG_NAME, SD_CONFIG_NAME, ) @@ -49,7 +48,6 @@ def generate(self): for topo_id, topo in self.args.topo_dicts.items(): self._add_as_config(config, topo_id, topo) - self._add_dispatcher(config) self._write_config(config, os.path.join(self.args.output_dir, SUPERVISOR_CONF)) @@ -97,19 +95,6 @@ def _sciond_entry(self, topo_id, conf_dir): ] return (sd_name, self._common_entry(sd_name, cmd_args)) - def _add_dispatcher(self, config): - name, entry = self._dispatcher_entry() - self._add_prog(config, name, entry) - - def _dispatcher_entry(self): - name = "dispatcher" - conf_dir = os.path.join(self.args.output_dir, name) - cmd_args = [ - "bin/dispatcher", "--config", - os.path.join(conf_dir, DISP_CONFIG_NAME) - ] - return (name, self._common_entry(name, cmd_args)) - def _add_prog(self, config, name, entry): config["program:%s" % name] = entry @@ -125,9 +110,6 @@ def _common_entry(self, name, cmd_args): 'priority': 100, 'command': ' '.join(shlex.quote(a) for a in cmd_args), } - if name == "dispatcher": - entry['startsecs'] = 1 - entry['priority'] = 50 return entry def _write_config(self, config, path): diff --git a/tools/topology/topo.py b/tools/topology/topo.py index 46f8c9a58f..6bdcf54ddc 100644 --- a/tools/topology/topo.py +++ b/tools/topology/topo.py @@ -179,6 +179,7 @@ def _register_br_entry(self, local, l_ifid, remote, r_ifid, remote_type, attrs, def _register_sig(self, topo_id, as_conf): addr_type = addr_type_from_underlay(as_conf.get('underlay', DEFAULT_UNDERLAY)) self._reg_addr(topo_id, "sig" + topo_id.file_fmt(), addr_type) + self._reg_addr(topo_id, "sig_setup" + topo_id.file_fmt(), addr_type) def _register_sciond(self, topo_id, as_conf): addr_type = addr_type_from_underlay(as_conf.get('underlay', DEFAULT_UNDERLAY)) @@ -263,7 +264,7 @@ def _gen_srv_entries(self, topo_id, as_conf): self._gen_srv_entry(topo_id, as_conf, conf_key, def_num, nick, topo_key) def _gen_srv_entry(self, topo_id, as_conf, conf_key, def_num, nick, - topo_key, uses_dispatcher=True): + topo_key): addr_type = addr_type_from_underlay(as_conf.get('underlay', DEFAULT_UNDERLAY)) count = self._srv_count(as_conf, conf_key, def_num) for i in range(1, count + 1):