From 5911b4a8ee4386616a991e0d5361acc7891f3603 Mon Sep 17 00:00:00 2001 From: "Juan A. Garcia Pardo" Date: Tue, 3 Dec 2024 10:25:18 +0100 Subject: [PATCH] Fixing and unifying hummingbird integration-tests. --- BUILD.bazel | 2 - tools/end2end/BUILD.bazel | 1 + tools/end2end/extras.go | 22 + tools/end2end/main.go | 51 +- tools/end2end_hbird/BUILD.bazel | 31 - tools/end2end_hbird/main.go | 469 --------------- tools/end2end_hbird_integration/BUILD.bazel | 31 - tools/end2end_hbird_integration/main.go | 616 -------------------- tools/end2end_integration/BUILD.bazel | 12 +- tools/end2end_integration/hummingbird.go | 135 +++++ tools/end2end_integration/main.go | 94 +++ 11 files changed, 310 insertions(+), 1154 deletions(-) create mode 100644 tools/end2end/extras.go delete mode 100644 tools/end2end_hbird/BUILD.bazel delete mode 100644 tools/end2end_hbird/main.go delete mode 100644 tools/end2end_hbird_integration/BUILD.bazel delete mode 100644 tools/end2end_hbird_integration/main.go create mode 100644 tools/end2end_integration/hummingbird.go diff --git a/BUILD.bazel b/BUILD.bazel index fd5038bf11..f63e8c8a85 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -210,8 +210,6 @@ pkg_tar( "//tools/braccept", "//tools/buildkite/cmd/buildkite_artifacts", "//tools/end2end", - "//tools/end2end_hbird", - "//tools/end2end_hbird_integration", "//tools/end2end_integration", "//tools/pktgen/cmd/pktgen", "//tools/scion_integration", diff --git a/tools/end2end/BUILD.bazel b/tools/end2end/BUILD.bazel index 5b730c92b5..c694fab034 100644 --- a/tools/end2end/BUILD.bazel +++ b/tools/end2end/BUILD.bazel @@ -4,6 +4,7 @@ load("//:scion.bzl", "scion_go_binary") go_library( name = "go_default_library", srcs = [ + "extras.go", "fabrid.go", "main.go", ], diff --git a/tools/end2end/extras.go b/tools/end2end/extras.go new file mode 100644 index 0000000000..a59490739c --- /dev/null +++ b/tools/end2end/extras.go @@ -0,0 +1,22 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +func bToi(b bool) int { + if b { + return 1 + } + return 0 +} diff --git a/tools/end2end/main.go b/tools/end2end/main.go index fe3bbe4431..4f6ad3ecee 100644 --- a/tools/end2end/main.go +++ b/tools/end2end/main.go @@ -75,6 +75,7 @@ var ( scmpErrorsCounter = scionPacketConnMetrics.SCMPErrors epic bool fabrid bool + hummingbird bool ) func main() { @@ -112,6 +113,7 @@ func addFlags() { flag.Var(timeout, "timeout", "The timeout for each attempt") flag.BoolVar(&epic, "epic", false, "Enable EPIC") flag.BoolVar(&fabrid, "fabrid", false, "Enable FABRID") + flag.BoolVar(&hummingbird, "hummingbird", false, "Enable Hummingbird") } func validateFlags() { @@ -126,10 +128,10 @@ func validateFlags() { integration.LogFatal("Invalid timeout provided", "timeout", timeout) } } - if epic && fabrid { - integration.LogFatal("FABRID is incompatible with EPIC") - } log.Info("Flags", "timeout", timeout, "epic", epic, "fabrid", fabrid, "remote", remote) + if bToi(epic)+bToi(fabrid)+bToi(hummingbird) > 1 { + integration.LogFatal("Only one of EPIC, FABRID, HUMMINGBIRD flags is allowed") + } } type server struct{} @@ -425,7 +427,7 @@ func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) { fabridPath, err := snetpath.NewFABRIDDataplanePath(scionPath, hops, policies, fabridConfig) if err != nil { - return nil, serrors.New("Error creating FABRID path", "err", err) + return nil, serrors.WrapStr("Error creating FABRID path", err) } remote.Path = fabridPath fabridPath.RegisterDRKeyFetcher(c.sdConn.FabridKeys) @@ -433,6 +435,47 @@ func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) { log.Info("FABRID flag was set for client in non-FABRID AS. Proceeding without FABRID.") remote.Path = path.Dataplane() } + } else if hummingbird { + // This works: + // Directly query the scion daemon. + reservations, err := c.sdConn.GetReservations(ctx, integration.Local.IA, remote.IA, 1, true) + if err != nil { + return nil, serrors.WrapStr("getting reservations from daemon", err) + } + reservation := reservations[0] + + // // This works: + // // Build with no flyovers. + // reservation, err := hummingbird.NewReservation( + // hummingbird.WithScionPath(path, nil)) + // if err != nil { + // logger.Error("Error converting path to Hummingbird", "err", err) + // return false + // } + + // // This works: + // // Get flyovers and build path. + // flyovers, err := c.sdConn.ListFlyovers(ctx) + // if err != nil { + // logger.Error("listing flyovers", "err", err) + // return false + // } + // reservation, err := hummingbird.NewReservation( + // hummingbird.WithScionPath(path, hummingbird.FlyoversToMap(flyovers))) + // if err != nil { + // logger.Error("Error converting path to Hummingbird", "err", err) + // return false + // } + + decoded := reservation.DeriveDataPlanePath(113, time.Now()) + raw := snetpath.Hummingbird{ + Raw: make([]byte, decoded.Len()), + } + err = decoded.SerializeTo(raw.Raw) + if err != nil { + return nil, serrors.WrapStr("Error assembling hummingbird path", err) + } + remote.Path = raw } else { remote.Path = path.Dataplane() } diff --git a/tools/end2end_hbird/BUILD.bazel b/tools/end2end_hbird/BUILD.bazel deleted file mode 100644 index d44857ed93..0000000000 --- a/tools/end2end_hbird/BUILD.bazel +++ /dev/null @@ -1,31 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") -load("//:scion.bzl", "scion_go_binary") - -go_library( - name = "go_default_library", - srcs = ["main.go"], - importpath = "github.com/scionproto/scion/tools/end2end_hbird", - visibility = ["//visibility:private"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/daemon:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/common:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/private/util:go_default_library", - "//pkg/snet:go_default_library", - "//pkg/snet/metrics:go_default_library", - "//pkg/snet/path:go_default_library", - "//private/tracing:go_default_library", - "//tools/integration:go_default_library", - "//tools/integration/integrationlib:go_default_library", - "@com_github_opentracing_opentracing_go//:go_default_library", - "@com_github_opentracing_opentracing_go//ext:go_default_library", - ], -) - -scion_go_binary( - name = "end2end_hbird", - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) diff --git a/tools/end2end_hbird/main.go b/tools/end2end_hbird/main.go deleted file mode 100644 index 14e94d2863..0000000000 --- a/tools/end2end_hbird/main.go +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright 2018 ETH Zurich -// Copyright 2019 ETH Zurich, 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. -// 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. - -// This is a general purpose client/server code for end2end tests. The client -// sends pings to the server until it receives at least one pong from the -// server or a given deadline is reached. The server responds to all pings and -// the client wait for a response before doing anything else. - -package main - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "flag" - "fmt" - "net" - "os" - "time" - - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/daemon" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/common" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/private/util" - "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/snet/metrics" - snetpath "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/private/tracing" - libint "github.com/scionproto/scion/tools/integration" - integration "github.com/scionproto/scion/tools/integration/integrationlib" -) - -const ( - ping = "ping" - pong = "pong" -) - -type Ping struct { - Server addr.IA `json:"server"` - Message string `json:"message"` - Trace []byte `json:"trace"` -} - -type Pong struct { - Client addr.IA `json:"client"` - Server addr.IA `json:"server"` - Message string `json:"message"` - Trace []byte `json:"trace"` -} - -var ( - remote snet.UDPAddr - timeout = &util.DurWrap{Duration: 10 * time.Second} - scionPacketConnMetrics = metrics.NewSCIONPacketConnMetrics() - scmpErrorsCounter = scionPacketConnMetrics.SCMPErrors - epic bool -) - -func main() { - os.Exit(realMain()) -} - -func realMain() int { - defer log.HandlePanic() - defer log.Flush() - addFlags() - err := integration.Setup() - if err != nil { - log.Error("Parsing common flags failed", "err", err) - return 1 - } - validateFlags() - - closeTracer, err := integration.InitTracer("end2end_hbird-" + integration.Mode) - if err != nil { - log.Error("Tracer initialization failed", "err", err) - return 1 - } - defer closeTracer() - - if integration.Mode == integration.ModeServer { - server{}.run() - return 0 - } - c := client{} - return c.run() -} - -func addFlags() { - flag.Var(&remote, "remote", "(Mandatory for clients) address to connect to") - flag.Var(timeout, "timeout", "The timeout for each attempt") - flag.BoolVar(&epic, "epic", false, "Enable EPIC") -} - -func validateFlags() { - if integration.Mode == integration.ModeClient { - if remote.Host == nil { - integration.LogFatal("Missing remote address") - } - if remote.Host.Port == 0 { - integration.LogFatal("Invalid remote port", "remote port", remote.Host.Port) - } - if timeout.Duration == 0 { - integration.LogFatal("Invalid timeout provided", "timeout", timeout) - } - } - log.Info("Flags", "timeout", timeout, "epic", epic, "remote", remote) -} - -type server struct{} - -func (s server) run() { - log.Info("Starting server", "isd_as", integration.Local.IA) - defer log.Info("Finished server", "isd_as", integration.Local.IA) - - sdConn := integration.SDConn() - defer sdConn.Close() - sn := &snet.SCIONNetwork{ - SCMPHandler: snet.DefaultSCMPHandler{ - RevocationHandler: daemon.RevHandler{Connector: sdConn}, - SCMPErrors: scmpErrorsCounter, - }, - PacketConnMetrics: scionPacketConnMetrics, - Topology: sdConn, - } - conn, err := sn.Listen(context.Background(), "udp", integration.Local.Host) - if err != nil { - integration.LogFatal("Error listening", "err", err) - } - defer conn.Close() - localAddr := conn.LocalAddr().(*snet.UDPAddr) - if len(os.Getenv(libint.GoIntegrationEnv)) > 0 { - // Needed for integration test ready signal. - fmt.Printf("Port=%d\n", localAddr.Host.Port) - fmt.Printf("%s%s\n\n", libint.ReadySignal, integration.Local.IA) - } - log.Info("Listening", "local", fmt.Sprintf("%v:%d", localAddr.Host.IP, localAddr.Host.Port)) - // Receive ping message - for { - if err := s.handlePing(conn); err != nil { - log.Error("Error handling ping", "err", err) - } - } -} - -func (s server) handlePing(conn *snet.Conn) error { - rawPld := make([]byte, common.MaxMTU) - n, clientAddr, err := readFrom(conn, rawPld) - if err != nil { - return serrors.WrapStr("reading packet", err) - } - - var pld Ping - if err := json.Unmarshal(rawPld[:n], &pld); err != nil { - return serrors.New("invalid payload contents", - "data", string(rawPld), - ) - } - - spanCtx, err := opentracing.GlobalTracer().Extract( - opentracing.Binary, - bytes.NewReader(pld.Trace), - ) - if err != nil { - return serrors.WrapStr("extracting trace information", err) - } - span, _ := opentracing.StartSpanFromContext( - context.Background(), - "handle_ping", - ext.RPCServerOption(spanCtx), - ) - defer span.Finish() - withTag := func(err error) error { - tracing.Error(span, err) - return err - } - clientUDPAddr := clientAddr.(*snet.UDPAddr) - if pld.Message != ping || !pld.Server.Equal(integration.Local.IA) { - return withTag(serrors.New("unexpected data in payload", - "remote", clientUDPAddr, - "data", pld, - )) - } - log.Info(fmt.Sprintf("Ping received from %v, sending pong.", clientUDPAddr)) - raw, err := json.Marshal(Pong{ - Client: clientUDPAddr.IA, - Server: integration.Local.IA, - Message: pong, - Trace: pld.Trace, - }) - if err != nil { - return withTag(serrors.WrapStr("packing pong", err)) - } - // Send pong - if _, err := conn.WriteTo(raw, clientUDPAddr); err != nil { - return withTag(serrors.WrapStr("sending reply", err)) - } - log.Info("Sent pong to", "client", clientUDPAddr) - return nil -} - -type client struct { - network *snet.SCIONNetwork - conn *snet.Conn - sdConn daemon.Connector - - errorPaths map[snet.PathFingerprint]struct{} -} - -func (c *client) run() int { - pair := fmt.Sprintf("%s -> %s", integration.Local.IA, remote.IA) - log.Info("Starting", "pair", pair) - defer log.Info("Finished", "pair", pair) - defer integration.Done(integration.Local.IA, remote.IA) - c.sdConn = integration.SDConn() - defer c.sdConn.Close() - c.network = &snet.SCIONNetwork{ - SCMPHandler: snet.DefaultSCMPHandler{ - RevocationHandler: daemon.RevHandler{Connector: c.sdConn}, - SCMPErrors: scmpErrorsCounter, - }, - PacketConnMetrics: scionPacketConnMetrics, - Topology: c.sdConn, - } - log.Info("Send", "local", - fmt.Sprintf("%v,[%v] -> %v,[%v]", - integration.Local.IA, integration.Local.Host, - remote.IA, remote.Host)) - c.errorPaths = make(map[snet.PathFingerprint]struct{}) - return integration.AttemptRepeatedly("End2End", c.attemptRequest) -} - -// attemptRequest sends one ping packet and expect a pong. -// Returns true (which means "stop") *if both worked*. -func (c *client) attemptRequest(n int) bool { - timeoutCtx, cancel := context.WithTimeout(context.Background(), timeout.Duration) - defer cancel() - span, ctx := tracing.CtxWith(timeoutCtx, "attempt") - span.SetTag("attempt", n) - span.SetTag("src", integration.Local.IA) - span.SetTag("dst", remote.IA) - defer span.Finish() - logger := log.FromCtx(ctx) - - path, err := c.getRemote(ctx, n) - if err != nil { - logger.Error("Could not get remote", "err", err) - return false - } - span, ctx = tracing.StartSpanFromCtx(ctx, "attempt.ping") - defer span.Finish() - withTag := func(err error) error { - tracing.Error(span, err) - return err - } - - // Convert path to Hummingbird path - if path != nil { - // This works: - // Directly query the scion daemon. - reservations, err := c.sdConn.GetReservations(ctx, integration.Local.IA, remote.IA, 1, true) - if err != nil { - logger.Error("getting reservations from daemon", "err", err) - return false - } - reservation := reservations[0] - - // // This works: - // // Build with no flyovers. - // reservation, err := hummingbird.NewReservation( - // hummingbird.WithScionPath(path, nil)) - // if err != nil { - // logger.Error("Error converting path to Hummingbird", "err", err) - // return false - // } - - // // This works: - // // Get flyovers and build path. - // flyovers, err := c.sdConn.ListFlyovers(ctx) - // if err != nil { - // logger.Error("listing flyovers", "err", err) - // return false - // } - // reservation, err := hummingbird.NewReservation( - // hummingbird.WithScionPath(path, hummingbird.FlyoversToMap(flyovers))) - // if err != nil { - // logger.Error("Error converting path to Hummingbird", "err", err) - // return false - // } - - decoded := reservation.DeriveDataPlanePath(113, time.Now()) - raw := snetpath.Hummingbird{ - Raw: make([]byte, decoded.Len()), - } - err = decoded.SerializeTo(raw.Raw) - if err != nil { - logger.Error("Error assembling hummingbird path", "err", err) - } - remote.Path = raw - } - - // Send ping - close, err := c.ping(ctx, n, path) - if err != nil { - logger.Error("Could not send packet", "err", withTag(err)) - return false - } - defer close() - // Receive pong - if err := c.pong(ctx); err != nil { - logger.Error("Error receiving pong", "err", withTag(err)) - if path != nil { - c.errorPaths[snet.Fingerprint(path)] = struct{}{} - } - return false - } - return true -} - -func (c *client) ping(ctx context.Context, n int, path snet.Path) (func(), error) { - rawPing, err := json.Marshal(Ping{ - Server: remote.IA, - Message: ping, - Trace: tracing.IDFromCtx(ctx), - }) - if err != nil { - return nil, serrors.WrapStr("packing ping", err) - } - log.FromCtx(ctx).Info("Dialing", "remote", remote) - c.conn, err = c.network.Dial(ctx, "udp", integration.Local.Host, &remote) - if err != nil { - return nil, serrors.WrapStr("dialing conn", err) - } - if err := c.conn.SetWriteDeadline(getDeadline(ctx)); err != nil { - return nil, serrors.WrapStr("setting write deadline", err) - } - log.Info("sending ping", "attempt", n, "remote", c.conn.RemoteAddr()) - if _, err := c.conn.Write(rawPing); err != nil { - return nil, err - } - closer := func() { - if err := c.conn.Close(); err != nil { - log.Error("Unable to close connection", "err", err) - } - } - return closer, nil -} - -func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) { - if remote.IA.Equal(integration.Local.IA) { - remote.Path = snetpath.Empty{} - return nil, nil - } - span, ctx := tracing.StartSpanFromCtx(ctx, "attempt.get_remote") - defer span.Finish() - withTag := func(err error) error { - tracing.Error(span, err) - return err - } - - paths, err := c.sdConn.Paths(ctx, remote.IA, integration.Local.IA, - daemon.PathReqFlags{Refresh: n != 0}) - if err != nil { - return nil, withTag(serrors.WrapStr("requesting paths", err)) - } - // If all paths had an error, let's try them again. - if len(paths) <= len(c.errorPaths) { - c.errorPaths = make(map[snet.PathFingerprint]struct{}) - } - // Select first path that didn't error before. - var path snet.Path - for _, p := range paths { - if _, ok := c.errorPaths[snet.Fingerprint(p)]; ok { - continue - } - path = p - break - } - if path == nil { - return nil, withTag(serrors.New("no path found", - "candidates", len(paths), - "errors", len(c.errorPaths), - )) - } - // Extract forwarding path from the SCION Daemon response. - // If the epic flag is set, try to use the EPIC path type header. - if epic { - scionPath, ok := path.Dataplane().(snetpath.SCION) - if !ok { - return nil, serrors.New("provided path must be of type scion") - } - epicPath, err := snetpath.NewEPICDataplanePath(scionPath, path.Metadata().EpicAuths) - if err != nil { - return nil, err - } - remote.Path = epicPath - } else { - remote.Path = path.Dataplane() - } - remote.NextHop = path.UnderlayNextHop() - return path, nil -} - -func (c *client) pong(ctx context.Context) error { - if err := c.conn.SetReadDeadline(getDeadline(ctx)); err != nil { - return serrors.WrapStr("setting read deadline", err) - } - rawPld := make([]byte, common.MaxMTU) - n, serverAddr, err := readFrom(c.conn, rawPld) - if err != nil { - return serrors.WrapStr("reading packet", err) - } - - var pld Pong - if err := json.Unmarshal(rawPld[:n], &pld); err != nil { - return serrors.WrapStr("unpacking pong", err, "data", string(rawPld)) - } - - expected := Pong{ - Client: integration.Local.IA, - Server: remote.IA, - Message: pong, - } - if pld.Client != expected.Client || pld.Server != expected.Server || pld.Message != pong { - return serrors.New("unexpected contents received", "data", pld, "expected", expected) - } - log.Info("Received pong", "server", serverAddr) - return nil -} - -func getDeadline(ctx context.Context) time.Time { - dl, ok := ctx.Deadline() - if !ok { - integration.LogFatal("No deadline in context") - } - return dl -} - -func readFrom(conn *snet.Conn, pld []byte) (int, net.Addr, error) { - n, remoteAddr, err := conn.ReadFrom(pld) - // Attach more context to error - var opErr *snet.OpError - if !(errors.As(err, &opErr) && opErr.RevInfo() != nil) { - return n, remoteAddr, err - } - return n, remoteAddr, serrors.WithCtx(err, - "isd_as", opErr.RevInfo().IA(), - "interface", opErr.RevInfo().IfID, - ) -} diff --git a/tools/end2end_hbird_integration/BUILD.bazel b/tools/end2end_hbird_integration/BUILD.bazel deleted file mode 100644 index c8152a2e3b..0000000000 --- a/tools/end2end_hbird_integration/BUILD.bazel +++ /dev/null @@ -1,31 +0,0 @@ -load("//tools/lint:go.bzl", "go_library") -load("//:scion.bzl", "scion_go_binary") - -go_library( - name = "go_default_library", - srcs = ["main.go"], - importpath = "github.com/scionproto/scion/tools/end2end_hbird_integration", - visibility = ["//visibility:private"], - deps = [ - "//pkg/addr:go_default_library", - "//pkg/daemon:go_default_library", - "//pkg/hummingbird:go_default_library", - "//pkg/log:go_default_library", - "//pkg/private/common:go_default_library", - "//pkg/private/serrors:go_default_library", - "//pkg/private/util:go_default_library", - "//pkg/slayers/path/hummingbird:go_default_library", - "//pkg/snet:go_default_library", - "//private/app/feature:go_default_library", - "//private/keyconf:go_default_library", - "//private/topology:go_default_library", - "//router/control:go_default_library", - "//tools/integration:go_default_library", - ], -) - -scion_go_binary( - name = "end2end_hbird_integration", - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) diff --git a/tools/end2end_hbird_integration/main.go b/tools/end2end_hbird_integration/main.go deleted file mode 100644 index bc618a26c0..0000000000 --- a/tools/end2end_hbird_integration/main.go +++ /dev/null @@ -1,616 +0,0 @@ -// Copyright 2018 ETH Zurich, 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. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "context" - "crypto/aes" - "flag" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "sync" - "time" - - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/daemon" - "github.com/scionproto/scion/pkg/hummingbird" - "github.com/scionproto/scion/pkg/log" - "github.com/scionproto/scion/pkg/private/common" - "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/private/util" - hbirddp "github.com/scionproto/scion/pkg/slayers/path/hummingbird" - "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/private/app/feature" - "github.com/scionproto/scion/private/keyconf" - "github.com/scionproto/scion/private/topology" - "github.com/scionproto/scion/router/control" - "github.com/scionproto/scion/tools/integration" -) - -var ( - subset string - attempts int - timeout = &util.DurWrap{Duration: 10 * time.Second} - parallelism int - name string - cmd string - features string - epic bool - fabrid bool -) - -func getCmd() (string, bool) { - return cmd, strings.Contains(cmd, "end2end_hbird") -} - -func main() { - os.Exit(realMain()) -} - -func realMain() int { - addFlags() - if err := integration.Init(); err != nil { - fmt.Fprintf(os.Stderr, "Failed to init: %s\n", err) - return 1 - } - defer log.HandlePanic() - defer log.Flush() - if len(features) != 0 { - if _, err := feature.ParseDefault(strings.Split(features, ",")); err != nil { - fmt.Fprintf(os.Stderr, "Error parsing features: %s\n", err) - return 1 - } - } - - clientArgs := []string{ - "-log.console", "debug", - "-attempts", strconv.Itoa(attempts), - "-timeout", timeout.String(), - "-local", integration.SrcAddrPattern + ":0", - "-remote", integration.DstAddrPattern + ":" + integration.ServerPortReplace, - fmt.Sprintf("-epic=%t", epic), - fmt.Sprintf("-fabrid=%t", fabrid), - } - serverArgs := []string{ - "-mode", "server", - "-local", integration.DstAddrPattern + ":0", - fmt.Sprintf("-fabrid=%t", fabrid), - } - if len(features) != 0 { - clientArgs = append(clientArgs, "--features", features) - serverArgs = append(serverArgs, "--features", features) - } - if !*integration.Docker { - clientArgs = append(clientArgs, "-sciond", integration.Daemon) - serverArgs = append(serverArgs, "-sciond", integration.Daemon) - } - var in integration.Integration - if fabrid { - in = integration.NewBinaryEndhostIntegration(name, cmd, clientArgs, serverArgs) - } else { - in = integration.NewBinaryIntegration(name, cmd, clientArgs, serverArgs) - } - var pairs []integration.IAPair - var err error - if fabrid { - pairs, err = getPairs(integration.SDAddr) - } else { - pairs, err = getPairs(integration.CSAddr) - } - if err != nil { - log.Error("Error selecting tests", "err", err) - return 1 - } - err = addMockFlyovers(time.Now(), pairs) - if err != nil { - log.Error("Error adding mock flyovers", "err", err) - return 1 - } - if err := runTests(in, pairs); err != nil { - log.Error("Error during tests", "err", err) - return 1 - } - - return 0 -} - -// addFlags adds the necessary flags. -func addFlags() { - flag.IntVar(&attempts, "attempts", 1, "Number of attempts per client before giving up.") - flag.StringVar(&cmd, "cmd", "./bin/end2end_hbird", - "The end2end binary to run (default: ./bin/end2end_hbird)") - flag.StringVar(&name, "name", "end2end_hbird_integration", - "The name of the test that is running (default: end2end_hbird_integration)") - flag.Var(timeout, "timeout", "The timeout for each attempt") - flag.StringVar(&subset, "subset", "all", "Subset of pairs to run (all|#[#] "+ - " where =[|core|noncore] dst=[|core|noncore] and "+ - " is [localAS|remoteAS|localISD|remoteISD])") - flag.IntVar(¶llelism, "parallelism", 1, "How many end2end tests run in parallel.") - flag.StringVar(&features, "features", "", - fmt.Sprintf("enable development features (%v)", feature.String(&feature.Default{}, "|"))) - flag.BoolVar(&epic, "epic", false, "Enable EPIC.") - flag.BoolVar(&fabrid, "fabrid", false, "Enable FABRID.") -} - -// runTests runs the end2end_hbird tests for all pairs. In case of an error the -// function is terminated immediately. -func runTests(in integration.Integration, pairs []integration.IAPair) error { - return integration.ExecuteTimed(in.Name(), func() error { - // Make sure that all executed commands can write to the RPC server - // after shutdown. - defer time.Sleep(time.Second) - - // Estimating the timeout we should have is hard. CI will abort after 10 - // minutes anyway. Thus this value. - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) - defer cancel() - - // First run all servers - type srvResult struct { - cleaner func() - err error - } - // Start servers in parallel. - srvResults := make(chan srvResult) - for _, dst := range integration.ExtractUniqueDsts(pairs) { - go func(dst *snet.UDPAddr) { - defer log.HandlePanic() - - srvCtx, cancel := context.WithCancel(ctx) - waiter, err := in.StartServer(srvCtx, dst) - if err != nil { - log.Error(fmt.Sprintf("Error in server: %s", dst.String()), "err", err) - } - cleaner := func() { - cancel() - if waiter != nil { - _ = waiter.Wait() - } - } - srvResults <- srvResult{cleaner: cleaner, err: err} - }(dst) - } - // Wait for all servers being started. - var errs serrors.List - for range integration.ExtractUniqueDsts(pairs) { - res := <-srvResults - // We need to register a cleanup for all servers. - // Do not short-cut exit here. - if res.err != nil { - errs = append(errs, res.err) - } - defer res.cleaner() - } - if err := errs.ToError(); err != nil { - return err - } - - // Start a done signal listener. This is how the end2end binary - // communicates with this integration test. This is solely used to print - // the progress of the test. - var ctrMtx sync.Mutex - var ctr int - doneDir, err := filepath.Abs(filepath.Join(integration.LogDir(), "socks")) - if err != nil { - return serrors.WrapStr("determining abs path", err) - } - if err := os.MkdirAll(doneDir, os.ModePerm); err != nil { - return serrors.WrapStr("creating socks directory", err) - } - // this is a bit of a hack, socket file names have a max length of 108 - // and inside bazel tests we easily have longer paths, therefore we - // create a temporary symlink to the directory where we put the socket - // file. - tmpDir, err := os.MkdirTemp("", "e2e_hbird_integration") - if err != nil { - return serrors.WrapStr("creating temp dir", err) - } - if err := os.Remove(tmpDir); err != nil { - return serrors.WrapStr("deleting temp dir", err) - } - if err := os.Symlink(doneDir, tmpDir); err != nil { - return serrors.WrapStr("symlinking socks dir", err) - } - doneDir = tmpDir - defer os.Remove(doneDir) - socket, clean, err := integration.ListenDone(doneDir, func(src, dst addr.IA) { - ctrMtx.Lock() - defer ctrMtx.Unlock() - ctr++ - testInfo := fmt.Sprintf("%v -> %v (%v/%v)", src, dst, ctr, len(pairs)) - log.Info(fmt.Sprintf("Test %v: %s", in.Name(), testInfo)) - }) - if err != nil { - return serrors.WrapStr("creating done listener", err) - } - defer clean() - - if *integration.Docker { - socket = strings.Replace(socket, doneDir, "/share/logs/socks", -1) - } - - // CI collapses if parallelism is too high. - semaphore := make(chan struct{}, parallelism) - - // Docker exec comes with a 1 second overhead. We group all the pairs by - // the clients. And run all pairs for a given client in one execution. - // Thus, reducing the overhead dramatically. - groups := integration.GroupBySource(pairs) - clientResults := make(chan error, len(groups)) - for src, dsts := range groups { - go func(src *snet.UDPAddr, dsts []*snet.UDPAddr) { - defer log.HandlePanic() - - semaphore <- struct{}{} - defer func() { <-semaphore }() - // Aggregate all the commands that need to be run. - cmds := make([]integration.Cmd, 0, len(dsts)) - for _, dst := range dsts { - cmd, err := clientTemplate(socket).Template(src, dst) - if err != nil { - clientResults <- err - return - } - cmds = append(cmds, cmd) - } - var tester string - if *integration.Docker { - tester = integration.TesterID(src) - if fabrid { - tester = integration.EndhostID(src) - } - } - logFile := fmt.Sprintf("%s/client_%s.log", - logDir(), - addr.FormatIA(src.IA, addr.WithFileSeparator()), - ) - err := integration.Run(ctx, integration.RunConfig{ - Commands: cmds, - LogFile: logFile, - Tester: tester, - }) - if err != nil { - err = serrors.WithCtx(err, "file", relFile(logFile)) - } - clientResults <- err - }(src, dsts) - } - - // We started everything that could be started. So the best window for perf mertics - // opens somewhere around now. - metricsBegin := time.Now().Unix() - errs = nil - end_reported := false - for range groups { - err := <-clientResults - if !end_reported { - end_reported = true - // The first client has finished. So the performance metrics have begun losing - // quality. - metricsEnd := time.Now().Unix() - // The test harness looks for this output. - fmt.Printf("metricsBegin: %d metricsEnd: %d\n", metricsBegin, metricsEnd) - } - if err != nil { - errs = append(errs, err) - } - } - return errs.ToError() - }) -} - -func clientTemplate(progressSock string) integration.Cmd { - bin, progress := getCmd() - cmd := integration.Cmd{ - Binary: bin, - Args: []string{ - "-log.console", "debug", - "-attempts", strconv.Itoa(attempts), - "-timeout", timeout.String(), - "-local", integration.SrcAddrPattern + ":0", - "-remote", integration.DstAddrPattern + ":" + integration.ServerPortReplace, - fmt.Sprintf("-epic=%t", epic), - fmt.Sprintf("-fabrid=%t", fabrid), - }, - } - if len(features) != 0 { - cmd.Args = append(cmd.Args, "--features", features) - } - if progress { - cmd.Args = append(cmd.Args, "-progress", progressSock) - } - if !*integration.Docker { - cmd.Args = append(cmd.Args, "-sciond", integration.Daemon) - } - return cmd -} - -// getPairs returns the pairs to test according to the specified subset. -// The subset can be based on role, as follows: -// role1#role2[#local] selects all src:dst pairs matching such that src has role1 -// and dst has role2 (and NOT the other way around). If local[ISD|AS]/remote[ISD|AS] is specified -// then src and dst must/must-not be in the same ISD/AS -// -// This implies that IFF role1 == role2, then h1:h2 pairs are mirrored with h2:h1 and, unless -// remote[ISD/AS] is specified, h2:h2 and h1:h1. Not all combinations yield something useful... -// caveat emptor. -func getPairs(hostAddr integration.HostAddr) ([]integration.IAPair, error) { - pairs := integration.IAPairs(hostAddr) - if subset == "all" { - return pairs, nil - } - parts := strings.Split(subset, "#") - switch len(parts) { - case 2: - return filter(parts[0], parts[1], "", pairs, integration.LoadedASList), nil - case 3: - return filter(parts[0], parts[1], parts[2], pairs, integration.LoadedASList), nil - default: - return nil, serrors.New("Invalid subset", "subset", subset) - } -} - -// filter returns the list of ASes that are part of the desired subset. -func filter( - src, dst, local string, - pairs []integration.IAPair, - ases *integration.ASList, -) []integration.IAPair { - - var res []integration.IAPair - s, err1 := addr.ParseIA(src) - d, err2 := addr.ParseIA(dst) - if err1 == nil && err2 == nil { - for _, pair := range pairs { - if pair.Src.IA.Equal(s) && pair.Dst.IA.Equal(d) { - res = append(res, pair) - return res - } - } - } - - // Selection based on role. - for _, pair := range pairs { - filter := !contains(ases, src != "noncore", pair.Src.IA) - filter = filter || !contains(ases, dst != "noncore", pair.Dst.IA) - switch local { - case "localISD": - filter = filter || pair.Src.IA.ISD() != pair.Dst.IA.ISD() - case "remoteISD": - filter = filter || pair.Src.IA.ISD() == pair.Dst.IA.ISD() - case "localAS": - filter = filter || pair.Src.IA != pair.Dst.IA - case "remoteAS": - filter = filter || pair.Src.IA == pair.Dst.IA - default: - } - if !filter { - res = append(res, pair) - } - } - return res -} - -func contains(ases *integration.ASList, core bool, ia addr.IA) bool { - l := ases.Core - if !core { - l = ases.NonCore - } - for _, as := range l { - if ia.Equal(as) { - return true - } - } - return false -} - -func logDir() string { - return filepath.Join(integration.LogDir(), name) -} - -func relFile(file string) string { - rel, err := filepath.Rel(filepath.Dir(integration.LogDir()), file) - if err != nil { - return file - } - return rel -} - -// addMockFlyovers creates and stores the necessary flyovers for the given pairs. -// It uses the scion daemon to add the flyovers to the DB. -func addMockFlyovers(now time.Time, pairs []integration.IAPair) error { - perAS, err := getTopoPerAS(pairs) - if err != nil { - return nil - } - - flyovers, err := createMockFlyovers(perAS, now) - if err != nil { - return err - } - - // Insert each flyover into the DB of each AS. Allow timeout.Duration per AS to do so. - wg := sync.WaitGroup{} - wg.Add(len(perAS)) - errCh := make(chan error) - for ia, c := range perAS { - ia, c := ia, c - go func() { - defer log.HandlePanic() - defer wg.Done() - ctx, cancelF := context.WithTimeout(context.Background(), timeout.Duration) - defer cancelF() - errCh <- insertFlyoversInAS(ctx, ia, c, flyovers) - }() - } - // Collect any possible error and bail on the first non nil one. - go func() { - defer log.HandlePanic() - for errPerAS := range errCh { - if err != nil { - err = errPerAS - } - } - }() - wg.Wait() - close(errCh) - - if err != nil { - return serrors.WrapStr("at least one AS returned an error while inserting flyovers", err) - } - return nil -} - -type topoPerAS struct { - ASDirName string - Interfaces []common.IFIDType -} - -func getTopoPerAS(pairs []integration.IAPair) (map[addr.IA]topoPerAS, error) { - m := make(map[addr.IA]topoPerAS) - for _, pair := range pairs { - ia := pair.Src.IA - if _, ok := m[ia]; ok { - continue - } - - // Load their topology. - path := integration.GenFile( - filepath.Join( - addr.FormatAS(ia.AS(), addr.WithDefaultPrefix(), addr.WithFileSeparator()), - "topology.json", - ), - ) - topo, err := topology.FromJSONFile(path) - if err != nil { - return nil, serrors.WrapStr("loading topology", err, "ia", ia) - } - - // Set the values for this AS. - m[ia] = topoPerAS{ - ASDirName: addr.FormatAS(ia.AS(), - addr.WithDefaultPrefix(), addr.WithFileSeparator()), - Interfaces: topo.InterfaceIDs(), - } - } - - return m, nil -} - -func createMockFlyovers( - perAS map[addr.IA]topoPerAS, - now time.Time, -) ([]*hummingbird.Flyover, error) { - - // Per IA, insert a flyover with BW units of bandwidth for each interface pair. - // Note that BW has to be enough for one AS to send a ping to another. - const BW = uint16(10) - flyovers := make([]*hummingbird.Flyover, 0) - for ia, c := range perAS { - var resIDPerIA uint32 // reservation ID unique per IA - // Load master key for this ia. It is used to create the mock flyover, by deriving here - // the correct Ak that the border routers will check. - masterFile := integration.GenFile(filepath.Join(c.ASDirName, "keys")) - master0, err := keyconf.LoadMaster(masterFile) - if err != nil { - return nil, serrors.WrapStr("could not load master secret for IA", err, "ia", ia) - } - - // Add the "itself" interface ID to the slice. - ifaces := append(c.Interfaces, 0) - // Create a flyover for each possible ingress->egress s.t. ingress <> egress - inToEgressesMap := ifIDSequenceToMap(ifaces) - for in, egressInterfaces := range inToEgressesMap { - for _, eg := range egressInterfaces { - f := hummingbird.Flyover{ - BaseHop: hummingbird.BaseHop{ - IA: ia, - Ingress: uint16(in), - Egress: uint16(eg), - }, - Bw: BW, - StartTime: util.TimeToSecs(now), - // Duration: 60, // 1 Minute - // deleteme: change to 1 minute again - Duration: 300, // 1 Hour - ResID: resIDPerIA, // unique per ia - } - - key0 := control.DeriveHbirdSecretValue(master0.Key0) - prf, _ := aes.NewCipher(key0) - buffer := make([]byte, 16) - ak := hbirddp.DeriveAuthKey(prf, f.ResID, f.Bw, f.Ingress, f.Egress, - f.StartTime, f.Duration, buffer) - copy(f.Ak[:], ak[0:16]) - - // Increment the reservation ID per AS to make it unique (per AS). - resIDPerIA++ - - flyovers = append(flyovers, &f) - } - } - } - return flyovers, nil -} - -func insertFlyoversInAS( - ctx context.Context, - ia addr.IA, - config topoPerAS, - flyovers []*hummingbird.Flyover, -) error { - - daemonAddr, err := integration.GetSCIONDAddress( - integration.GenFile(integration.DaemonAddressesFile), ia) - if err != nil { - return serrors.WrapStr("getting the sciond address", err, "ia", ia) - } - conn, err := daemon.NewService(daemonAddr).Connect(ctx) - if err != nil { - return serrors.WrapStr("opening daemon connection", err, "ia", ia) - } - - err = conn.StoreFlyovers(ctx, flyovers) - if err != nil { - return serrors.WrapStr("storing flyovers using daemon", err, "ia", ia) - } - - err = conn.Close() - if err != nil { - return serrors.WrapStr("closing daemon connection", err, "ia", ia) - } - - return nil -} - -// ifIDSequenceToMap takes a slice of interfaces and returns a map where each ingress has -// a list to egress interfaces from the slice. -func ifIDSequenceToMap(ifSeq []common.IFIDType) map[common.IFIDType][]common.IFIDType { - - m := make(map[common.IFIDType][]common.IFIDType, len(ifSeq)) - for _, src := range ifSeq { - for _, dst := range ifSeq { - if src == dst { - continue - } - m[src] = append(m[src], dst) - } - } - return m -} diff --git a/tools/end2end_integration/BUILD.bazel b/tools/end2end_integration/BUILD.bazel index 59a314acb2..d7be620cdd 100644 --- a/tools/end2end_integration/BUILD.bazel +++ b/tools/end2end_integration/BUILD.bazel @@ -3,16 +3,26 @@ load("//:scion.bzl", "scion_go_binary") go_library( name = "go_default_library", - srcs = ["main.go"], + srcs = [ + "hummingbird.go", + "main.go", + ], importpath = "github.com/scionproto/scion/tools/end2end_integration", visibility = ["//visibility:private"], deps = [ "//pkg/addr:go_default_library", + "//pkg/daemon:go_default_library", + "//pkg/hummingbird:go_default_library", "//pkg/log:go_default_library", + "//pkg/private/common:go_default_library", "//pkg/private/serrors:go_default_library", "//pkg/private/util:go_default_library", + "//pkg/slayers/path/hummingbird:go_default_library", "//pkg/snet:go_default_library", "//private/app/feature:go_default_library", + "//private/keyconf:go_default_library", + "//private/topology:go_default_library", + "//router/control:go_default_library", "//tools/integration:go_default_library", ], ) diff --git a/tools/end2end_integration/hummingbird.go b/tools/end2end_integration/hummingbird.go new file mode 100644 index 0000000000..9a992122b4 --- /dev/null +++ b/tools/end2end_integration/hummingbird.go @@ -0,0 +1,135 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "crypto/aes" + "path/filepath" + "time" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/daemon" + hbird "github.com/scionproto/scion/pkg/hummingbird" + "github.com/scionproto/scion/pkg/private/common" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/private/util" + hbirddp "github.com/scionproto/scion/pkg/slayers/path/hummingbird" + "github.com/scionproto/scion/private/keyconf" + "github.com/scionproto/scion/router/control" + "github.com/scionproto/scion/tools/integration" +) + +func createMockFlyovers( + perAS map[addr.IA]topoPerAS, + now time.Time, +) ([]*hbird.Flyover, error) { + + // Per IA, insert a flyover with BW units of bandwidth for each interface pair. + // Note that BW has to be enough for one AS to send a ping to another. + const BW = uint16(10) + flyovers := make([]*hbird.Flyover, 0) + for ia, c := range perAS { + var resIDPerIA uint32 // reservation ID unique per IA + // Load master key for this ia. It is used to create the mock flyover, by deriving here + // the correct Ak that the border routers will check. + masterFile := integration.GenFile(filepath.Join(c.ASDirName, "keys")) + master0, err := keyconf.LoadMaster(masterFile) + if err != nil { + return nil, serrors.WrapStr("could not load master secret for IA", err, "ia", ia) + } + + // Add the "itself" interface ID to the slice. + ifaces := append(c.Interfaces, 0) + // Create a flyover for each possible ingress->egress s.t. ingress <> egress + inToEgressesMap := ifIDSequenceToMap(ifaces) + for in, egressInterfaces := range inToEgressesMap { + for _, eg := range egressInterfaces { + f := hbird.Flyover{ + BaseHop: hbird.BaseHop{ + IA: ia, + Ingress: uint16(in), + Egress: uint16(eg), + }, + Bw: BW, + StartTime: util.TimeToSecs(now), + // Duration: 60, // 1 Minute + // deleteme: change to 1 minute again + Duration: 300, // 1 Hour + ResID: resIDPerIA, // unique per ia + } + + key0 := control.DeriveHbirdSecretValue(master0.Key0) + prf, _ := aes.NewCipher(key0) + buffer := make([]byte, 16) + ak := hbirddp.DeriveAuthKey(prf, f.ResID, f.Bw, f.Ingress, f.Egress, + f.StartTime, f.Duration, buffer) + copy(f.Ak[:], ak[0:16]) + + // Increment the reservation ID per AS to make it unique (per AS). + resIDPerIA++ + + flyovers = append(flyovers, &f) + } + } + } + return flyovers, nil +} + +func insertFlyoversInAS( + ctx context.Context, + ia addr.IA, + config topoPerAS, + flyovers []*hbird.Flyover, +) error { + + daemonAddr, err := integration.GetSCIONDAddress( + integration.GenFile(integration.DaemonAddressesFile), ia) + if err != nil { + return serrors.WrapStr("getting the sciond address", err, "ia", ia) + } + conn, err := daemon.NewService(daemonAddr).Connect(ctx) + if err != nil { + return serrors.WrapStr("opening daemon connection", err, "ia", ia) + } + + err = conn.StoreFlyovers(ctx, flyovers) + if err != nil { + return serrors.WrapStr("storing flyovers using daemon", err, "ia", ia) + } + + err = conn.Close() + if err != nil { + return serrors.WrapStr("closing daemon connection", err, "ia", ia) + } + + return nil +} + +// ifIDSequenceToMap takes a slice of interfaces and returns a map where each ingress has +// a list to egress interfaces from the slice. +func ifIDSequenceToMap(ifSeq []common.IFIDType) map[common.IFIDType][]common.IFIDType { + + m := make(map[common.IFIDType][]common.IFIDType, len(ifSeq)) + for _, src := range ifSeq { + for _, dst := range ifSeq { + if src == dst { + continue + } + m[src] = append(m[src], dst) + } + } + return m +} diff --git a/tools/end2end_integration/main.go b/tools/end2end_integration/main.go index 61540cfe20..df6cf9a0f4 100644 --- a/tools/end2end_integration/main.go +++ b/tools/end2end_integration/main.go @@ -28,10 +28,12 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/private/util" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/private/app/feature" + "github.com/scionproto/scion/private/topology" "github.com/scionproto/scion/tools/integration" ) @@ -45,6 +47,7 @@ var ( features string epic bool fabrid bool + hummingbird bool ) func getCmd() (string, bool) { @@ -78,11 +81,13 @@ func realMain() int { "-remote", integration.DstAddrPattern + ":" + integration.ServerPortReplace, fmt.Sprintf("-epic=%t", epic), fmt.Sprintf("-fabrid=%t", fabrid), + fmt.Sprintf("-hummingbird=%t", hummingbird), } serverArgs := []string{ "-mode", "server", "-local", integration.DstAddrPattern + ":0", fmt.Sprintf("-fabrid=%t", fabrid), + fmt.Sprintf("-hummingbird=%t", hummingbird), } if len(features) != 0 { clientArgs = append(clientArgs, "--features", features) @@ -109,6 +114,12 @@ func realMain() int { log.Error("Error selecting tests", "err", err) return 1 } + if hummingbird { + if err := addMockFlyovers(time.Now(), pairs); err != nil { + log.Error("Error adding mock flyovers", "err", err) + return 1 + } + } if err := runTests(in, pairs); err != nil { log.Error("Error during tests", "err", err) return 1 @@ -133,6 +144,7 @@ func addFlags() { fmt.Sprintf("enable development features (%v)", feature.String(&feature.Default{}, "|"))) flag.BoolVar(&epic, "epic", false, "Enable EPIC.") flag.BoolVar(&fabrid, "fabrid", false, "Enable FABRID.") + flag.BoolVar(&hummingbird, "hummingbird", false, "Enable HUMMINGBIRD") } // runTests runs the end2end tests for all pairs. In case of an error the @@ -314,6 +326,7 @@ func clientTemplate(progressSock string) integration.Cmd { "-remote", integration.DstAddrPattern + ":" + integration.ServerPortReplace, fmt.Sprintf("-epic=%t", epic), fmt.Sprintf("-fabrid=%t", fabrid), + fmt.Sprintf("-hummingbird=%t", hummingbird), }, } if len(features) != 0 { @@ -418,3 +431,84 @@ func relFile(file string) string { } return rel } + +// addMockFlyovers creates and stores the necessary flyovers for the given pairs. +// It uses the scion daemon to add the flyovers to the DB. +func addMockFlyovers(now time.Time, pairs []integration.IAPair) error { + perAS, err := getTopoPerAS(pairs) + if err != nil { + return nil + } + + flyovers, err := createMockFlyovers(perAS, now) + if err != nil { + return err + } + + // Insert each flyover into the DB of each AS. Allow timeout.Duration per AS to do so. + wg := sync.WaitGroup{} + wg.Add(len(perAS)) + errCh := make(chan error) + for ia, c := range perAS { + ia, c := ia, c + go func() { + defer log.HandlePanic() + defer wg.Done() + ctx, cancelF := context.WithTimeout(context.Background(), timeout.Duration) + defer cancelF() + errCh <- insertFlyoversInAS(ctx, ia, c, flyovers) + }() + } + // Collect any possible error and bail on the first non nil one. + go func() { + defer log.HandlePanic() + for errPerAS := range errCh { + if err != nil { + err = errPerAS + } + } + }() + wg.Wait() + close(errCh) + + if err != nil { + return serrors.WrapStr("at least one AS returned an error while inserting flyovers", err) + } + return nil +} + +type topoPerAS struct { + ASDirName string + Interfaces []common.IFIDType +} + +func getTopoPerAS(pairs []integration.IAPair) (map[addr.IA]topoPerAS, error) { + m := make(map[addr.IA]topoPerAS) + for _, pair := range pairs { + ia := pair.Src.IA + if _, ok := m[ia]; ok { + continue + } + + // Load their topology. + path := integration.GenFile( + filepath.Join( + addr.FormatAS(ia.AS(), addr.WithDefaultPrefix(), addr.WithFileSeparator()), + "topology.json", + ), + ) + topo, err := topology.FromJSONFile(path) + if err != nil { + return nil, serrors.WrapStr("loading topology", err, "ia", ia) + } + + // Set the values for this AS. + m[ia] = topoPerAS{ + ASDirName: addr.FormatAS(ia.AS(), + addr.WithDefaultPrefix(), addr.WithFileSeparator()), + Interfaces: topo.InterfaceIDs(), + } + } + + return m, nil +}