From d90161d18c91b9639362237bea4380fc66a5c9bb Mon Sep 17 00:00:00 2001 From: Jean-Christophe Hugly Date: Wed, 15 Nov 2023 18:23:35 +0100 Subject: [PATCH 01/40] router: The beginings of a new benchmark using an isolated router. --- BUILD.bazel | 1 + acceptance/router_newbenchmark/BUILD.bazel | 39 ++++ acceptance/router_newbenchmark/conf/br.toml | 14 ++ .../router_newbenchmark/conf/keys/master0.key | 1 + .../router_newbenchmark/conf/keys/master1.key | 1 + .../router_newbenchmark/conf/prometheus.yml | 10 + .../router_newbenchmark/conf/topology.json | 56 ++++++ acceptance/router_newbenchmark/pause.tar | Bin 0 -> 258560 bytes acceptance/router_newbenchmark/test.py | 172 ++++++++++++++++++ tools/brload/BUILD.bazel | 33 ++++ tools/brload/cases.go | 133 ++++++++++++++ tools/brload/main.go | 160 ++++++++++++++++ 12 files changed, 620 insertions(+) create mode 100644 acceptance/router_newbenchmark/BUILD.bazel create mode 100644 acceptance/router_newbenchmark/conf/br.toml create mode 100644 acceptance/router_newbenchmark/conf/keys/master0.key create mode 100644 acceptance/router_newbenchmark/conf/keys/master1.key create mode 100644 acceptance/router_newbenchmark/conf/prometheus.yml create mode 100644 acceptance/router_newbenchmark/conf/topology.json create mode 100644 acceptance/router_newbenchmark/pause.tar create mode 100644 acceptance/router_newbenchmark/test.py create mode 100644 tools/brload/BUILD.bazel create mode 100644 tools/brload/cases.go create mode 100644 tools/brload/main.go diff --git a/BUILD.bazel b/BUILD.bazel index c43159ca4e..106951d6ac 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -195,6 +195,7 @@ pkg_tar( "//acceptance/cmd/sig_ping_acceptance", "//pkg/private/xtest/graphupdater", "//tools/braccept", + "//tools/brload", "//tools/buildkite/cmd/buildkite_artifacts", "//tools/end2end", "//tools/end2end_integration", diff --git a/acceptance/router_newbenchmark/BUILD.bazel b/acceptance/router_newbenchmark/BUILD.bazel new file mode 100644 index 0000000000..07604a90e6 --- /dev/null +++ b/acceptance/router_newbenchmark/BUILD.bazel @@ -0,0 +1,39 @@ +load("@io_bazel_rules_docker//container:container.bzl", "container_image") +load("//acceptance/common:raw.bzl", "raw_test") + +exports_files([ + "conf", + "test.py", + "pause.tar", +]) + +args = [ + "--executable", + "brload:$(location //tools/brload)", + "--container-loader", + "posix-router:latest#$(location //acceptance/router_newbenchmark:router)", + "--pause_tar", + "$(location //acceptance/router_newbenchmark:pause.tar)", +] + +data = [ + "pause.tar", + ":conf", + ":router", + "//tools/brload", +] + +raw_test( + name = "test", + src = "test.py", + args = args, + data = data, + homedir = "$(rootpath :router)", + # This test uses sudo and accesses /var/run/netns. + local = True, +) + +container_image( + name = "router", + base = "//docker:posix_router", +) diff --git a/acceptance/router_newbenchmark/conf/br.toml b/acceptance/router_newbenchmark/conf/br.toml new file mode 100644 index 0000000000..f3857e2727 --- /dev/null +++ b/acceptance/router_newbenchmark/conf/br.toml @@ -0,0 +1,14 @@ +[general] +id = "br1a" +config_dir = "/share/conf" + +[metrics] +prometheus = "192.168.0.1:30442" + +[features] + +[api] +addr = "192.168.0.1:31142" + +[log.console] +level = "error" diff --git a/acceptance/router_newbenchmark/conf/keys/master0.key b/acceptance/router_newbenchmark/conf/keys/master0.key new file mode 100644 index 0000000000..8bd063d0ac --- /dev/null +++ b/acceptance/router_newbenchmark/conf/keys/master0.key @@ -0,0 +1 @@ +V9SOOHqV4KdAL6utGnvEtA== \ No newline at end of file diff --git a/acceptance/router_newbenchmark/conf/keys/master1.key b/acceptance/router_newbenchmark/conf/keys/master1.key new file mode 100644 index 0000000000..5505c43fa4 --- /dev/null +++ b/acceptance/router_newbenchmark/conf/keys/master1.key @@ -0,0 +1 @@ +mdPcnujoYi4JmWALGeKbSQ== \ No newline at end of file diff --git a/acceptance/router_newbenchmark/conf/prometheus.yml b/acceptance/router_newbenchmark/conf/prometheus.yml new file mode 100644 index 0000000000..d0abecdfc1 --- /dev/null +++ b/acceptance/router_newbenchmark/conf/prometheus.yml @@ -0,0 +1,10 @@ +global: + evaluation_interval: 1s + external_labels: + monitor: scion-monitor + scrape_interval: 1s +scrape_configs: + - job_name: BR + static_configs: + - targets: + - 192.168.0.1:30442 diff --git a/acceptance/router_newbenchmark/conf/topology.json b/acceptance/router_newbenchmark/conf/topology.json new file mode 100644 index 0000000000..6edf1873cd --- /dev/null +++ b/acceptance/router_newbenchmark/conf/topology.json @@ -0,0 +1,56 @@ +{ + "attributes": [ + "core" + ], + "isd_as": "1-ff00:0:1", + "mtu": 1400, + "control_service": { + "cs1-ff00_0_1-1": { + "addr": "172.20.0.28:30252" + } + }, + "discovery_service": { + "cs1-ff00_0_1-1": { + "addr": "172.20.0.28:30252" + } + }, + "border_routers": { + "br1a": { + "internal_addr": "192.168.0.1:30042", + "interfaces": { + "2": { + "underlay": { + "public": "192.168.2.1:50000", + "remote": "192.168.2.2:50000" + }, + "isd_as": "1-ff00:0:2", + "link_to": "child", + "mtu": 1280 + }, + "3": { + "underlay": { + "public": "192.168.3.1:50000", + "remote": "192.168.3.3:50000" + }, + "isd_as": "1-ff00:0:3", + "link_to": "child", + "mtu": 1280 + } + } + }, + "br1b": { + "internal_addr": "192.168.0.2:30042", + "interfaces": { + "4": { + "underlay": { + "public": "192.168.4.1:50000", + "remote": "192.168.4.4:50000" + }, + "isd_as": "2-ff00:0:4", + "link_to": "core", + "mtu": 1280 + } + } + } + } +} diff --git a/acceptance/router_newbenchmark/pause.tar b/acceptance/router_newbenchmark/pause.tar new file mode 100644 index 0000000000000000000000000000000000000000..da03eed189f8c5fa9423f1be5876f2ea05f3e08c GIT binary patch literal 258560 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 NJ#g9s|37=+e*oxG3EBVv literal 0 HcmV?d00001 diff --git a/acceptance/router_newbenchmark/test.py b/acceptance/router_newbenchmark/test.py new file mode 100644 index 0000000000..fedc9df74c --- /dev/null +++ b/acceptance/router_newbenchmark/test.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +# 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. + + +import shutil +import time + +from typing import List +from plumbum import cli +from plumbum import cmd + +from acceptance.common import base + +import logging + +logger = logging.getLogger(__name__) + +# Those values are valid expectations only when running in the CI environment. +EXPECTATIONS = { + 'in': 53000, + 'out': 26000, + 'in_transit': 73000, + 'out_transit': 49000, + 'br_transit': 73000, +} + + +def exec_docker(command: str) -> str: + return cmd.docker(str.split(command)) + + +def sudo(command: str) -> str: + # -A, --askpass makes sure command is failing and does not wait for + # interactive password input. + return cmd.sudo("-A", str.split(command)) + + +def create_veth(host: str, container: str, ip: str, mac: str, ns: str, neighbors: List[str]): + sudo(f"ip link add {host} mtu 8000 type veth peer name {container} mtu 8000") + sudo(f"sysctl -qw net.ipv6.conf.{host}.disable_ipv6=1") + sudo(f"ip link set {host} up") + sudo(f"ip link set {container} netns {ns}") + sudo(f"ip netns exec {ns} sysctl -qw net.ipv6.conf.{container}.disable_ipv6=1") + sudo(f"ip netns exec {ns} ethtool -K {container} rx off tx off") + sudo(f"ip netns exec {ns} ip link set {container} address {mac}") + sudo(f"ip netns exec {ns} ip addr add {ip} dev {container}") + for n in neighbors: + sudo(f"ip netns exec {ns} ip neigh add {n} " + f"lladdr f0:0d:ca:fe:be:ef nud permanent dev {container}") + sudo(f"ip netns exec {ns} ip link set {container} up") + + +class RouterBMTest(base.TestBase): + """ + Tests that the implementation of a router has sufficient performance (in terms of packets + per available machine*second (i.e. one accumulated second of all configured CPUs available + to execute the router code). + + This test depends on an image called pause. This image is very thin, 250KB, + and is used to keep the network namespace open during the test. It is + stored locally by executing `docker save kubernetes/pause > pause.tar`. It + can be replaced at any time with any other image e.g. Alpine. + + This test runs and execute a single router. The outgoing packets are not observed, the incoming + packets are fed directly by the test driver. The other routers in the topology are a fiction, + they exist only in the routers configuration. + + The topology (./conf/topology.json) is the following: + + AS2 (br2) ---+== (br1a) AS1 (br1b) ---- (br4) AS4 + | + AS3 (br3) ---+ + + Only br1 is executed and observed. + + Pretend traffic is injected as follows: + + * from AS2 to AS3 at br1a external interface 1 to cause "br_transit" traffic. + * from AS2 to AS4 at br1a external interface 1 to cause "in_transit" traffic. + * from AS2 to AS1 at br1a external interface 1 to cause "in" traffic. + * from AS1 to AS2 at br1a internal interface to cause "out" traffic. + * from AS4 to AS2 at br1a internal interface to cause "out_transit" traffic. + """ + + ci = cli.Flag( + "ci", + help="Do extra checks for CI", + envname="CI" + ) + + pause_tar = cli.SwitchAttr( + "pause_tar", + str, + help="taball with the pause image", + ) + + def setup_prepare(self): + super().setup_prepare() + + shutil.copytree("acceptance/router_newbenchmark/conf/", self.artifacts / "conf") + sudo("mkdir -p /var/run/netns") + + pause_image = exec_docker(f"image load -q -i {self.pause_tar}").rsplit(' ', 1)[1] + exec_docker(f"run -d --network=none --name pause {pause_image}") + ns = exec_docker("inspect pause -f '{{.NetworkSettings.SandboxKey}}'").replace("'", "") + + # WTF do we need to alias the namespace to "pause" while creating the veths? + sudo(f"ln -sfT {ns} /var/run/netns/pause") + self.create_veths("pause") + sudo("rm /var/run/netns/pause") + + def setup_start(self): + super().setup_start() + + envs = ["SCION_EXPERIMENTAL_BFD_DISABLE=true"] + exec_docker(f"run -v {self.artifacts}/conf:/share/conf " + "-d " + f"-e {' '.join(envs)} " + "--network container:pause " + "--name router " + "bazel/acceptance/router_newbenchmark:router") + + exec_docker(f"run -v {self.artifacts}/conf:/share/conf " + "-d " + "--network container:pause " + "--name prometheus " + "prom/prometheus:v2.47.2 " + "/bin/prometheus --config_file /share/conf/prometheus.yml") + + time.sleep(1) + + def teardown(self): + cmd.docker["logs", "router"].run_fg(retcode=None) + exec_docker("rm -f prometheus") + exec_docker("rm -f router") + exec_docker("rm -f pause") # veths are deleted automatically + sudo(f"chown -R {cmd.whoami()} {self.artifacts}") + + # Wire virtual interfaces around the one router that we run. + def create_veths(self, ns: str): + # Set default TTL for outgoing packets to the common value 64, so that packets sent + # from router will match the expected value. + sudo(f"ip netns exec {ns} sysctl -w net.ipv4.ip_default_ttl=64") + + create_veth("veth_int_host", "veth_int", "192.168.0.1/24", "f0:0d:ca:fe:00:01", ns, + ["192.168.0.2"]) + create_veth("veth_2_host", "veth_2", "192.168.2.1/24", "f0:0d:ca:fe:00:02", ns, + ["192.168.2.2"]) + create_veth("veth_3_host", "veth_3", "192.168.3.1/24", "f0:0d:ca:fe:00:03", ns, + ["192.168.3.3"]) + + def _run(self): + logger.info("==> Starting load br-transit") + brload = self.get_executable("brload") + sudo(f"{brload.executable} -artifacts {self.artifacts} -case br_transit") + + +if __name__ == "__main__": + base.main(RouterBMTest) diff --git a/tools/brload/BUILD.bazel b/tools/brload/BUILD.bazel new file mode 100644 index 0000000000..4829c719f8 --- /dev/null +++ b/tools/brload/BUILD.bazel @@ -0,0 +1,33 @@ +load("//tools/lint:go.bzl", "go_library") +load("//:scion.bzl", "scion_go_binary") + +go_library( + name = "go_default_library", + srcs = [ + "cases.go", + "main.go", + ], + importpath = "github.com/scionproto/scion/tools/brload", + visibility = ["//visibility:private"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/private/util:go_default_library", + "//pkg/private/xtest:go_default_library", + "//pkg/scrypto:go_default_library", + "//pkg/slayers:go_default_library", + "//pkg/slayers/path:go_default_library", + "//pkg/slayers/path/scion:go_default_library", + "//private/keyconf:go_default_library", + "@com_github_google_gopacket//:go_default_library", + "@com_github_google_gopacket//afpacket:go_default_library", + "@com_github_google_gopacket//layers:go_default_library", + ], +) + +scion_go_binary( + name = "brload", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/tools/brload/cases.go b/tools/brload/cases.go new file mode 100644 index 0000000000..c6df2163ed --- /dev/null +++ b/tools/brload/cases.go @@ -0,0 +1,133 @@ +// 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 main + +import ( + "hash" + "net" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/private/util" + "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" +) + +// BrTransit generates one packet of transit traffic over the same BR host. +// The outcome is a packet and which interface to send it to. +func BrTransit(mac hash.Hash) (string, []byte) { + options := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + + // Ethernet: these addresses don't matter. The interfaces are not actually created. + ethernet := &layers.Ethernet{ + SrcMAC: net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0x01, 0x01}, + DstMAC: net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0x01, 0x02}, + EthernetType: layers.EthernetTypeIPv4, + } + // These do mater. They're known neighbors of our router under test. See + // router_newbenchmark/topology.json. + // IP4: Src=192.168.2.2 Dst=192.168.2.3 NextHdr=UDP Flags=DF + ip := &layers.IPv4{ + Version: 4, + IHL: 5, + TTL: 64, + SrcIP: net.IP{192, 168, 2, 2}, + DstIP: net.IP{192, 168, 3, 3}, + Protocol: layers.IPProtocolUDP, + Flags: layers.IPv4DontFragment, + } + // UDP: Src=50000 Dst=50000 + udp := &layers.UDP{ + SrcPort: layers.UDPPort(50000), + DstPort: layers.UDPPort(50000), + } + _ = udp.SetNetworkLayerForChecksum(ip) + + // pkt0.ParsePacket(` + // SCION: NextHdr=UDP CurrInfoF=0 CurrHopF=1 SrcType=IPv4 DstType=IPv4 + // ADDR: SrcIA=1-ff00:0:2 Src=192.168.2.2 DstIA=1-ff00:0:3 Dst=192.168.3.3 + // IF_2: ISD=1 Hops=2 Flags=non-ConsDir + // HF_1: ConsIngress=0 ConsEgress=0 + // HF_0: ConsIngress=131 ConsEgress=141 + // UDP_1: Src=40111 Dst=40222 + // `) + sp := &scion.Decoded{ + Base: scion.Base{ + PathMeta: scion.MetaHdr{ + CurrHF: 1, + SegLen: [3]uint8{2, 2, 0}, + }, + NumINF: 2, + NumHops: 4, + }, + InfoFields: []path.InfoField{ + { + SegID: 0x111, + Timestamp: util.TimeToSecs(time.Now()), + ConsDir: false, + }, + }, + HopFields: []path.HopField{ + {ConsIngress: 0, ConsEgress: 2}, // Processed here (non-consdir) + {ConsIngress: 22, ConsEgress: 0}, // From there (non-consdir) + {ConsIngress: 0, ConsEgress: 3}, // Down via this + {ConsIngress: 33, ConsEgress: 0}, // To there + }, + } + sp.HopFields[1].Mac = path.MAC(mac, sp.InfoFields[0], sp.HopFields[1], nil) + sp.InfoFields[0].UpdateSegID(sp.HopFields[1].Mac) + + scionL := &slayers.SCION{ + Version: 0, + TrafficClass: 0xb8, + FlowID: 0xdead, + NextHdr: slayers.L4UDP, + PathType: scion.PathType, + SrcIA: xtest.MustParseIA("1-ff00:0:2"), + DstIA: xtest.MustParseIA("1-ff00:0:3"), + Path: sp, + } + if err := scionL.SetSrcAddr(addr.MustParseHost("192.168.2.2")); err != nil { + panic(err) + } + if err := scionL.SetDstAddr(addr.MustParseHost("182.168.3.3")); err != nil { + panic(err) + } + + scionudp := &slayers.UDP{} + scionudp.SrcPort = 50000 + scionudp.DstPort = 50000 + scionudp.SetNetworkLayerForChecksum(scionL) + + payload := []byte("actualpayloadbytes") + + // Prepare input packet + input := gopacket.NewSerializeBuffer() + if err := gopacket.SerializeLayers(input, options, + ethernet, ip, udp, scionL, scionudp, gopacket.Payload(payload), + ); err != nil { + panic(err) + } + + return "veth_2_host", input.Bytes() +} diff --git a/tools/brload/main.go b/tools/brload/main.go new file mode 100644 index 0000000000..8a76a1f796 --- /dev/null +++ b/tools/brload/main.go @@ -0,0 +1,160 @@ +// 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 main + +import ( + "flag" + "fmt" + "hash" + "net" + "os" + "path/filepath" + "reflect" + "strings" + + "github.com/google/gopacket/layers" + + "github.com/google/gopacket/afpacket" + "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/scrypto" + "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/private/keyconf" +) + +type Case func(mac hash.Hash) (string, []byte) + +var ( + allCases = map[string]Case{ + "br_transit": BrTransit, + } + logConsole = flag.String("log.console", "debug", "Console logging level: debug|info|error") + dir = flag.String("artifacts", "", "Artifacts directory") + numPackets = flag.Int("num_packets", 1000, "Number of packets to send") + caseToRun = flag.String("case", "", + fmt.Sprintf("Which traffic case to evaluate %v", + reflect.ValueOf(allCases).MapKeys())) + handles = make(map[string]*afpacket.TPacket) +) + +// InitDevices inventories the available network interfaces, picks the ones that a case may inject +// traffic into, and associates them with a AF Packet interface. +func InitDevices() error { + devs, err := net.Interfaces() + if err != nil { + return serrors.WrapStr("listing network interfaces", err) + } + + for _, dev := range devs { + if !strings.HasPrefix(dev.Name, "veth_") || !strings.HasSuffix(dev.Name, "_host") { + continue + } + handle, err := afpacket.NewTPacket(afpacket.OptInterface(dev.Name)) + if err != nil { + return serrors.WrapStr("creating TPacket", err) + } + handles[dev.Name] = handle + } + + return nil +} + +func main() { + os.Exit(realMain()) +} + +func realMain() int { + flag.Parse() + logCfg := log.Config{Console: log.ConsoleConfig{Level: *logConsole}} + if err := log.Setup(logCfg); err != nil { + flag.Usage() + fmt.Fprintf(os.Stderr, "%s\n", err) + return 1 + } + defer log.HandlePanic() + + artifactsDir, err := os.MkdirTemp("", "brload_") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return 1 + } + if *dir != "" { + artifactsDir = *dir + } + if v := os.Getenv("TEST_ARTIFACTS_DIR"); v != "" { + artifactsDir = v + } + hfMAC, err := loadKey(artifactsDir) + if err != nil { + fmt.Fprintf(os.Stderr, "Loading keys failed: %v\n", err) + return 1 + } + + err = InitDevices() + if err != nil { + fmt.Fprintf(os.Stderr, "Loading devices failed: %v\n", err) + return 1 + } + + registerScionPorts() + + log.Info("BRLoad acceptance tests:") + + for caseName, caseFunc := range allCases { + caseDev, rawPkt := caseFunc(hfMAC) + + writePktTo, ok := handles[caseDev] + if !ok { + log.Error("device not found", "device", caseDev) + return 1 + } + for i := 0; i < *numPackets; i++ { + if err := writePktTo.WritePacketData(rawPkt); err != nil { + log.Error("writing input packet", "case", caseName, "error", err) + return 1 + } + } + + // For now we don't read the packets coming out. There are other tests + // for that. At some point we might just look at how many were dropped + // by the interface to get an idea of how many made it that far. + } + return 0 +} + +func loadKey(artifactsDir string) (hash.Hash, error) { + keysDir := filepath.Join(artifactsDir, "conf", "keys") + mk, err := keyconf.LoadMaster(keysDir) + if err != nil { + return nil, err + } + macGen, err := scrypto.HFMacFactory(mk.Key0) + if err != nil { + return nil, err + } + return macGen(), nil +} + +// registerScionPorts registers the following UDP ports in gopacket such as SCION is the +// next layer. In other words, map the following ports to expect SCION as the payload. +func registerScionPorts() { + layers.RegisterUDPPortLayerType(layers.UDPPort(30041), slayers.LayerTypeSCION) + for i := 30000; i < 30010; i++ { + layers.RegisterUDPPortLayerType(layers.UDPPort(i), slayers.LayerTypeSCION) + } + for i := 50000; i < 50010; i++ { + layers.RegisterUDPPortLayerType(layers.UDPPort(i), slayers.LayerTypeSCION) + } +} From 9d6e29747f8b75f5de75c1f1175a7b6c45678dae Mon Sep 17 00:00:00 2001 From: Jean-Christophe Hugly Date: Thu, 16 Nov 2023 18:56:43 +0100 Subject: [PATCH 02/40] router: add a check for forwarded packets. This still has some issues but I don't like carrying unsub code on my laptop. --- tools/brload/cases.go | 8 +-- tools/brload/main.go | 115 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/tools/brload/cases.go b/tools/brload/cases.go index c6df2163ed..a02da39a8c 100644 --- a/tools/brload/cases.go +++ b/tools/brload/cases.go @@ -32,7 +32,7 @@ import ( // BrTransit generates one packet of transit traffic over the same BR host. // The outcome is a packet and which interface to send it to. -func BrTransit(mac hash.Hash) (string, []byte) { +func BrTransit(payload string, mac hash.Hash) (string, string, []byte) { options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -119,15 +119,15 @@ func BrTransit(mac hash.Hash) (string, []byte) { scionudp.DstPort = 50000 scionudp.SetNetworkLayerForChecksum(scionL) - payload := []byte("actualpayloadbytes") + payloadBytes := []byte(payload) // Prepare input packet input := gopacket.NewSerializeBuffer() if err := gopacket.SerializeLayers(input, options, - ethernet, ip, udp, scionL, scionudp, gopacket.Payload(payload), + ethernet, ip, udp, scionL, scionudp, gopacket.Payload(payloadBytes), ); err != nil { panic(err) } - return "veth_2_host", input.Bytes() + return "veth_2_host", "veth_3_host", input.Bytes() } diff --git a/tools/brload/main.go b/tools/brload/main.go index 8a76a1f796..a245602d75 100644 --- a/tools/brload/main.go +++ b/tools/brload/main.go @@ -24,6 +24,7 @@ import ( "reflect" "strings" + "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/afpacket" @@ -34,7 +35,7 @@ import ( "github.com/scionproto/scion/private/keyconf" ) -type Case func(mac hash.Hash) (string, []byte) +type Case func(payload string, mac hash.Hash) (string, string, []byte) var ( allCases = map[string]Case{ @@ -49,9 +50,9 @@ var ( handles = make(map[string]*afpacket.TPacket) ) -// InitDevices inventories the available network interfaces, picks the ones that a case may inject +// initDevices inventories the available network interfaces, picks the ones that a case may inject // traffic into, and associates them with a AF Packet interface. -func InitDevices() error { +func initDevices() error { devs, err := net.Interfaces() if err != nil { return serrors.WrapStr("listing network interfaces", err) @@ -71,6 +72,12 @@ func InitDevices() error { return nil } +func closeDevices() { + for _, h := range handles { + h.Close() + } +} + func main() { os.Exit(realMain()) } @@ -85,9 +92,16 @@ func realMain() int { } defer log.HandlePanic() + caseFunc, ok := allCases[*caseToRun] + if !ok { + log.Error("Unknown case", "case", *caseToRun) + flag.Usage() + return 1 + } + artifactsDir, err := os.MkdirTemp("", "brload_") if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) + log.Error("Cannot create tmp dir", "err", err) return 1 } if *dir != "" { @@ -98,13 +112,13 @@ func realMain() int { } hfMAC, err := loadKey(artifactsDir) if err != nil { - fmt.Fprintf(os.Stderr, "Loading keys failed: %v\n", err) + log.Error("Loading keys failed", "err", err) return 1 } - err = InitDevices() + err = initDevices() if err != nil { - fmt.Fprintf(os.Stderr, "Loading devices failed: %v\n", err) + log.Error("Loading devices failed", "err", err) return 1 } @@ -112,25 +126,84 @@ func realMain() int { log.Info("BRLoad acceptance tests:") - for caseName, caseFunc := range allCases { - caseDev, rawPkt := caseFunc(hfMAC) + payloadString := "actualpayloadbytes" + caseDevIn, caseDevOut, rawPkt := caseFunc(payloadString, hfMAC) - writePktTo, ok := handles[caseDev] - if !ok { - log.Error("device not found", "device", caseDev) - return 1 - } - for i := 0; i < *numPackets; i++ { - if err := writePktTo.WritePacketData(rawPkt); err != nil { - log.Error("writing input packet", "case", caseName, "error", err) - return 1 + writePktTo, ok := handles[caseDevIn] + if !ok { + log.Error("device not found", "device", caseDevIn) + return 1 + } + + readPktFrom, ok := handles[caseDevOut] + if !ok { + log.Error("device not found", "device", caseDevOut) + return 1 + } + + // Try and pick-up one packet and return the payload. If that works, we're content + // that this test works. + listenerChan := make(chan bool) + go func() { + defer log.HandlePanic() + + gotOne := false + + defer func() { + fmt.Println("********** listener channel closing") + listenerChan <- gotOne + close(listenerChan) + }() + + packetSource := gopacket.NewPacketSource(readPktFrom, layers.LinkTypeEthernet) + ch := packetSource.Packets() + + for { + fmt.Println("***** listener listening") + got, ok := <-ch + if !ok { + // No more packets + fmt.Println("***** listener bailing") + return + } + if err := got.ErrorLayer(); err != nil { + log.Error("error decoding packet", "err", err) + } + layer := got.Layer(gopacket.LayerTypePayload) + if layer == nil { + log.Error("error fetching packet payload: no PayLoad") } + payload, ok := layer.(gopacket.Payload) + if !ok { + log.Error("error fetching packet payload: not a PayLoad!") + } + if payload.GoString() == payloadString { + fmt.Println("********** listener happy") + gotOne = true + } + fmt.Println("********** listener done") + return // One is all we need. } + }() - // For now we don't read the packets coming out. There are other tests - // for that. At some point we might just look at how many were dropped - // by the interface to get an idea of how many made it that far. + for i := 0; i < *numPackets; i++ { + if err := writePktTo.WritePacketData(rawPkt); err != nil { + log.Error("writing input packet", "case", *caseToRun, "error", err) + return 1 + } + } + + // If our listener is still stuck there, unstick it. + closeDevices() + fmt.Println("********** devices closed") + + outcome, ok := <-listenerChan + if !ok || !outcome { + log.Error("Never saw a valid packet being forwarded") + return 1 } + + fmt.Println("********** main done") return 0 } From 943d52713aa3169cbfdd5537ce069a5e432cc800 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Hugly Date: Fri, 17 Nov 2023 11:07:17 +0100 Subject: [PATCH 03/40] router: make sure brload terminates. --- tools/brload/main.go | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/tools/brload/main.go b/tools/brload/main.go index a245602d75..4b55aac17b 100644 --- a/tools/brload/main.go +++ b/tools/brload/main.go @@ -72,12 +72,6 @@ func initDevices() error { return nil } -func closeDevices() { - for _, h := range handles { - h.Close() - } -} - func main() { os.Exit(realMain()) } @@ -143,27 +137,24 @@ func realMain() int { // Try and pick-up one packet and return the payload. If that works, we're content // that this test works. + packetSource := gopacket.NewPacketSource(readPktFrom, layers.LinkTypeEthernet) + packetChan := packetSource.Packets() listenerChan := make(chan bool) + go func() { defer log.HandlePanic() gotOne := false defer func() { - fmt.Println("********** listener channel closing") listenerChan <- gotOne close(listenerChan) }() - packetSource := gopacket.NewPacketSource(readPktFrom, layers.LinkTypeEthernet) - ch := packetSource.Packets() - for { - fmt.Println("***** listener listening") - got, ok := <-ch + got, ok := <-packetChan if !ok { // No more packets - fmt.Println("***** listener bailing") return } if err := got.ErrorLayer(); err != nil { @@ -178,10 +169,8 @@ func realMain() int { log.Error("error fetching packet payload: not a PayLoad!") } if payload.GoString() == payloadString { - fmt.Println("********** listener happy") gotOne = true } - fmt.Println("********** listener done") return // One is all we need. } }() @@ -193,9 +182,9 @@ func realMain() int { } } - // If our listener is still stuck there, unstick it. - closeDevices() - fmt.Println("********** devices closed") + // If our listener is still stuck there, unstick it. Closing the devices doesn't cause the + // packet channel to close (presumably a bug). Close the channel ourselves. + close(packetChan) outcome, ok := <-listenerChan if !ok || !outcome { @@ -203,7 +192,6 @@ func realMain() int { return 1 } - fmt.Println("********** main done") return 0 } From ef94d2b3afd9bfcb81c7e3be00958ab92380bbef Mon Sep 17 00:00:00 2001 From: Jean-Christophe Hugly Date: Fri, 17 Nov 2023 18:58:01 +0100 Subject: [PATCH 04/40] router: new benchmark made to work. Only collects br_transit metrics for now. Test runs fast so metrics window is short only 8 seconds for 5M packets. This makes the pkt/s measurement flaky. Will have to try and increase the window size. --- acceptance/common/docker.py | 2 +- acceptance/router_newbenchmark/test.py | 143 ++++++++++++++++++++----- router/connector.go | 2 +- router/dataplane.go | 2 +- tools/brload/cases.go | 47 +++++--- tools/brload/main.go | 42 +++++--- 6 files changed, 172 insertions(+), 66 deletions(-) diff --git a/acceptance/common/docker.py b/acceptance/common/docker.py index 0af9a69bc4..c6f56fb2c8 100644 --- a/acceptance/common/docker.py +++ b/acceptance/common/docker.py @@ -197,7 +197,7 @@ def assert_no_networks(writer=None): writer.write("Docker networking assertions are OFF\n") return - allowed_nets = ['bridge', 'host', 'none'] + allowed_nets = ['bridge', 'host', 'none', 'benchmark'] unexpected_nets = [] for net in _get_networks(): if net.name not in allowed_nets: diff --git a/acceptance/router_newbenchmark/test.py b/acceptance/router_newbenchmark/test.py index fedc9df74c..3be64d249f 100644 --- a/acceptance/router_newbenchmark/test.py +++ b/acceptance/router_newbenchmark/test.py @@ -20,47 +20,51 @@ from typing import List from plumbum import cli -from plumbum import cmd +from plumbum.cmd import sudo,docker,whoami from acceptance.common import base import logging +import json +import yaml +from http.client import HTTPConnection +from urllib.parse import urlencode logger = logging.getLogger(__name__) # Those values are valid expectations only when running in the CI environment. EXPECTATIONS = { - 'in': 53000, - 'out': 26000, - 'in_transit': 73000, - 'out_transit': 49000, + # 'in': 53000, + # 'out': 26000, + # 'in_transit': 73000, + # 'out_transit': 49000, 'br_transit': 73000, } def exec_docker(command: str) -> str: - return cmd.docker(str.split(command)) + return docker(str.split(command)) -def sudo(command: str) -> str: +def exec_sudo(command: str) -> str: # -A, --askpass makes sure command is failing and does not wait for # interactive password input. - return cmd.sudo("-A", str.split(command)) + return sudo("-A", str.split(command)) def create_veth(host: str, container: str, ip: str, mac: str, ns: str, neighbors: List[str]): - sudo(f"ip link add {host} mtu 8000 type veth peer name {container} mtu 8000") - sudo(f"sysctl -qw net.ipv6.conf.{host}.disable_ipv6=1") - sudo(f"ip link set {host} up") - sudo(f"ip link set {container} netns {ns}") - sudo(f"ip netns exec {ns} sysctl -qw net.ipv6.conf.{container}.disable_ipv6=1") - sudo(f"ip netns exec {ns} ethtool -K {container} rx off tx off") - sudo(f"ip netns exec {ns} ip link set {container} address {mac}") - sudo(f"ip netns exec {ns} ip addr add {ip} dev {container}") + exec_sudo(f"ip link add {host} mtu 8000 type veth peer name {container} mtu 8000") + exec_sudo(f"sysctl -qw net.ipv6.conf.{host}.disable_ipv6=1") + exec_sudo(f"ip link set {host} up") + exec_sudo(f"ip link set {container} netns {ns}") + exec_sudo(f"ip netns exec {ns} sysctl -qw net.ipv6.conf.{container}.disable_ipv6=1") + exec_sudo(f"ip netns exec {ns} ethtool -K {container} rx off tx off") + exec_sudo(f"ip netns exec {ns} ip link set {container} address {mac}") + exec_sudo(f"ip netns exec {ns} ip addr add {ip} dev {container}") for n in neighbors: - sudo(f"ip netns exec {ns} ip neigh add {n} " + exec_sudo(f"ip netns exec {ns} ip neigh add {n} " f"lladdr f0:0d:ca:fe:be:ef nud permanent dev {container}") - sudo(f"ip netns exec {ns} ip link set {container} up") + exec_sudo(f"ip netns exec {ns} ip link set {container} up") class RouterBMTest(base.TestBase): @@ -111,16 +115,18 @@ def setup_prepare(self): super().setup_prepare() shutil.copytree("acceptance/router_newbenchmark/conf/", self.artifacts / "conf") - sudo("mkdir -p /var/run/netns") + exec_sudo("mkdir -p /var/run/netns") + + exec_docker("network create -d bridge benchmark") pause_image = exec_docker(f"image load -q -i {self.pause_tar}").rsplit(' ', 1)[1] - exec_docker(f"run -d --network=none --name pause {pause_image}") + exec_docker(f"run -d --network=benchmark --publish 9999:9090 --name pause {pause_image}") ns = exec_docker("inspect pause -f '{{.NetworkSettings.SandboxKey}}'").replace("'", "") # WTF do we need to alias the namespace to "pause" while creating the veths? - sudo(f"ln -sfT {ns} /var/run/netns/pause") + exec_sudo(f"ln -sfT {ns} /var/run/netns/pause") self.create_veths("pause") - sudo("rm /var/run/netns/pause") + exec_sudo("rm /var/run/netns/pause") def setup_start(self): super().setup_start() @@ -138,23 +144,23 @@ def setup_start(self): "--network container:pause " "--name prometheus " "prom/prometheus:v2.47.2 " - "/bin/prometheus --config_file /share/conf/prometheus.yml") + "--config.file /share/conf/prometheus.yml") - time.sleep(1) + time.sleep(2) def teardown(self): - cmd.docker["logs", "router"].run_fg(retcode=None) + docker["logs", "router"].run_fg(retcode=None) exec_docker("rm -f prometheus") exec_docker("rm -f router") exec_docker("rm -f pause") # veths are deleted automatically - sudo(f"chown -R {cmd.whoami()} {self.artifacts}") + exec_docker("network rm benchmark") + exec_sudo(f"chown -R {whoami()} {self.artifacts}") # Wire virtual interfaces around the one router that we run. def create_veths(self, ns: str): # Set default TTL for outgoing packets to the common value 64, so that packets sent # from router will match the expected value. - sudo(f"ip netns exec {ns} sysctl -w net.ipv4.ip_default_ttl=64") - + exec_sudo(f"ip netns exec {ns} sysctl -w net.ipv4.ip_default_ttl=64") create_veth("veth_int_host", "veth_int", "192.168.0.1/24", "f0:0d:ca:fe:00:01", ns, ["192.168.0.2"]) create_veth("veth_2_host", "veth_2", "192.168.2.1/24", "f0:0d:ca:fe:00:02", ns, @@ -165,7 +171,86 @@ def create_veths(self, ns: str): def _run(self): logger.info("==> Starting load br-transit") brload = self.get_executable("brload") - sudo(f"{brload.executable} -artifacts {self.artifacts} -case br_transit") + output = exec_sudo(f"{brload.executable} -artifacts {self.artifacts} " + "-case br_transit -num_packets 5000000") + for line in output.splitlines(): + print(line) + if line.startswith('metricsBegin'): + _, beg, _, end = line.split() + + logger.info('==> Collecting br-transit performance metrics...') + + # The raw metrics are expressed in terms of core*seconds. We convert to machine*seconds + # which allows us to provide a projected packet/s; ...more intuitive than packets/core*s. + # We're interested only in br_transit traffic. We measure the rate over 10s. For best + # results we sample the end of the middle 10s of the run. "beg" is the start time of the + # real action and "end" is the end time. + sampleTime = (int(beg) + int(end) + 10) / 2 + promQuery = urlencode({ + 'time': f'{sampleTime}', + 'query': ( + 'sum by (instance, job) (' + ' rate(router_output_pkts_total{job="BR", type="br_transit"}[10s])' + ')' + '/ on (instance, job) group_left()' + 'sum by (instance, job) (' + ' 1 - (rate(process_runnable_seconds_total[10s])' + ' / go_sched_maxprocs_threads)' + ')' + ) + }) + conn = HTTPConnection("localhost:9999") + conn.request('GET', f'/api/v1/query?{promQuery}') + resp = conn.getresponse() + if resp.status != 200: + raise RuntimeError(f'Unexpected response: {resp.status} {resp.reason}') + + # There's only one router that has br_transit traffic. + pld = json.loads(resp.read().decode('utf-8')) + results = pld['data']['result'] + rateMap = {} + tt = 'br_transit' + rateMap[tt] = 0 + for result in results: + ts, val = result['value'] + r = int(float(val)) + if r != 0: + rateMap[tt] = r + + # Fetch and log the number of cores used by Go. This may inform performance + # modeling later. + logger.info('==> Collecting number of cores...') + promQuery = urlencode({ + 'query': 'go_sched_maxprocs_threads{job="BR"}' + }) + + conn = HTTPConnection("localhost:9999") + conn.request('GET', f'/api/v1/query?{promQuery}') + resp = conn.getresponse() + if resp.status != 200: + raise RuntimeError(f'Unexpected response: {resp.status} {resp.reason}') + + pld = json.loads(resp.read().decode('utf-8')) + results = pld['data']['result'] + for result in results: + instance = result['metric']['instance'] + _, val = result['value'] + logger.info(f'Router Cores for {instance}: {int(val)}') + + # Log and check the performance... + # If this is used as a CI test. Make sure that the performance is within the expected + # ballpark. + rateTooLow = [] + for tt, exp in EXPECTATIONS.items(): + if self.ci: + logger.info(f'Packets/(machine*s) for {tt}: {rateMap[tt]} expected: {exp}') + if rateMap[tt] < 0.8 * exp: + rateTooLow.append(tt) + else: + logger.info(f'Packets/(machine*s) for {tt}: {rateMap[tt]}') + + if len(rateTooLow) != 0: + raise RuntimeError(f'Insufficient performance for: {rateTooLow}') if __name__ == "__main__": diff --git a/router/connector.go b/router/connector.go index 7b744b2c41..cd7e5c04e2 100644 --- a/router/connector.go +++ b/router/connector.go @@ -85,7 +85,7 @@ func (c *Connector) AddExternalInterface(localIfID common.IFIDType, link control intf := uint16(localIfID) log.Debug("Adding external interface", "interface", localIfID, "local_isd_as", link.Local.IA, "local_addr", link.Local.Addr, - "remote_isd_as", link.Remote.IA, "remote_addr", link.Remote.IA, + "remote_isd_as", link.Remote.IA, "remote_addr", link.Remote.Addr, "owned", owned, "bfd", !link.BFD.Disable) if !c.ia.Equal(link.Local.IA) { diff --git a/router/dataplane.go b/router/dataplane.go index 2a1e06e3d6..97aa7111ba 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -959,12 +959,12 @@ func (d *DataPlane) runForwarder(ifID uint16, conn BatchConn, cfg *RunConfig, c } } written, _ := conn.WriteBatch(msgs[:toWrite], 0) + log.Debug("Wrote packets", "count", written) if written < 0 { // WriteBatch returns -1 on error, we just consider this as // 0 packets written written = 0 } - updateOutputMetrics(metrics, pkts[:written]) for _, p := range pkts[:written] { diff --git a/tools/brload/cases.go b/tools/brload/cases.go index a02da39a8c..9762dd0113 100644 --- a/tools/brload/cases.go +++ b/tools/brload/cases.go @@ -38,21 +38,22 @@ func BrTransit(payload string, mac hash.Hash) (string, string, []byte) { ComputeChecksums: true, } - // Ethernet: these addresses don't matter. The interfaces are not actually created. + // Point-to-point. Src might not mater. Dst probably must match what test.py configured + // for interface 2 of the router. ethernet := &layers.Ethernet{ - SrcMAC: net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0x01, 0x01}, - DstMAC: net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0x01, 0x02}, + SrcMAC: net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0xbe, 0xef}, + DstMAC: net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0x00, 0x02}, EthernetType: layers.EthernetTypeIPv4, } - // These do mater. They're known neighbors of our router under test. See - // router_newbenchmark/topology.json. - // IP4: Src=192.168.2.2 Dst=192.168.2.3 NextHdr=UDP Flags=DF + + // Point-to-point. This is the real IP: the underlay network. + // IP4: Src=192.168.2.2 Dst=192.168.2.1 NextHdr=UDP Flags=DF ip := &layers.IPv4{ Version: 4, IHL: 5, TTL: 64, SrcIP: net.IP{192, 168, 2, 2}, - DstIP: net.IP{192, 168, 3, 3}, + DstIP: net.IP{192, 168, 2, 1}, Protocol: layers.IPProtocolUDP, Flags: layers.IPv4DontFragment, } @@ -63,14 +64,7 @@ func BrTransit(payload string, mac hash.Hash) (string, string, []byte) { } _ = udp.SetNetworkLayerForChecksum(ip) - // pkt0.ParsePacket(` - // SCION: NextHdr=UDP CurrInfoF=0 CurrHopF=1 SrcType=IPv4 DstType=IPv4 - // ADDR: SrcIA=1-ff00:0:2 Src=192.168.2.2 DstIA=1-ff00:0:3 Dst=192.168.3.3 - // IF_2: ISD=1 Hops=2 Flags=non-ConsDir - // HF_1: ConsIngress=0 ConsEgress=0 - // HF_0: ConsIngress=131 ConsEgress=141 - // UDP_1: Src=40111 Dst=40222 - // `) + // Fully correct path. sp := &scion.Decoded{ Base: scion.Base{ PathMeta: scion.MetaHdr{ @@ -86,17 +80,33 @@ func BrTransit(payload string, mac hash.Hash) (string, string, []byte) { Timestamp: util.TimeToSecs(time.Now()), ConsDir: false, }, + { + SegID: 0x222, + Timestamp: util.TimeToSecs(time.Now()), + ConsDir: true, + }, }, HopFields: []path.HopField{ - {ConsIngress: 0, ConsEgress: 2}, // Processed here (non-consdir) {ConsIngress: 22, ConsEgress: 0}, // From there (non-consdir) + {ConsIngress: 0, ConsEgress: 2}, // <- Processed here (non-consdir) {ConsIngress: 0, ConsEgress: 3}, // Down via this {ConsIngress: 33, ConsEgress: 0}, // To there }, } + + // Calculate MACs... + // Seg0: Hops are in non-consdir. sp.HopFields[1].Mac = path.MAC(mac, sp.InfoFields[0], sp.HopFields[1], nil) sp.InfoFields[0].UpdateSegID(sp.HopFields[1].Mac) + sp.HopFields[0].Mac = path.MAC(mac, sp.InfoFields[0], sp.HopFields[0], nil) + + // Seg1: in the natural order. + sp.HopFields[2].Mac = path.MAC(mac, sp.InfoFields[1], sp.HopFields[2], nil) + sp.InfoFields[1].UpdateSegID(sp.HopFields[2].Mac) // tmp + sp.HopFields[3].Mac = path.MAC(mac, sp.InfoFields[1], sp.HopFields[2], nil) + sp.InfoFields[1].SegID = 0x222 // Restore to initial. + // End-to-end. Src is the originator and Dst is the final destination. scionL := &slayers.SCION{ Version: 0, TrafficClass: 0xb8, @@ -107,10 +117,13 @@ func BrTransit(payload string, mac hash.Hash) (string, string, []byte) { DstIA: xtest.MustParseIA("1-ff00:0:3"), Path: sp, } + + // These aren't necessarily IP addresses. They're host addresses within the + // src and dst ASes. if err := scionL.SetSrcAddr(addr.MustParseHost("192.168.2.2")); err != nil { panic(err) } - if err := scionL.SetDstAddr(addr.MustParseHost("182.168.3.3")); err != nil { + if err := scionL.SetDstAddr(addr.MustParseHost("192.168.3.3")); err != nil { panic(err) } diff --git a/tools/brload/main.go b/tools/brload/main.go index 4b55aac17b..3a69dcb023 100644 --- a/tools/brload/main.go +++ b/tools/brload/main.go @@ -23,6 +23,7 @@ import ( "path/filepath" "reflect" "strings" + "time" "github.com/google/gopacket" "github.com/google/gopacket/layers" @@ -43,7 +44,7 @@ var ( } logConsole = flag.String("log.console", "debug", "Console logging level: debug|info|error") dir = flag.String("artifacts", "", "Artifacts directory") - numPackets = flag.Int("num_packets", 1000, "Number of packets to send") + numPackets = flag.Int("num_packets", 10, "Number of packets to send") caseToRun = flag.String("case", "", fmt.Sprintf("Which traffic case to evaluate %v", reflect.ValueOf(allCases).MapKeys())) @@ -135,19 +136,18 @@ func realMain() int { return 1 } - // Try and pick-up one packet and return the payload. If that works, we're content + // Try and pick-up one packet and check the payload. If that works, we're content // that this test works. packetSource := gopacket.NewPacketSource(readPktFrom, layers.LinkTypeEthernet) packetChan := packetSource.Packets() - listenerChan := make(chan bool) + listenerChan := make(chan int) go func() { defer log.HandlePanic() - - gotOne := false + numRcv := 0 defer func() { - listenerChan <- gotOne + listenerChan <- numRcv close(listenerChan) }() @@ -155,43 +155,51 @@ func realMain() int { got, ok := <-packetChan if !ok { // No more packets + log.Info("No more Packets") return } if err := got.ErrorLayer(); err != nil { log.Error("error decoding packet", "err", err) + continue } layer := got.Layer(gopacket.LayerTypePayload) if layer == nil { log.Error("error fetching packet payload: no PayLoad") + continue } - payload, ok := layer.(gopacket.Payload) - if !ok { - log.Error("error fetching packet payload: not a PayLoad!") - } - if payload.GoString() == payloadString { - gotOne = true + if string(layer.LayerContents()) == payloadString { + // return // One is all we need. But continue and count for now. + numRcv++ } - return // One is all we need. } }() + // We started everything that could be started. So the best window for perf mertics + // opens somewhere around now. + metricsBegin := time.Now().Unix() for i := 0; i < *numPackets; i++ { if err := writePktTo.WritePacketData(rawPkt); err != nil { log.Error("writing input packet", "case", *caseToRun, "error", err) return 1 } } + metricsEnd := time.Now().Unix() + // The test harness looks for this output. + fmt.Printf("metricsBegin: %d metricsEnd: %d\n", metricsBegin, metricsEnd) + + time.Sleep(time.Second * time.Duration(2)) - // If our listener is still stuck there, unstick it. Closing the devices doesn't cause the + // If our listener is still stuck there, unstick it. Closing the device doesn't cause the // packet channel to close (presumably a bug). Close the channel ourselves. close(packetChan) - outcome, ok := <-listenerChan - if !ok || !outcome { - log.Error("Never saw a valid packet being forwarded") + outcome := <-listenerChan + if outcome == 0 { + log.Error("Listener never saw a valid packet being forwarded") return 1 } + fmt.Printf("Listener results: %d\n", outcome) return 0 } From 15765c0da23898b17a3b47dfe8508c6e58bba18e Mon Sep 17 00:00:00 2001 From: Jean-Christophe Hugly Date: Fri, 17 Nov 2023 19:10:07 +0100 Subject: [PATCH 05/40] router: Enlarge the test to get more stable measurements. --- acceptance/router_newbenchmark/test.py | 2 +- tools/brload/main.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/acceptance/router_newbenchmark/test.py b/acceptance/router_newbenchmark/test.py index 3be64d249f..5028a8009b 100644 --- a/acceptance/router_newbenchmark/test.py +++ b/acceptance/router_newbenchmark/test.py @@ -172,7 +172,7 @@ def _run(self): logger.info("==> Starting load br-transit") brload = self.get_executable("brload") output = exec_sudo(f"{brload.executable} -artifacts {self.artifacts} " - "-case br_transit -num_packets 5000000") + "-case br_transit -num_packets 10000000") for line in output.splitlines(): print(line) if line.startswith('metricsBegin'): diff --git a/tools/brload/main.go b/tools/brload/main.go index 3a69dcb023..647d87ae10 100644 --- a/tools/brload/main.go +++ b/tools/brload/main.go @@ -187,7 +187,8 @@ func realMain() int { // The test harness looks for this output. fmt.Printf("metricsBegin: %d metricsEnd: %d\n", metricsBegin, metricsEnd) - time.Sleep(time.Second * time.Duration(2)) + // In short tests (<1M packets), we finish sending before the first packets arrive. + time.Sleep(time.Second * time.Duration(1)) // If our listener is still stuck there, unstick it. Closing the device doesn't cause the // packet channel to close (presumably a bug). Close the channel ourselves. From 2a67ef0169ef03abf2e3e5ef941bc959af7de79a Mon Sep 17 00:00:00 2001 From: Jean-Christophe Hugly Date: Mon, 20 Nov 2023 18:57:40 +0100 Subject: [PATCH 06/40] router: newbenchmark improvements Mainly: * Get rid of the "pause" container. Use the prometheus container for the same purpose. * Add multi paralle streams and core-count support. * Slight cleanup of the addressing/numbering scheme and the begining of using it to simplify test cases. --- acceptance/router_newbenchmark/test.py | 74 +++++++++++++----------- tools/brload/cases.go | 80 +++++++++++++++++++++----- tools/brload/main.go | 7 ++- 3 files changed, 111 insertions(+), 50 deletions(-) diff --git a/acceptance/router_newbenchmark/test.py b/acceptance/router_newbenchmark/test.py index 5028a8009b..764a21c189 100644 --- a/acceptance/router_newbenchmark/test.py +++ b/acceptance/router_newbenchmark/test.py @@ -18,7 +18,7 @@ import shutil import time -from typing import List +from typing import List, Tuple from plumbum import cli from plumbum.cmd import sudo,docker,whoami @@ -51,8 +51,8 @@ def exec_sudo(command: str) -> str: # interactive password input. return sudo("-A", str.split(command)) - -def create_veth(host: str, container: str, ip: str, mac: str, ns: str, neighbors: List[str]): +def create_veth(host: str, container: str, ip: str, mac: str, ns: str, + neighbors: List[Tuple[str, str]]): exec_sudo(f"ip link add {host} mtu 8000 type veth peer name {container} mtu 8000") exec_sudo(f"sysctl -qw net.ipv6.conf.{host}.disable_ipv6=1") exec_sudo(f"ip link set {host} up") @@ -62,8 +62,8 @@ def create_veth(host: str, container: str, ip: str, mac: str, ns: str, neighbors exec_sudo(f"ip netns exec {ns} ip link set {container} address {mac}") exec_sudo(f"ip netns exec {ns} ip addr add {ip} dev {container}") for n in neighbors: - exec_sudo(f"ip netns exec {ns} ip neigh add {n} " - f"lladdr f0:0d:ca:fe:be:ef nud permanent dev {container}") + exec_sudo(f"ip netns exec {ns} ip neigh add {n[0]} " + f"lladdr {n[1]} nud permanent dev {container}") exec_sudo(f"ip netns exec {ns} ip link set {container} up") @@ -85,7 +85,7 @@ class RouterBMTest(base.TestBase): The topology (./conf/topology.json) is the following: AS2 (br2) ---+== (br1a) AS1 (br1b) ---- (br4) AS4 - | + | AS3 (br3) ---+ Only br1 is executed and observed. @@ -119,41 +119,49 @@ def setup_prepare(self): exec_docker("network create -d bridge benchmark") - pause_image = exec_docker(f"image load -q -i {self.pause_tar}").rsplit(' ', 1)[1] - exec_docker(f"run -d --network=benchmark --publish 9999:9090 --name pause {pause_image}") - ns = exec_docker("inspect pause -f '{{.NetworkSettings.SandboxKey}}'").replace("'", "") + # This test is useless without prometheus. Also, we need a running container to have + # a usable network namespace that we can configuer before the router runs. So, start + # prometheus now and then have the router share the same network stack. - # WTF do we need to alias the namespace to "pause" while creating the veths? - exec_sudo(f"ln -sfT {ns} /var/run/netns/pause") - self.create_veths("pause") - exec_sudo("rm /var/run/netns/pause") + # FWIW, the alternative would be to start the router's container without the router, + # configure the interfaces, and then start the router. We'd still need prometheus, though, + # and we'd need to expose the router's metrics port to prometheus in some way. So, this is + # simpler. + exec_docker(f"run -v {self.artifacts}/conf:/share/conf " + "-d " + "--network benchmark " + "--publish 9999:9090 " + "--name prometheus " + "prom/prometheus:v2.47.2 " + "--config.file /share/conf/prometheus.yml") - def setup_start(self): - super().setup_start() + ns = exec_docker("inspect prometheus -f '{{.NetworkSettings.SandboxKey}}'").replace("'", "") + + # Link that namespace to where the ip commands expect it. While at it give it a simple name. + # Then create all the interfaces that the router will see... the test will be using the + # other end of the pairs to feed it with (and possibly capture) traffic. + exec_sudo(f"ln -sfT {ns} /var/run/netns/benchmark") + self.create_veths("benchmark") + exec_sudo("rm /var/run/netns/benchmark") - envs = ["SCION_EXPERIMENTAL_BFD_DISABLE=true"] + # Then the router. exec_docker(f"run -v {self.artifacts}/conf:/share/conf " "-d " - f"-e {' '.join(envs)} " - "--network container:pause " + "-e SCION_EXPERIMENTAL_BFD_DISABLE=true -e GOMAXPROCS=6 " + "--network container:prometheus " "--name router " "bazel/acceptance/router_newbenchmark:router") - exec_docker(f"run -v {self.artifacts}/conf:/share/conf " - "-d " - "--network container:pause " - "--name prometheus " - "prom/prometheus:v2.47.2 " - "--config.file /share/conf/prometheus.yml") - time.sleep(2) + def setup_start(self): + super().setup_start() + def teardown(self): docker["logs", "router"].run_fg(retcode=None) exec_docker("rm -f prometheus") exec_docker("rm -f router") - exec_docker("rm -f pause") # veths are deleted automatically - exec_docker("network rm benchmark") + exec_docker("network rm benchmark") # veths are deleted automatically exec_sudo(f"chown -R {whoami()} {self.artifacts}") # Wire virtual interfaces around the one router that we run. @@ -162,17 +170,17 @@ def create_veths(self, ns: str): # from router will match the expected value. exec_sudo(f"ip netns exec {ns} sysctl -w net.ipv4.ip_default_ttl=64") create_veth("veth_int_host", "veth_int", "192.168.0.1/24", "f0:0d:ca:fe:00:01", ns, - ["192.168.0.2"]) - create_veth("veth_2_host", "veth_2", "192.168.2.1/24", "f0:0d:ca:fe:00:02", ns, - ["192.168.2.2"]) - create_veth("veth_3_host", "veth_3", "192.168.3.1/24", "f0:0d:ca:fe:00:03", ns, - ["192.168.3.3"]) + [("192.168.0.2", "f0:0d:ca:fe:00:02")]) + create_veth("veth_2_host", "veth_2", "192.168.2.1/24", "f0:0d:ca:fe:02:01", ns, + [("192.168.2.2", "f0:0d:ca:fe:02:02")]) + create_veth("veth_3_host", "veth_3", "192.168.3.1/24", "f0:0d:ca:fe:03:01", ns, + [("192.168.3.3", "f0:0d:ca:fe:03:03")]) def _run(self): logger.info("==> Starting load br-transit") brload = self.get_executable("brload") output = exec_sudo(f"{brload.executable} -artifacts {self.artifacts} " - "-case br_transit -num_packets 10000000") + "-case br_transit -num_packets 10000000 -num_streams 2") for line in output.splitlines(): print(line) if line.startswith('metricsBegin'): diff --git a/tools/brload/cases.go b/tools/brload/cases.go index 9762dd0113..707a5e3280 100644 --- a/tools/brload/cases.go +++ b/tools/brload/cases.go @@ -30,9 +30,55 @@ import ( "github.com/scionproto/scion/pkg/slayers/path/scion" ) -// BrTransit generates one packet of transit traffic over the same BR host. -// The outcome is a packet and which interface to send it to. -func BrTransit(payload string, mac hash.Hash) (string, string, []byte) { +// Topology (see accept/router_newbenchmark/conf/topology.json) +// AS2 (br2) ---+== (br1a) AS1 (br1b) ---- (br4) AS4 +// | +// AS3 (br3) ---+ +// +// We're only executing and monitoring br1a. All the others are a fiction (except for the knowledge +// about them configured in br1a) from which we construct packets that get injected at one of the +// br1a interfaces. +// +// In the tests cases, the various addresses used to construct packets are: +// originIA: the ISD/AS number of the AS of the initial sender. +// originIP: the underlay address (and so SCION host) of the initial sender. +// srcIP: the IP address of the router interface sending to br1a. +// srcMac: the ethernet address of the router interface sending to br1a. +// dstIP: the IP address of the br1a interface that should receives the packet. +// dstMac: the ethernet address of the br1a interface that should receive the packet. +// targetIA: the ISD/AS number of the AS of the final recipient. +// targetIP: the underlay address (and so SCION host) of the final recipient. +// +// To further simplify the explicit configuration that we need, the topology follows a convention +// to assign addresses, so that an address can be inferred from a single node and interface number. +// ISD/AS: <1 or 2>-ff00:0: +// interface number: +// public IP address: 192.168.. +// internal IP address: 192.168.0. +// MAC Address: 0xf0, 0x0d, 0xfe, 0xbe, +// Internal port: 30042 +// External port: 50000 +// As a result, children ASes (like AS2) have addresses ending in N.N and interface N where N is +// the AS number. For br1a/b, interfaces are numbered after the child on the other side, the +// public IPS are .1 and the internal IP ends in 0.1 or 0.2. The MAC addresses follow. + +// TODO: add functions that produce the right numbers from an intuitive descriptor. + +// oneBrTransit generates one packet of transit traffic over the same BR host. +// The outcome is a raw packet. +func oneBrTransit(payload string, mac hash.Hash, flowId uint32) []byte { + + var ( + originIA = xtest.MustParseIA("1-ff00:0:2") + originIP = "192.168.2.2" + srcIP = net.IP{192, 168, 2, 2} + srcMAC = net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0x02, 0x02} + dstIP = net.IP{192, 168, 2, 1} + dstMAC = net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0x02, 0x01} + targetIP = "192.168.3.3" + targetIA = xtest.MustParseIA("1-ff00:0:3") + ) + options := gopacket.SerializeOptions{ FixLengths: true, ComputeChecksums: true, @@ -41,19 +87,18 @@ func BrTransit(payload string, mac hash.Hash) (string, string, []byte) { // Point-to-point. Src might not mater. Dst probably must match what test.py configured // for interface 2 of the router. ethernet := &layers.Ethernet{ - SrcMAC: net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0xbe, 0xef}, - DstMAC: net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0x00, 0x02}, + SrcMAC: srcMAC, + DstMAC: dstMAC, EthernetType: layers.EthernetTypeIPv4, } // Point-to-point. This is the real IP: the underlay network. - // IP4: Src=192.168.2.2 Dst=192.168.2.1 NextHdr=UDP Flags=DF ip := &layers.IPv4{ Version: 4, IHL: 5, TTL: 64, - SrcIP: net.IP{192, 168, 2, 2}, - DstIP: net.IP{192, 168, 2, 1}, + SrcIP: srcIP, + DstIP: dstIP, Protocol: layers.IPProtocolUDP, Flags: layers.IPv4DontFragment, } @@ -110,20 +155,20 @@ func BrTransit(payload string, mac hash.Hash) (string, string, []byte) { scionL := &slayers.SCION{ Version: 0, TrafficClass: 0xb8, - FlowID: 0xdead, + FlowID: flowId, NextHdr: slayers.L4UDP, PathType: scion.PathType, - SrcIA: xtest.MustParseIA("1-ff00:0:2"), - DstIA: xtest.MustParseIA("1-ff00:0:3"), + SrcIA: originIA, + DstIA: targetIA, Path: sp, } // These aren't necessarily IP addresses. They're host addresses within the // src and dst ASes. - if err := scionL.SetSrcAddr(addr.MustParseHost("192.168.2.2")); err != nil { + if err := scionL.SetSrcAddr(addr.MustParseHost(originIP)); err != nil { panic(err) } - if err := scionL.SetDstAddr(addr.MustParseHost("192.168.3.3")); err != nil { + if err := scionL.SetDstAddr(addr.MustParseHost(targetIP)); err != nil { panic(err) } @@ -141,6 +186,13 @@ func BrTransit(payload string, mac hash.Hash) (string, string, []byte) { ); err != nil { panic(err) } + return input.Bytes() +} - return "veth_2_host", "veth_3_host", input.Bytes() +func BrTransit(payload string, mac hash.Hash, numDistinct int) (string, string, [][]byte) { + packets := make([][]byte, numDistinct, numDistinct) + for i := 0; i < numDistinct; i++ { + packets[i] = oneBrTransit(payload, mac, uint32(i+1)) + } + return "veth_2_host", "veth_3_host", packets } diff --git a/tools/brload/main.go b/tools/brload/main.go index 647d87ae10..157b10a639 100644 --- a/tools/brload/main.go +++ b/tools/brload/main.go @@ -36,7 +36,7 @@ import ( "github.com/scionproto/scion/private/keyconf" ) -type Case func(payload string, mac hash.Hash) (string, string, []byte) +type Case func(payload string, mac hash.Hash, numDistinct int) (string, string, [][]byte) var ( allCases = map[string]Case{ @@ -45,6 +45,7 @@ var ( logConsole = flag.String("log.console", "debug", "Console logging level: debug|info|error") dir = flag.String("artifacts", "", "Artifacts directory") numPackets = flag.Int("num_packets", 10, "Number of packets to send") + numStreams = flag.Int("num_streams", 4, "Number of independent streams (flow IDs) to use") caseToRun = flag.String("case", "", fmt.Sprintf("Which traffic case to evaluate %v", reflect.ValueOf(allCases).MapKeys())) @@ -122,7 +123,7 @@ func realMain() int { log.Info("BRLoad acceptance tests:") payloadString := "actualpayloadbytes" - caseDevIn, caseDevOut, rawPkt := caseFunc(payloadString, hfMAC) + caseDevIn, caseDevOut, rawPkts := caseFunc(payloadString, hfMAC, *numStreams) writePktTo, ok := handles[caseDevIn] if !ok { @@ -178,7 +179,7 @@ func realMain() int { // opens somewhere around now. metricsBegin := time.Now().Unix() for i := 0; i < *numPackets; i++ { - if err := writePktTo.WritePacketData(rawPkt); err != nil { + if err := writePktTo.WritePacketData(rawPkts[i%*numStreams]); err != nil { log.Error("writing input packet", "case", *caseToRun, "error", err) return 1 } From a1a379c6a3e412d0db3e3ceb4f6fb36c2855c29a Mon Sep 17 00:00:00 2001 From: Jean-Christophe Hugly Date: Tue, 21 Nov 2023 10:28:30 +0100 Subject: [PATCH 07/40] router: move br_load into new_benchmark. brload no-longer makes sense as a stand-alone tool; it is tightly bound to the associated test topology, so move it in with router_newbenchmark which is in charge of bringing up that topology. That way, at least, all the inter-dependent pieces are in the same location. --- BUILD.bazel | 1 - acceptance/router_newbenchmark/BUILD.bazel | 47 ++++++++++++++++-- .../router_newbenchmark}/cases.go | 0 .../router_newbenchmark}/main.go | 0 acceptance/router_newbenchmark/pause.tar | Bin 258560 -> 0 bytes tools/brload/BUILD.bazel | 33 ------------ 6 files changed, 42 insertions(+), 39 deletions(-) rename {tools/brload => acceptance/router_newbenchmark}/cases.go (100%) rename {tools/brload => acceptance/router_newbenchmark}/main.go (100%) delete mode 100644 acceptance/router_newbenchmark/pause.tar delete mode 100644 tools/brload/BUILD.bazel diff --git a/BUILD.bazel b/BUILD.bazel index 106951d6ac..c43159ca4e 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -195,7 +195,6 @@ pkg_tar( "//acceptance/cmd/sig_ping_acceptance", "//pkg/private/xtest/graphupdater", "//tools/braccept", - "//tools/brload", "//tools/buildkite/cmd/buildkite_artifacts", "//tools/end2end", "//tools/end2end_integration", diff --git a/acceptance/router_newbenchmark/BUILD.bazel b/acceptance/router_newbenchmark/BUILD.bazel index 07604a90e6..b35b9478f2 100644 --- a/acceptance/router_newbenchmark/BUILD.bazel +++ b/acceptance/router_newbenchmark/BUILD.bazel @@ -1,5 +1,39 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary") load("@io_bazel_rules_docker//container:container.bzl", "container_image") load("//acceptance/common:raw.bzl", "raw_test") +load("//tools/lint:go.bzl", "go_library") +load("//:scion.bzl", "scion_go_binary") + +go_library( + name = "go_default_library", + srcs = [ + "cases.go", + "main.go", + ], + importpath = "github.com/scionproto/scion/acceptance/router_newbenchmark", + visibility = ["//visibility:private"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/private/util:go_default_library", + "//pkg/private/xtest:go_default_library", + "//pkg/scrypto:go_default_library", + "//pkg/slayers:go_default_library", + "//pkg/slayers/path:go_default_library", + "//pkg/slayers/path/scion:go_default_library", + "//private/keyconf:go_default_library", + "@com_github_google_gopacket//:go_default_library", + "@com_github_google_gopacket//afpacket:go_default_library", + "@com_github_google_gopacket//layers:go_default_library", + ], +) + +scion_go_binary( + name = "brload", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) exports_files([ "conf", @@ -9,18 +43,15 @@ exports_files([ args = [ "--executable", - "brload:$(location //tools/brload)", + "brload:$(location //acceptance/router_newbenchmark:brload)", "--container-loader", "posix-router:latest#$(location //acceptance/router_newbenchmark:router)", - "--pause_tar", - "$(location //acceptance/router_newbenchmark:pause.tar)", ] data = [ - "pause.tar", ":conf", ":router", - "//tools/brload", + ":brload", ] raw_test( @@ -37,3 +68,9 @@ container_image( name = "router", base = "//docker:posix_router", ) + +go_binary( + name = "router_newbenchmark", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/tools/brload/cases.go b/acceptance/router_newbenchmark/cases.go similarity index 100% rename from tools/brload/cases.go rename to acceptance/router_newbenchmark/cases.go diff --git a/tools/brload/main.go b/acceptance/router_newbenchmark/main.go similarity index 100% rename from tools/brload/main.go rename to acceptance/router_newbenchmark/main.go diff --git a/acceptance/router_newbenchmark/pause.tar b/acceptance/router_newbenchmark/pause.tar deleted file mode 100644 index da03eed189f8c5fa9423f1be5876f2ea05f3e08c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 258560 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 NJ#g9s|37=+e*oxG3EBVv diff --git a/tools/brload/BUILD.bazel b/tools/brload/BUILD.bazel deleted file mode 100644 index 4829c719f8..0000000000 --- a/tools/brload/BUILD.bazel +++ /dev/null @@ -1,33 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") -load("//:scion.bzl", "scion_go_binary") - -go_library( - name = "go_default_library", - srcs = [ - "cases.go", - "main.go", - ], - importpath = "github.com/scionproto/scion/tools/brload", - visibility = ["//visibility:private"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/private/util:go_default_library", - "//pkg/private/xtest:go_default_library", - "//pkg/scrypto:go_default_library", - "//pkg/slayers:go_default_library", - "//pkg/slayers/path:go_default_library", - "//pkg/slayers/path/scion:go_default_library", - "//private/keyconf:go_default_library", - "@com_github_google_gopacket//:go_default_library", - "@com_github_google_gopacket//afpacket:go_default_library", - "@com_github_google_gopacket//layers:go_default_library", - ], -) - -scion_go_binary( - name = "brload", - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) From c4ac9413cf0db745ad9d16a5c05c40b2c6c2f1a9 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Hugly Date: Tue, 21 Nov 2023 14:00:28 +0100 Subject: [PATCH 08/40] router: mild improvements to the benchmark code. Rely on address assignment conventions to simplify concrete addresses out of test cases. --- acceptance/router_newbenchmark/BUILD.bazel | 1 + acceptance/router_newbenchmark/cases.go | 55 +++++-------------- acceptance/router_newbenchmark/conf/br.toml | 4 +- .../router_newbenchmark/conf/prometheus.yml | 2 +- .../router_newbenchmark/conf/topology.json | 4 +- acceptance/router_newbenchmark/test.py | 4 +- 6 files changed, 21 insertions(+), 49 deletions(-) diff --git a/acceptance/router_newbenchmark/BUILD.bazel b/acceptance/router_newbenchmark/BUILD.bazel index b35b9478f2..10a6f42aca 100644 --- a/acceptance/router_newbenchmark/BUILD.bazel +++ b/acceptance/router_newbenchmark/BUILD.bazel @@ -9,6 +9,7 @@ go_library( srcs = [ "cases.go", "main.go", + "topo.go", ], importpath = "github.com/scionproto/scion/acceptance/router_newbenchmark", visibility = ["//visibility:private"], diff --git a/acceptance/router_newbenchmark/cases.go b/acceptance/router_newbenchmark/cases.go index 707a5e3280..58563602dd 100644 --- a/acceptance/router_newbenchmark/cases.go +++ b/acceptance/router_newbenchmark/cases.go @@ -16,15 +16,12 @@ package main import ( "hash" - "net" "time" "github.com/google/gopacket" "github.com/google/gopacket/layers" - "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/private/util" - "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" @@ -34,49 +31,23 @@ import ( // AS2 (br2) ---+== (br1a) AS1 (br1b) ---- (br4) AS4 // | // AS3 (br3) ---+ -// -// We're only executing and monitoring br1a. All the others are a fiction (except for the knowledge -// about them configured in br1a) from which we construct packets that get injected at one of the -// br1a interfaces. -// -// In the tests cases, the various addresses used to construct packets are: -// originIA: the ISD/AS number of the AS of the initial sender. -// originIP: the underlay address (and so SCION host) of the initial sender. -// srcIP: the IP address of the router interface sending to br1a. -// srcMac: the ethernet address of the router interface sending to br1a. -// dstIP: the IP address of the br1a interface that should receives the packet. -// dstMac: the ethernet address of the br1a interface that should receive the packet. -// targetIA: the ISD/AS number of the AS of the final recipient. -// targetIP: the underlay address (and so SCION host) of the final recipient. -// -// To further simplify the explicit configuration that we need, the topology follows a convention -// to assign addresses, so that an address can be inferred from a single node and interface number. -// ISD/AS: <1 or 2>-ff00:0: -// interface number: -// public IP address: 192.168.. -// internal IP address: 192.168.0. -// MAC Address: 0xf0, 0x0d, 0xfe, 0xbe, -// Internal port: 30042 -// External port: 50000 -// As a result, children ASes (like AS2) have addresses ending in N.N and interface N where N is -// the AS number. For br1a/b, interfaces are numbered after the child on the other side, the -// public IPS are .1 and the internal IP ends in 0.1 or 0.2. The MAC addresses follow. - -// TODO: add functions that produce the right numbers from an intuitive descriptor. +// See topo.go // oneBrTransit generates one packet of transit traffic over the same BR host. // The outcome is a raw packet. func oneBrTransit(payload string, mac hash.Hash, flowId uint32) []byte { var ( - originIA = xtest.MustParseIA("1-ff00:0:2") - originIP = "192.168.2.2" - srcIP = net.IP{192, 168, 2, 2} - srcMAC = net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0x02, 0x02} - dstIP = net.IP{192, 168, 2, 1} - dstMAC = net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, 0x02, 0x01} - targetIP = "192.168.3.3" - targetIA = xtest.MustParseIA("1-ff00:0:3") + originIA = isdAS(2) + originIP = publicIP(2, 1) + originHost = hostAddr(originIP) + srcIP = publicIP(2, 1) + srcMAC = macAddr(srcIP) + dstIP = publicIP(1, 2) + dstMAC = macAddr(dstIP) + targetIA = isdAS(3) + targetIP = publicIP(3, 1) + targetHost = hostAddr(targetIP) ) options := gopacket.SerializeOptions{ @@ -165,10 +136,10 @@ func oneBrTransit(payload string, mac hash.Hash, flowId uint32) []byte { // These aren't necessarily IP addresses. They're host addresses within the // src and dst ASes. - if err := scionL.SetSrcAddr(addr.MustParseHost(originIP)); err != nil { + if err := scionL.SetSrcAddr(originHost); err != nil { panic(err) } - if err := scionL.SetDstAddr(addr.MustParseHost(targetIP)); err != nil { + if err := scionL.SetDstAddr(targetHost); err != nil { panic(err) } diff --git a/acceptance/router_newbenchmark/conf/br.toml b/acceptance/router_newbenchmark/conf/br.toml index f3857e2727..d583bb7307 100644 --- a/acceptance/router_newbenchmark/conf/br.toml +++ b/acceptance/router_newbenchmark/conf/br.toml @@ -3,12 +3,12 @@ id = "br1a" config_dir = "/share/conf" [metrics] -prometheus = "192.168.0.1:30442" +prometheus = "192.168.10.1:30442" [features] [api] -addr = "192.168.0.1:31142" +addr = "192.168.10.1:31142" [log.console] level = "error" diff --git a/acceptance/router_newbenchmark/conf/prometheus.yml b/acceptance/router_newbenchmark/conf/prometheus.yml index d0abecdfc1..d35f139f99 100644 --- a/acceptance/router_newbenchmark/conf/prometheus.yml +++ b/acceptance/router_newbenchmark/conf/prometheus.yml @@ -7,4 +7,4 @@ scrape_configs: - job_name: BR static_configs: - targets: - - 192.168.0.1:30442 + - 192.168.10.1:30442 diff --git a/acceptance/router_newbenchmark/conf/topology.json b/acceptance/router_newbenchmark/conf/topology.json index 6edf1873cd..f24e967610 100644 --- a/acceptance/router_newbenchmark/conf/topology.json +++ b/acceptance/router_newbenchmark/conf/topology.json @@ -16,7 +16,7 @@ }, "border_routers": { "br1a": { - "internal_addr": "192.168.0.1:30042", + "internal_addr": "192.168.10.1:30042", "interfaces": { "2": { "underlay": { @@ -39,7 +39,7 @@ } }, "br1b": { - "internal_addr": "192.168.0.2:30042", + "internal_addr": "192.168.10.2:30042", "interfaces": { "4": { "underlay": { diff --git a/acceptance/router_newbenchmark/test.py b/acceptance/router_newbenchmark/test.py index 764a21c189..8bf6d5794e 100644 --- a/acceptance/router_newbenchmark/test.py +++ b/acceptance/router_newbenchmark/test.py @@ -169,8 +169,8 @@ def create_veths(self, ns: str): # Set default TTL for outgoing packets to the common value 64, so that packets sent # from router will match the expected value. exec_sudo(f"ip netns exec {ns} sysctl -w net.ipv4.ip_default_ttl=64") - create_veth("veth_int_host", "veth_int", "192.168.0.1/24", "f0:0d:ca:fe:00:01", ns, - [("192.168.0.2", "f0:0d:ca:fe:00:02")]) + create_veth("veth_int_host", "veth_int", "192.168.10.1/24", "f0:0d:ca:fe:10:01", ns, + [("192.168.10.2", "f0:0d:ca:fe:10:02")]) create_veth("veth_2_host", "veth_2", "192.168.2.1/24", "f0:0d:ca:fe:02:01", ns, [("192.168.2.2", "f0:0d:ca:fe:02:02")]) create_veth("veth_3_host", "veth_3", "192.168.3.1/24", "f0:0d:ca:fe:03:01", ns, From e6192961b62ddeb8450abdb50087f61f89b0711e Mon Sep 17 00:00:00 2001 From: Jean-Christophe Hugly Date: Tue, 21 Nov 2023 17:42:15 +0100 Subject: [PATCH 09/40] router: cleanup newbenchmark. Namely: * Contained all topology knowledge in brload: test.py is told what to do by the --showInterfaces option of brload. * Contained all interface names knowledge (mac addresses are next) in test.py: brload is told the real interface names via the -interface flag. --- acceptance/router_newbenchmark/cases.go | 8 +- acceptance/router_newbenchmark/main.go | 27 ++++- acceptance/router_newbenchmark/test.py | 99 ++++++++++-------- acceptance/router_newbenchmark/topo.go | 132 ++++++++++++++++++++++++ 4 files changed, 219 insertions(+), 47 deletions(-) create mode 100644 acceptance/router_newbenchmark/topo.go diff --git a/acceptance/router_newbenchmark/cases.go b/acceptance/router_newbenchmark/cases.go index 58563602dd..d11190c627 100644 --- a/acceptance/router_newbenchmark/cases.go +++ b/acceptance/router_newbenchmark/cases.go @@ -1,4 +1,4 @@ -// Copyright 2020 Anapaya Systems +// Copyright 2023 SCION Association // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -160,10 +160,14 @@ func oneBrTransit(payload string, mac hash.Hash, flowId uint32) []byte { return input.Bytes() } +// BrTransit generates numDistinct packets (each with a unique flowID) with the given payload +// constructed to cause br_transit traffic at the br1a router. +// numDistrinct is a small number, only to enable multiple parallel streams. Each distinct packet +// is meant to be replayed a large number of times for performance measurement. func BrTransit(payload string, mac hash.Hash, numDistinct int) (string, string, [][]byte) { packets := make([][]byte, numDistinct, numDistinct) for i := 0; i < numDistinct; i++ { packets[i] = oneBrTransit(payload, mac, uint32(i+1)) } - return "veth_2_host", "veth_3_host", packets + return interfaceName(1, 2), interfaceName(1, 3), packets } diff --git a/acceptance/router_newbenchmark/main.go b/acceptance/router_newbenchmark/main.go index 157b10a639..90e657cafd 100644 --- a/acceptance/router_newbenchmark/main.go +++ b/acceptance/router_newbenchmark/main.go @@ -1,4 +1,4 @@ -// Copyright 2020 Anapaya Systems +// Copyright 2023 SCION Association // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -36,6 +36,19 @@ import ( "github.com/scionproto/scion/private/keyconf" ) +// Created so that multiple inputs can be accecpted +type arrayFlags []string + +func (i *arrayFlags) String() string { + // change this, this is just can example to satisfy the interface + return "A repeatable string argument" +} + +func (i *arrayFlags) Set(value string) error { + *i = append(*i, strings.TrimSpace(value)) + return nil +} + type Case func(payload string, mac hash.Hash, numDistinct int) (string, string, [][]byte) var ( @@ -49,7 +62,9 @@ var ( caseToRun = flag.String("case", "", fmt.Sprintf("Which traffic case to evaluate %v", reflect.ValueOf(allCases).MapKeys())) - handles = make(map[string]*afpacket.TPacket) + showIntf = flag.Bool("show_interfaces", false, "Show interfaces needed by the test") + intfNames = arrayFlags{} + handles = make(map[string]*afpacket.TPacket) ) // initDevices inventories the available network interfaces, picks the ones that a case may inject @@ -79,7 +94,14 @@ func main() { } func realMain() int { + flag.Var(&intfNames, "interface", + "label=host_interface; use host_interface to send traffic to the interface named label") flag.Parse() + if *showIntf { + fmt.Println(ShowInterfaces()) + return 0 + } + logCfg := log.Config{Console: log.ConsoleConfig{Level: *logConsole}} if err := log.Setup(logCfg); err != nil { flag.Usage() @@ -112,6 +134,7 @@ func realMain() int { return 1 } + LoadInterfaceMap(intfNames) err = initDevices() if err != nil { log.Error("Loading devices failed", "err", err) diff --git a/acceptance/router_newbenchmark/test.py b/acceptance/router_newbenchmark/test.py index 8bf6d5794e..f727c0d2ad 100644 --- a/acceptance/router_newbenchmark/test.py +++ b/acceptance/router_newbenchmark/test.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2021 Anapaya Systems +# Copyright 2023 SCION Association # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import shutil import time +from collections import namedtuple from typing import List, Tuple from plumbum import cli from plumbum.cmd import sudo,docker,whoami @@ -51,21 +52,25 @@ def exec_sudo(command: str) -> str: # interactive password input. return sudo("-A", str.split(command)) -def create_veth(host: str, container: str, ip: str, mac: str, ns: str, - neighbors: List[Tuple[str, str]]): - exec_sudo(f"ip link add {host} mtu 8000 type veth peer name {container} mtu 8000") - exec_sudo(f"sysctl -qw net.ipv6.conf.{host}.disable_ipv6=1") - exec_sudo(f"ip link set {host} up") - exec_sudo(f"ip link set {container} netns {ns}") - exec_sudo(f"ip netns exec {ns} sysctl -qw net.ipv6.conf.{container}.disable_ipv6=1") - exec_sudo(f"ip netns exec {ns} ethtool -K {container} rx off tx off") - exec_sudo(f"ip netns exec {ns} ip link set {container} address {mac}") - exec_sudo(f"ip netns exec {ns} ip addr add {ip} dev {container}") - for n in neighbors: - exec_sudo(f"ip netns exec {ns} ip neigh add {n[0]} " - f"lladdr {n[1]} nud permanent dev {container}") - exec_sudo(f"ip netns exec {ns} ip link set {container} up") +# Convenience type to carry interface params. +Intf = namedtuple("Intf", "label, prefixLen, ip, mac, peerIp, peerMac") + +def create_interface(intf: Intf, ns: str) -> str: + hostIntf = f"veth_{intf.label}_host" + brIntf = f"veth_{intf.label}" + exec_sudo(f"ip link add {hostIntf} mtu 8000 type veth peer name {brIntf} mtu 8000") + exec_sudo(f"sysctl -qw net.ipv6.conf.{hostIntf}.disable_ipv6=1") + exec_sudo(f"ip link set {hostIntf} up") + exec_sudo(f"ip link set {brIntf} netns {ns}") + exec_sudo(f"ip netns exec {ns} sysctl -qw net.ipv6.conf.{brIntf}.disable_ipv6=1") + exec_sudo(f"ip netns exec {ns} ethtool -K {brIntf} rx off tx off") + exec_sudo(f"ip netns exec {ns} ip link set {brIntf} address {intf.mac}") + exec_sudo(f"ip netns exec {ns} ip addr add {intf.ip}/{intf.prefixLen} dev {brIntf}") + exec_sudo(f"ip netns exec {ns} ip neigh add {intf.peerIp} " + f"lladdr {intf.peerMac} nud permanent dev {brIntf}") + exec_sudo(f"ip netns exec {ns} ip link set {brIntf} up") + return hostIntf class RouterBMTest(base.TestBase): """ @@ -105,18 +110,13 @@ class RouterBMTest(base.TestBase): envname="CI" ) - pause_tar = cli.SwitchAttr( - "pause_tar", - str, - help="taball with the pause image", - ) - def setup_prepare(self): super().setup_prepare() + # get the config where the router can find it. shutil.copytree("acceptance/router_newbenchmark/conf/", self.artifacts / "conf") - exec_sudo("mkdir -p /var/run/netns") + # We need a custom network so can create veth interfaces of our own chosing. exec_docker("network create -d bridge benchmark") # This test is useless without prometheus. Also, we need a running container to have @@ -135,16 +135,37 @@ def setup_prepare(self): "prom/prometheus:v2.47.2 " "--config.file /share/conf/prometheus.yml") - ns = exec_docker("inspect prometheus -f '{{.NetworkSettings.SandboxKey}}'").replace("'", "") - # Link that namespace to where the ip commands expect it. While at it give it a simple name. - # Then create all the interfaces that the router will see... the test will be using the - # other end of the pairs to feed it with (and possibly capture) traffic. + exec_sudo("mkdir -p /var/run/netns") + ns = exec_docker("inspect prometheus -f '{{.NetworkSettings.SandboxKey}}'").replace("'", "") exec_sudo(f"ln -sfT {ns} /var/run/netns/benchmark") - self.create_veths("benchmark") + + # Set default TTL for outgoing packets to the common value 64, so that packets sent + # from router will match the expected value. + exec_sudo("ip netns exec benchmark sysctl -w net.ipv4.ip_default_ttl=64") + + # Run test brload test with --show_interfaces and set up the veth that it needs. + # The router uses one end and the test uses the other end to feed it with (and possibly + # capture) traffic. + # We supply the label->host-side-name mapping to brload when we start it. + self.intfHostMap = {} + brload = self.get_executable("brload") + output = exec_sudo(f"{brload.executable} -artifacts {self.artifacts} " + "-show_interfaces") + + for line in output.splitlines(): + print(line) + elems = line.split(" ") + if len(elems) < 6: + continue + t = Intf._make(elems) + + self.intfHostMap[t.label] = create_interface(t, "benchmark") + + # We don't need that symlink any more exec_sudo("rm /var/run/netns/benchmark") - # Then the router. + # Now the router can start. exec_docker(f"run -v {self.artifacts}/conf:/share/conf " "-d " "-e SCION_EXPERIMENTAL_BFD_DISABLE=true -e GOMAXPROCS=6 " @@ -154,9 +175,6 @@ def setup_prepare(self): time.sleep(2) - def setup_start(self): - super().setup_start() - def teardown(self): docker["logs", "router"].run_fg(retcode=None) exec_docker("rm -f prometheus") @@ -164,23 +182,18 @@ def teardown(self): exec_docker("network rm benchmark") # veths are deleted automatically exec_sudo(f"chown -R {whoami()} {self.artifacts}") - # Wire virtual interfaces around the one router that we run. - def create_veths(self, ns: str): - # Set default TTL for outgoing packets to the common value 64, so that packets sent - # from router will match the expected value. - exec_sudo(f"ip netns exec {ns} sysctl -w net.ipv4.ip_default_ttl=64") - create_veth("veth_int_host", "veth_int", "192.168.10.1/24", "f0:0d:ca:fe:10:01", ns, - [("192.168.10.2", "f0:0d:ca:fe:10:02")]) - create_veth("veth_2_host", "veth_2", "192.168.2.1/24", "f0:0d:ca:fe:02:01", ns, - [("192.168.2.2", "f0:0d:ca:fe:02:02")]) - create_veth("veth_3_host", "veth_3", "192.168.3.1/24", "f0:0d:ca:fe:03:01", ns, - [("192.168.3.3", "f0:0d:ca:fe:03:03")]) - def _run(self): + # Build the interface mapping arg: + mapArgs = [f"-interface {label}={infName}" for label, infName in self.intfHostMap.items()] + print(mapArgs) + + # At long last... logger.info("==> Starting load br-transit") brload = self.get_executable("brload") output = exec_sudo(f"{brload.executable} -artifacts {self.artifacts} " + f"{' '.join(mapArgs)} " "-case br_transit -num_packets 10000000 -num_streams 2") + for line in output.splitlines(): print(line) if line.startswith('metricsBegin'): diff --git a/acceptance/router_newbenchmark/topo.go b/acceptance/router_newbenchmark/topo.go new file mode 100644 index 0000000000..e101b37e52 --- /dev/null +++ b/acceptance/router_newbenchmark/topo.go @@ -0,0 +1,132 @@ +// Copyright 2023 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 main + +import ( + "fmt" + "net" + "net/netip" + "strings" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/private/xtest" +) + +// Topology (see accept/router_newbenchmark/conf/topology.json) +// AS2 (br2) ---+== (br1a) AS1 (br1b) ---- (br4) AS4 +// | +// AS3 (br3) ---+ +// +// We're only executing and monitoring br1a. All the others are a fiction (except for the knowledge +// about them configured in br1a) from which we construct packets that get injected at one of the +// br1a interfaces. +// +// To further simplify the explicit configuration that we need, the topology follows a convention +// to assign addresses, so that an address can be derived from a minimal descriptor. AS-1 is the hub +// of the test. +// All IPs are V4. +// ISD/AS: <1 or 2>-ff00:0: +// interface number: +// public IP address for interfaces of AS-1: 192.168.. +// public IP address for interfaces of others: 192.168.. +// internal IP address: 192.168.*10. +// MAC Address: 0xf0, 0x0d, 0xfe, 0xbe, +// Internal port: 30042 +// External port: 50000 +// As a result, children ASes (like AS2) have addresses ending in N.N and interface N where N is +// the AS number. For br1a/b, interfaces are numbered after the child on the other side, the +// public IPS are .1 and the internal IP ends in 0.1 or 0.2. The MAC addresses follow. +// +// The invoker of this test is in charge of configuring a router with the custom topology +// (conf/topology.json) and to setup host-side interfaces as needed, through which this test can +// inject traffic. This test does not control the names of the host-side interfaces; they're +// supplied by the invoker. +// +// To make the invoker's life easier, this test output what host side interfaces it may need +// (and connected to what) when invoked with --show_interfaces. If the router runs inside a network +// namespace, the invoker must configure veths accordingly; otherwise, find which real interfaces +// this test should use. + +var ( + intfMap map[string]string = map[string]string{} +) + +func LoadInterfaceMap(pairs []string) { + for _, pair := range pairs { + p := strings.Split(pair, "=") + intfMap[p[0]] = p[1] + } +} + +// We give abstract names to our interfaces. These names are those used when responding to +// --show_interfaces and used to translate --interface. +func interfaceLabel(AS int, intf int) string { + return fmt.Sprintf("%d_%d", AS, intf) +} + +func interfaceName(AS int, intf int) string { + return intfMap[interfaceLabel(AS, intf)] +} + +// Local and remote AS are enough. One of them is the central AS (1). The subnet number is that of +// the other AS. The host is always the local AS. If neither is 1, then we follow the lowest, but +// it's an unexpected config +func publicIP(localAS byte, remoteAS byte) net.IP { + if localAS < remoteAS { + return net.IP{192, 168, remoteAS, localAS} + } + return net.IP{192, 168, localAS, localAS} +} + +func internalIP(AS byte, router byte) net.IP { + return net.IP{192, 168, AS * 10, router} +} + +// All are in ISD-1, except AS 4. +func isdAS(AS byte) addr.IA { + if AS == 4 { + return xtest.MustParseIA(fmt.Sprintf("2-ff00:0:%d", AS)) + } + return xtest.MustParseIA(fmt.Sprintf("1-ff00:0:%d", AS)) +} + +// Macs derive from IP in the most straighforward manner. +func macAddr(ip net.IP) net.HardwareAddr { + return net.HardwareAddr{0xf0, 0x0d, 0xca, 0xfe, ip[2], ip[3]} +} + +// SCION Hosts addresses are (except for SVC addresses, a restating of the underlay public IP. +func hostAddr(ip net.IP) addr.Host { + as4bytes := [4]uint8{ip[0], ip[1], ip[2], ip[3]} + return addr.HostIP(netip.AddrFrom4(as4bytes)) +} + +// Outputs a string describing the interfaces of the router under test. +// This test needs access to interfaces that connect to them. In a container setting, it also needs +// the container network configured accordingly, (hence the inclusion of router-side addresses. +// The given names are only labels, the real interface names (and mac addresses associated with +// them) shall be provided when the test is executed for real (without the --show_interfaces +// option). +func ShowInterfaces() string { + // For now, we only need: + // AS1 interface 0 (internal) + // AS1 interface 2 + // AS1 interface 3 + return "" + + interfaceLabel(1, 0) + " 24 192.168.10.1 f0:0d:ca:fe:10:01 192.168.10.2 f0:0d:ca:fe:10:02\n" + + interfaceLabel(1, 2) + " 24 192.168.2.1 f0:0d:ca:fe:02:01 192.168.2.2 f0:0d:ca:fe:02:02\n" + + interfaceLabel(1, 3) + " 24 192.168.3.1 f0:0d:ca:fe:03:01 192.168.3.3 f0:0d:ca:fe:03:03\n" + +} From 45abaf245f62fd5de9bc6a19d5fe1ea636fd0e2a Mon Sep 17 00:00:00 2001 From: Jean-Christophe Hugly Date: Wed, 22 Nov 2023 18:44:29 +0100 Subject: [PATCH 10/40] router: improvements to newbenchmark. Moved knowledge of mac addresses outside brload. The test harness now tells brload what the interface mac addresses are and not the other way around. In the usual automated test context it makes no difference (the same naming and addressing scheme is used), but in case we ever run the test on a real self-contained machine, the test doesn't get to choose the interface names and macs. --- acceptance/router_newbenchmark/cases.go | 10 +- acceptance/router_newbenchmark/main.go | 18 ++- acceptance/router_newbenchmark/test.py | 66 +++++---- acceptance/router_newbenchmark/topo.go | 189 +++++++++++++++--------- 4 files changed, 169 insertions(+), 114 deletions(-) diff --git a/acceptance/router_newbenchmark/cases.go b/acceptance/router_newbenchmark/cases.go index d11190c627..d14c1a559d 100644 --- a/acceptance/router_newbenchmark/cases.go +++ b/acceptance/router_newbenchmark/cases.go @@ -55,8 +55,7 @@ func oneBrTransit(payload string, mac hash.Hash, flowId uint32) []byte { ComputeChecksums: true, } - // Point-to-point. Src might not mater. Dst probably must match what test.py configured - // for interface 2 of the router. + // Point-to-point. ethernet := &layers.Ethernet{ SrcMAC: srcMAC, DstMAC: dstMAC, @@ -68,8 +67,8 @@ func oneBrTransit(payload string, mac hash.Hash, flowId uint32) []byte { Version: 4, IHL: 5, TTL: 64, - SrcIP: srcIP, - DstIP: dstIP, + SrcIP: srcIP.AsSlice(), + DstIP: dstIP.AsSlice(), Protocol: layers.IPProtocolUDP, Flags: layers.IPv4DontFragment, } @@ -133,9 +132,6 @@ func oneBrTransit(payload string, mac hash.Hash, flowId uint32) []byte { DstIA: targetIA, Path: sp, } - - // These aren't necessarily IP addresses. They're host addresses within the - // src and dst ASes. if err := scionL.SetSrcAddr(originHost); err != nil { panic(err) } diff --git a/acceptance/router_newbenchmark/main.go b/acceptance/router_newbenchmark/main.go index 90e657cafd..adb6fc8db9 100644 --- a/acceptance/router_newbenchmark/main.go +++ b/acceptance/router_newbenchmark/main.go @@ -62,9 +62,9 @@ var ( caseToRun = flag.String("case", "", fmt.Sprintf("Which traffic case to evaluate %v", reflect.ValueOf(allCases).MapKeys())) - showIntf = flag.Bool("show_interfaces", false, "Show interfaces needed by the test") - intfNames = arrayFlags{} - handles = make(map[string]*afpacket.TPacket) + showIntf = flag.Bool("show_interfaces", false, "Show interfaces needed by the test") + interfaces = arrayFlags{} + handles = make(map[string]*afpacket.TPacket) ) // initDevices inventories the available network interfaces, picks the ones that a case may inject @@ -94,11 +94,14 @@ func main() { } func realMain() int { - flag.Var(&intfNames, "interface", - "label=host_interface; use host_interface to send traffic to the interface named label") + flag.Var(&interfaces, "interface", + `label=host_interface,mac,peer_mac where: + host_interface: use this to exchange traffic with interface