From be0252c205755139771ec8dd1f31628557c06eb5 Mon Sep 17 00:00:00 2001 From: Marc Odermatt Date: Wed, 7 Aug 2024 12:33:28 +0200 Subject: [PATCH 1/6] Refactor fabrid accumulator --- daemon/internal/servers/BUILD.bazel | 2 +- daemon/internal/servers/grpc.go | 12 ++++++------ pkg/experimental/fabrid/graphutils/BUILD.bazel | 14 ++++++++++++++ .../experimental/fabrid/graphutils/maps.go | 4 ++-- private/path/combinator/BUILD.bazel | 4 +--- private/path/combinator/graph.go | 7 ++++--- 6 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 pkg/experimental/fabrid/graphutils/BUILD.bazel rename private/path/combinator/fabrid_accumulator.go => pkg/experimental/fabrid/graphutils/maps.go (97%) diff --git a/daemon/internal/servers/BUILD.bazel b/daemon/internal/servers/BUILD.bazel index 01e28ca333..290053034c 100644 --- a/daemon/internal/servers/BUILD.bazel +++ b/daemon/internal/servers/BUILD.bazel @@ -14,6 +14,7 @@ go_library( "//pkg/addr:go_default_library", "//pkg/drkey:go_default_library", "//pkg/experimental/fabrid:go_default_library", + "//pkg/experimental/fabrid/graphutils:go_default_library", "//pkg/grpc:go_default_library", "//pkg/log:go_default_library", "//pkg/metrics:go_default_library", @@ -28,7 +29,6 @@ go_library( "//pkg/segment/extensions/fabrid:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", - "//private/path/combinator:go_default_library", "//private/revcache:go_default_library", "//private/topology:go_default_library", "//private/trust:go_default_library", diff --git a/daemon/internal/servers/grpc.go b/daemon/internal/servers/grpc.go index 5592ee5606..4928db80e1 100644 --- a/daemon/internal/servers/grpc.go +++ b/daemon/internal/servers/grpc.go @@ -31,6 +31,7 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/drkey" "github.com/scionproto/scion/pkg/experimental/fabrid" + fabrid_utils "github.com/scionproto/scion/pkg/experimental/fabrid/graphutils" libgrpc "github.com/scionproto/scion/pkg/grpc" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" @@ -45,7 +46,6 @@ import ( fabrid_ext "github.com/scionproto/scion/pkg/segment/extensions/fabrid" "github.com/scionproto/scion/pkg/snet" snetpath "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/private/path/combinator" "github.com/scionproto/scion/private/revcache" "github.com/scionproto/scion/private/topology" "github.com/scionproto/scion/private/trust" @@ -138,13 +138,13 @@ func updateFabridInfo(ctx context.Context, dialer libgrpc.Dialer, detachedHops [ } defer conn.Close() client := experimental.NewFABRIDIntraServiceClient(conn) - fabridMaps := make(map[addr.IA]combinator.FabridMapEntry) + fabridMaps := make(map[addr.IA]fabrid_utils.FabridMapEntry) for _, detachedHop := range detachedHops { if _, ok := fabridMaps[detachedHop.IA]; !ok { fabridMaps[detachedHop.IA] = fetchMaps(ctx, detachedHop.IA, client, detachedHop.Meta.FabridInfo[detachedHop.fiIdx].Digest) } - detachedHop.Meta.FabridInfo[detachedHop.fiIdx] = *combinator. + detachedHop.Meta.FabridInfo[detachedHop.fiIdx] = *fabrid_utils. GetFabridInfoForIntfs(detachedHop.IA, detachedHop.Ingress, detachedHop.Egress, fabridMaps, true) } @@ -194,7 +194,7 @@ func findDetachedHops(paths []snet.Path) []tempHopInfo { // It uses the provided client to communicate with the Control Service and returns a FabridMapEntry // to be used directly in the combinator. func fetchMaps(ctx context.Context, ia addr.IA, client experimental.FABRIDIntraServiceClient, - digest []byte) combinator.FabridMapEntry { + digest []byte) fabrid_utils.FabridMapEntry { maps, err := client.RemoteMaps(ctx, &experimental.RemoteMapsRequest{ Digest: digest, IsdAs: uint64(ia), @@ -202,14 +202,14 @@ func fetchMaps(ctx context.Context, ia addr.IA, client experimental.FABRIDIntraS if err != nil || maps.Maps == nil { log.FromCtx(ctx).Debug("Retrieving remote map from CS failed", "err", err, "ia", ia) - return combinator.FabridMapEntry{} + return fabrid_utils.FabridMapEntry{} } detached := fabrid_ext.Detached{ SupportedIndicesMap: fabrid_ext.SupportedIndicesMapFromPB(maps.Maps.SupportedIndicesMap), IndexIdentiferMap: fabrid_ext.IndexIdentifierMapFromPB(maps.Maps.IndexIdentifierMap), } - return combinator.FabridMapEntry{ + return fabrid_utils.FabridMapEntry{ Map: &detached, Ts: time.Now(), Digest: []byte{}, // leave empty, it can be calculated using detached.Hash() diff --git a/pkg/experimental/fabrid/graphutils/BUILD.bazel b/pkg/experimental/fabrid/graphutils/BUILD.bazel new file mode 100644 index 0000000000..dd0add83ca --- /dev/null +++ b/pkg/experimental/fabrid/graphutils/BUILD.bazel @@ -0,0 +1,14 @@ +load("//tools/lint:go.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["maps.go"], + importpath = "github.com/scionproto/scion/pkg/experimental/fabrid/graphutils", + visibility = ["//visibility:public"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/experimental/fabrid:go_default_library", + "//pkg/segment/extensions/fabrid:go_default_library", + "//pkg/snet:go_default_library", + ], +) diff --git a/private/path/combinator/fabrid_accumulator.go b/pkg/experimental/fabrid/graphutils/maps.go similarity index 97% rename from private/path/combinator/fabrid_accumulator.go rename to pkg/experimental/fabrid/graphutils/maps.go index 1040b3b65d..131f251e9f 100644 --- a/private/path/combinator/fabrid_accumulator.go +++ b/pkg/experimental/fabrid/graphutils/maps.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package combinator +package graphutils import ( "time" @@ -34,7 +34,7 @@ type FabridMapEntry struct { Digest []byte } -func collectFabridPolicies(ifaces []snet.PathInterface, +func CollectFabridPolicies(ifaces []snet.PathInterface, maps map[addr.IA]FabridMapEntry) []snet.FabridInfo { switch { diff --git a/private/path/combinator/BUILD.bazel b/private/path/combinator/BUILD.bazel index de9b76cb07..f03deeb2ab 100644 --- a/private/path/combinator/BUILD.bazel +++ b/private/path/combinator/BUILD.bazel @@ -4,7 +4,6 @@ go_library( name = "go_default_library", srcs = [ "combinator.go", - "fabrid_accumulator.go", "graph.go", "staticinfo_accumulator.go", ], @@ -12,12 +11,11 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/addr:go_default_library", - "//pkg/experimental/fabrid:go_default_library", + "//pkg/experimental/fabrid/graphutils:go_default_library", "//pkg/private/common:go_default_library", "//pkg/private/ctrl/path_mgmt/proto:go_default_library", "//pkg/private/util:go_default_library", "//pkg/segment:go_default_library", - "//pkg/segment/extensions/fabrid:go_default_library", "//pkg/segment/extensions/staticinfo:go_default_library", "//pkg/slayers/path:go_default_library", "//pkg/slayers/path/scion:go_default_library", diff --git a/private/path/combinator/graph.go b/private/path/combinator/graph.go index 253d8e5576..cbedda1e52 100644 --- a/private/path/combinator/graph.go +++ b/private/path/combinator/graph.go @@ -23,6 +23,7 @@ import ( "time" "github.com/scionproto/scion/pkg/addr" + fabrid_utils "github.com/scionproto/scion/pkg/experimental/fabrid/graphutils" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/ctrl/path_mgmt/proto" "github.com/scionproto/scion/pkg/private/util" @@ -314,7 +315,7 @@ type pathSolution struct { func (solution *pathSolution) Path() Path { mtu := ^uint16(0) var segments segmentList - fabridMaps := make(map[addr.IA]FabridMapEntry) + fabridMaps := make(map[addr.IA]fabrid_utils.FabridMapEntry) var epicPathAuths [][]byte for _, solEdge := range solution.edges { var hops []path.HopField @@ -384,7 +385,7 @@ func (solution *pathSolution) Path() Path { fabridMap, exists := fabridMaps[asEntry.Local] if (!exists || fabridMap.Ts.Before(solEdge.segment.Info.Timestamp)) && asEntry. Extensions.Digests != nil { - fabridMaps[asEntry.Local] = FabridMapEntry{ + fabridMaps[asEntry.Local] = fabrid_utils.FabridMapEntry{ Map: asEntry.UnsignedExtensions.FabridDetached, Digest: asEntry.Extensions.Digests.Fabrid.Digest, Ts: solEdge.segment.Info.Timestamp, @@ -425,7 +426,7 @@ func (solution *pathSolution) Path() Path { interfaces := segments.Interfaces() asEntries := segments.ASEntries() staticInfo := collectMetadata(interfaces, asEntries) - fabridInfo := collectFabridPolicies(interfaces, fabridMaps) + fabridInfo := fabrid_utils.CollectFabridPolicies(interfaces, fabridMaps) path := Path{ SCIONPath: segments.ScionPath(), Metadata: snet.PathMetadata{ From 47135bf94108a85bdf3c45eba3dfc7ca8bf312a7 Mon Sep 17 00:00:00 2001 From: Marc Odermatt Date: Thu, 29 Aug 2024 11:08:10 +0200 Subject: [PATCH 2/6] Wip control options 1 --- pkg/experimental/fabrid/common/BUILD.bazel | 19 + pkg/experimental/fabrid/common/fabrid.go | 454 ++++++++++++++++++ .../fabrid/crypto/fabrid_crypto.go | 77 ++- .../fabrid/crypto/fabrid_crypto_test.go | 6 +- pkg/slayers/extension/BUILD.bazel | 2 + pkg/slayers/extension/fabrid_control.go | 237 +++++++++ pkg/slayers/extension/fabrid_control_test.go | 320 ++++++++++++ pkg/slayers/extn.go | 1 + tools/end2end/main.go | 2 +- 9 files changed, 1108 insertions(+), 10 deletions(-) create mode 100644 pkg/experimental/fabrid/common/BUILD.bazel create mode 100644 pkg/experimental/fabrid/common/fabrid.go create mode 100644 pkg/slayers/extension/fabrid_control.go create mode 100644 pkg/slayers/extension/fabrid_control_test.go diff --git a/pkg/experimental/fabrid/common/BUILD.bazel b/pkg/experimental/fabrid/common/BUILD.bazel new file mode 100644 index 0000000000..124c563fe6 --- /dev/null +++ b/pkg/experimental/fabrid/common/BUILD.bazel @@ -0,0 +1,19 @@ +load("//tools/lint:go.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fabrid.go"], + importpath = "github.com/scionproto/scion/pkg/experimental/fabrid", + visibility = ["//visibility:public"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/daemon/helper:go_default_library", + "//pkg/drkey:go_default_library", + "//pkg/drkey/specific:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/proto/control_plane:go_default_library", + "//pkg/slayers:go_default_library", + "//pkg/slayers/extension:go_default_library", + ], +) diff --git a/pkg/experimental/fabrid/common/fabrid.go b/pkg/experimental/fabrid/common/fabrid.go new file mode 100644 index 0000000000..cc8a68a779 --- /dev/null +++ b/pkg/experimental/fabrid/common/fabrid.go @@ -0,0 +1,454 @@ +// Copyright 2021 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 common + +import ( + "context" + crand "crypto/rand" + "github.com/scionproto/scion/pkg/addr" + drhelper "github.com/scionproto/scion/pkg/daemon/helper" + "github.com/scionproto/scion/pkg/drkey" + "github.com/scionproto/scion/pkg/drkey/specific" + "github.com/scionproto/scion/pkg/experimental/fabrid" + "github.com/scionproto/scion/pkg/experimental/fabrid/crypto" + "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/serrors" + drpb "github.com/scionproto/scion/pkg/proto/control_plane" + "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/extension" + "github.com/scionproto/scion/pkg/snet" + "google.golang.org/grpc" + "time" +) + +// Testing options for failing validation +const CLIENT_FLAKINESS = 0 +const SERVER_FLAKINESS = 0 + +type SimpleFabridConfig struct { + DestinationIA addr.IA + DestinationAddr string + LocalIA addr.IA + LocalAddr string + ValidationRatio uint8 + Policy fabrid.Policy + ValidationHandler func(*PathState, *extension.FabridControlOption, bool) error +} + +type Statistics struct { + TotalPackets uint32 + InvalidPackets uint32 +} + +type ClientConnection struct { + Source snet.UDPAddr + ValidationRatio uint8 + Stats Statistics + fabridControlBuffer []byte + tmpBuffer []byte + pathKey drkey.Key +} + +type Server struct { + Local snet.UDPAddr + grpcConn *grpc.ClientConn + Connections map[string]*ClientConnection + ASKeyCache map[addr.IA]drkey.HostASKey + MaxValidationRatio uint8 + ValidationHandler func(*ClientConnection, *extension.IdentifierOption, bool) error +} + +type Client struct { + Destination snet.UDPAddr + validationRatio uint8 + fabridControlBuffer []byte + PathKey drkey.HostHostKey + Paths map[snet.PathFingerprint]*PathState + Config SimpleFabridConfig + drkeyPathFn func(context.Context, drkey.HostHostMeta) (drkey.HostHostKey, error) + GrpcConn *grpc.ClientConn +} + +type validationIdentifier struct { + timestamp uint32 + packetId uint32 +} + +type PathState struct { + ValidationRatio uint8 + UpdateValRatio bool + RequestStatistics bool + Stats Statistics + expectedValResponses map[validationIdentifier]uint32 +} + +func NewFabridClient(remote snet.UDPAddr, config SimpleFabridConfig, + grpcConn *grpc.ClientConn) *Client { + state := &Client{ + Destination: remote, + validationRatio: config.ValidationRatio, + fabridControlBuffer: make([]byte, 20*3), + Paths: make(map[snet.PathFingerprint]*PathState), + Config: config, + GrpcConn: grpcConn, + } + return state +} + +func (c *Client) NewFabridPathState(fingerprint snet.PathFingerprint) *PathState { + state := &PathState{ + ValidationRatio: c.validationRatio, + UpdateValRatio: false, + RequestStatistics: false, + expectedValResponses: make(map[validationIdentifier]uint32), + } + c.Paths[fingerprint] = state + + log.Debug("New FABRID PathState") + return state +} + +func (c *Client) GetFabridPathState(fingerprint snet.PathFingerprint) (*PathState, error) { + state, found := c.Paths[fingerprint] + if !found { + return nil, serrors.New("No state found", "pathFingerprint", fingerprint) + } + return state, nil +} + +func (c *Client) SetValidationRatio(newRatio uint8) { + for _, pathState := range c.Paths { + if pathState.ValidationRatio != newRatio { + pathState.ValidationRatio = newRatio + pathState.UpdateValRatio = true + } + } + c.validationRatio = newRatio +} + +func NewFabridServer(local *snet.UDPAddr, grpcConn *grpc.ClientConn) *Server { + server := &Server{ + Local: *local, + grpcConn: grpcConn, + Connections: make(map[string]*ClientConnection), + ASKeyCache: make(map[addr.IA]drkey.HostASKey), + ValidationHandler: func(_ *ClientConnection, _ *extension.IdentifierOption, _ bool) error { + return nil + }, + } + return server +} + +func (s *Server) fetchHostASKey(t time.Time, dstIA addr.IA) (drkey.HostASKey, error) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + drkeyClient := drpb.NewDRKeyIntraServiceClient(s.grpcConn) + meta := drkey.HostASMeta{ + Validity: t, + SrcIA: s.Local.IA, + SrcHost: s.Local.Host.IP.String(), + DstIA: dstIA, + ProtoId: drkey.FABRID, + } + rep, err := drkeyClient.DRKeyHostAS(ctx, drhelper.HostASMetaToProtoRequest(meta)) + if err != nil { + return drkey.HostASKey{}, err + } + key, err := drhelper.GetHostASKeyFromReply(rep, meta) + if err != nil { + return drkey.HostASKey{}, err + } + return key, nil +} + +func (s *Server) DeriveHostHostKey(dstHost snet.UDPAddr) (drkey.Key, error) { + var err error + hostAsKey, ok := s.ASKeyCache[dstHost.IA] + if !ok { + hostAsKey, err = s.fetchHostASKey(time.Now(), dstHost.IA) + if err != nil { + return drkey.Key{}, err + } + s.ASKeyCache[dstHost.IA] = hostAsKey + } + + d := specific.Deriver{} + hostHostKey, err := d.DeriveHostHost(dstHost.Host.IP.String(), hostAsKey.Key) + if err != nil { + return drkey.Key{}, err + } + return hostHostKey, nil +} + +func (s *Server) HandleFabridPacket(remote snet.UDPAddr, fabridOption *extension.FabridOption, + identifierOption *extension.IdentifierOption, + controlOptions []*extension.FabridControlOption) (*slayers.EndToEndExtn, error) { + client, found := s.Connections[remote.String()] + if !found { + log.Info("Opening new connection", "remote", remote.String()) + pathKey, err := s.DeriveHostHostKey(remote) + if err != nil { + return nil, err + } + client = &ClientConnection{ + Source: remote, + ValidationRatio: 0, + Stats: Statistics{}, + fabridControlBuffer: make([]byte, 28*3), + tmpBuffer: make([]byte, 192), + pathKey: pathKey, + } + s.Connections[remote.String()] = client + } + + client.Stats.TotalPackets++ + validationNumber, validationReply, success, err := crypto.VerifyPathValidator(fabridOption, + client.tmpBuffer, client.pathKey[:]) + if err != nil { + return nil, nil + } + err = s.ValidationHandler(client, identifierOption, success) + if err != nil { + return nil, err + } + + var replyOpts []*extension.FabridControlOption + for _, controlOption := range controlOptions { + err = crypto.VerifyFabridControlValidator(controlOption, identifierOption, + client.pathKey[:]) + if err != nil { + return nil, err + } + controlReplyOpt := &extension.FabridControlOption{} + ts, _ := controlOption.Timestamp() + controlReplyOpt.SetTimestamp(ts) + packetID, _ := controlOption.PacketID() + controlReplyOpt.SetPacketID(packetID) + replyOpts = append(replyOpts, controlReplyOpt) + + switch controlOption.Type { + case extension.ValidationConfig: + requestedRatio, err := controlOption.ValidationRatio() + if err != nil { + return nil, err + } + if requestedRatio > s.MaxValidationRatio { + log.Debug("FABRID control: requested ratio too large", "requested", requestedRatio, + "max", s.MaxValidationRatio) + requestedRatio = s.MaxValidationRatio + } + log.Debug("FABRID control: updated validation ratio", "new", requestedRatio, + "old", client.ValidationRatio) + client.ValidationRatio = requestedRatio + + // Prepare ACK + controlReplyOpt.Type = extension.ValidationConfigAck + controlReplyOpt.Data = make([]byte, 9) + err = controlReplyOpt.SetValidationRatio(client.ValidationRatio) + if err != nil { + return nil, err + } + case extension.StatisticsRequest: + log.Debug("FABRID control: statistics request") + // Prepare statistics reply + controlReplyOpt.Type = extension.StatisticsResponse + controlReplyOpt.Data = make([]byte, 24) + err := controlReplyOpt.SetStatistics(client.Stats.TotalPackets, + client.Stats.InvalidPackets) + if err != nil { + return nil, err + } + } + } + if validationNumber < client.ValidationRatio { + log.Debug("Send validation response", "packetID", identifierOption.PacketID) + validationReplyOpt := &extension.FabridControlOption{} + validationReplyOpt.SetTimestamp(identifierOption.GetRelativeTimestamp()) + validationReplyOpt.SetPacketID(identifierOption.PacketID) + replyOpts = append(replyOpts, validationReplyOpt) + validationReplyOpt.Type = extension.ValidationResponse + validationReplyOpt.Data = make([]byte, 20) + // TODO: Remove testing code + randInt := make([]byte, 1) + crand.Read(randInt) + if randInt[0] < SERVER_FLAKINESS { + validationReply ^= 0xFFFFFFFF + } + err = validationReplyOpt.SetPathValidatorReply(validationReply) + if err != nil { + return nil, err + } + } + + if len(replyOpts) > 0 { + e2eExt := &slayers.EndToEndExtn{} + for i, replyOpt := range replyOpts { + err = crypto.InitFabridControlValidator(replyOpt, identifierOption, client.pathKey[:]) + if err != nil { + return nil, err + } + buffer := client.fabridControlBuffer[i*28 : (i+1)*28] + err = replyOpt.SerializeTo(buffer) + if err != nil { + return nil, err + } + fabridReplyOptionLength := extension.BaseFabridControlLen + + extension.FabridControlOptionDataLen(replyOpt.Type) + e2eExt.Options = append(e2eExt.Options, + &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: buffer, + OptDataLen: uint8(fabridReplyOptionLength), + ActualLength: fabridReplyOptionLength, + }) + } + return e2eExt, nil + } + return nil, nil +} + +func (c *Client) RenewPathKey(t time.Time) error { + if c.PathKey.Epoch.NotAfter.Before(t) { + // key is expired, renew it + newKey, err := c.fetchHostHostKey(t) + if err != nil { + return err + } + c.PathKey = newKey + } + return nil +} + +func (c *Client) fetchHostHostKey(t time.Time) (drkey.HostHostKey, error) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + drkeyClient := drpb.NewDRKeyIntraServiceClient(c.GrpcConn) + meta := drkey.HostHostMeta{ + Validity: t, + SrcIA: c.Config.DestinationIA, + SrcHost: c.Config.DestinationAddr, + DstIA: c.Config.LocalIA, + DstHost: c.Config.LocalAddr, + ProtoId: drkey.FABRID, + } + rep, err := drkeyClient.DRKeyHostHost(ctx, drhelper.HostHostMetaToProtoRequest(meta)) + if err != nil { + return drkey.HostHostKey{}, err + } + key, err := drhelper.GetHostHostKeyFromReply(rep, meta) + if err != nil { + return drkey.HostHostKey{}, err + } + return key, nil +} + +func (c *Client) HandleFabridControlOption(fp snet.PathFingerprint, + controlOption *extension.FabridControlOption, + identifierOption *extension.IdentifierOption) error { + + err := crypto.VerifyFabridControlValidator(controlOption, identifierOption, c.PathKey.Key[:]) + if err != nil { + return err + } + ps := c.Paths[fp] + + switch controlOption.Type { + case extension.ValidationConfigAck: + confirmedRatio, err := controlOption.ValidationRatio() + if err != nil { + return err + } + if confirmedRatio == ps.ValidationRatio { + log.Debug("FABRID control: validation ratio confirmed", "ratio", confirmedRatio) + } else if confirmedRatio < ps.ValidationRatio { + log.Info("FABRID control: validation ratio reduced by server", + "requested", ps.ValidationRatio, "confirmed", confirmedRatio) + ps.ValidationRatio = confirmedRatio + } + case extension.ValidationResponse: + err = c.CheckValidationResponse(fp, controlOption) + if err != nil { + return err + } + err = c.Config.ValidationHandler(ps, controlOption, true) + if err != nil { + return err + } + //log.Debug("FABRID control: validation response", + //"packetID", controlOption.PacketID, "success", success) + + case extension.StatisticsResponse: + totalPkts, invalidPkts, err := controlOption.Statistics() + if err != nil { + return err + } + log.Info("FABRID control: statistics response", "totalPackets", totalPkts, + "invalidPackets", invalidPkts) + } + return nil +} + +func (c *Client) StoreValidationResponse(fp snet.PathFingerprint, validator uint32, + timestamp uint32, packetID uint32) error { + ps := c.Paths[fp] + valIdent := validationIdentifier{ + timestamp: timestamp, + packetId: packetID, + } + _, found := ps.expectedValResponses[valIdent] + if found { + return serrors.New("Validation response already stored", "validationIdentifier", valIdent) + } + log.Debug("Storing validation response", "packetID", packetID) + ps.expectedValResponses[valIdent] = validator + return nil +} + +func (c *Client) CheckValidationResponse(fp snet.PathFingerprint, + fco *extension.FabridControlOption) error { + timestamp, err := fco.Timestamp() + if err != nil { + return err + } + packetID, err := fco.PacketID() + if err != nil { + return err + } + validatorReply, err := fco.PathValidatorReply() + if err != nil { + return err + } + ps := c.Paths[fp] + valIdent := validationIdentifier{ + timestamp: timestamp, + packetId: packetID, + } + //log.Debug("Checking validation response", "timestamp", timestamp, "packetID", packetID) + validatorStored, found := ps.expectedValResponses[valIdent] + if !found { + return serrors.New("Unknown validation response", "validationIdentifier", valIdent) + } + if validatorStored != validatorReply { + return serrors.New("Wrong path validation response", "validationIdentifier", valIdent, + "expected", validatorStored, "actual", validatorReply) + } + return nil +} + +func (c *Client) RequestStatistics() { + for _, state := range c.Paths { + state.RequestStatistics = true + } +} diff --git a/pkg/experimental/fabrid/crypto/fabrid_crypto.go b/pkg/experimental/fabrid/crypto/fabrid_crypto.go index 6428e9b7ad..c6bbf7b2c6 100644 --- a/pkg/experimental/fabrid/crypto/fabrid_crypto.go +++ b/pkg/experimental/fabrid/crypto/fabrid_crypto.go @@ -152,13 +152,14 @@ func EncryptPolicyID(f fabrid.PolicyID, id *ext.IdentifierOption, } // VerifyPathValidator recomputes the path validator from the updated HVFs and compares it -// with the path validator in the packet. Returns the secret 5th byte of the computed validator. +// with the path validator in the packet. Returns validation number and reply for path validation. // `tmpBuffer` requires at least (numHops*3 rounded up to next multiple of 16) + 16 bytes -func VerifyPathValidator(f *ext.FabridOption, tmpBuffer []byte, pathKey []byte) (uint8, error) { +func VerifyPathValidator(f *ext.FabridOption, tmpBuffer []byte, + pathKey []byte) (uint8, uint32, bool, error) { inputLength := 3 * len(f.HopfieldMetadata) requiredBufferLength := 16 + (inputLength+15)&^15 if len(tmpBuffer) < requiredBufferLength { - return 0, serrors.New("tmpBuffer length is invalid", "expected", + return 0, 0, false, serrors.New("tmpBuffer length is invalid", "expected", requiredBufferLength, "actual", len(tmpBuffer)) } @@ -167,15 +168,16 @@ func VerifyPathValidator(f *ext.FabridOption, tmpBuffer []byte, pathKey []byte) } err := macBlock(pathKey, tmpBuffer[:16], tmpBuffer[16:16+inputLength], tmpBuffer[16:]) if err != nil { - return 0, err + return 0, 0, false, err } validationNumber := tmpBuffer[20] + validationReply := binary.BigEndian.Uint32(tmpBuffer[21:25]) if !bytes.Equal(tmpBuffer[16:20], f.PathValidator[:]) { - return validationNumber, serrors.New("Path validator is not valid", + return validationNumber, validationReply, false, serrors.New("Path validator is not valid", "validator", base64.StdEncoding.EncodeToString(f.PathValidator[:]), "computed", base64.StdEncoding.EncodeToString(tmpBuffer[16:20])) } - return validationNumber, nil + return validationNumber, validationReply, true, nil } // InitValidators sets all HVFs of the FABRID option and computes the @@ -229,6 +231,69 @@ func InitValidators(f *ext.FabridOption, id *ext.IdentifierOption, s *slayers.SC return nil } +func computeFabridControlValidator(fc *ext.FabridControlOption, id *ext.IdentifierOption, + resultBuffer []byte, pathKey []byte) error { + dataLen := ext.FabridControlOptionDataLen(fc.Type) + var fcMacInputLength int + switch fc.Type { + case ext.ValidationConfig, ext.StatisticsRequest: + fcMacInputLength = 1 + 8 + dataLen + case ext.ValidationConfigAck, ext.ValidationResponse, ext.StatisticsResponse: + fcMacInputLength = 1 + dataLen + } + macInputBuf := make([]byte, (fcMacInputLength+15)&^15) // Next multiple of 16 for macBlock() + tmpBuf := make([]byte, 16) + macInputBuf[0] = uint8(fc.Type) + copy(macInputBuf[1:1+dataLen], fc.Data) + binary.BigEndian.PutUint32(macInputBuf[1+dataLen:5+dataLen], id.GetRelativeTimestamp()) + binary.BigEndian.PutUint32(macInputBuf[5+dataLen:9+dataLen], id.PacketID) + + err := macBlock(pathKey, tmpBuf, macInputBuf[:fcMacInputLength], resultBuffer) + //log.Debug("Computing FABRID control validator", + // "key", base64.StdEncoding.EncodeToString(PathKey), + // "input", base64.StdEncoding.EncodeToString(macInputBuf[:fcMacInputLength]), + // "output", base64.StdEncoding.EncodeToString(resultBuffer[:4])) + if err != nil { + return err + } + return nil +} + +func InitFabridControlValidator(fc *ext.FabridControlOption, + id *ext.IdentifierOption, pathKey []byte) error { + outBuffer := make([]byte, 16) + err := computeFabridControlValidator(fc, id, outBuffer, pathKey) + if err != nil { + return err + } + outBuffer[0] &= 0xF // ignore first four bits + //log.Debug("Computing FABRID control validator", + // "key", base64.StdEncoding.EncodeToString(pathKey), + // "controlOption", fc, + // "computedValidator", base64.StdEncoding.EncodeToString(outBuffer[:4])) + copy(fc.Auth[:4], outBuffer[:4]) + return nil +} + +func VerifyFabridControlValidator(fc *ext.FabridControlOption, + id *ext.IdentifierOption, pathKey []byte) error { + computedValidator := make([]byte, 16) + err := computeFabridControlValidator(fc, id, computedValidator, pathKey) + if err != nil { + return err + } + computedValidator[0] &= 0xF // ignore first four bits + //log.Debug("Verifying FABRID control validator", + // "key", base64.StdEncoding.EncodeToString(pathKey), + // "controlOption", fc, + // "pktValidator", base64.StdEncoding.EncodeToString(fc.Auth[:]), + // "computedValidator", base64.StdEncoding.EncodeToString(computedValidator[:4])) + if !bytes.Equal(computedValidator[:4], fc.Auth[:]) { + return serrors.New("Fabrid control validator is not valid") + } + return nil +} + var zeroBlock [16]byte func macBlock(key []byte, tmp []byte, src []byte, dst []byte) error { diff --git a/pkg/experimental/fabrid/crypto/fabrid_crypto_test.go b/pkg/experimental/fabrid/crypto/fabrid_crypto_test.go index 9722efd4a3..7bf2cdbf1f 100644 --- a/pkg/experimental/fabrid/crypto/fabrid_crypto_test.go +++ b/pkg/experimental/fabrid/crypto/fabrid_crypto_test.go @@ -135,12 +135,12 @@ func TestFailedValidation(t *testing.T) { assert.NoError(t, err) } } - _, err = crypto.VerifyPathValidator(f, tmpBuffer, pathKey.Key[:]) + _, _, _, err = crypto.VerifyPathValidator(f, tmpBuffer, pathKey.Key[:]) assert.NoError(t, err) // until now we are in the success case. But now we modify a HVF to simulate // adversarial actions and make sure that the path validator fails f.HopfieldMetadata[0].HopValidationField = [3]byte{0, 0, 0} - _, err = crypto.VerifyPathValidator(f, tmpBuffer, pathKey.Key[:]) + _, _, _, err = crypto.VerifyPathValidator(f, tmpBuffer, pathKey.Key[:]) assert.ErrorContains(t, err, "Path validator is not valid") }, }, @@ -271,7 +271,7 @@ func TestSuccessfullValidators(t *testing.T) { assert.NoError(t, err) } } - _, err = crypto.VerifyPathValidator(f, tmpBuffer, pathKey.Key[:]) + _, _, _, err = crypto.VerifyPathValidator(f, tmpBuffer, pathKey.Key[:]) assert.NoError(t, err) } }) diff --git a/pkg/slayers/extension/BUILD.bazel b/pkg/slayers/extension/BUILD.bazel index 8c0cb605be..17223b7cbf 100644 --- a/pkg/slayers/extension/BUILD.bazel +++ b/pkg/slayers/extension/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "go_default_library", srcs = [ "fabrid.go", + "fabrid_control.go", "identifier.go", ], importpath = "github.com/scionproto/scion/pkg/slayers/extension", @@ -18,6 +19,7 @@ go_test( name = "go_default_test", srcs = [ "fabrid_test.go", + "fabrid_control_test.go", "identifier_test.go", ], deps = [ diff --git a/pkg/slayers/extension/fabrid_control.go b/pkg/slayers/extension/fabrid_control.go new file mode 100644 index 0000000000..671b7f0d32 --- /dev/null +++ b/pkg/slayers/extension/fabrid_control.go @@ -0,0 +1,237 @@ +// Copyright 2023 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The FABRID control option format is as follows: +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | NextHdr | ExtLen | OptType = 5 | OptLen | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Type | E2E Mac | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ... | +// | [Content] | +// | ... | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +package extension + +import ( + "encoding/binary" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/slayers" +) + +const BaseFabridControlLen int = 4 + +type FabridControlOptionType uint8 + +// Definition of FABRID control option type constants +const ( + ValidationConfig FabridControlOptionType = iota + ValidationConfigAck + ValidationResponse + StatisticsRequest + StatisticsResponse +) + +type FabridControlOption struct { + // Type of the control message + Type FabridControlOptionType + // E2E MAC of the option content + Auth [4]byte + Data []byte +} + +// Validates the length of FabridControlOption. Requires Type to be set +func (fc *FabridControlOption) validate(b []byte) error { + if fc == nil { + return serrors.New("Fabrid control option must not be nil") + } + if fc.Type > StatisticsResponse { + return serrors.New("Invalid fabrid control option type") + } + if len(b) < BaseFabridControlLen+FabridControlOptionDataLen(fc.Type) { + return serrors.New("Raw Fabrid control option too short", "is", len(b), + "expected", BaseFabridControlLen+FabridControlOptionDataLen(fc.Type)) + } + return nil +} + +func (fc *FabridControlOption) Decode(b []byte) error { + fc.Type = FabridControlOptionType(b[0] >> 4) + if err := fc.validate(b); err != nil { + return err + } + copy(fc.Auth[:], b[0:4]) + fc.Auth[0] &= 0xF + + fc.Data = make([]byte, FabridControlOptionDataLen(fc.Type)) + copy(fc.Data[:], b[4:4+FabridControlOptionDataLen(fc.Type)]) + return nil +} + +func (fc *FabridControlOption) SerializeTo(b []byte) error { + if fc == nil { + return serrors.New("Fabrid control option must not be nil") + } + if len(b) < BaseFabridControlLen+FabridControlOptionDataLen(fc.Type) { + return serrors.New("Buffer too short", "is", len(b), + "expected", BaseFabridControlLen+FabridControlOptionDataLen(fc.Type)) + } + // Set authenticator before type, so it is not overwritten + copy(b[0:4], fc.Auth[:]) + b[0] &= 0xF // clear the first 4 (left) bits + b[0] |= uint8(fc.Type) << 4 + if len(fc.Data) < FabridControlOptionDataLen(fc.Type) { + return serrors.New("Data too short", "is", len(fc.Data), + "expected", FabridControlOptionDataLen(fc.Type)) + } + + copy(b[4:], fc.Data[:FabridControlOptionDataLen(fc.Type)]) + return nil +} + +// Getter and Setter functions + +func (fc *FabridControlOption) Timestamp() (uint32, error) { + switch fc.Type { + case ValidationConfigAck, ValidationResponse, StatisticsResponse: + return binary.BigEndian.Uint32(fc.Data[:4]), nil + case ValidationConfig, StatisticsRequest: + return 0, serrors.New("Wrong option type", + "expected", string(ValidationConfigAck)+", "+string(ValidationResponse)+ + " or "+string(StatisticsResponse), "actual", fc.Type) + } + return 0, serrors.New("Invalid fabrid control option type", "type", fc.Type) +} + +func (fc *FabridControlOption) SetTimestamp(timestamp uint32) error { + switch fc.Type { + case ValidationConfigAck, ValidationResponse, StatisticsResponse: + binary.BigEndian.PutUint32(fc.Data[:4], timestamp) + return nil + case ValidationConfig, StatisticsRequest: + return serrors.New("Wrong option type", + "expected", string(ValidationConfigAck)+", "+string(ValidationResponse)+ + " or "+string(StatisticsResponse), "actual", fc.Type) + } + return serrors.New("Invalid fabrid control option type", "type", fc.Type) +} + +func (fc *FabridControlOption) PacketID() (uint32, error) { + switch fc.Type { + case ValidationConfigAck, ValidationResponse, StatisticsResponse: + return binary.BigEndian.Uint32(fc.Data[4:8]), nil + case ValidationConfig, StatisticsRequest: + return 0, serrors.New("Wrong option type", + "expected", string(ValidationConfigAck)+", "+string(ValidationResponse)+ + " or "+string(StatisticsResponse), "actual", fc.Type) + } + return 0, serrors.New("Invalid fabrid control option type", "type", fc.Type) +} + +func (fc *FabridControlOption) SetPacketID(packetID uint32) error { + switch fc.Type { + case ValidationConfigAck, ValidationResponse, StatisticsResponse: + binary.BigEndian.PutUint32(fc.Data[4:8], packetID) + return nil + case ValidationConfig, StatisticsRequest: + return serrors.New("Wrong option type", + "expected", string(ValidationConfigAck)+", "+string(ValidationResponse)+ + " or "+string(StatisticsResponse), "actual", fc.Type) + } + return serrors.New("Invalid fabrid control option type", "type", fc.Type) +} + +func (fc *FabridControlOption) ValidationRatio() (uint8, error) { + if fc.Type == ValidationConfig { + return fc.Data[0], nil + } else if fc.Type == ValidationConfigAck { + return fc.Data[8], nil + } + return 0, serrors.New("Wrong option type", + "expected", string(ValidationConfig)+" or "+string(ValidationConfigAck), "actual", fc.Type) +} + +func (fc *FabridControlOption) SetValidationRatio(valRatio uint8) error { + if fc.Type == ValidationConfig { + fc.Data[0] = valRatio + return nil + } else if fc.Type == ValidationConfigAck { + fc.Data[8] = valRatio + return nil + } + return serrors.New("Wrong option type", + "expected", string(ValidationConfig)+" or "+string(ValidationConfigAck), "actual", fc.Type) +} + +func (fc *FabridControlOption) PathValidatorReply() (uint32, error) { + if fc.Type == ValidationResponse { + return binary.BigEndian.Uint32(fc.Data[8:12]), nil + } + return 0, serrors.New("Wrong option type", "expected", ValidationResponse, "actual", fc.Type) +} + +func (fc *FabridControlOption) SetPathValidatorReply(pathValReply uint32) error { + if fc.Type == ValidationResponse { + binary.BigEndian.PutUint32(fc.Data[8:12], pathValReply) + return nil + } + return serrors.New("Wrong option type", "expected", ValidationResponse, "actual", fc.Type) +} + +func (fc *FabridControlOption) Statistics() (uint32, uint32, error) { + if fc.Type == StatisticsResponse { + return binary.BigEndian.Uint32(fc.Data[8:12]), binary.BigEndian.Uint32(fc.Data[12:16]), nil + } + return 0, 0, serrors.New("Wrong option type", "expected", StatisticsResponse, "actual", fc.Type) +} + +func (fc *FabridControlOption) SetStatistics(totalPackets uint32, invalidPackets uint32) error { + if fc.Type == StatisticsResponse { + binary.BigEndian.PutUint32(fc.Data[8:12], totalPackets) + binary.BigEndian.PutUint32(fc.Data[12:16], invalidPackets) + return nil + } + return serrors.New("Wrong option type", "expected", StatisticsResponse, "actual", fc.Type) +} + +func FabridControlOptionDataLen(controlOptionType FabridControlOptionType) int { + switch controlOptionType { + case ValidationConfig: + return 1 + case ValidationConfigAck: + return 9 + case ValidationResponse: + return 12 + case StatisticsRequest: + return 0 + case StatisticsResponse: + return 16 + default: + return 0 + } +} + +func ParseFabridControlOption(o *slayers.EndToEndOption) (*FabridControlOption, error) { + if o.OptType != slayers.OptTypeFabridControl { + return nil, + serrors.New("Wrong option type", "expected", slayers.OptTypeFabridControl, + "actual", o.OptType) + } + fc := &FabridControlOption{} + if err := fc.Decode(o.OptData); err != nil { + return nil, err + } + return fc, nil +} diff --git a/pkg/slayers/extension/fabrid_control_test.go b/pkg/slayers/extension/fabrid_control_test.go new file mode 100644 index 0000000000..be15adf410 --- /dev/null +++ b/pkg/slayers/extension/fabrid_control_test.go @@ -0,0 +1,320 @@ +// Copyright 2023 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extension_test + +import ( + "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/extension" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestFabridControlDecode(t *testing.T) { + type test struct { + name string + o *slayers.EndToEndOption + validate func(*extension.FabridControlOption, error, *testing.T) + } + tests := []test{ + { + name: "Wrong option type", + o: &slayers.EndToEndOption{ + OptType: slayers.OptTypeIdentifier, + OptData: make([]byte, 4), + }, + validate: func(fco *extension.FabridControlOption, err error, t *testing.T) { + assert.Error(t, err) + }, + }, + { + name: "Wrong fabrid control option type", + o: &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: []byte{0x50}, + }, + validate: func(fco *extension.FabridControlOption, err error, t *testing.T) { + assert.Error(t, err) + }, + }, + { + name: "Raw fabrid too short", + o: &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: make([]byte, 4), + }, + validate: func(fco *extension.FabridControlOption, err error, t *testing.T) { + assert.Error(t, err) + }, + }, + { + name: "Raw fabrid parses with correct length", + o: &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: make([]byte, 5), + }, + validate: func(fco *extension.FabridControlOption, err error, t *testing.T) { + assert.NoError(t, err) + }, + }, + { + name: "Parses fabrid validation config correctly", + o: &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: []byte{ + 0x0F, 0x22, 0x33, 0x44, + 0xaa, + }, + }, + validate: func(fco *extension.FabridControlOption, err error, t *testing.T) { + assert.NoError(t, err) + assert.Equal(t, extension.ValidationConfig, fco.Type, "Wrong type") + assert.Equal(t, [4]byte{0x0F, 0x22, 0x33, 0x44}, fco.Auth, "Wrong auth") + assert.Equal(t, []byte{0xaa}, fco.Data, "Wrong data") + }, + }, + { + name: "Parses fabrid validation config ACK correctly", + o: &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: []byte{ + 0x1f, 0x22, 0x33, 0x44, + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0x11, + }, + }, + validate: func(fco *extension.FabridControlOption, err error, t *testing.T) { + assert.NoError(t, err) + assert.Equal(t, extension.ValidationConfigAck, fco.Type, "Wrong type") + assert.Equal(t, [4]byte{0x0F, 0x22, 0x33, 0x44}, fco.Auth, "Wrong auth") + assert.Equal(t, []byte{ + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0x11}, fco.Data, "Wrong data") + }, + }, + { + name: "Parses fabrid validation response correctly", + o: &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: []byte{ + 0x2f, 0x22, 0x33, 0x44, + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0xcc, 0xdd, 0xee, 0xff, + }, + }, + validate: func(fco *extension.FabridControlOption, err error, t *testing.T) { + assert.NoError(t, err) + assert.Equal(t, extension.ValidationResponse, fco.Type, "Wrong type") + assert.Equal(t, [4]byte{0x0f, 0x22, 0x33, 0x44}, fco.Auth, "Wrong auth") + assert.Equal(t, []byte{ + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0xcc, 0xdd, 0xee, 0xff}, + fco.Data, "Wrong path validator reply") + }, + }, + { + name: "Parses fabrid statistics request correctly", + o: &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: []byte{ + 0x3f, 0x22, 0x33, 0x44, + }, + }, + validate: func(fco *extension.FabridControlOption, err error, t *testing.T) { + assert.NoError(t, err) + assert.Equal(t, extension.StatisticsRequest, fco.Type, "Wrong type") + assert.Equal(t, [4]byte{0x0f, 0x22, 0x33, 0x44}, fco.Auth, "Wrong auth") + assert.Empty(t, fco.Data, "Wrong data") + }, + }, + { + name: "Parses fabrid statistics response correctly", + o: &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: []byte{ + 0x4f, 0x22, 0x33, 0x44, + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0xaa, 0xbb, 0xcc, 0xdd, + 0x0a, 0x0b, 0x0c, 0x0d, + }, + }, + validate: func(fco *extension.FabridControlOption, err error, t *testing.T) { + assert.NoError(t, err) + assert.Equal(t, extension.StatisticsResponse, fco.Type, "Wrong type") + assert.Equal(t, [4]byte{0x0f, 0x22, 0x33, 0x44}, fco.Auth, "Wrong auth") + assert.Equal(t, []byte{ + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0xaa, 0xbb, 0xcc, 0xdd, + 0x0a, 0x0b, 0x0c, 0x0d, + }, fco.Data, "Wrong data") + }, + }, + } + + for _, tc := range tests { + func(tc test) { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + fc, err := extension.ParseFabridControlOption(tc.o) + tc.validate(fc, err, t) + }) + }(tc) + } +} + +func TestFabridControlSerialize(t *testing.T) { + type test struct { + name string + fc *extension.FabridControlOption + buffer []byte + validate func([]byte, error, *testing.T) + } + + tests := []test{ + { + name: "Fabrid control option is nil", + fc: nil, + validate: func(b []byte, err error, t *testing.T) { + assert.Error(t, err) + }, + }, + { + name: "Buffer too small", + fc: &extension.FabridControlOption{ + Type: extension.ValidationConfig, + Data: make([]byte, 1), + }, + buffer: make([]byte, 4), + validate: func(b []byte, err error, t *testing.T) { + assert.Error(t, err) + }, + }, + { + name: "Data buffer too small", + fc: &extension.FabridControlOption{ + Type: extension.ValidationConfig, + }, + buffer: make([]byte, 5), + validate: func(b []byte, err error, t *testing.T) { + assert.Error(t, err) + }, + }, + { + name: "Fabrid validation config serializes correctly", + fc: &extension.FabridControlOption{ + Type: extension.ValidationConfig, + Auth: [4]byte{0x07, 0xb2, 0xc3, 0xd4}, + Data: []byte{0x99}, + }, + buffer: make([]byte, 5), + validate: func(b []byte, err error, t *testing.T) { + assert.NoError(t, err) + assert.Equal(t, []byte{0x07, 0xb2, 0xc3, 0xd4}, b[0:4], "Wrong type or Auth") + assert.Equal(t, []byte{0x99}, b[4:5], "Wrong Data") + }, + }, + { + name: "Fabrid validation configuration ACK serializes correctly", + fc: &extension.FabridControlOption{ + Type: extension.ValidationConfigAck, + Auth: [4]byte{0x0f, 0xb2, 0xc3, 0xd4}, + Data: []byte{ + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0x11}, + }, + buffer: make([]byte, 13), + validate: func(b []byte, err error, t *testing.T) { + assert.NoError(t, err) + assert.Equal(t, []byte{0x1f, 0xb2, 0xc3, 0xd4}, b[0:4], "Wrong type or Auth") + assert.Equal(t, []byte{ + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0x11}, b[4:13], "Wrong Data") + }, + }, + { + name: "Fabrid validation response serializes correctly", + fc: &extension.FabridControlOption{ + Type: extension.ValidationResponse, + Auth: [4]byte{0x0f, 0xb2, 0xc3, 0xd4}, + Data: []byte{ + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0x11, 0x00, 0x99, 0x88}, + }, + buffer: make([]byte, 16), + validate: func(b []byte, err error, t *testing.T) { + assert.NoError(t, err) + assert.Equal(t, []byte{0x2f, 0xb2, 0xc3, 0xd4}, b[0:4], "Wrong type or Auth") + assert.Equal(t, []byte{ + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0x11, 0x00, 0x99, 0x88, + }, b[4:16], "Wrong Data") + }, + }, + { + name: "Fabrid statistics request serializes correctly", + fc: &extension.FabridControlOption{ + Type: extension.StatisticsRequest, + Auth: [4]byte{0x0f, 0xb2, 0xc3, 0xd4}, + }, + buffer: make([]byte, 4), + validate: func(b []byte, err error, t *testing.T) { + assert.NoError(t, err) + assert.Equal(t, []byte{0x3f, 0xb2, 0xc3, 0xd4}, b[0:4], "Wrong type or Auth") + }, + }, + { + name: "Fabrid statistics response serializes correctly", + fc: &extension.FabridControlOption{ + Type: extension.StatisticsResponse, + Auth: [4]byte{0x0f, 0xb2, 0xc3, 0xd4}, + Data: []byte{ + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0x99, 0x88, 0x77, 0x66, + 0x55, 0x44, 0x33, 0x22}, + }, + buffer: make([]byte, 20), + validate: func(b []byte, err error, t *testing.T) { + assert.NoError(t, err) + assert.Equal(t, []byte{0x4f, 0xb2, 0xc3, 0xd4}, b[0:4], "Wrong type or Auth") + assert.Equal(t, []byte{ + 0x07, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, 0xaa, 0xbb, + 0x99, 0x88, 0x77, 0x66, + 0x55, 0x44, 0x33, 0x22, + }, b[4:20], "Wrong Data") + }, + }, + } + + for _, tc := range tests { + func(tc test) { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := tc.fc.SerializeTo(tc.buffer) + tc.validate(tc.buffer, err, t) + }) + }(tc) + } +} diff --git a/pkg/slayers/extn.go b/pkg/slayers/extn.go index fded8696a8..eb41a51bf2 100644 --- a/pkg/slayers/extn.go +++ b/pkg/slayers/extn.go @@ -36,6 +36,7 @@ const ( OptTypeAuthenticator OptTypeIdentifier OptTypeFabrid + OptTypeFabridControl ) type tlvOption struct { diff --git a/tools/end2end/main.go b/tools/end2end/main.go index 654d5c319d..21371d7357 100644 --- a/tools/end2end/main.go +++ b/tools/end2end/main.go @@ -314,7 +314,7 @@ func (s server) handlePingFabrid(conn snet.PacketConn) error { } tmpBuffer := make([]byte, (len(fabridOption.HopfieldMetadata)*3+15)&^15+16) - _, err = crypto.VerifyPathValidator(fabridOption, tmpBuffer, hostHostKey.Key[:]) + _, _, _, err = crypto.VerifyPathValidator(fabridOption, tmpBuffer, hostHostKey.Key[:]) if err != nil { return err } From e76fa83efa63f0df87318942d6a1a9ee56949a32 Mon Sep 17 00:00:00 2001 From: Marc Odermatt Date: Thu, 29 Aug 2024 14:56:51 +0200 Subject: [PATCH 3/6] wip client integration --- pkg/experimental/fabrid/common/BUILD.bazel | 7 +- pkg/experimental/fabrid/common/fabrid.go | 89 ++++++------------- .../fabrid/crypto/fabrid_crypto.go | 17 ++-- pkg/snet/path/BUILD.bazel | 1 + pkg/snet/path/fabrid.go | 74 ++++++++++++++- tools/end2end/BUILD.bazel | 1 + tools/end2end/main.go | 66 +++++++++----- tools/end2end_integration/main.go | 1 + 8 files changed, 164 insertions(+), 92 deletions(-) diff --git a/pkg/experimental/fabrid/common/BUILD.bazel b/pkg/experimental/fabrid/common/BUILD.bazel index 124c563fe6..6ad29133dc 100644 --- a/pkg/experimental/fabrid/common/BUILD.bazel +++ b/pkg/experimental/fabrid/common/BUILD.bazel @@ -3,17 +3,22 @@ load("//tools/lint:go.bzl", "go_library") go_library( name = "go_default_library", srcs = ["fabrid.go"], - importpath = "github.com/scionproto/scion/pkg/experimental/fabrid", + importpath = "github.com/scionproto/scion/pkg/experimental/fabrid/common", visibility = ["//visibility:public"], deps = [ "//pkg/addr:go_default_library", + "//pkg/daemon:go_default_library", "//pkg/daemon/helper:go_default_library", "//pkg/drkey:go_default_library", "//pkg/drkey/specific:go_default_library", + "//pkg/experimental/fabrid:go_default_library", + "//pkg/experimental/fabrid/crypto:go_default_library", "//pkg/log:go_default_library", "//pkg/private/serrors:go_default_library", "//pkg/proto/control_plane:go_default_library", "//pkg/slayers:go_default_library", "//pkg/slayers/extension:go_default_library", + "//pkg/snet:go_default_library", + "@org_golang_google_grpc//:go_default_library", ], ) diff --git a/pkg/experimental/fabrid/common/fabrid.go b/pkg/experimental/fabrid/common/fabrid.go index cc8a68a779..cc2b54e063 100644 --- a/pkg/experimental/fabrid/common/fabrid.go +++ b/pkg/experimental/fabrid/common/fabrid.go @@ -18,18 +18,15 @@ import ( "context" crand "crypto/rand" "github.com/scionproto/scion/pkg/addr" - drhelper "github.com/scionproto/scion/pkg/daemon/helper" + "github.com/scionproto/scion/pkg/daemon" "github.com/scionproto/scion/pkg/drkey" - "github.com/scionproto/scion/pkg/drkey/specific" "github.com/scionproto/scion/pkg/experimental/fabrid" "github.com/scionproto/scion/pkg/experimental/fabrid/crypto" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" - drpb "github.com/scionproto/scion/pkg/proto/control_plane" "github.com/scionproto/scion/pkg/slayers" "github.com/scionproto/scion/pkg/slayers/extension" "github.com/scionproto/scion/pkg/snet" - "google.golang.org/grpc" "time" ) @@ -53,7 +50,7 @@ type Statistics struct { } type ClientConnection struct { - Source snet.UDPAddr + Source snet.SCIONAddress ValidationRatio uint8 Stats Statistics fabridControlBuffer []byte @@ -63,7 +60,7 @@ type ClientConnection struct { type Server struct { Local snet.UDPAddr - grpcConn *grpc.ClientConn + sdConn daemon.Connector Connections map[string]*ClientConnection ASKeyCache map[addr.IA]drkey.HostASKey MaxValidationRatio uint8 @@ -78,7 +75,7 @@ type Client struct { Paths map[snet.PathFingerprint]*PathState Config SimpleFabridConfig drkeyPathFn func(context.Context, drkey.HostHostMeta) (drkey.HostHostKey, error) - GrpcConn *grpc.ClientConn + SDConn daemon.Connector } type validationIdentifier struct { @@ -95,14 +92,14 @@ type PathState struct { } func NewFabridClient(remote snet.UDPAddr, config SimpleFabridConfig, - grpcConn *grpc.ClientConn) *Client { + sdConn daemon.Connector) *Client { state := &Client{ Destination: remote, validationRatio: config.ValidationRatio, fabridControlBuffer: make([]byte, 20*3), Paths: make(map[snet.PathFingerprint]*PathState), Config: config, - GrpcConn: grpcConn, + SDConn: sdConn, } return state } @@ -138,10 +135,10 @@ func (c *Client) SetValidationRatio(newRatio uint8) { c.validationRatio = newRatio } -func NewFabridServer(local *snet.UDPAddr, grpcConn *grpc.ClientConn) *Server { +func NewFabridServer(local *snet.UDPAddr, sdConn daemon.Connector) *Server { server := &Server{ Local: *local, - grpcConn: grpcConn, + sdConn: sdConn, Connections: make(map[string]*ClientConnection), ASKeyCache: make(map[addr.IA]drkey.HostASKey), ValidationHandler: func(_ *ClientConnection, _ *extension.IdentifierOption, _ bool) error { @@ -151,73 +148,48 @@ func NewFabridServer(local *snet.UDPAddr, grpcConn *grpc.ClientConn) *Server { return server } -func (s *Server) fetchHostASKey(t time.Time, dstIA addr.IA) (drkey.HostASKey, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - drkeyClient := drpb.NewDRKeyIntraServiceClient(s.grpcConn) - meta := drkey.HostASMeta{ - Validity: t, +func (s *Server) FetchHostHostKey(dstHost snet.SCIONAddress, validity time.Time) (drkey.Key, error) { + meta := drkey.HostHostMeta{ + Validity: validity, SrcIA: s.Local.IA, SrcHost: s.Local.Host.IP.String(), - DstIA: dstIA, + DstIA: dstHost.IA, + DstHost: dstHost.Host.IP().String(), ProtoId: drkey.FABRID, } - rep, err := drkeyClient.DRKeyHostAS(ctx, drhelper.HostASMetaToProtoRequest(meta)) - if err != nil { - return drkey.HostASKey{}, err - } - key, err := drhelper.GetHostASKeyFromReply(rep, meta) - if err != nil { - return drkey.HostASKey{}, err - } - return key, nil -} - -func (s *Server) DeriveHostHostKey(dstHost snet.UDPAddr) (drkey.Key, error) { - var err error - hostAsKey, ok := s.ASKeyCache[dstHost.IA] - if !ok { - hostAsKey, err = s.fetchHostASKey(time.Now(), dstHost.IA) - if err != nil { - return drkey.Key{}, err - } - s.ASKeyCache[dstHost.IA] = hostAsKey - } - - d := specific.Deriver{} - hostHostKey, err := d.DeriveHostHost(dstHost.Host.IP.String(), hostAsKey.Key) + hostHostKey, err := s.sdConn.DRKeyGetHostHostKey(context.Background(), meta) if err != nil { - return drkey.Key{}, err + return drkey.Key{}, serrors.WrapStr("getting host key", err) } - return hostHostKey, nil + return hostHostKey.Key, nil } -func (s *Server) HandleFabridPacket(remote snet.UDPAddr, fabridOption *extension.FabridOption, +func (s *Server) HandleFabridPacket(remote snet.SCIONAddress, fabridOption *extension.FabridOption, identifierOption *extension.IdentifierOption, controlOptions []*extension.FabridControlOption) (*slayers.EndToEndExtn, error) { client, found := s.Connections[remote.String()] if !found { - log.Info("Opening new connection", "remote", remote.String()) - pathKey, err := s.DeriveHostHostKey(remote) + pathKey, err := s.FetchHostHostKey(remote, identifierOption.Timestamp) if err != nil { return nil, err } client = &ClientConnection{ Source: remote, - ValidationRatio: 0, + ValidationRatio: 255, Stats: Statistics{}, fabridControlBuffer: make([]byte, 28*3), tmpBuffer: make([]byte, 192), pathKey: pathKey, } s.Connections[remote.String()] = client + log.Info("Opened new connection", "remote", remote.String()) } client.Stats.TotalPackets++ validationNumber, validationReply, success, err := crypto.VerifyPathValidator(fabridOption, client.tmpBuffer, client.pathKey[:]) if err != nil { - return nil, nil + return nil, err } err = s.ValidationHandler(client, identifierOption, success) if err != nil { @@ -322,7 +294,7 @@ func (s *Server) HandleFabridPacket(remote snet.UDPAddr, fabridOption *extension func (c *Client) RenewPathKey(t time.Time) error { if c.PathKey.Epoch.NotAfter.Before(t) { // key is expired, renew it - newKey, err := c.fetchHostHostKey(t) + newKey, err := c.FetchHostHostKey(t) if err != nil { return err } @@ -331,27 +303,20 @@ func (c *Client) RenewPathKey(t time.Time) error { return nil } -func (c *Client) fetchHostHostKey(t time.Time) (drkey.HostHostKey, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - drkeyClient := drpb.NewDRKeyIntraServiceClient(c.GrpcConn) +func (c *Client) FetchHostHostKey(validity time.Time) (drkey.HostHostKey, error) { meta := drkey.HostHostMeta{ - Validity: t, + Validity: validity, SrcIA: c.Config.DestinationIA, SrcHost: c.Config.DestinationAddr, DstIA: c.Config.LocalIA, DstHost: c.Config.LocalAddr, ProtoId: drkey.FABRID, } - rep, err := drkeyClient.DRKeyHostHost(ctx, drhelper.HostHostMetaToProtoRequest(meta)) - if err != nil { - return drkey.HostHostKey{}, err - } - key, err := drhelper.GetHostHostKeyFromReply(rep, meta) + hostHostKey, err := c.SDConn.DRKeyGetHostHostKey(context.Background(), meta) if err != nil { - return drkey.HostHostKey{}, err + return drkey.HostHostKey{}, serrors.WrapStr("getting host key", err) } - return key, nil + return hostHostKey, nil } func (c *Client) HandleFabridControlOption(fp snet.PathFingerprint, diff --git a/pkg/experimental/fabrid/crypto/fabrid_crypto.go b/pkg/experimental/fabrid/crypto/fabrid_crypto.go index c6bbf7b2c6..4618ea338b 100644 --- a/pkg/experimental/fabrid/crypto/fabrid_crypto.go +++ b/pkg/experimental/fabrid/crypto/fabrid_crypto.go @@ -184,7 +184,7 @@ func VerifyPathValidator(f *ext.FabridOption, tmpBuffer []byte, // path validator. func InitValidators(f *ext.FabridOption, id *ext.IdentifierOption, s *slayers.SCION, tmpBuffer []byte, pathKey *drkey.FabridKey, asHostKeys map[addr.IA]*drkey.FabridKey, - asAsKeys map[addr.IA]*drkey.FabridKey, hops []snet.HopInterface) error { + asAsKeys map[addr.IA]*drkey.FabridKey, hops []snet.HopInterface) (uint8, uint32, error) { outBuffer := make([]byte, 16) var pathValInputLength int @@ -203,14 +203,14 @@ func InitValidators(f *ext.FabridOption, id *ext.IdentifierOption, s *slayers.SC key, found = asHostKeys[hops[i].IA] } if !found { - return serrors.New("InitValidators expected AS to AS key but was not in"+ + return 0, 0, serrors.New("InitValidators expected AS to AS key but was not in"+ " dictionary", "AS", hops[i].IA) } err := computeFabridHVF(meta, id, s, tmpBuffer, outBuffer, key.Key[:], uint16(hops[i].IgIf), uint16(hops[i].EgIf)) if err != nil { - return err + return 0, 0, err } outBuffer[0] &= 0x3f // ignore first two (left) bits outBuffer[3] &= 0x3f // ignore first two (left) bits @@ -224,11 +224,11 @@ func InitValidators(f *ext.FabridOption, id *ext.IdentifierOption, s *slayers.SC err := macBlock(pathKey.Key[:], tmpBuffer[:16], pathValBuffer[:pathValInputLength], pathValBuffer) if err != nil { - return err + return 0, 0, err } copy(f.PathValidator[:4], pathValBuffer[:4]) } - return nil + return pathValBuffer[4], binary.BigEndian.Uint32(pathValBuffer[5:9]), nil } func computeFabridControlValidator(fc *ext.FabridControlOption, id *ext.IdentifierOption, @@ -245,8 +245,11 @@ func computeFabridControlValidator(fc *ext.FabridControlOption, id *ext.Identifi tmpBuf := make([]byte, 16) macInputBuf[0] = uint8(fc.Type) copy(macInputBuf[1:1+dataLen], fc.Data) - binary.BigEndian.PutUint32(macInputBuf[1+dataLen:5+dataLen], id.GetRelativeTimestamp()) - binary.BigEndian.PutUint32(macInputBuf[5+dataLen:9+dataLen], id.PacketID) + switch fc.Type { + case ext.ValidationConfig, ext.StatisticsRequest: + binary.BigEndian.PutUint32(macInputBuf[1+dataLen:5+dataLen], id.GetRelativeTimestamp()) + binary.BigEndian.PutUint32(macInputBuf[5+dataLen:9+dataLen], id.PacketID) + } err := macBlock(pathKey, tmpBuf, macInputBuf[:fcMacInputLength], resultBuffer) //log.Debug("Computing FABRID control validator", diff --git a/pkg/snet/path/BUILD.bazel b/pkg/snet/path/BUILD.bazel index 4f9c63b3a6..b85e34cef0 100644 --- a/pkg/snet/path/BUILD.bazel +++ b/pkg/snet/path/BUILD.bazel @@ -17,6 +17,7 @@ go_library( "//pkg/drkey:go_default_library", "//pkg/experimental/epic:go_default_library", "//pkg/experimental/fabrid:go_default_library", + "//pkg/experimental/fabrid/common:go_default_library", "//pkg/experimental/fabrid/crypto:go_default_library", "//pkg/private/serrors:go_default_library", "//pkg/private/util:go_default_library", diff --git a/pkg/snet/path/fabrid.go b/pkg/snet/path/fabrid.go index b6dd13aa40..f80a66d9ac 100644 --- a/pkg/snet/path/fabrid.go +++ b/pkg/snet/path/fabrid.go @@ -16,6 +16,7 @@ package path import ( "context" + "github.com/scionproto/scion/pkg/experimental/fabrid/common" "time" "github.com/scionproto/scion/pkg/addr" @@ -50,10 +51,12 @@ type FABRID struct { policyIDs []*fabrid.PolicyID numHops int hops []snet.HopInterface + client *common.Client + fingerprint snet.PathFingerprint } func NewFABRIDDataplanePath(p SCION, hops []snet.HopInterface, policyIDs []*fabrid.PolicyID, - conf *FabridConfig) (*FABRID, error) { + conf *FabridConfig, client *common.Client, fingerprint snet.PathFingerprint) (*FABRID, error) { numHops := len(hops) var decoded scion.Decoded if err := decoded.DecodeFromBytes(p.Raw); err != nil { @@ -81,6 +84,8 @@ func NewFABRIDDataplanePath(p SCION, hops []snet.HopInterface, policyIDs []*fabr fabridBuffer: make([]byte, 8+4*numHops), Raw: append([]byte(nil), p.Raw...), policyIDs: policyIDs, + client: client, + fingerprint: fingerprint, } // Get ingress/egress IFs and IAs from path interfaces @@ -146,7 +151,7 @@ func (f *FABRID) SetExtensions(s *slayers.SCION, p *snet.PacketInfo) error { } fabridOption.HopfieldMetadata[i] = meta } - err = crypto.InitValidators(fabridOption, identifierOption, s, f.tmpBuffer, f.pathKey, + valNumber, pathValReply, err := crypto.InitValidators(fabridOption, identifierOption, s, f.tmpBuffer, f.pathKey.Key[:], f.keys, nil, f.hops) if err != nil { return serrors.WrapStr("initializing validators failed", err) @@ -173,7 +178,72 @@ func (f *FABRID) SetExtensions(s *slayers.SCION, p *snet.PacketInfo) error { OptDataLen: uint8(fabridLength), ActualLength: fabridLength, }) + + pathState, _ := f.client.GetFabridPathState(f.fingerprint) + + if valNumber < pathState.ValidationRatio { + err = f.client.StoreValidationResponse(f.fingerprint, pathValReply, identifierOption.GetRelativeTimestamp(), f.counter) + if err != nil { + return err + } + } + + var e2eOpts []*extension.FabridControlOption + if pathState.UpdateValRatio { + valConfigOption := &extension.FabridControlOption{ + Type: extension.ValidationConfig, + Auth: [4]byte{}, + Timestamp: identifierOption.GetRelativeTimestamp(), + PacketID: identifierOption.PacketID, + Data: make([]byte, 1), + } + err = valConfigOption.SetValidationRatio(pathState.ValidationRatio) + if err != nil { + return err + } + e2eOpts = append(e2eOpts, valConfigOption) + pathState.UpdateValRatio = false + log.Debug("FABRID control: outgoing validation config", "valRatio", pathState.ValidationRatio) + } + if pathState.RequestStatistics { + statisticsRequestOption := &extension.FabridControlOption{ + Type: extension.StatisticsRequest, + Auth: [4]byte{}, + Timestamp: identifierOption.GetRelativeTimestamp(), + PacketID: identifierOption.PacketID, + } + + e2eOpts = append(e2eOpts, statisticsRequestOption) + pathState.RequestStatistics = false + log.Debug("FABRID control: sending statistics request") + } + if len(e2eOpts) > 0 { + if p.E2eExtension == nil { + p.E2eExtension = &slayers.EndToEndExtn{} + } + for i, replyOpt := range e2eOpts { + err = crypto.InitFabridControlValidator(replyOpt, f.client.PathKey.Key[:]) + if err != nil { + return err + } + buffer := f.e2eBuffer[i*5 : (i+1)*5] + err = replyOpt.SerializeTo(buffer) + if err != nil { + return err + } + fabridReplyOptionLength := extension.BaseFabridControlLen + extension.FabridControlOptionDataLen(replyOpt.Type) + p.E2eExtension.Options = append(p.E2eExtension.Options, + &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: buffer, + OptDataLen: uint8(fabridReplyOptionLength), + ActualLength: fabridReplyOptionLength, + }) + } + } + f.counter++ + pathState.Stats.TotalPackets++ return nil } diff --git a/tools/end2end/BUILD.bazel b/tools/end2end/BUILD.bazel index eabd6e2754..e86cc170bc 100644 --- a/tools/end2end/BUILD.bazel +++ b/tools/end2end/BUILD.bazel @@ -11,6 +11,7 @@ go_library( "//pkg/daemon:go_default_library", "//pkg/drkey:go_default_library", "//pkg/experimental/fabrid:go_default_library", + "//pkg/experimental/fabrid/common:go_default_library", "//pkg/experimental/fabrid/crypto:go_default_library", "//pkg/log:go_default_library", "//pkg/private/common:go_default_library", diff --git a/tools/end2end/main.go b/tools/end2end/main.go index 21371d7357..8d5c32b81b 100644 --- a/tools/end2end/main.go +++ b/tools/end2end/main.go @@ -28,6 +28,7 @@ import ( "errors" "flag" "fmt" + common2 "github.com/scionproto/scion/pkg/experimental/fabrid/common" "net" "os" "time" @@ -37,9 +38,7 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon" - "github.com/scionproto/scion/pkg/drkey" libfabrid "github.com/scionproto/scion/pkg/experimental/fabrid" - "github.com/scionproto/scion/pkg/experimental/fabrid/crypto" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" @@ -137,7 +136,9 @@ func validateFlags() { log.Info("Flags", "timeout", timeout, "epic", epic, "fabrid", fabrid, "remote", remote) } -type server struct{} +type server struct { + fabridServer *common2.Server +} func (s server) run() { log.Info("Starting server", "isd_as", integration.Local.IA) @@ -167,6 +168,14 @@ func (s server) run() { } log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host.IP, localAddr.Port)) + s.fabridServer = common2.NewFabridServer(&integration.Local, integration.SDConn()) + s.fabridServer.ValidationHandler = func(connection *common2.ClientConnection, option *extension.IdentifierOption, b bool) error { + log.Debug("Validation handler", "connection", connection, "success", b) + if !b { + return serrors.New("Failed validation") + } + return nil + } // Receive ping message for { if err := s.handlePingFabrid(conn); err != nil { @@ -259,6 +268,8 @@ func (s server) handlePingFabrid(conn snet.PacketConn) error { return serrors.WrapStr("reading packet", err) } + var valResponse *slayers.EndToEndExtn + // If the packet is from remote IA, validate the FABRID path if p.Source.IA != integration.Local.IA { if p.HbhExtension == nil { @@ -268,6 +279,7 @@ func (s server) handlePingFabrid(conn snet.PacketConn) error { // Check extensions for relevant options var identifierOption *extension.IdentifierOption var fabridOption *extension.FabridOption + var controlOptions []*extension.FabridControlOption var err error for _, opt := range p.HbhExtension.Options { @@ -291,6 +303,19 @@ func (s server) handlePingFabrid(conn snet.PacketConn) error { } } } + if p.E2eExtension != nil { + + for _, opt := range p.E2eExtension.Options { + switch opt.OptType { + case slayers.OptTypeFabridControl: + controlOption, err := extension.ParseFabridControlOption(opt) + if err != nil { + return err + } + controlOptions = append(controlOptions, controlOption) + } + } + } if identifierOption == nil { return serrors.New("Missing identifier option") @@ -299,22 +324,7 @@ func (s server) handlePingFabrid(conn snet.PacketConn) error { if fabridOption == nil { return serrors.New("Missing FABRID option") } - - meta := drkey.HostHostMeta{ - Validity: identifierOption.Timestamp, - SrcIA: integration.Local.IA, - SrcHost: integration.Local.Host.IP.String(), - DstIA: p.Source.IA, - DstHost: p.Source.Host.IP().String(), - ProtoId: drkey.FABRID, - } - hostHostKey, err := integration.SDConn().DRKeyGetHostHostKey(context.Background(), meta) - if err != nil { - return serrors.WrapStr("getting host key", err) - } - - tmpBuffer := make([]byte, (len(fabridOption.HopfieldMetadata)*3+15)&^15+16) - _, _, _, err = crypto.VerifyPathValidator(fabridOption, tmpBuffer, hostHostKey.Key[:]) + valResponse, err = s.fabridServer.HandleFabridPacket(p.Source, fabridOption, identifierOption, controlOptions) if err != nil { return err } @@ -382,7 +392,7 @@ func (s server) handlePingFabrid(conn snet.PacketConn) error { // Remove header extension for reverse path p.HbhExtension = nil - p.E2eExtension = nil + p.E2eExtension = valResponse // reverse path rpath, ok := p.Path.(snet.RawPath) @@ -586,6 +596,22 @@ func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) { } remote.Path = fabridPath fabridPath.RegisterDRKeyFetcher(c.sdConn.FabridKeys) + polIdentifier := libfabrid.Policy{ + IsLocal: false, + Identifier: 0, + Index: 0, + } + clientConfig := common2.SimpleFabridConfig{ + DestinationIA: remote.IA, + DestinationAddr: remote.Host.IP.String(), + LocalIA: integration.Local.IA, + LocalAddr: integration.Local.Host.IP.String(), + ValidationRatio: 255, + Policy: polIdentifier, + ValidationHandler: nil, + } + + common2.NewFabridClient(remote, clientConfig, integration.SDConn()) } else { log.Info("FABRID flag was set for client in non-FABRID AS. Proceeding without FABRID.") remote.Path = path.Dataplane() diff --git a/tools/end2end_integration/main.go b/tools/end2end_integration/main.go index 61540cfe20..ef526409f9 100644 --- a/tools/end2end_integration/main.go +++ b/tools/end2end_integration/main.go @@ -80,6 +80,7 @@ func realMain() int { fmt.Sprintf("-fabrid=%t", fabrid), } serverArgs := []string{ + "-log.console", "debug", "-mode", "server", "-local", integration.DstAddrPattern + ":0", fmt.Sprintf("-fabrid=%t", fabrid), From 27ef35c97540980cd0b6e2e2cd1574e85dab7a26 Mon Sep 17 00:00:00 2001 From: Marc Odermatt Date: Fri, 30 Aug 2024 12:45:37 +0200 Subject: [PATCH 4/6] Validation at source end2end test performs validation at source - client requests validation ratio - server replies with validation responses - client checks validation responses --- pkg/experimental/fabrid/common/BUILD.bazel | 11 - pkg/experimental/fabrid/common/fabrid.go | 336 +----------------- .../fabrid/crypto/fabrid_crypto_test.go | 4 +- pkg/experimental/fabrid/defs.go | 4 +- pkg/experimental/fabrid/server/BUILD.bazel | 20 ++ pkg/experimental/fabrid/server/server.go | 222 ++++++++++++ pkg/slayers/extension/BUILD.bazel | 2 +- pkg/slayers/extension/fabrid_control.go | 9 + pkg/slayers/extension/fabrid_control_test.go | 6 +- pkg/snet/path/BUILD.bazel | 1 + pkg/snet/path/fabrid.go | 119 +++++-- private/app/path/path.go | 2 +- tools/end2end/BUILD.bazel | 3 +- tools/end2end/main.go | 206 +++++++++-- 14 files changed, 526 insertions(+), 419 deletions(-) create mode 100644 pkg/experimental/fabrid/server/BUILD.bazel create mode 100644 pkg/experimental/fabrid/server/server.go diff --git a/pkg/experimental/fabrid/common/BUILD.bazel b/pkg/experimental/fabrid/common/BUILD.bazel index 6ad29133dc..3bb103a82f 100644 --- a/pkg/experimental/fabrid/common/BUILD.bazel +++ b/pkg/experimental/fabrid/common/BUILD.bazel @@ -6,19 +6,8 @@ go_library( importpath = "github.com/scionproto/scion/pkg/experimental/fabrid/common", visibility = ["//visibility:public"], deps = [ - "//pkg/addr:go_default_library", - "//pkg/daemon:go_default_library", - "//pkg/daemon/helper:go_default_library", - "//pkg/drkey:go_default_library", - "//pkg/drkey/specific:go_default_library", - "//pkg/experimental/fabrid:go_default_library", - "//pkg/experimental/fabrid/crypto:go_default_library", "//pkg/log:go_default_library", "//pkg/private/serrors:go_default_library", - "//pkg/proto/control_plane:go_default_library", - "//pkg/slayers:go_default_library", "//pkg/slayers/extension:go_default_library", - "//pkg/snet:go_default_library", - "@org_golang_google_grpc//:go_default_library", ], ) diff --git a/pkg/experimental/fabrid/common/fabrid.go b/pkg/experimental/fabrid/common/fabrid.go index cc2b54e063..e65b4c9e50 100644 --- a/pkg/experimental/fabrid/common/fabrid.go +++ b/pkg/experimental/fabrid/common/fabrid.go @@ -15,69 +15,20 @@ package common import ( - "context" - crand "crypto/rand" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/daemon" - "github.com/scionproto/scion/pkg/drkey" - "github.com/scionproto/scion/pkg/experimental/fabrid" - "github.com/scionproto/scion/pkg/experimental/fabrid/crypto" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" - "github.com/scionproto/scion/pkg/slayers" "github.com/scionproto/scion/pkg/slayers/extension" - "github.com/scionproto/scion/pkg/snet" - "time" ) // Testing options for failing validation const CLIENT_FLAKINESS = 0 const SERVER_FLAKINESS = 0 -type SimpleFabridConfig struct { - DestinationIA addr.IA - DestinationAddr string - LocalIA addr.IA - LocalAddr string - ValidationRatio uint8 - Policy fabrid.Policy - ValidationHandler func(*PathState, *extension.FabridControlOption, bool) error -} - type Statistics struct { TotalPackets uint32 InvalidPackets uint32 } -type ClientConnection struct { - Source snet.SCIONAddress - ValidationRatio uint8 - Stats Statistics - fabridControlBuffer []byte - tmpBuffer []byte - pathKey drkey.Key -} - -type Server struct { - Local snet.UDPAddr - sdConn daemon.Connector - Connections map[string]*ClientConnection - ASKeyCache map[addr.IA]drkey.HostASKey - MaxValidationRatio uint8 - ValidationHandler func(*ClientConnection, *extension.IdentifierOption, bool) error -} - -type Client struct { - Destination snet.UDPAddr - validationRatio uint8 - fabridControlBuffer []byte - PathKey drkey.HostHostKey - Paths map[snet.PathFingerprint]*PathState - Config SimpleFabridConfig - drkeyPathFn func(context.Context, drkey.HostHostMeta) (drkey.HostHostKey, error) - SDConn daemon.Connector -} - type validationIdentifier struct { timestamp uint32 packetId uint32 @@ -91,283 +42,18 @@ type PathState struct { expectedValResponses map[validationIdentifier]uint32 } -func NewFabridClient(remote snet.UDPAddr, config SimpleFabridConfig, - sdConn daemon.Connector) *Client { - state := &Client{ - Destination: remote, - validationRatio: config.ValidationRatio, - fabridControlBuffer: make([]byte, 20*3), - Paths: make(map[snet.PathFingerprint]*PathState), - Config: config, - SDConn: sdConn, - } - return state -} - -func (c *Client) NewFabridPathState(fingerprint snet.PathFingerprint) *PathState { +func NewFabridPathState(valRatio uint8) *PathState { state := &PathState{ - ValidationRatio: c.validationRatio, - UpdateValRatio: false, + ValidationRatio: valRatio, + UpdateValRatio: true, RequestStatistics: false, expectedValResponses: make(map[validationIdentifier]uint32), } - c.Paths[fingerprint] = state - - log.Debug("New FABRID PathState") return state } -func (c *Client) GetFabridPathState(fingerprint snet.PathFingerprint) (*PathState, error) { - state, found := c.Paths[fingerprint] - if !found { - return nil, serrors.New("No state found", "pathFingerprint", fingerprint) - } - return state, nil -} - -func (c *Client) SetValidationRatio(newRatio uint8) { - for _, pathState := range c.Paths { - if pathState.ValidationRatio != newRatio { - pathState.ValidationRatio = newRatio - pathState.UpdateValRatio = true - } - } - c.validationRatio = newRatio -} - -func NewFabridServer(local *snet.UDPAddr, sdConn daemon.Connector) *Server { - server := &Server{ - Local: *local, - sdConn: sdConn, - Connections: make(map[string]*ClientConnection), - ASKeyCache: make(map[addr.IA]drkey.HostASKey), - ValidationHandler: func(_ *ClientConnection, _ *extension.IdentifierOption, _ bool) error { - return nil - }, - } - return server -} - -func (s *Server) FetchHostHostKey(dstHost snet.SCIONAddress, validity time.Time) (drkey.Key, error) { - meta := drkey.HostHostMeta{ - Validity: validity, - SrcIA: s.Local.IA, - SrcHost: s.Local.Host.IP.String(), - DstIA: dstHost.IA, - DstHost: dstHost.Host.IP().String(), - ProtoId: drkey.FABRID, - } - hostHostKey, err := s.sdConn.DRKeyGetHostHostKey(context.Background(), meta) - if err != nil { - return drkey.Key{}, serrors.WrapStr("getting host key", err) - } - return hostHostKey.Key, nil -} - -func (s *Server) HandleFabridPacket(remote snet.SCIONAddress, fabridOption *extension.FabridOption, - identifierOption *extension.IdentifierOption, - controlOptions []*extension.FabridControlOption) (*slayers.EndToEndExtn, error) { - client, found := s.Connections[remote.String()] - if !found { - pathKey, err := s.FetchHostHostKey(remote, identifierOption.Timestamp) - if err != nil { - return nil, err - } - client = &ClientConnection{ - Source: remote, - ValidationRatio: 255, - Stats: Statistics{}, - fabridControlBuffer: make([]byte, 28*3), - tmpBuffer: make([]byte, 192), - pathKey: pathKey, - } - s.Connections[remote.String()] = client - log.Info("Opened new connection", "remote", remote.String()) - } - - client.Stats.TotalPackets++ - validationNumber, validationReply, success, err := crypto.VerifyPathValidator(fabridOption, - client.tmpBuffer, client.pathKey[:]) - if err != nil { - return nil, err - } - err = s.ValidationHandler(client, identifierOption, success) - if err != nil { - return nil, err - } - - var replyOpts []*extension.FabridControlOption - for _, controlOption := range controlOptions { - err = crypto.VerifyFabridControlValidator(controlOption, identifierOption, - client.pathKey[:]) - if err != nil { - return nil, err - } - controlReplyOpt := &extension.FabridControlOption{} - ts, _ := controlOption.Timestamp() - controlReplyOpt.SetTimestamp(ts) - packetID, _ := controlOption.PacketID() - controlReplyOpt.SetPacketID(packetID) - replyOpts = append(replyOpts, controlReplyOpt) - - switch controlOption.Type { - case extension.ValidationConfig: - requestedRatio, err := controlOption.ValidationRatio() - if err != nil { - return nil, err - } - if requestedRatio > s.MaxValidationRatio { - log.Debug("FABRID control: requested ratio too large", "requested", requestedRatio, - "max", s.MaxValidationRatio) - requestedRatio = s.MaxValidationRatio - } - log.Debug("FABRID control: updated validation ratio", "new", requestedRatio, - "old", client.ValidationRatio) - client.ValidationRatio = requestedRatio - - // Prepare ACK - controlReplyOpt.Type = extension.ValidationConfigAck - controlReplyOpt.Data = make([]byte, 9) - err = controlReplyOpt.SetValidationRatio(client.ValidationRatio) - if err != nil { - return nil, err - } - case extension.StatisticsRequest: - log.Debug("FABRID control: statistics request") - // Prepare statistics reply - controlReplyOpt.Type = extension.StatisticsResponse - controlReplyOpt.Data = make([]byte, 24) - err := controlReplyOpt.SetStatistics(client.Stats.TotalPackets, - client.Stats.InvalidPackets) - if err != nil { - return nil, err - } - } - } - if validationNumber < client.ValidationRatio { - log.Debug("Send validation response", "packetID", identifierOption.PacketID) - validationReplyOpt := &extension.FabridControlOption{} - validationReplyOpt.SetTimestamp(identifierOption.GetRelativeTimestamp()) - validationReplyOpt.SetPacketID(identifierOption.PacketID) - replyOpts = append(replyOpts, validationReplyOpt) - validationReplyOpt.Type = extension.ValidationResponse - validationReplyOpt.Data = make([]byte, 20) - // TODO: Remove testing code - randInt := make([]byte, 1) - crand.Read(randInt) - if randInt[0] < SERVER_FLAKINESS { - validationReply ^= 0xFFFFFFFF - } - err = validationReplyOpt.SetPathValidatorReply(validationReply) - if err != nil { - return nil, err - } - } - - if len(replyOpts) > 0 { - e2eExt := &slayers.EndToEndExtn{} - for i, replyOpt := range replyOpts { - err = crypto.InitFabridControlValidator(replyOpt, identifierOption, client.pathKey[:]) - if err != nil { - return nil, err - } - buffer := client.fabridControlBuffer[i*28 : (i+1)*28] - err = replyOpt.SerializeTo(buffer) - if err != nil { - return nil, err - } - fabridReplyOptionLength := extension.BaseFabridControlLen + - extension.FabridControlOptionDataLen(replyOpt.Type) - e2eExt.Options = append(e2eExt.Options, - &slayers.EndToEndOption{ - OptType: slayers.OptTypeFabridControl, - OptData: buffer, - OptDataLen: uint8(fabridReplyOptionLength), - ActualLength: fabridReplyOptionLength, - }) - } - return e2eExt, nil - } - return nil, nil -} - -func (c *Client) RenewPathKey(t time.Time) error { - if c.PathKey.Epoch.NotAfter.Before(t) { - // key is expired, renew it - newKey, err := c.FetchHostHostKey(t) - if err != nil { - return err - } - c.PathKey = newKey - } - return nil -} - -func (c *Client) FetchHostHostKey(validity time.Time) (drkey.HostHostKey, error) { - meta := drkey.HostHostMeta{ - Validity: validity, - SrcIA: c.Config.DestinationIA, - SrcHost: c.Config.DestinationAddr, - DstIA: c.Config.LocalIA, - DstHost: c.Config.LocalAddr, - ProtoId: drkey.FABRID, - } - hostHostKey, err := c.SDConn.DRKeyGetHostHostKey(context.Background(), meta) - if err != nil { - return drkey.HostHostKey{}, serrors.WrapStr("getting host key", err) - } - return hostHostKey, nil -} - -func (c *Client) HandleFabridControlOption(fp snet.PathFingerprint, - controlOption *extension.FabridControlOption, - identifierOption *extension.IdentifierOption) error { - - err := crypto.VerifyFabridControlValidator(controlOption, identifierOption, c.PathKey.Key[:]) - if err != nil { - return err - } - ps := c.Paths[fp] - - switch controlOption.Type { - case extension.ValidationConfigAck: - confirmedRatio, err := controlOption.ValidationRatio() - if err != nil { - return err - } - if confirmedRatio == ps.ValidationRatio { - log.Debug("FABRID control: validation ratio confirmed", "ratio", confirmedRatio) - } else if confirmedRatio < ps.ValidationRatio { - log.Info("FABRID control: validation ratio reduced by server", - "requested", ps.ValidationRatio, "confirmed", confirmedRatio) - ps.ValidationRatio = confirmedRatio - } - case extension.ValidationResponse: - err = c.CheckValidationResponse(fp, controlOption) - if err != nil { - return err - } - err = c.Config.ValidationHandler(ps, controlOption, true) - if err != nil { - return err - } - //log.Debug("FABRID control: validation response", - //"packetID", controlOption.PacketID, "success", success) - - case extension.StatisticsResponse: - totalPkts, invalidPkts, err := controlOption.Statistics() - if err != nil { - return err - } - log.Info("FABRID control: statistics response", "totalPackets", totalPkts, - "invalidPackets", invalidPkts) - } - return nil -} - -func (c *Client) StoreValidationResponse(fp snet.PathFingerprint, validator uint32, +func (ps *PathState) StoreValidationResponse(validator uint32, timestamp uint32, packetID uint32) error { - ps := c.Paths[fp] valIdent := validationIdentifier{ timestamp: timestamp, packetId: packetID, @@ -376,13 +62,12 @@ func (c *Client) StoreValidationResponse(fp snet.PathFingerprint, validator uint if found { return serrors.New("Validation response already stored", "validationIdentifier", valIdent) } - log.Debug("Storing validation response", "packetID", packetID) + log.Debug("Storing validation response", "packetID", packetID, "timestamp", timestamp) ps.expectedValResponses[valIdent] = validator return nil } -func (c *Client) CheckValidationResponse(fp snet.PathFingerprint, - fco *extension.FabridControlOption) error { +func (ps *PathState) CheckValidationResponse(fco *extension.FabridControlOption) error { timestamp, err := fco.Timestamp() if err != nil { return err @@ -395,12 +80,11 @@ func (c *Client) CheckValidationResponse(fp snet.PathFingerprint, if err != nil { return err } - ps := c.Paths[fp] valIdent := validationIdentifier{ timestamp: timestamp, packetId: packetID, } - //log.Debug("Checking validation response", "timestamp", timestamp, "packetID", packetID) + log.Debug("Checking validation response", "timestamp", timestamp, "packetID", packetID) validatorStored, found := ps.expectedValResponses[valIdent] if !found { return serrors.New("Unknown validation response", "validationIdentifier", valIdent) @@ -411,9 +95,3 @@ func (c *Client) CheckValidationResponse(fp snet.PathFingerprint, } return nil } - -func (c *Client) RequestStatistics() { - for _, state := range c.Paths { - state.RequestStatistics = true - } -} diff --git a/pkg/experimental/fabrid/crypto/fabrid_crypto_test.go b/pkg/experimental/fabrid/crypto/fabrid_crypto_test.go index 7bf2cdbf1f..a9c2fa01d1 100644 --- a/pkg/experimental/fabrid/crypto/fabrid_crypto_test.go +++ b/pkg/experimental/fabrid/crypto/fabrid_crypto_test.go @@ -115,7 +115,7 @@ func TestFailedValidation(t *testing.T) { } } - err := crypto.InitValidators(f, id, s, tmpBuffer, pathKey, asHostKeys, + _, _, err := crypto.InitValidators(f, id, s, tmpBuffer, pathKey, asHostKeys, asAsKeys, hops) assert.NoError(t, err) @@ -250,7 +250,7 @@ func TestSuccessfullValidators(t *testing.T) { } } - err := crypto.InitValidators(f, id, s, tmpBuffer, pathKey, asHostKeys, + _, _, err := crypto.InitValidators(f, id, s, tmpBuffer, pathKey, asHostKeys, asAsKeys, hops) assert.NoError(t, err) diff --git a/pkg/experimental/fabrid/defs.go b/pkg/experimental/fabrid/defs.go index 23a6ca3269..c08e06e96a 100644 --- a/pkg/experimental/fabrid/defs.go +++ b/pkg/experimental/fabrid/defs.go @@ -14,7 +14,9 @@ package fabrid -import "fmt" +import ( + "fmt" +) type PolicyID uint8 diff --git a/pkg/experimental/fabrid/server/BUILD.bazel b/pkg/experimental/fabrid/server/BUILD.bazel new file mode 100644 index 0000000000..e89fc0c970 --- /dev/null +++ b/pkg/experimental/fabrid/server/BUILD.bazel @@ -0,0 +1,20 @@ +load("//tools/lint:go.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["server.go"], + importpath = "github.com/scionproto/scion/pkg/experimental/fabrid/server", + visibility = ["//visibility:public"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/daemon:go_default_library", + "//pkg/drkey:go_default_library", + "//pkg/experimental/fabrid/common:go_default_library", + "//pkg/experimental/fabrid/crypto:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/slayers:go_default_library", + "//pkg/slayers/extension:go_default_library", + "//pkg/snet:go_default_library", + ], +) diff --git a/pkg/experimental/fabrid/server/server.go b/pkg/experimental/fabrid/server/server.go new file mode 100644 index 0000000000..25f7caeca7 --- /dev/null +++ b/pkg/experimental/fabrid/server/server.go @@ -0,0 +1,222 @@ +// Copyright 2021 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 server + +import ( + "context" + crand "crypto/rand" + "time" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/daemon" + "github.com/scionproto/scion/pkg/drkey" + "github.com/scionproto/scion/pkg/experimental/fabrid/common" + "github.com/scionproto/scion/pkg/experimental/fabrid/crypto" + "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/extension" + "github.com/scionproto/scion/pkg/snet" +) + +type ClientConnection struct { + Source snet.SCIONAddress + ValidationRatio uint8 + Stats common.Statistics + fabridControlBuffer []byte + tmpBuffer []byte + pathKey drkey.Key +} + +type Server struct { + Local snet.UDPAddr + sdConn daemon.Connector + Connections map[string]*ClientConnection + ASKeyCache map[addr.IA]drkey.HostASKey + MaxValidationRatio uint8 + ValidationHandler func(*ClientConnection, *extension.IdentifierOption, bool) error +} + +func NewFabridServer(local *snet.UDPAddr, sdConn daemon.Connector) *Server { + server := &Server{ + Local: *local, + sdConn: sdConn, + Connections: make(map[string]*ClientConnection), + ASKeyCache: make(map[addr.IA]drkey.HostASKey), + ValidationHandler: func(_ *ClientConnection, _ *extension.IdentifierOption, _ bool) error { + return nil + }, + MaxValidationRatio: 255, + } + return server +} + +func (s *Server) FetchHostHostKey(dstHost snet.SCIONAddress, + validity time.Time) (drkey.Key, error) { + meta := drkey.HostHostMeta{ + Validity: validity, + SrcIA: s.Local.IA, + SrcHost: s.Local.Host.IP.String(), + DstIA: dstHost.IA, + DstHost: dstHost.Host.IP().String(), + ProtoId: drkey.FABRID, + } + hostHostKey, err := s.sdConn.DRKeyGetHostHostKey(context.Background(), meta) + if err != nil { + return drkey.Key{}, serrors.WrapStr("getting host key", err) + } + return hostHostKey.Key, nil +} + +func (s *Server) HandleFabridPacket(remote snet.SCIONAddress, fabridOption *extension.FabridOption, + identifierOption *extension.IdentifierOption, + controlOptions []*extension.FabridControlOption) (*slayers.EndToEndExtn, error) { + client, found := s.Connections[remote.String()] + if !found { + pathKey, err := s.FetchHostHostKey(remote, identifierOption.Timestamp) + if err != nil { + return nil, err + } + client = &ClientConnection{ + Source: remote, + ValidationRatio: 255, + Stats: common.Statistics{}, + fabridControlBuffer: make([]byte, 28*3), + tmpBuffer: make([]byte, 192), + pathKey: pathKey, + } + s.Connections[remote.String()] = client + log.Info("Opened new connection", "remote", remote.String()) + } + + client.Stats.TotalPackets++ + validationNumber, validationReply, success, err := crypto.VerifyPathValidator(fabridOption, + client.tmpBuffer, client.pathKey[:]) + if err != nil { + return nil, err + } + err = s.ValidationHandler(client, identifierOption, success) + if err != nil { + return nil, err + } + + var replyOpts []*extension.FabridControlOption + for _, controlOption := range controlOptions { + err = crypto.VerifyFabridControlValidator(controlOption, identifierOption, + client.pathKey[:]) + if err != nil { + return nil, err + } + controlReplyOpt := &extension.FabridControlOption{} + + switch controlOption.Type { + case extension.ValidationConfig: + requestedRatio, err := controlOption.ValidationRatio() + if err != nil { + return nil, err + } + if requestedRatio > s.MaxValidationRatio { + log.Debug("FABRID control: requested ratio too large", "requested", requestedRatio, + "max", s.MaxValidationRatio) + requestedRatio = s.MaxValidationRatio + } + log.Debug("FABRID control: updated validation ratio", "new", requestedRatio, + "old", client.ValidationRatio) + client.ValidationRatio = requestedRatio + + // Prepare ACK + controlReplyOpt.Type = extension.ValidationConfigAck + controlReplyOpt.Data = make([]byte, 9) + err = controlReplyOpt.SetValidationRatio(client.ValidationRatio) + if err != nil { + return nil, err + } + case extension.StatisticsRequest: + log.Debug("FABRID control: statistics request") + // Prepare statistics reply + controlReplyOpt.Type = extension.StatisticsResponse + controlReplyOpt.Data = make([]byte, 24) + err := controlReplyOpt.SetStatistics(client.Stats.TotalPackets, + client.Stats.InvalidPackets) + if err != nil { + return nil, err + } + } + ts, _ := controlOption.Timestamp() + err = controlReplyOpt.SetTimestamp(ts) + if err != nil { + return nil, err + } + packetID, _ := controlOption.PacketID() + err = controlReplyOpt.SetPacketID(packetID) + if err != nil { + return nil, err + } + replyOpts = append(replyOpts, controlReplyOpt) + } + if validationNumber < client.ValidationRatio { + log.Debug("Send validation response", "packetID", identifierOption.PacketID, + "timestamp", identifierOption.GetRelativeTimestamp()) + validationReplyOpt := extension.NewFabridControlOption(extension.ValidationResponse) + err = validationReplyOpt.SetTimestamp(identifierOption.GetRelativeTimestamp()) + if err != nil { + return nil, err + } + err = validationReplyOpt.SetPacketID(identifierOption.PacketID) + if err != nil { + return nil, err + } + replyOpts = append(replyOpts, validationReplyOpt) + // TODO: Remove testing code + randInt := make([]byte, 1) + _, err2 := crand.Read(randInt) + if err2 != nil { + return nil, err2 + } + if randInt[0] < common.SERVER_FLAKINESS { + validationReply ^= 0xFFFFFFFF + } + err = validationReplyOpt.SetPathValidatorReply(validationReply) + if err != nil { + return nil, err + } + } + + if len(replyOpts) > 0 { + e2eExt := &slayers.EndToEndExtn{} + for i, replyOpt := range replyOpts { + err = crypto.InitFabridControlValidator(replyOpt, identifierOption, client.pathKey[:]) + if err != nil { + return nil, err + } + buffer := client.fabridControlBuffer[i*28 : (i+1)*28] + err = replyOpt.SerializeTo(buffer) + if err != nil { + return nil, err + } + fabridReplyOptionLength := extension.BaseFabridControlLen + + extension.FabridControlOptionDataLen(replyOpt.Type) + e2eExt.Options = append(e2eExt.Options, + &slayers.EndToEndOption{ + OptType: slayers.OptTypeFabridControl, + OptData: buffer, + OptDataLen: uint8(fabridReplyOptionLength), + ActualLength: fabridReplyOptionLength, + }) + } + return e2eExt, nil + } + return nil, nil +} diff --git a/pkg/slayers/extension/BUILD.bazel b/pkg/slayers/extension/BUILD.bazel index 17223b7cbf..c5d96b55c0 100644 --- a/pkg/slayers/extension/BUILD.bazel +++ b/pkg/slayers/extension/BUILD.bazel @@ -18,8 +18,8 @@ go_library( go_test( name = "go_default_test", srcs = [ - "fabrid_test.go", "fabrid_control_test.go", + "fabrid_test.go", "identifier_test.go", ], deps = [ diff --git a/pkg/slayers/extension/fabrid_control.go b/pkg/slayers/extension/fabrid_control.go index 671b7f0d32..8c739f2587 100644 --- a/pkg/slayers/extension/fabrid_control.go +++ b/pkg/slayers/extension/fabrid_control.go @@ -27,6 +27,7 @@ package extension import ( "encoding/binary" + "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/slayers" ) @@ -52,6 +53,14 @@ type FabridControlOption struct { Data []byte } +func NewFabridControlOption(t FabridControlOptionType) *FabridControlOption { + return &FabridControlOption{ + Type: t, + Auth: [4]byte{}, + Data: make([]byte, BaseFabridControlLen+FabridControlOptionDataLen(t)), + } +} + // Validates the length of FabridControlOption. Requires Type to be set func (fc *FabridControlOption) validate(b []byte) error { if fc == nil { diff --git a/pkg/slayers/extension/fabrid_control_test.go b/pkg/slayers/extension/fabrid_control_test.go index be15adf410..396d691650 100644 --- a/pkg/slayers/extension/fabrid_control_test.go +++ b/pkg/slayers/extension/fabrid_control_test.go @@ -15,10 +15,12 @@ package extension_test import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/scionproto/scion/pkg/slayers" "github.com/scionproto/scion/pkg/slayers/extension" - "github.com/stretchr/testify/assert" - "testing" ) func TestFabridControlDecode(t *testing.T) { diff --git a/pkg/snet/path/BUILD.bazel b/pkg/snet/path/BUILD.bazel index b85e34cef0..1115714a9b 100644 --- a/pkg/snet/path/BUILD.bazel +++ b/pkg/snet/path/BUILD.bazel @@ -19,6 +19,7 @@ go_library( "//pkg/experimental/fabrid:go_default_library", "//pkg/experimental/fabrid/common:go_default_library", "//pkg/experimental/fabrid/crypto:go_default_library", + "//pkg/log:go_default_library", "//pkg/private/serrors:go_default_library", "//pkg/private/util:go_default_library", "//pkg/slayers:go_default_library", diff --git a/pkg/snet/path/fabrid.go b/pkg/snet/path/fabrid.go index f80a66d9ac..186b7bfb2b 100644 --- a/pkg/snet/path/fabrid.go +++ b/pkg/snet/path/fabrid.go @@ -16,13 +16,14 @@ package path import ( "context" - "github.com/scionproto/scion/pkg/experimental/fabrid/common" "time" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/drkey" "github.com/scionproto/scion/pkg/experimental/fabrid" + "github.com/scionproto/scion/pkg/experimental/fabrid/common" "github.com/scionproto/scion/pkg/experimental/fabrid/crypto" + "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/slayers" "github.com/scionproto/scion/pkg/slayers/extension" @@ -31,10 +32,11 @@ import ( ) type FabridConfig struct { - LocalIA addr.IA - LocalAddr string - DestinationIA addr.IA - DestinationAddr string + LocalIA addr.IA + LocalAddr string + DestinationIA addr.IA + DestinationAddr string + ValidationHandler func(*common.PathState, *extension.FabridControlOption, bool) error } type FABRID struct { @@ -48,15 +50,15 @@ type FABRID struct { tmpBuffer []byte identifierBuffer []byte fabridBuffer []byte + e2eBuffer []byte policyIDs []*fabrid.PolicyID numHops int hops []snet.HopInterface - client *common.Client - fingerprint snet.PathFingerprint + pathState *common.PathState } func NewFABRIDDataplanePath(p SCION, hops []snet.HopInterface, policyIDs []*fabrid.PolicyID, - conf *FabridConfig, client *common.Client, fingerprint snet.PathFingerprint) (*FABRID, error) { + conf *FabridConfig, validationRatio uint8) (*FABRID, error) { numHops := len(hops) var decoded scion.Decoded if err := decoded.DecodeFromBytes(p.Raw); err != nil { @@ -82,10 +84,10 @@ func NewFABRIDDataplanePath(p SCION, hops []snet.HopInterface, policyIDs []*fabr tmpBuffer: make([]byte, 64), identifierBuffer: make([]byte, 8), fabridBuffer: make([]byte, 8+4*numHops), + e2eBuffer: make([]byte, 5*2), Raw: append([]byte(nil), p.Raw...), policyIDs: policyIDs, - client: client, - fingerprint: fingerprint, + pathState: common.NewFabridPathState(validationRatio), } // Get ingress/egress IFs and IAs from path interfaces @@ -151,8 +153,8 @@ func (f *FABRID) SetExtensions(s *slayers.SCION, p *snet.PacketInfo) error { } fabridOption.HopfieldMetadata[i] = meta } - valNumber, pathValReply, err := crypto.InitValidators(fabridOption, identifierOption, s, f.tmpBuffer, f.pathKey.Key[:], - f.keys, nil, f.hops) + valNumber, pathValReply, err := crypto.InitValidators(fabridOption, identifierOption, s, + f.tmpBuffer, f.pathKey, f.keys, nil, f.hops) if err != nil { return serrors.WrapStr("initializing validators failed", err) } @@ -179,42 +181,43 @@ func (f *FABRID) SetExtensions(s *slayers.SCION, p *snet.PacketInfo) error { ActualLength: fabridLength, }) - pathState, _ := f.client.GetFabridPathState(f.fingerprint) - - if valNumber < pathState.ValidationRatio { - err = f.client.StoreValidationResponse(f.fingerprint, pathValReply, identifierOption.GetRelativeTimestamp(), f.counter) + if valNumber < f.pathState.ValidationRatio { + err = f.pathState.StoreValidationResponse(pathValReply, + identifierOption.GetRelativeTimestamp(), + f.counter) if err != nil { return err } } var e2eOpts []*extension.FabridControlOption - if pathState.UpdateValRatio { - valConfigOption := &extension.FabridControlOption{ - Type: extension.ValidationConfig, - Auth: [4]byte{}, - Timestamp: identifierOption.GetRelativeTimestamp(), - PacketID: identifierOption.PacketID, - Data: make([]byte, 1), - } - err = valConfigOption.SetValidationRatio(pathState.ValidationRatio) + if f.pathState.UpdateValRatio { + valConfigOption := extension.NewFabridControlOption(extension.ValidationConfig) + err = valConfigOption.SetValidationRatio(f.pathState.ValidationRatio) if err != nil { return err } e2eOpts = append(e2eOpts, valConfigOption) - pathState.UpdateValRatio = false - log.Debug("FABRID control: outgoing validation config", "valRatio", pathState.ValidationRatio) + f.pathState.UpdateValRatio = false + log.Debug("FABRID control: outgoing validation config", + "valRatio", f.pathState.ValidationRatio) } - if pathState.RequestStatistics { + if f.pathState.RequestStatistics { statisticsRequestOption := &extension.FabridControlOption{ - Type: extension.StatisticsRequest, - Auth: [4]byte{}, - Timestamp: identifierOption.GetRelativeTimestamp(), - PacketID: identifierOption.PacketID, + Type: extension.StatisticsRequest, + Auth: [4]byte{}, + } + err = statisticsRequestOption.SetTimestamp(identifierOption.GetRelativeTimestamp()) + if err != nil { + return err + } + err = statisticsRequestOption.SetPacketID(identifierOption.PacketID) + if err != nil { + return err } e2eOpts = append(e2eOpts, statisticsRequestOption) - pathState.RequestStatistics = false + f.pathState.RequestStatistics = false log.Debug("FABRID control: sending statistics request") } if len(e2eOpts) > 0 { @@ -222,7 +225,7 @@ func (f *FABRID) SetExtensions(s *slayers.SCION, p *snet.PacketInfo) error { p.E2eExtension = &slayers.EndToEndExtn{} } for i, replyOpt := range e2eOpts { - err = crypto.InitFabridControlValidator(replyOpt, f.client.PathKey.Key[:]) + err = crypto.InitFabridControlValidator(replyOpt, identifierOption, f.pathKey.Key[:]) if err != nil { return err } @@ -231,7 +234,8 @@ func (f *FABRID) SetExtensions(s *slayers.SCION, p *snet.PacketInfo) error { if err != nil { return err } - fabridReplyOptionLength := extension.BaseFabridControlLen + extension.FabridControlOptionDataLen(replyOpt.Type) + fabridReplyOptionLength := extension.BaseFabridControlLen + + extension.FabridControlOptionDataLen(replyOpt.Type) p.E2eExtension.Options = append(p.E2eExtension.Options, &slayers.EndToEndOption{ OptType: slayers.OptTypeFabridControl, @@ -243,7 +247,50 @@ func (f *FABRID) SetExtensions(s *slayers.SCION, p *snet.PacketInfo) error { } f.counter++ - pathState.Stats.TotalPackets++ + f.pathState.Stats.TotalPackets++ + return nil +} + +func (f *FABRID) HandleFabridControlOption(controlOption *extension.FabridControlOption, + identifierOption *extension.IdentifierOption) error { + + err := crypto.VerifyFabridControlValidator(controlOption, identifierOption, f.pathKey.Key[:]) + if err != nil { + return err + } + switch controlOption.Type { + case extension.ValidationConfigAck: + confirmedRatio, err := controlOption.ValidationRatio() + if err != nil { + return err + } + if confirmedRatio == f.pathState.ValidationRatio { + log.Debug("FABRID control: validation ratio confirmed", "ratio", confirmedRatio) + } else if confirmedRatio < f.pathState.ValidationRatio { + log.Info("FABRID control: validation ratio reduced by server", + "requested", f.pathState.ValidationRatio, "confirmed", confirmedRatio) + f.pathState.ValidationRatio = confirmedRatio + } + case extension.ValidationResponse: + err = f.pathState.CheckValidationResponse(controlOption) + if err != nil { + return err + } + err = f.conf.ValidationHandler(f.pathState, controlOption, true) + if err != nil { + return err + } + //log.Debug("FABRID control: validation response", + //"packetID", controlOption.PacketID, "success", success) + + case extension.StatisticsResponse: + totalPkts, invalidPkts, err := controlOption.Statistics() + if err != nil { + return err + } + log.Info("FABRID control: statistics response", "totalPackets", totalPkts, + "invalidPackets", invalidPkts) + } return nil } diff --git a/private/app/path/path.go b/private/app/path/path.go index 921f1c4e3f..62585e1750 100644 --- a/private/app/path/path.go +++ b/private/app/path/path.go @@ -123,7 +123,7 @@ func Choose( continue } fabridPath, err := snetpath.NewFABRIDDataplanePath(scionPath, p.Metadata().Hops(), - pols.Policies(), &o.fabrid.FabridConfig) + pols.Policies(), &o.fabrid.FabridConfig, 0) if err != nil { return nil, serrors.WrapStr("creating fabrid path from scion path", err) } diff --git a/tools/end2end/BUILD.bazel b/tools/end2end/BUILD.bazel index e86cc170bc..5cf4db5024 100644 --- a/tools/end2end/BUILD.bazel +++ b/tools/end2end/BUILD.bazel @@ -9,10 +9,9 @@ go_library( deps = [ "//pkg/addr:go_default_library", "//pkg/daemon:go_default_library", - "//pkg/drkey:go_default_library", "//pkg/experimental/fabrid:go_default_library", "//pkg/experimental/fabrid/common:go_default_library", - "//pkg/experimental/fabrid/crypto:go_default_library", + "//pkg/experimental/fabrid/server:go_default_library", "//pkg/log:go_default_library", "//pkg/private/common:go_default_library", "//pkg/private/serrors:go_default_library", diff --git a/tools/end2end/main.go b/tools/end2end/main.go index 8d5c32b81b..1c477a2ab3 100644 --- a/tools/end2end/main.go +++ b/tools/end2end/main.go @@ -28,8 +28,8 @@ import ( "errors" "flag" "fmt" - common2 "github.com/scionproto/scion/pkg/experimental/fabrid/common" "net" + "net/netip" "os" "time" @@ -39,6 +39,8 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon" libfabrid "github.com/scionproto/scion/pkg/experimental/fabrid" + common2 "github.com/scionproto/scion/pkg/experimental/fabrid/common" + fabridserver "github.com/scionproto/scion/pkg/experimental/fabrid/server" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/serrors" @@ -137,7 +139,7 @@ func validateFlags() { } type server struct { - fabridServer *common2.Server + fabridServer *fabridserver.Server } func (s server) run() { @@ -168,8 +170,9 @@ func (s server) run() { } log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host.IP, localAddr.Port)) - s.fabridServer = common2.NewFabridServer(&integration.Local, integration.SDConn()) - s.fabridServer.ValidationHandler = func(connection *common2.ClientConnection, option *extension.IdentifierOption, b bool) error { + s.fabridServer = fabridserver.NewFabridServer(&integration.Local, integration.SDConn()) + s.fabridServer.ValidationHandler = func(connection *fabridserver.ClientConnection, + option *extension.IdentifierOption, b bool) error { log.Debug("Validation handler", "connection", connection, "success", b) if !b { return serrors.New("Failed validation") @@ -324,7 +327,8 @@ func (s server) handlePingFabrid(conn snet.PacketConn) error { if fabridOption == nil { return serrors.New("Missing FABRID option") } - valResponse, err = s.fabridServer.HandleFabridPacket(p.Source, fabridOption, identifierOption, controlOptions) + valResponse, err = s.fabridServer.HandleFabridPacket(p.Source, fabridOption, + identifierOption, controlOptions) if err != nil { return err } @@ -416,6 +420,7 @@ func (s server) handlePingFabrid(conn snet.PacketConn) error { type client struct { network *snet.SCIONNetwork conn *snet.Conn + rawConn snet.PacketConn sdConn daemon.Connector errorPaths map[snet.PathFingerprint]struct{} @@ -468,20 +473,42 @@ func (c *client) attemptRequest(n int) bool { return err } - // 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{}{} + if fabrid && remote.IA != integration.Local.IA { + for i := 0; i < 10; i++ { + + // Send ping + close, err := c.fabridPing(ctx, n, path) + if err != nil { + logger.Error("Could not send packet", "err", withTag(err)) + return false + } + defer close() + // Receive FABRID pong + if err := c.fabridPong(ctx); err != nil { + logger.Error("Error receiving pong", "err", withTag(err)) + if path != nil { + c.errorPaths[snet.Fingerprint(path)] = struct{}{} + } + return false + } + } + } else { + // 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 false } return true } @@ -515,6 +542,56 @@ func (c *client) ping(ctx context.Context, n int, path snet.Path) (func(), error return closer, nil } +func (c *client) fabridPing(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.rawConn, err = c.network.OpenRaw(ctx, integration.Local.Host) + if err != nil { + return nil, serrors.WrapStr("dialing conn", err) + } + if err := c.rawConn.SetWriteDeadline(getDeadline(ctx)); err != nil { + return nil, serrors.WrapStr("setting write deadline", err) + } + log.Info("sending ping", "attempt", n, "remote", remote, "local", c.rawConn.LocalAddr()) + localAddr := c.rawConn.LocalAddr().(*net.UDPAddr) + hostIP, _ := netip.AddrFromSlice(remote.Host.IP) + dst := snet.SCIONAddress{IA: remote.IA, Host: addr.HostIP(hostIP)} + localHostIP, _ := netip.AddrFromSlice(integration.Local.Host.IP) + pkt := &snet.Packet{ + Bytes: make([]byte, common.SupportedMTU), + PacketInfo: snet.PacketInfo{ + Destination: dst, + Source: snet.SCIONAddress{ + IA: integration.Local.IA, + Host: addr.HostIP(localHostIP), + }, + Path: remote.Path, + Payload: snet.UDPPayload{ + SrcPort: uint16(localAddr.Port), + DstPort: uint16(remote.Host.Port), + Payload: rawPing, + }, + }, + } + log.Info("sending packet", "packet", pkt) + if err := c.rawConn.WriteTo(pkt, remote.NextHop); err != nil { + return nil, err + } + closer := func() { + if err := c.rawConn.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{} @@ -579,6 +656,14 @@ func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) { DestinationIA: remote.IA, DestinationAddr: remote.Host.IP.String(), } + fabridConfig.ValidationHandler = func(ps *common2.PathState, + option *extension.FabridControlOption, b bool) error { + log.Debug("Validation handler", "pathState", ps, "success", b) + if !b { + return serrors.New("Failed validation") + } + return nil + } hops := path.Metadata().Hops() log.Info("Fabrid path", "path", path, "hops", hops) // Use ZERO policy for all hops with fabrid, to just do path validation @@ -590,28 +675,13 @@ func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) { } } fabridPath, err := snetpath.NewFABRIDDataplanePath(scionPath, hops, - policies, fabridConfig) + policies, fabridConfig, 125) if err != nil { return nil, serrors.New("Error creating FABRID path", "err", err) } remote.Path = fabridPath fabridPath.RegisterDRKeyFetcher(c.sdConn.FabridKeys) - polIdentifier := libfabrid.Policy{ - IsLocal: false, - Identifier: 0, - Index: 0, - } - clientConfig := common2.SimpleFabridConfig{ - DestinationIA: remote.IA, - DestinationAddr: remote.Host.IP.String(), - LocalIA: integration.Local.IA, - LocalAddr: integration.Local.Host.IP.String(), - ValidationRatio: 255, - Policy: polIdentifier, - ValidationHandler: nil, - } - common2.NewFabridClient(remote, clientConfig, integration.SDConn()) } else { log.Info("FABRID flag was set for client in non-FABRID AS. Proceeding without FABRID.") remote.Path = path.Dataplane() @@ -650,6 +720,74 @@ func (c *client) pong(ctx context.Context) error { return nil } +func (c *client) fabridPong(ctx context.Context) error { + + if err := c.rawConn.SetReadDeadline(getDeadline(ctx)); err != nil { + return serrors.WrapStr("setting read deadline", err) + } + var p snet.Packet + var ov net.UDPAddr + err := readFromFabrid(c.rawConn, &p, &ov) + if err != nil { + return serrors.WrapStr("reading packet", err) + } + if p.Source.IA != integration.Local.IA { + // Check extensions for relevant options + var controlOptions []*extension.FabridControlOption + + if p.E2eExtension != nil { + + for _, opt := range p.E2eExtension.Options { + switch opt.OptType { + case slayers.OptTypeFabridControl: + controlOption, err := extension.ParseFabridControlOption(opt) + if err != nil { + return err + } + controlOptions = append(controlOptions, controlOption) + log.Debug("Parsed control option", "option", controlOption) + } + } + } + switch s := remote.Path.(type) { + case *snetpath.FABRID: + for _, option := range controlOptions { + err := s.HandleFabridControlOption(option, nil) + if err != nil { + return err + } + } + + default: + return serrors.New("unsupported path type") + } + } + + udp, ok := p.Payload.(snet.UDPPayload) + if !ok { + return serrors.New("unexpected payload received", + "source", p.Source, + "destination", p.Destination, + "type", common.TypeOf(p.Payload), + ) + } + var pld Pong + if err := json.Unmarshal(udp.Payload, &pld); err != nil { + return serrors.WrapStr("unpacking pong", err, "data", string(udp.Payload)) + } + + 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", ov) + return nil +} + func getDeadline(ctx context.Context) time.Time { dl, ok := ctx.Deadline() if !ok { From d68beba0326ca637e156b6a2df68d2a6365295c8 Mon Sep 17 00:00:00 2001 From: Marc Odermatt Date: Wed, 11 Sep 2024 11:20:41 +0200 Subject: [PATCH 5/6] fabrid demo copy --- demo/fabrid/BUILD.bazel | 49 ++++++ demo/fabrid/README.md | 26 ++++ demo/fabrid/main.go | 326 ++++++++++++++++++++++++++++++++++++++++ demo/fabrid/test.py | 151 +++++++++++++++++++ 4 files changed, 552 insertions(+) create mode 100644 demo/fabrid/BUILD.bazel create mode 100644 demo/fabrid/README.md create mode 100644 demo/fabrid/main.go create mode 100644 demo/fabrid/test.py diff --git a/demo/fabrid/BUILD.bazel b/demo/fabrid/BUILD.bazel new file mode 100644 index 0000000000..1479aa40e1 --- /dev/null +++ b/demo/fabrid/BUILD.bazel @@ -0,0 +1,49 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary") +load("//:scion.bzl", "scion_go_binary") +load("//acceptance/common:topogen.bzl", "topogen_test") +load("//tools/lint:go.bzl", "go_library") + +topogen_test( + name = "test", + src = "test.py", + args = [ + "--executable=drkey-demo:$(location //demo/drkey:drkey-demo)", + ], + data = ["//demo/drkey:drkey-demo"], + topo = "//topology:tiny4.topo", +) + +go_library( + name = "go_default_library", + srcs = ["main.go"], + importpath = "github.com/scionproto/scion/demo/drkey", + visibility = ["//visibility:private"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/daemon:go_default_library", + "//pkg/drkey:go_default_library", + "//pkg/drkey/generic:go_default_library", + "//pkg/drkey/specific:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/proto/control_plane:go_default_library", + "//pkg/proto/drkey:go_default_library", + "//pkg/scrypto/cppki:go_default_library", + "//pkg/snet:go_default_library", + "//private/app/flag:go_default_library", + "@com_github_spf13_pflag//:go_default_library", + "@org_golang_google_grpc//:go_default_library", + "@org_golang_google_protobuf//types/known/timestamppb:go_default_library", + ], +) + +scion_go_binary( + name = "drkey-demo", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) + +go_binary( + name = "drkey", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/demo/fabrid/README.md b/demo/fabrid/README.md new file mode 100644 index 0000000000..67eec9d2c9 --- /dev/null +++ b/demo/fabrid/README.md @@ -0,0 +1,26 @@ +# DRKey demo + +This demo shows how two hosts can obtain a shared key with the DRKey system. +The "server" side host can locally derive keys for any other host. +The slower "client" side host can fetch its corresponding key from +the DRKey infrastructure running in the control services. + +Note that in this demo, no data is transmitted between "client" and "server". +In a practical usage, the server would derive the key for the client's address +after receiving a packet from the client. + +The demo consists of the following steps: + +1. Enable and configure DRKey and start the topology. +1. Demonstrate the server side key derivation +1. Demonstrate the client side key fetching +1. Compare the keys + +## Run the demo + +1. [set up the development environment](https://docs.scion.org/en/latest/build/setup.html) +1. `bazel test --test_output=streamed --cache_test_results=no //demo/drkey:test` + +Note: this demo works on any SCION network topology. To run the demo on a +different network topology, modify the `topo` parameter in `BUILD.bazel` to +point to a different topology file. diff --git a/demo/fabrid/main.go b/demo/fabrid/main.go new file mode 100644 index 0000000000..3337fabd74 --- /dev/null +++ b/demo/fabrid/main.go @@ -0,0 +1,326 @@ +// Copyright 2020 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" + "encoding/hex" + "fmt" + "os" + "time" + + flag "github.com/spf13/pflag" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/daemon" + "github.com/scionproto/scion/pkg/drkey" + "github.com/scionproto/scion/pkg/drkey/generic" + "github.com/scionproto/scion/pkg/drkey/specific" + "github.com/scionproto/scion/pkg/private/serrors" + cppb "github.com/scionproto/scion/pkg/proto/control_plane" + dkpb "github.com/scionproto/scion/pkg/proto/drkey" + "github.com/scionproto/scion/pkg/scrypto/cppki" + "github.com/scionproto/scion/pkg/snet" + env "github.com/scionproto/scion/private/app/flag" +) + +func main() { + os.Exit(realMain()) +} + +func realMain() int { + var serverMode bool + var serverAddrStr, clientAddrStr string + var protocol uint16 + var fetchSV bool + var scionEnv env.SCIONEnvironment + + scionEnv.Register(flag.CommandLine) + flag.BoolVar(&serverMode, "server", false, "Demonstrate server-side key derivation."+ + " (default demonstrate client-side key fetching)") + flag.StringVar(&serverAddrStr, "server-addr", "", "SCION address for the server-side.") + flag.StringVar(&clientAddrStr, "client-addr", "", "SCION address for the client-side.") + flag.Uint16Var(&protocol, "protocol", 1 /* SCMP */, "DRKey protocol identifier.") + flag.BoolVar(&fetchSV, "fetch-sv", false, + "Fetch protocol specific secret value to derive server-side keys.") + flag.Parse() + if err := scionEnv.LoadExternalVars(); err != nil { + fmt.Fprintln(os.Stderr, "Error reading SCION environment:", err) + return 2 + } + + // NOTE: should parse addresses as snet.SCIONAddress not snet.UDPAddress, but + // these parsing functions don't exist yet. + serverAddr, err := snet.ParseUDPAddr(serverAddrStr) + if err != nil { + fmt.Fprintf(os.Stderr, "Invalid --server-addr '%s': %s\n", serverAddrStr, err) + return 2 + } + clientAddr, err := snet.ParseUDPAddr(clientAddrStr) + if err != nil { + fmt.Fprintf(os.Stderr, "Invalid --client-addr '%s': %s\n", clientAddrStr, err) + return 2 + } + + if !serverMode && fetchSV { + fmt.Fprintf(os.Stderr, "Invalid flag --fetch-sv for client-side key derivation\n") + return 2 + } + + ctx, cancelF := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelF() + + // meta describes the key that both client and server derive + meta := drkey.HostHostMeta{ + ProtoId: drkey.Protocol(protocol), + // Validity timestamp; both sides need to use a validity time stamp in the same epoch. + // Usually this is coordinated by means of a timestamp in the message. + Validity: time.Now(), + // SrcIA is the AS on the "fast side" of the DRKey derivation; + // the server side in this example. + SrcIA: serverAddr.IA, + // DstIA is the AS on the "slow side" of the DRKey derivation; + // the client side in this example. + DstIA: clientAddr.IA, + SrcHost: serverAddr.Host.IP.String(), + DstHost: clientAddr.Host.IP.String(), + } + + daemon, err := daemon.NewService(scionEnv.Daemon()).Connect(ctx) + if err != nil { + fmt.Fprintln(os.Stderr, "Error dialing SCION Daemon:", err) + return 1 + } + + if serverMode { + // Server: get the Secret Value (SV) for the protocol and derive all + // subsequent keys in-process + server := Server{daemon} + var serverKey drkey.HostHostKey + var t0, t1, t2 time.Time + if fetchSV { + // Fetch the Secret Value (SV); in a real application, this is only done at + // startup and refreshed for each epoch. + t0 = time.Now() + sv, err := server.FetchSV(ctx, drkey.SecretValueMeta{ + ProtoId: meta.ProtoId, + Validity: meta.Validity, + }) + if err != nil { + fmt.Fprintln(os.Stderr, "Error fetching secret value:", err) + return 1 + } + t1 = time.Now() + serverKey, err = server.DeriveHostHostKeySpecific(sv, meta) + if err != nil { + fmt.Fprintln(os.Stderr, "Error deriving key:", err) + return 1 + } + t2 = time.Now() + } else { + // Fetch host-AS key (Level 2). This key can be used to derive keys for + // all hosts in the destination AS. Depending on the application, it can + // be cached and refreshed for each epoch. + t0 = time.Now() + hostASKey, err := server.FetchHostASKey(ctx, drkey.HostASMeta{ + ProtoId: meta.ProtoId, + Validity: meta.Validity, + SrcIA: meta.SrcIA, + DstIA: meta.DstIA, + SrcHost: meta.SrcHost, + }) + if err != nil { + fmt.Fprintln(os.Stderr, "Error fetching host-AS key:", err) + return 1 + } + t1 = time.Now() + serverKey, err = server.DeriveHostHostKeyGeneric(hostASKey, meta) + if err != nil { + fmt.Fprintln(os.Stderr, "Error deriving key:", err) + return 1 + } + t2 = time.Now() + } + fmt.Printf( + "Server: host key = %s, protocol = %s, fetch-sv = %v"+ + "\n\tduration without cache: %s\n\tduration with cache: %s\n", + hex.EncodeToString(serverKey.Key[:]), meta.ProtoId, fetchSV, t2.Sub(t0), t2.Sub(t1), + ) + } else { + // Client: fetch key from daemon + // The daemon will in turn obtain the key from the local CS + // The CS will fetch the Lvl1 key from the CS in the SrcIA (the server's AS) + // and derive the Host key based on this. + client := Client{daemon} + var t0, t1 time.Time + t0 = time.Now() + clientKey, err := client.FetchHostHostKey(ctx, meta) + if err != nil { + fmt.Fprintln(os.Stderr, "Error fetching key:", err) + return 1 + } + t1 = time.Now() + + fmt.Printf( + "Client: host key = %s, protocol = %s\n\tduration: %s\n", + hex.EncodeToString(clientKey.Key[:]), meta.ProtoId, t1.Sub(t0), + ) + } + return 0 +} + +type Client struct { + daemon daemon.Connector +} + +func (c Client) FetchHostHostKey( + ctx context.Context, meta drkey.HostHostMeta) (drkey.HostHostKey, error) { + + // get level 3 key: (slow path) + return c.daemon.DRKeyGetHostHostKey(ctx, meta) +} + +type Server struct { + daemon daemon.Connector +} + +func (s Server) DeriveHostHostKeySpecific( + sv drkey.SecretValue, + meta drkey.HostHostMeta, +) (drkey.HostHostKey, error) { + + var deriver specific.Deriver + lvl1, err := deriver.DeriveLevel1(meta.DstIA, sv.Key) + if err != nil { + return drkey.HostHostKey{}, serrors.WrapStr("deriving level 1 key", err) + } + asHost, err := deriver.DeriveHostAS(meta.SrcHost, lvl1) + if err != nil { + return drkey.HostHostKey{}, serrors.WrapStr("deriving host-AS key", err) + } + hosthost, err := deriver.DeriveHostHost(meta.DstHost, asHost) + if err != nil { + return drkey.HostHostKey{}, serrors.WrapStr("deriving host-host key", err) + } + return drkey.HostHostKey{ + ProtoId: sv.ProtoId, + Epoch: sv.Epoch, + SrcIA: meta.SrcIA, + DstIA: meta.DstIA, + SrcHost: meta.SrcHost, + DstHost: meta.DstHost, + Key: hosthost, + }, nil +} + +func (s Server) DeriveHostHostKeyGeneric( + hostAS drkey.HostASKey, + meta drkey.HostHostMeta, +) (drkey.HostHostKey, error) { + + deriver := generic.Deriver{ + Proto: hostAS.ProtoId, + } + hosthost, err := deriver.DeriveHostHost(meta.DstHost, hostAS.Key) + if err != nil { + return drkey.HostHostKey{}, serrors.WrapStr("deriving host-host key", err) + } + return drkey.HostHostKey{ + ProtoId: hostAS.ProtoId, + Epoch: hostAS.Epoch, + SrcIA: meta.SrcIA, + DstIA: meta.DstIA, + SrcHost: meta.SrcHost, + DstHost: meta.DstHost, + Key: hosthost, + }, nil +} + +// FetchSV obtains the Secret Value (SV) for the selected protocol/epoch. +// From this SV, all keys for this protocol/epoch can be derived locally. +// The IP address of the server must be explicitly allowed to abtain this SV +// from the the control server. +func (s Server) FetchSV( + ctx context.Context, + meta drkey.SecretValueMeta, +) (drkey.SecretValue, error) { + + // Obtain CS address from scion daemon + svcs, err := s.daemon.SVCInfo(ctx, nil) + if err != nil { + return drkey.SecretValue{}, serrors.WrapStr("obtaining control service address", err) + } + cs := svcs[addr.SvcCS] + if len(cs) == 0 { + return drkey.SecretValue{}, serrors.New("no control service address found") + } + + // Contact CS directly for SV + conn, err := grpc.DialContext(ctx, cs[0], grpc.WithInsecure()) + if err != nil { + return drkey.SecretValue{}, serrors.WrapStr("dialing control service", err) + } + defer conn.Close() + client := cppb.NewDRKeyIntraServiceClient(conn) + + rep, err := client.DRKeySecretValue(ctx, &cppb.DRKeySecretValueRequest{ + ValTime: timestamppb.New(meta.Validity), + ProtocolId: dkpb.Protocol(meta.ProtoId), + }) + if err != nil { + return drkey.SecretValue{}, serrors.WrapStr("requesting drkey secret value", err) + } + + key, err := getSecretFromReply(meta.ProtoId, rep) + if err != nil { + return drkey.SecretValue{}, serrors.WrapStr("validating drkey secret value reply", err) + } + + return key, nil +} + +func getSecretFromReply( + proto drkey.Protocol, + rep *cppb.DRKeySecretValueResponse, +) (drkey.SecretValue, error) { + + if err := rep.EpochBegin.CheckValid(); err != nil { + return drkey.SecretValue{}, err + } + if err := rep.EpochEnd.CheckValid(); err != nil { + return drkey.SecretValue{}, err + } + epoch := drkey.Epoch{ + Validity: cppki.Validity{ + NotBefore: rep.EpochBegin.AsTime(), + NotAfter: rep.EpochEnd.AsTime(), + }, + } + returningKey := drkey.SecretValue{ + ProtoId: proto, + Epoch: epoch, + } + copy(returningKey.Key[:], rep.Key) + return returningKey, nil +} + +func (s Server) FetchHostASKey( + ctx context.Context, meta drkey.HostASMeta) (drkey.HostASKey, error) { + + // get level 2 key: (fast path) + return s.daemon.DRKeyGetHostASKey(ctx, meta) +} diff --git a/demo/fabrid/test.py b/demo/fabrid/test.py new file mode 100644 index 0000000000..0a6c589670 --- /dev/null +++ b/demo/fabrid/test.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 + +# Copyright 2022 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. + +import logging +import os +import random +import re +import time +import yaml + +from plumbum import local +from plumbum.path import LocalPath + +from acceptance.common import base, scion +from tools.topology.scion_addr import ISD_AS + +logger = logging.getLogger(__name__) + + +class Test(base.TestTopogen): + def _init_as_list(self): + # load list of ASes (generated by topogen scripts) + as_list = self.artifacts / "gen/as_list.yml" + self.isd_ases = scion.ASList.load(as_list).all + + # pick two "random" ASes (can be the same) for our "server" and "client" side key derivation + # demonstration. + # Note: fix random seed between invocations (prepare and run stages stages can be run + # separately) + random.seed(os.path.getctime(as_list)) + self.server_isd_as, self.client_isd_as = random.choices(self.isd_ases, k=2) + + def setup_prepare(self): + super().setup_prepare() + + self._init_as_list() + + # Enable DRKey in all CSes and SDs + for isd_as in self.isd_ases: + conf_dir = self._conf_dir(isd_as) + scion.update_toml({ + "drkey": { + "level1_db": { + "connection": "/share/cache/cs%s-1.drkey_level1.db" % isd_as.file_fmt(), + }, + "secret_value_db": { + "connection": "/share/cache/cs%s-1.secret_value.db" % isd_as.file_fmt() + } + } + }, conf_dir // "cs*-1.toml") + + scion.update_toml({ + "drkey_level2_db": { + "connection": "/share/cache/sd%s.drkey_level2.db" % isd_as.file_fmt() + } + }, [conf_dir / "sd.toml"]) + + # Enable delegation for tester host on the fast side (server side), i.e. + # allow the tester host to directly request the secret value from which + # keys can be derived locally for any host. + tester_ip = self._container_ip("disp_tester_%s" % self.server_isd_as.file_fmt()) + cs_config = self._conf_dir(self.server_isd_as) // "cs*-1.toml" + scion.update_toml({"drkey.delegation.scmp": [tester_ip]}, cs_config) + + def _run(self): + time.sleep(10) # wait until CSes are all up and running + + self._init_as_list() + + # install demo binary in tester containers: + drkey_demo = local["realpath"](self.get_executable("drkey-demo").executable).strip() + for ia in {self.server_isd_as, self.client_isd_as}: + self.dc("cp", drkey_demo, "tester_%s" % ia.file_fmt() + ":/bin/") + + # Define DRKey protocol identifiers and derivation typ for test + for test in [ + {"protocol": "1", "fetch_sv": "--fetch-sv"}, # SCMP based on protocol specific SV + {"protocol": "1", "fetch_sv": ""}, # SCMP based on generic key derivation + {"protocol": "7", "fetch_sv": ""}, # Generic "niche" protocol + ]: + # Determine server and client addresses for test. + # Because communication to the control services does not happen + # directly from the respective end hosts but via daemon processes on + # both sides, the IPs of the corresponding daemon hosts are used for + # this purpose. See also function _endhost_ip for more details. + server_ip = self._endhost_ip(self.server_isd_as) + client_ip = self._endhost_ip(self.client_isd_as) + server_addr = "%s,%s" % (self.server_isd_as, server_ip) + client_addr = "%s,%s" % (self.client_isd_as, client_ip) + + # Demonstrate deriving key (fast) on server side + rs = self.dc.execute("tester_%s" % self.server_isd_as.file_fmt(), + "drkey-demo", "--server", + "--protocol", test["protocol"], test["fetch_sv"], + "--server-addr", server_addr, "--client-addr", client_addr) + print(rs) + + # Demonstrate obtaining key (slow) on client side + rc = self.dc.execute("tester_%s" % self.client_isd_as.file_fmt(), + "drkey-demo", "--protocol", test["protocol"], + "--server-addr", server_addr, "--client-addr", client_addr) + print(rc) + + # Extract printed keys from output and verify that the keys match + key_regex = re.compile( + r"^(?:Client|Server):\s*host key\s*=\s*([a-f0-9]+)", re.MULTILINE) + server_key_match = key_regex.search(rs) + if server_key_match is None: + raise AssertionError("Key not found in server output") + server_key = server_key_match.group(1) + client_key_match = key_regex.search(rc) + if client_key_match is None: + raise AssertionError("Key not found in client output") + client_key = client_key_match.group(1) + if server_key != client_key: + raise AssertionError("Key derived by server does not match key derived by client!", + server_key, client_key) + + def _endhost_ip(self, isd_as: ISD_AS) -> str: + """ Determine the IP used for the end host (client or server) in the given ISD-AS """ + # The address must be the daemon IP (as it makes requests to the control + # service on behalf of the end host application). + return self._container_ip("sd%s" % isd_as.file_fmt()) + + def _container_ip(self, container: str) -> str: + """ Determine the IP of the container """ + dc_config = yaml.safe_load(self.dc.compose_file.read()) + networks = dc_config["services"][container]["networks"] + addresses = next(iter(networks.values())) + return next(iter(addresses.values())) + + def _conf_dir(self, isd_as: ISD_AS) -> LocalPath: + """ Returns the path of the configuration directory for the given ISD-AS """ + return self.artifacts / "gen" / ("AS" + isd_as.as_file_fmt()) + + +if __name__ == '__main__': + base.main(Test) From 0b06c0e28bf8baefe1050eade2071b9837d59aae Mon Sep 17 00:00:00 2001 From: Marc Odermatt Date: Wed, 11 Sep 2024 15:27:31 +0200 Subject: [PATCH 6/6] fabrid demo wip --- acceptance/common/topogen.bzl | 5 + demo/fabrid/BUILD.bazel | 11 +- demo/fabrid/README.md | 4 +- demo/fabrid/main.go | 563 +++++++++++++++++++++++++++++++++- demo/fabrid/test.py | 48 +-- 5 files changed, 583 insertions(+), 48 deletions(-) diff --git a/acceptance/common/topogen.bzl b/acceptance/common/topogen.bzl index b400803b2b..3a15ab97fa 100644 --- a/acceptance/common/topogen.bzl +++ b/acceptance/common/topogen.bzl @@ -11,6 +11,7 @@ def topogen_test( src, topo, gateway = False, + fabrid = False, debug = False, args = [], deps = [], @@ -57,6 +58,10 @@ def topogen_test( if gateway: common_args.append("--setup-params='--sig'") + if fabrid: + common_args.append("--setup-params='--fabrid'") + common_args.append("--setup-params='--endhost'") + common_data = [ "//scion-pki/cmd/scion-pki", "//tools:topogen", diff --git a/demo/fabrid/BUILD.bazel b/demo/fabrid/BUILD.bazel index 1479aa40e1..647ca9099d 100644 --- a/demo/fabrid/BUILD.bazel +++ b/demo/fabrid/BUILD.bazel @@ -7,16 +7,17 @@ topogen_test( name = "test", src = "test.py", args = [ - "--executable=drkey-demo:$(location //demo/drkey:drkey-demo)", + "--executable=fabrid-demo:$(location //demo/fabrid:fabrid-demo)", ], - data = ["//demo/drkey:drkey-demo"], + data = ["//demo/fabrid:fabrid-demo"], topo = "//topology:tiny4.topo", + fabrid = True, ) go_library( name = "go_default_library", srcs = ["main.go"], - importpath = "github.com/scionproto/scion/demo/drkey", + importpath = "github.com/scionproto/scion/demo/fabrid", visibility = ["//visibility:private"], deps = [ "//pkg/addr:go_default_library", @@ -37,13 +38,13 @@ go_library( ) scion_go_binary( - name = "drkey-demo", + name = "fabrid-demo", embed = [":go_default_library"], visibility = ["//visibility:public"], ) go_binary( - name = "drkey", + name = "fabrid", embed = [":go_default_library"], visibility = ["//visibility:public"], ) diff --git a/demo/fabrid/README.md b/demo/fabrid/README.md index 67eec9d2c9..9f3d29f3e7 100644 --- a/demo/fabrid/README.md +++ b/demo/fabrid/README.md @@ -1,4 +1,4 @@ -# DRKey demo +# FABRID demo This demo shows how two hosts can obtain a shared key with the DRKey system. The "server" side host can locally derive keys for any other host. @@ -19,7 +19,7 @@ The demo consists of the following steps: ## Run the demo 1. [set up the development environment](https://docs.scion.org/en/latest/build/setup.html) -1. `bazel test --test_output=streamed --cache_test_results=no //demo/drkey:test` +1. `bazel test --test_output=streamed --cache_test_results=no //demo/fabrid:test` Note: this demo works on any SCION network topology. To run the demo on a different network topology, modify the `topo` parameter in `BUILD.bazel` to diff --git a/demo/fabrid/main.go b/demo/fabrid/main.go index 3337fabd74..9c2984f29c 100644 --- a/demo/fabrid/main.go +++ b/demo/fabrid/main.go @@ -1,4 +1,4 @@ -// Copyright 2020 ETH Zurich +// 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. @@ -15,9 +15,27 @@ package main import ( + "bytes" "context" "encoding/hex" + "encoding/json" "fmt" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" + libfabrid "github.com/scionproto/scion/pkg/experimental/fabrid" + common2 "github.com/scionproto/scion/pkg/experimental/fabrid/common" + fabridserver "github.com/scionproto/scion/pkg/experimental/fabrid/server" + "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/common" + "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/extension" + "github.com/scionproto/scion/pkg/slayers/path/scion" + 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" + "net" + "net/netip" "os" "time" @@ -324,3 +342,546 @@ func (s Server) FetchHostASKey( // get level 2 key: (fast path) return s.daemon.DRKeyGetHostASKey(ctx, meta) } + +type server struct { + fabridServer *fabridserver.Server +} + +func (s server) run() { + fmt.Printf("Starting server", "isd_as", integration.Local.IA) + defer fmt.Printf("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.OpenRaw(context.Background(), integration.Local.Host) + if err != nil { + integration.LogFatal("Error listening", "err", err) + } + defer conn.Close() + localAddr := conn.LocalAddr().(*net.UDPAddr) + if len(os.Getenv(libint.GoIntegrationEnv)) > 0 { + // Needed for integration test ready signal. + fmt.Printf("Port=%d\n", localAddr.Port) + fmt.Printf("%s%s\n\n", libint.ReadySignal, integration.Local.IA) + } + fmt.Printf("Listening", "local", + fmt.Sprintf("%v:%d", integration.Local.Host.IP, localAddr.Port)) + s.fabridServer = fabridserver.NewFabridServer(&integration.Local, integration.SDConn()) + s.fabridServer.ValidationHandler = func(connection *fabridserver.ClientConnection, + option *extension.IdentifierOption, b bool) error { + log.Debug("Validation handler", "connection", connection, "success", b) + if !b { + return serrors.New("Failed validation") + } + return nil + } + // Receive ping message + for { + if err := s.handlePingFabrid(conn); err != nil { + log.Error("Error handling ping", "err", err) + } + } +} + +func (s server) handlePingFabrid(conn snet.PacketConn) error { + var p snet.Packet + var ov net.UDPAddr + err := readFromFabrid(conn, &p, &ov) + if err != nil { + return serrors.WrapStr("reading packet", err) + } + + var valResponse *slayers.EndToEndExtn + + // If the packet is from remote IA, validate the FABRID path + if p.Source.IA != integration.Local.IA { + if p.HbhExtension == nil { + return serrors.New("Missing HBH extension") + } + + // Check extensions for relevant options + var identifierOption *extension.IdentifierOption + var fabridOption *extension.FabridOption + var controlOptions []*extension.FabridControlOption + var err error + + for _, opt := range p.HbhExtension.Options { + switch opt.OptType { + case slayers.OptTypeIdentifier: + decoded := scion.Decoded{} + err = decoded.DecodeFromBytes(p.Path.(snet.RawPath).Raw) + if err != nil { + return err + } + baseTimestamp := decoded.InfoFields[0].Timestamp + identifierOption, err = extension.ParseIdentifierOption(opt, baseTimestamp) + if err != nil { + return err + } + case slayers.OptTypeFabrid: + fabridOption, err = extension.ParseFabridOptionFullExtension(opt, + (opt.OptDataLen-4)/4) + if err != nil { + return err + } + } + } + if p.E2eExtension != nil { + + for _, opt := range p.E2eExtension.Options { + switch opt.OptType { + case slayers.OptTypeFabridControl: + controlOption, err := extension.ParseFabridControlOption(opt) + if err != nil { + return err + } + controlOptions = append(controlOptions, controlOption) + } + } + } + + if identifierOption == nil { + return serrors.New("Missing identifier option") + } + + if fabridOption == nil { + return serrors.New("Missing FABRID option") + } + valResponse, err = s.fabridServer.HandleFabridPacket(p.Source, fabridOption, + identifierOption, controlOptions) + if err != nil { + return err + } + } + + udp, ok := p.Payload.(snet.UDPPayload) + if !ok { + return serrors.New("unexpected payload received", + "source", p.Source, + "destination", p.Destination, + "type", common.TypeOf(p.Payload), + ) + } + var pld Ping + if err := json.Unmarshal(udp.Payload, &pld); err != nil { + return serrors.New("invalid payload contents", + "source", p.Source, + "destination", p.Destination, + "data", string(udp.Payload), + ) + } + + 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 + } + + if pld.Message != ping || !pld.Server.Equal(integration.Local.IA) { + return withTag(serrors.New("unexpected data in payload", + "source", p.Source, + "destination", p.Destination, + "data", pld, + )) + } + fmt.Printf(fmt.Sprintf("Ping received from %s, sending pong.", p.Source)) + raw, err := json.Marshal(Pong{ + Client: p.Source.IA, + Server: integration.Local.IA, + Message: pong, + Trace: pld.Trace, + }) + if err != nil { + return withTag(serrors.WrapStr("packing pong", err)) + } + + p.Destination, p.Source = p.Source, p.Destination + p.Payload = snet.UDPPayload{ + DstPort: udp.SrcPort, + SrcPort: udp.DstPort, + Payload: raw, + } + + // Remove header extension for reverse path + p.HbhExtension = nil + p.E2eExtension = valResponse + + // reverse path + rpath, ok := p.Path.(snet.RawPath) + if !ok { + return serrors.New("unexpected path", "type", common.TypeOf(p.Path)) + } + replypather := snet.DefaultReplyPather{} + replyPath, err := replypather.ReplyPath(rpath) + if err != nil { + return serrors.WrapStr("creating reply path", err) + } + p.Path = replyPath + // Send pong + if err := conn.WriteTo(&p, &ov); err != nil { + return withTag(serrors.WrapStr("sending reply", err)) + } + fmt.Printf("Sent pong to", "client", p.Destination) + return nil +} + +type client struct { + network *snet.SCIONNetwork + conn *snet.Conn + rawConn snet.PacketConn + sdConn daemon.Connector + + errorPaths map[snet.PathFingerprint]struct{} +} + +func (c *client) run() int { + pair := fmt.Sprintf("%s -> %s", integration.Local.IA, remote.IA) + fmt.Printf("Starting", "pair", pair) + defer fmt.Printf("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, + } + fmt.Printf("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 + } + + for i := 0; i < 10; i++ { + + // Send ping + close, err := c.fabridPing(ctx, n, path) + if err != nil { + logger.Error("Could not send packet", "err", withTag(err)) + return false + } + defer close() + // Receive FABRID pong + if err := c.fabridPong(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) fabridPing(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.rawConn, err = c.network.OpenRaw(ctx, integration.Local.Host) + if err != nil { + return nil, serrors.WrapStr("dialing conn", err) + } + if err := c.rawConn.SetWriteDeadline(getDeadline(ctx)); err != nil { + return nil, serrors.WrapStr("setting write deadline", err) + } + fmt.Printf("sending ping", "attempt", n, "remote", remote, "local", c.rawConn.LocalAddr()) + localAddr := c.rawConn.LocalAddr().(*net.UDPAddr) + hostIP, _ := netip.AddrFromSlice(remote.Host.IP) + dst := snet.SCIONAddress{IA: remote.IA, Host: addr.HostIP(hostIP)} + localHostIP, _ := netip.AddrFromSlice(integration.Local.Host.IP) + pkt := &snet.Packet{ + Bytes: make([]byte, common.SupportedMTU), + PacketInfo: snet.PacketInfo{ + Destination: dst, + Source: snet.SCIONAddress{ + IA: integration.Local.IA, + Host: addr.HostIP(localHostIP), + }, + Path: remote.Path, + Payload: snet.UDPPayload{ + SrcPort: uint16(localAddr.Port), + DstPort: uint16(remote.Host.Port), + Payload: []byte("ping"), + }, + }, + } + fmt.Printf("sending packet", "packet", pkt) + if err := c.rawConn.WriteTo(pkt, remote.NextHop); err != nil { + return nil, err + } + closer := func() { + if err := c.rawConn.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), + )) + } + // If the fabrid flag is set, try to create FABRID dataplane path. + if len(path.Metadata().FabridInfo) > 0 { + // Check if fabrid info is available, otherwise the source + // AS does not support fabrid + + scionPath, ok := path.Dataplane().(snetpath.SCION) + if !ok { + return nil, serrors.New("provided path must be of type scion") + } + fabridConfig := &snetpath.FabridConfig{ + LocalIA: integration.Local.IA, + LocalAddr: integration.Local.Host.IP.String(), + DestinationIA: remote.IA, + DestinationAddr: remote.Host.IP.String(), + } + fabridConfig.ValidationHandler = func(ps *common2.PathState, + option *extension.FabridControlOption, b bool) error { + log.Debug("Validation handler", "pathState", ps, "success", b) + if !b { + return serrors.New("Failed validation") + } + return nil + } + hops := path.Metadata().Hops() + fmt.Printf("Fabrid path", "path", path, "hops", hops) + // Use ZERO policy for all hops with fabrid, to just do path validation + policies := make([]*libfabrid.PolicyID, len(hops)) + zeroPol := libfabrid.PolicyID(0) + for i, hop := range hops { + if hop.FabridEnabled { + policies[i] = &zeroPol + } + } + fabridPath, err := snetpath.NewFABRIDDataplanePath(scionPath, hops, + policies, fabridConfig, 125) + if err != nil { + return nil, serrors.New("Error creating FABRID path", "err", err) + } + remote.Path = fabridPath + fabridPath.RegisterDRKeyFetcher(c.sdConn.FabridKeys) + + } else { + fmt.Printf("FABRID flag was set for client in non-FABRID AS. Proceeding without FABRID.") + 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) + } + fmt.Printf("Received pong", "server", serverAddr) + return nil +} + +func (c *client) fabridPong(ctx context.Context) error { + + if err := c.rawConn.SetReadDeadline(getDeadline(ctx)); err != nil { + return serrors.WrapStr("setting read deadline", err) + } + var p snet.Packet + var ov net.UDPAddr + err := readFromFabrid(c.rawConn, &p, &ov) + if err != nil { + return serrors.WrapStr("reading packet", err) + } + if p.Source.IA != integration.Local.IA { + // Check extensions for relevant options + var controlOptions []*extension.FabridControlOption + + if p.E2eExtension != nil { + + for _, opt := range p.E2eExtension.Options { + switch opt.OptType { + case slayers.OptTypeFabridControl: + controlOption, err := extension.ParseFabridControlOption(opt) + if err != nil { + return err + } + controlOptions = append(controlOptions, controlOption) + log.Debug("Parsed control option", "option", controlOption) + } + } + } + switch s := remote.Path.(type) { + case *snetpath.FABRID: + for _, option := range controlOptions { + err := s.HandleFabridControlOption(option, nil) + if err != nil { + return err + } + } + + default: + return serrors.New("unsupported path type") + } + } + + udp, ok := p.Payload.(snet.UDPPayload) + if !ok { + return serrors.New("unexpected payload received", + "source", p.Source, + "destination", p.Destination, + "type", common.TypeOf(p.Payload), + ) + } + var pld Pong + if err := json.Unmarshal(udp.Payload, &pld); err != nil { + return serrors.WrapStr("unpacking pong", err, "data", string(udp.Payload)) + } + + 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) + } + fmt.Printf("Received pong", "server", ov) + 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, + ) +} + +func readFromFabrid(conn snet.PacketConn, pkt *snet.Packet, ov *net.UDPAddr) error { + err := conn.ReadFrom(pkt, ov) + // Attach more context to error + var opErr *snet.OpError + if !(errors.As(err, &opErr) && opErr.RevInfo() != nil) { + return err + } + return serrors.WithCtx(err, + "isd_as", opErr.RevInfo().IA(), + "interface", opErr.RevInfo().IfID, + ) +} diff --git a/demo/fabrid/test.py b/demo/fabrid/test.py index 0a6c589670..5ec082ce45 100644 --- a/demo/fabrid/test.py +++ b/demo/fabrid/test.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2022 ETH Zurich +# 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. @@ -43,47 +43,15 @@ def _init_as_list(self): random.seed(os.path.getctime(as_list)) self.server_isd_as, self.client_isd_as = random.choices(self.isd_ases, k=2) - def setup_prepare(self): - super().setup_prepare() - - self._init_as_list() - - # Enable DRKey in all CSes and SDs - for isd_as in self.isd_ases: - conf_dir = self._conf_dir(isd_as) - scion.update_toml({ - "drkey": { - "level1_db": { - "connection": "/share/cache/cs%s-1.drkey_level1.db" % isd_as.file_fmt(), - }, - "secret_value_db": { - "connection": "/share/cache/cs%s-1.secret_value.db" % isd_as.file_fmt() - } - } - }, conf_dir // "cs*-1.toml") - - scion.update_toml({ - "drkey_level2_db": { - "connection": "/share/cache/sd%s.drkey_level2.db" % isd_as.file_fmt() - } - }, [conf_dir / "sd.toml"]) - - # Enable delegation for tester host on the fast side (server side), i.e. - # allow the tester host to directly request the secret value from which - # keys can be derived locally for any host. - tester_ip = self._container_ip("disp_tester_%s" % self.server_isd_as.file_fmt()) - cs_config = self._conf_dir(self.server_isd_as) // "cs*-1.toml" - scion.update_toml({"drkey.delegation.scmp": [tester_ip]}, cs_config) - def _run(self): time.sleep(10) # wait until CSes are all up and running self._init_as_list() # install demo binary in tester containers: - drkey_demo = local["realpath"](self.get_executable("drkey-demo").executable).strip() + fabrid_demo = local["realpath"](self.get_executable("fabrid-demo").executable).strip() for ia in {self.server_isd_as, self.client_isd_as}: - self.dc("cp", drkey_demo, "tester_%s" % ia.file_fmt() + ":/bin/") + self.dc("cp", fabrid_demo, "endhost_%s" % ia.file_fmt() + ":/bin/") # Define DRKey protocol identifiers and derivation typ for test for test in [ @@ -102,15 +70,15 @@ def _run(self): client_addr = "%s,%s" % (self.client_isd_as, client_ip) # Demonstrate deriving key (fast) on server side - rs = self.dc.execute("tester_%s" % self.server_isd_as.file_fmt(), - "drkey-demo", "--server", + rs = self.dc.execute("endhost_%s" % self.server_isd_as.file_fmt(), + "fabrid-demo", "--server", "--protocol", test["protocol"], test["fetch_sv"], "--server-addr", server_addr, "--client-addr", client_addr) print(rs) # Demonstrate obtaining key (slow) on client side - rc = self.dc.execute("tester_%s" % self.client_isd_as.file_fmt(), - "drkey-demo", "--protocol", test["protocol"], + rc = self.dc.execute("endhost_%s" % self.client_isd_as.file_fmt(), + "fabrid-demo", "--protocol", test["protocol"], "--server-addr", server_addr, "--client-addr", client_addr) print(rc) @@ -133,7 +101,7 @@ def _endhost_ip(self, isd_as: ISD_AS) -> str: """ Determine the IP used for the end host (client or server) in the given ISD-AS """ # The address must be the daemon IP (as it makes requests to the control # service on behalf of the end host application). - return self._container_ip("sd%s" % isd_as.file_fmt()) + return self._container_ip("endhost_%s" % isd_as.file_fmt()) def _container_ip(self, container: str) -> str: """ Determine the IP of the container """