diff --git a/BUILD.bazel b/BUILD.bazel index 267914fd4b..a175cc7660 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -197,6 +197,8 @@ pkg_tar( "//tools/braccept", "//tools/buildkite/cmd/buildkite_artifacts", "//tools/end2end", + "//tools/end2end_hbird", + "//tools/end2end_hbird_integration", "//tools/end2end_integration", "//tools/pktgen/cmd/pktgen", "//tools/scion_integration", diff --git a/daemon/BUILD.bazel b/daemon/BUILD.bazel index 420946cf80..4bcda5f304 100644 --- a/daemon/BUILD.bazel +++ b/daemon/BUILD.bazel @@ -17,6 +17,7 @@ go_library( "//pkg/private/prom:go_default_library", "//pkg/private/serrors:go_default_library", "//private/env:go_default_library", + "//private/hummingbirddb:go_default_library", "//private/revcache:go_default_library", "//private/trust:go_default_library", "//private/trust/grpc:go_default_library", diff --git a/daemon/cmd/daemon/main.go b/daemon/cmd/daemon/main.go index ded6ef1106..1f7777268f 100644 --- a/daemon/cmd/daemon/main.go +++ b/daemon/cmd/daemon/main.go @@ -113,12 +113,24 @@ func realMain(ctx context.Context) error { }) defer pathDB.Close() defer revCache.Close() + + hbirdDB, err := storage.NewHummingbirdStorage(globalCfg.HbirdDB) + if err != nil { + return serrors.WrapStr("initializing hummingbird storage", err) + } + defer hbirdDB.Close() + cleaner := periodic.Start(pathdb.NewCleaner(pathDB, "sd_segments"), 300*time.Second, 295*time.Second) defer cleaner.Stop() rcCleaner := periodic.Start(revcache.NewCleaner(revCache, "sd_revocation"), 10*time.Second, 10*time.Second) defer rcCleaner.Stop() + defer cleaner.Stop() + + // Hummingbird DB has its cleaner started already in the New method. Use that one. + hbirdCleaner := hbirdDB.GetCleaner() + defer hbirdCleaner.Stop() dialer := &libgrpc.TCPDialer{ SvcResolver: func(dst addr.SVC) []resolver.Address { @@ -271,9 +283,11 @@ func realMain(ctx context.Context) error { Cfg: globalCfg.SD, }, ), + Engine: engine, RevCache: revCache, DRKeyClient: drkeyClientEngine, + FlyoverDB: hbirdDB, }, )) diff --git a/daemon/config/config.go b/daemon/config/config.go index d4724e83f2..1da619a903 100644 --- a/daemon/config/config.go +++ b/daemon/config/config.go @@ -46,6 +46,7 @@ type Config struct { Tracing env.Tracing `toml:"tracing,omitempty"` TrustDB storage.DBConfig `toml:"trust_db,omitempty"` PathDB storage.DBConfig `toml:"path_db,omitempty"` + HbirdDB storage.DBConfig `toml:"hbird_db,omitempty"` SD SDConfig `toml:"sd,omitempty"` TrustEngine trustengine.Config `toml:"trustengine,omitempty"` DRKeyLevel2DB storage.DBConfig `toml:"drkey_level2_db,omitempty"` @@ -61,6 +62,7 @@ func (cfg *Config) InitDefaults() { &cfg.Tracing, cfg.TrustDB.WithDefault(fmt.Sprintf(storage.DefaultTrustDBPath, "sd")), cfg.PathDB.WithDefault(fmt.Sprintf(storage.DefaultPathDBPath, "sd")), + cfg.HbirdDB.WithDefault(fmt.Sprintf(storage.DefaultHbirdDBPath, "sd")), &cfg.SD, &cfg.TrustEngine, ) @@ -75,6 +77,7 @@ func (cfg *Config) Validate() error { &cfg.API, &cfg.TrustDB, &cfg.PathDB, + &cfg.HbirdDB, &cfg.SD, &cfg.TrustEngine, &cfg.DRKeyLevel2DB, @@ -103,6 +106,13 @@ func (cfg *Config) Sample(dst io.Writer, path config.Path, _ config.CtxMap) { ), "path_db", ), + config.OverrideName( + config.FormatData( + &cfg.HbirdDB, + fmt.Sprintf(storage.DefaultHbirdDBPath, "sd"), + ), + "hbird_db", + ), &cfg.SD, &cfg.TrustEngine, config.OverrideName( diff --git a/daemon/daemon.go b/daemon/daemon.go index 8fbc106938..94e7bb8295 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -36,6 +36,7 @@ import ( "github.com/scionproto/scion/pkg/private/prom" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/private/env" + "github.com/scionproto/scion/private/hummingbirddb" "github.com/scionproto/scion/private/revcache" "github.com/scionproto/scion/private/trust" trustgrpc "github.com/scionproto/scion/private/trust/grpc" @@ -114,6 +115,8 @@ type ServerConfig struct { Engine trust.Engine Topology servers.Topology DRKeyClient *drkey.ClientEngine + + FlyoverDB hummingbirddb.DB } // NewServer constructs a daemon API server. @@ -126,6 +129,7 @@ func NewServer(cfg ServerConfig) *servers.DaemonServer { ASInspector: cfg.Engine.Inspector, RevCache: cfg.RevCache, DRKeyClient: cfg.DRKeyClient, + FlyoverDB: cfg.FlyoverDB, Metrics: servers.Metrics{ PathsRequests: servers.RequestMetrics{ Requests: metrics.NewPromCounterFrom(prometheus.CounterOpts{ diff --git a/daemon/internal/servers/BUILD.bazel b/daemon/internal/servers/BUILD.bazel index 1d1534f2c2..9b52fc875a 100644 --- a/daemon/internal/servers/BUILD.bazel +++ b/daemon/internal/servers/BUILD.bazel @@ -1,9 +1,10 @@ -load("//tools/lint:go.bzl", "go_library") +load("//tools/lint:go.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ "grpc.go", + "grpc_hummingbird.go", "metrics.go", ], importpath = "github.com/scionproto/scion/daemon/internal/servers", @@ -13,6 +14,7 @@ go_library( "//daemon/fetcher:go_default_library", "//pkg/addr:go_default_library", "//pkg/drkey:go_default_library", + "//pkg/hummingbird:go_default_library", "//pkg/log:go_default_library", "//pkg/metrics:go_default_library", "//pkg/private/common:go_default_library", @@ -24,6 +26,7 @@ go_library( "//pkg/proto/daemon:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/path:go_default_library", + "//private/hummingbirddb:go_default_library", "//private/revcache:go_default_library", "//private/topology:go_default_library", "//private/trust:go_default_library", @@ -33,3 +36,23 @@ go_library( "@org_golang_x_sync//singleflight:go_default_library", ], ) + +go_test( + name = "go_default_test", + srcs = ["grpc_hummingbird_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/hummingbird:go_default_library", + "//pkg/private/common:go_default_library", + "//pkg/private/util:go_default_library", + "//pkg/private/xtest:go_default_library", + "//pkg/proto/daemon:go_default_library", + "//pkg/slayers/path:go_default_library", + "//pkg/slayers/path/scion:go_default_library", + "//pkg/snet:go_default_library", + "//pkg/snet/path:go_default_library", + "//private/hummingbirddb:go_default_library", + "@com_github_stretchr_testify//require:go_default_library", + ], +) diff --git a/daemon/internal/servers/grpc.go b/daemon/internal/servers/grpc.go index b9e7af48fe..39305c115f 100644 --- a/daemon/internal/servers/grpc.go +++ b/daemon/internal/servers/grpc.go @@ -40,6 +40,7 @@ import ( sdpb "github.com/scionproto/scion/pkg/proto/daemon" "github.com/scionproto/scion/pkg/snet" snetpath "github.com/scionproto/scion/pkg/snet/path" + "github.com/scionproto/scion/private/hummingbirddb" "github.com/scionproto/scion/private/revcache" "github.com/scionproto/scion/private/topology" "github.com/scionproto/scion/private/trust" @@ -61,6 +62,8 @@ type DaemonServer struct { ASInspector trust.Inspector DRKeyClient *drkey_daemon.ClientEngine + FlyoverDB hummingbirddb.DB + Metrics Metrics foregroundPathDedupe singleflight.Group diff --git a/daemon/internal/servers/grpc_hummingbird.go b/daemon/internal/servers/grpc_hummingbird.go new file mode 100644 index 0000000000..c22931e71f --- /dev/null +++ b/daemon/internal/servers/grpc_hummingbird.go @@ -0,0 +1,256 @@ +// 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 servers + +import ( + "context" + "net" + "sort" + "time" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/hummingbird" + "github.com/scionproto/scion/pkg/private/common" + "github.com/scionproto/scion/pkg/private/serrors" + sdpb "github.com/scionproto/scion/pkg/proto/daemon" + "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" +) + +func (s *DaemonServer) StoreFlyovers( + ctx context.Context, + req *sdpb.StoreFlyoversRequest, +) (*sdpb.StoreFlyoversResponse, error) { + + // Translate flyovers from protobuf and store them. + err := s.FlyoverDB.StoreFlyovers(ctx, hummingbird.ConvertFlyoversFromPB(req.Flyovers)) + if err != nil { + return nil, err + } + + return &sdpb.StoreFlyoversResponse{}, nil +} +func (s *DaemonServer) ListFlyovers( + ctx context.Context, + req *sdpb.ListFlyoversRequest, +) (*sdpb.ListFlyoversResponse, error) { + + // Get all flyovers. + flyovers, err := s.FlyoverDB.GetFlyovers(ctx, nil) + if err != nil { + return nil, err + } + + return &sdpb.ListFlyoversResponse{ + Flyovers: hummingbird.ConvertFlyoversToPB(flyovers), + }, nil +} + +func (s *DaemonServer) GetReservations( + ctx context.Context, + req *sdpb.GetReservationsRequest, +) (*sdpb.GetReservationsResponse, error) { + + // Get SCION paths. + paths, err := s.getScionPaths(ctx, addr.IA(req.SourceIsdAs), addr.IA(req.DestinationIsdAs), + req.Refresh) + if err != nil { + return nil, err + } + + // Obtain reservations composing flyovers for those paths. + rsvs, err := s.getReservations(ctx, paths, time.Now(), uint16(req.MinBandwidth)) + if err != nil { + return nil, err + } + + // Prepare response. + res := &sdpb.GetReservationsResponse{ + Reservations: make([]*sdpb.Reservation, len(paths)), + } + for i := range res.Reservations { + res.Reservations[i], err = hummingbird.ConvertReservationToPB(rsvs[i]) + if err != nil { + return nil, err + } + } + + return res, nil +} + +func (s *DaemonServer) getScionPaths( + ctx context.Context, + src, dst addr.IA, + refresh bool, +) ([]path.Path, error) { + pathReq := &sdpb.PathsRequest{ + SourceIsdAs: uint64(src), + DestinationIsdAs: uint64(dst), + Refresh: refresh, + Hidden: false, + } + pathRes, err := s.paths(ctx, pathReq) + err = unwrapMetricsError(err) + if err != nil { + return nil, serrors.WrapStr("obtaining reservations", err) + } + + // Unwrap the response to a slice of path.Path + paths := make([]path.Path, len(pathRes.Paths)) + for i, p := range pathRes.Paths { + paths[i], err = convertPath(p, dst) + if err != nil { + return nil, err + } + } + + return paths, nil +} + +func (s *DaemonServer) getReservations( + ctx context.Context, + paths []path.Path, + now time.Time, + minBW uint16, +) ([]*hummingbird.Reservation, error) { + + // Make a set with all appearing IASet. Then a slice of them to obtain flyovers. + IASet := make(map[addr.IA]struct{}, 0) + for _, p := range paths { + for _, iface := range p.Meta.Interfaces { + IASet[iface.IA] = struct{}{} + } + } + IAs := make([]addr.IA, 0, len(IASet)) + for ia := range IASet { + IAs = append(IAs, ia) + } + + // Get flyovers on any AS present in the paths. + flyovers, err := s.FlyoverDB.GetFlyovers(ctx, IAs) + if err != nil { + return nil, err + } + mFlyovers := hummingbird.FlyoversToMap(flyovers) + + // For each path, try to assign as many flyovers as possible. + reservations := make([]*hummingbird.Reservation, len(paths)) + for i, p := range paths { + reservations[i], err = hummingbird.NewReservation( + hummingbird.WithNow(now), + hummingbird.WithMinBW(minBW), + hummingbird.WithScionPath(p, mFlyovers), + ) + if err != nil { + return nil, err + } + } + + // Rank the reservations by flyover / hop ratio. + sort.Slice(reservations, func(i, j int) bool { + nFa, nHFa := reservations[i].FlyoverAndHFCount() + ratioa := float64(nFa) / float64(nHFa) + nFb, nHFb := reservations[i].FlyoverAndHFCount() + ratiob := float64(nFb) / float64(nHFb) + + return ratioa < ratiob + }) + + return reservations, nil +} + +func convertPath(p *sdpb.Path, dst addr.IA) (path.Path, error) { + expiry := time.Unix(p.Expiration.Seconds, int64(p.Expiration.Nanos)) + if len(p.Interfaces) == 0 { + return path.Path{ + Src: dst, + Dst: dst, + Meta: snet.PathMetadata{ + MTU: uint16(p.Mtu), + Expiry: expiry, + }, + DataplanePath: path.Empty{}, + }, nil + } + underlayA, err := net.ResolveUDPAddr("udp", p.Interface.Address.Address) + if err != nil { + return path.Path{}, serrors.WrapStr("resolving underlay", err) + } + interfaces := make([]snet.PathInterface, len(p.Interfaces)) + for i, pi := range p.Interfaces { + interfaces[i] = snet.PathInterface{ + ID: common.IFIDType(pi.Id), + IA: addr.IA(pi.IsdAs), + } + } + latency := make([]time.Duration, len(p.Latency)) + for i, v := range p.Latency { + latency[i] = time.Second*time.Duration(v.Seconds) + time.Duration(v.Nanos) + } + geo := make([]snet.GeoCoordinates, len(p.Geo)) + for i, v := range p.Geo { + geo[i] = snet.GeoCoordinates{ + Latitude: v.Latitude, + Longitude: v.Longitude, + Address: v.Address, + } + } + linkType := make([]snet.LinkType, len(p.LinkType)) + for i, v := range p.LinkType { + linkType[i] = linkTypeFromPB(v) + } + + res := path.Path{ + Src: interfaces[0].IA, + Dst: dst, + DataplanePath: path.SCION{ + Raw: p.Raw, + }, + NextHop: underlayA, + Meta: snet.PathMetadata{ + Interfaces: interfaces, + MTU: uint16(p.Mtu), + Expiry: expiry, + Latency: latency, + Bandwidth: p.Bandwidth, + Geo: geo, + LinkType: linkType, + InternalHops: p.InternalHops, + Notes: p.Notes, + }, + } + + if p.EpicAuths == nil { + return res, nil + } + res.Meta.EpicAuths = snet.EpicAuths{ + AuthPHVF: append([]byte(nil), p.EpicAuths.AuthPhvf...), + AuthLHVF: append([]byte(nil), p.EpicAuths.AuthLhvf...), + } + return res, nil +} + +func linkTypeFromPB(lt sdpb.LinkType) snet.LinkType { + switch lt { + case sdpb.LinkType_LINK_TYPE_DIRECT: + return snet.LinkTypeDirect + case sdpb.LinkType_LINK_TYPE_MULTI_HOP: + return snet.LinkTypeMultihop + case sdpb.LinkType_LINK_TYPE_OPEN_NET: + return snet.LinkTypeOpennet + default: + return snet.LinkTypeUnset + } +} diff --git a/daemon/internal/servers/grpc_hummingbird_test.go b/daemon/internal/servers/grpc_hummingbird_test.go new file mode 100644 index 0000000000..22ca241d00 --- /dev/null +++ b/daemon/internal/servers/grpc_hummingbird_test.go @@ -0,0 +1,396 @@ +// 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 servers + +import ( + "context" + "database/sql" + "testing" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/hummingbird" + "github.com/scionproto/scion/pkg/private/common" + "github.com/scionproto/scion/pkg/private/util" + "github.com/scionproto/scion/pkg/private/xtest" + sdpb "github.com/scionproto/scion/pkg/proto/daemon" + pathlayers "github.com/scionproto/scion/pkg/slayers/path" + "github.com/scionproto/scion/pkg/slayers/path/scion" + "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" + "github.com/scionproto/scion/private/hummingbirddb" + "github.com/stretchr/testify/require" +) + +const currUnixTimestamp = 100 + +// TestGetReservation checks that given a set of SCION paths, the functions getting the +// reservation correctly finds the appropriate flyovers and uses them. +func TestGetReservation(t *testing.T) { + cases := map[string]struct { + // paths' hops, like { {0, "1-ff00:0:1", 1, 2, "1-ff00:0:2", 0} , ... } + scionPaths [][]any + expected [][]any // this is a slice of flyovers allowing nils in it + flyoverDB [][]any + }{ + "onepath_oneflyover": { + scionPaths: [][]any{ + { + 0, "1-ff00:0:1", 1, + 2, "1-ff00:0:2", 0, + }, + }, + expected: [][]any{ + { + 0, "1-ff00:0:1", 1, + nil, + }, + }, + flyoverDB: [][]any{ + {0, "1-ff00:0:1", 1}, + {0, "1-ff00:0:2", 1}, + {0, "1-ff00:0:3", 1}, + }, + }, + "onepath_twoflyovers": { + scionPaths: [][]any{ + { + 0, "1-ff00:0:1", 1, + 2, "1-ff00:0:2", 3, + 4, "1-ff00:0:3", 0, + }, + }, + expected: [][]any{ + { + 0, "1-ff00:0:1", 1, + 2, "1-ff00:0:2", 3, + nil, + }, + }, + flyoverDB: [][]any{ + {0, "1-ff00:0:1", 1}, + {2, "1-ff00:0:2", 3}, + {0, "1-ff00:0:2", 1}, + {0, "1-ff00:0:3", 1}, + }, + }, + } + + for name, tc := range cases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + deadline, _ := t.Deadline() + ctx, cancelF := context.WithDeadline(context.Background(), deadline) + defer cancelF() + + flyoverDB := make([]*hummingbird.Flyover, len(tc.flyoverDB)) + for i, flyoverDesc := range tc.flyoverDB { + flyover := getMockFlyovers(t, flyoverDesc...) + require.Len(t, flyover, 1, "bad test") + flyoverDB[i] = flyover[0] + } + mockHbirdServer := &mockServer{ + Flyovers: flyoverDB, + } + s := &DaemonServer{ + FlyoverDB: mockHbirdServer, + } + scionPaths := getMockScionPaths(t, tc.scionPaths) + rsvs, err := s.getReservations(ctx, scionPaths, util.SecsToTime(currUnixTimestamp), 0) + require.NoError(t, err) + + // Check the size. + require.Len(t, rsvs, len(scionPaths)) + + // For each path, check the flyovers. + for i, pRaw := range scionPaths { + // Decode pRaw into a SCION decoded path. + require.IsType(t, path.SCION{}, pRaw.DataplanePath) + dpRaw := pRaw.DataplanePath.(path.SCION) + + p := scion.Decoded{} + err := p.DecodeFromBytes(dpRaw.Raw) + require.NoError(t, err) + + // Same hop count in both SCION path and reservation. + r := rsvs[i] + flyoverSequence := r.FlyoverPerHopField() + require.Equal(t, len(p.HopFields), len(flyoverSequence)) + + // Check the flyover sequence. + expected := getMockFlyovers(t, tc.expected[i]...) + require.Equal(t, expected, flyoverSequence) + } + }) + } +} + +func TestStoreFlyovers(t *testing.T) { + deadline, _ := t.Deadline() + ctx, cancelF := context.WithDeadline(context.Background(), deadline) + defer cancelF() + + mockHbirdServer := &mockServer{ + Flyovers: nil, // empty + } + s := &DaemonServer{ + FlyoverDB: mockHbirdServer, + } + + expected := []*hummingbird.Flyover{ + { + BaseHop: hummingbird.BaseHop{ + IA: xtest.MustParseIA("1-ff00:0:333"), + Ingress: 3, + Egress: 2, + }, + ResID: 40, + Bw: 3, + StartTime: 10, + Duration: 60, + Ak: [16]byte{3, 4, 5, 6}, + }, + { // same as previous one, not unique (IA,resID) + BaseHop: hummingbird.BaseHop{ + IA: xtest.MustParseIA("1-ff00:0:333"), + Ingress: 3, + Egress: 2, + }, + ResID: 40, + Bw: 3, + StartTime: 10, + Duration: 60, + Ak: [16]byte{3, 4, 5, 6}, + }, + { + BaseHop: hummingbird.BaseHop{ + IA: xtest.MustParseIA("1-ff00:0:111"), + Ingress: 1, + Egress: 2, + }, + ResID: 40, + Bw: 3, + StartTime: 10, + Duration: 60, + Ak: [16]byte{3, 4, 5, 6}, + }, + } + req := &sdpb.StoreFlyoversRequest{ + Flyovers: hummingbird.ConvertFlyoversToPB(expected), + } + _, err := s.StoreFlyovers(ctx, req) + require.NoError(t, err) + + // Check DB. + require.EqualValues(t, expected, mockHbirdServer.Flyovers) +} + +func TestListFlyovers(t *testing.T) { + deadline, _ := t.Deadline() + ctx, cancelF := context.WithDeadline(context.Background(), deadline) + defer cancelF() + + expected := []*hummingbird.Flyover{ + { + BaseHop: hummingbird.BaseHop{ + IA: xtest.MustParseIA("1-ff00:0:333"), + Ingress: 3, + Egress: 2, + }, + ResID: 40, + Bw: 3, + StartTime: 10, + Duration: 60, + Ak: [16]byte{3, 4, 5, 6}, + }, + { + BaseHop: hummingbird.BaseHop{ + IA: xtest.MustParseIA("1-ff00:0:111"), + Ingress: 1, + Egress: 2, + }, + ResID: 40, + Bw: 3, + StartTime: 10, + Duration: 60, + Ak: [16]byte{3, 4, 5, 6}, + }, + } + + mockHbirdServer := &mockServer{ + Flyovers: expected, + } + s := &DaemonServer{ + FlyoverDB: mockHbirdServer, + } + + req := &sdpb.ListFlyoversRequest{} + res, err := s.ListFlyovers(ctx, req) + require.NoError(t, err) + + // Check response. + require.EqualValues(t, expected, hummingbird.ConvertFlyoversFromPB(res.Flyovers)) +} + +func getMockScionPaths(t require.TestingT, paths [][]any) []path.Path { + ret := make([]path.Path, len(paths)) + for i, p := range paths { + ret[i] = getMockScionPath(t, p...) + } + return ret +} + +// getMockScionPath returns a snet.path.Path that resembles a SCION path, with appropriate +// metadata included. +// The parameter `hops` must be of the form (0, "1-ff00:0:1", 1, 2, "1-ff00:0:2", 0) to indicate +// one hop between those two ASes. For more ASes, add more hops in the middle. +// First and last interface IDs must always be 0. +func getMockScionPath(t require.TestingT, hops ...any) path.Path { + // Check the arguments. + require.Equal(t, 0, len(hops)%3, "invalid hops field") + require.Equal(t, 0, hops[0].(int)) + require.Equal(t, 0, hops[len(hops)-1].(int)) + + // Parse hops argument. + hopFields := make([]pathlayers.HopField, len(hops)/3) + // interfaces has src and dst as extra. Will have to remove first and last items. + interfaces := make([]snet.PathInterface, len(hops)/3*2) + for i := 0; i < len(hops); i += 3 { + require.IsType(t, 0, hops[i]) // check is int + require.IsType(t, "", hops[i+1]) // check is string + require.IsType(t, 0, hops[i+2]) // check is int + + ia := xtest.MustParseIA(hops[i+1].(string)) + in := hops[i].(int) + eg := hops[i+2].(int) + + // Set the values for this hop. + hopFields[i/3].ConsIngress = uint16(in) + hopFields[i/3].ConsEgress = uint16(eg) + hopFields[i/3].ExpTime = currUnixTimestamp + 100 + hopFields[i/3].Mac = [6]byte{1, 2, 3, 4, 5, 6} + + // Set the values for the ingress and the egress interfaces for the metadata field. + interfaces[i/3*2].IA = ia + interfaces[i/3*2].ID = common.IFIDType(in) + interfaces[i/3*2+1].IA = ia + interfaces[i/3*2+1].ID = common.IFIDType(eg) + } + + // Build a SCION decoded path. + scionPath := scion.Decoded{ + Base: scion.Base{ + PathMeta: scion.MetaHdr{ + SegLen: [3]uint8{uint8(len(hops) / 3), 0, 0}, + }, + NumINF: 1, + NumHops: len(hopFields), + }, + InfoFields: []pathlayers.InfoField{ + { + ConsDir: true, + SegID: 1, + Timestamp: 10, + }, + }, + HopFields: hopFields, + } + + // Build a SCION path based on the decoded one. + raw, err := scionPath.ToRaw() + require.NoError(t, err) + return path.Path{ + Src: interfaces[0].IA, + Dst: interfaces[len(interfaces)-1].IA, + Meta: snet.PathMetadata{ + // Remove the extra start and end hops. + Interfaces: interfaces[1 : len(interfaces)-1], + }, + DataplanePath: path.SCION{ + Raw: raw.Raw, + }, + } +} + +// getMockFlyovers receives a []any like {0, "1-ff00:0:1", 1} and creates a flyover +// using those values. +func getMockFlyovers(t require.TestingT, hops ...any) []*hummingbird.Flyover { + // Parse hops argument. + flyovers := make([]*hummingbird.Flyover, 0) + for i := 0; i < len(hops); i++ { + var f *hummingbird.Flyover + if hops[i] != nil { + in := hops[i].(int) + ia := xtest.MustParseIA(hops[i+1].(string)) + eg := hops[i+2].(int) + f = &hummingbird.Flyover{ + BaseHop: hummingbird.BaseHop{ + IA: ia, + Ingress: uint16(in), + Egress: uint16(eg), + }, + StartTime: currUnixTimestamp, + Duration: 100, + } + i += 2 // advance faster + } + // Append a new flyover or nil. + flyovers = append(flyovers, f) + } + return flyovers +} + +type mockServer struct { + Flyovers []*hummingbird.Flyover +} + +func (m *mockServer) BeginTransaction(ctx context.Context, opts *sql.TxOptions, +) (hummingbirddb.Transaction, error) { + panic("not implemented") +} + +func (m *mockServer) GetFlyovers( + ctx context.Context, + owners []addr.IA, +) ([]*hummingbird.Flyover, error) { + + if len(owners) == 0 { + return m.Flyovers, nil + } + + // Create a set of the requested IAs. + ownerMap := make(map[addr.IA]struct{}) + for _, o := range owners { + ownerMap[o] = struct{}{} + } + + // Find any flyover with any such IA and return it. + ret := make([]*hummingbird.Flyover, 0) + for _, f := range m.Flyovers { + if _, ok := ownerMap[f.IA]; ok { + ret = append(ret, f) + } + } + return ret, nil +} + +func (m *mockServer) StoreFlyovers(ctx context.Context, flyovers []*hummingbird.Flyover) error { + m.Flyovers = append(m.Flyovers, flyovers...) + return nil +} + +func (m *mockServer) DeleteExpiredFlyovers(ctx context.Context) (int, error) { + panic("not implemented") +} diff --git a/pkg/daemon/BUILD.bazel b/pkg/daemon/BUILD.bazel index 1d0f0d9fdc..6bac6b037d 100644 --- a/pkg/daemon/BUILD.bazel +++ b/pkg/daemon/BUILD.bazel @@ -6,6 +6,7 @@ go_library( "apitypes.go", "daemon.go", "grpc.go", + "grpc_hummingbird.go", "metrics.go", ], importpath = "github.com/scionproto/scion/pkg/daemon", @@ -15,6 +16,7 @@ go_library( "//pkg/daemon/internal/metrics:go_default_library", "//pkg/drkey:go_default_library", "//pkg/grpc:go_default_library", + "//pkg/hummingbird:go_default_library", "//pkg/metrics:go_default_library", "//pkg/private/common:go_default_library", "//pkg/private/ctrl/path_mgmt:go_default_library", diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 24d0b6b9f8..7bcfc87fb1 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -23,6 +23,7 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/daemon/internal/metrics" "github.com/scionproto/scion/pkg/drkey" + "github.com/scionproto/scion/pkg/hummingbird" libmetrics "github.com/scionproto/scion/pkg/metrics" "github.com/scionproto/scion/pkg/private/common" "github.com/scionproto/scion/pkg/private/ctrl/path_mgmt" @@ -87,6 +88,12 @@ type Connector interface { DRKeyGetHostASKey(ctx context.Context, meta drkey.HostASMeta) (drkey.HostASKey, error) // DRKeyGetHostHostKey requests a Host-Host Key from the daemon. DRKeyGetHostHostKey(ctx context.Context, meta drkey.HostHostMeta) (drkey.HostHostKey, error) + + StoreFlyovers(ctx context.Context, flyovers []*hummingbird.Flyover) error + ListFlyovers(ctx context.Context) ([]*hummingbird.Flyover, error) + GetReservations(ctx context.Context, src, dst addr.IA, minBW uint16, refresh bool, + ) ([]*hummingbird.Reservation, error) + // Close shuts down the connection to the daemon. Close() error } diff --git a/pkg/daemon/grpc_hummingbird.go b/pkg/daemon/grpc_hummingbird.go new file mode 100644 index 0000000000..3246fd541c --- /dev/null +++ b/pkg/daemon/grpc_hummingbird.go @@ -0,0 +1,73 @@ +// 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 daemon + +import ( + "context" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/hummingbird" + sdpb "github.com/scionproto/scion/pkg/proto/daemon" +) + +func (c grpcConn) StoreFlyovers( + ctx context.Context, + flyovers []*hummingbird.Flyover, +) error { + + client := sdpb.NewDaemonServiceClient(c.conn) + _, err := client.StoreFlyovers(ctx, &sdpb.StoreFlyoversRequest{ + Flyovers: hummingbird.ConvertFlyoversToPB(flyovers), + }) + + return err +} + +func (c grpcConn) ListFlyovers(ctx context.Context, +) ([]*hummingbird.Flyover, error) { + + client := sdpb.NewDaemonServiceClient(c.conn) + res, err := client.ListFlyovers(ctx, &sdpb.ListFlyoversRequest{}) + if err != nil { + return nil, err + } + return hummingbird.ConvertFlyoversFromPB(res.Flyovers), nil +} + +func (c grpcConn) GetReservations( + ctx context.Context, + src addr.IA, + dst addr.IA, + minBW uint16, + refresh bool, +) ([]*hummingbird.Reservation, error) { + + client := sdpb.NewDaemonServiceClient(c.conn) + res, err := client.GetReservations(ctx, &sdpb.GetReservationsRequest{ + SourceIsdAs: uint64(src), + DestinationIsdAs: uint64(dst), + MinBandwidth: uint32(minBW), + Refresh: refresh, + }) + if err != nil { + return nil, err + } + + // Return those reservations. + if res == nil { + return nil, nil + } + return hummingbird.ConvertReservationsFromPB(res.Reservations) +} diff --git a/pkg/daemon/mock_daemon/mock.go b/pkg/daemon/mock_daemon/mock.go index 61c85508c3..eaf77055f1 100644 --- a/pkg/daemon/mock_daemon/mock.go +++ b/pkg/daemon/mock_daemon/mock.go @@ -115,6 +115,21 @@ func (mr *MockConnectorMockRecorder) DRKeyGetHostHostKey(arg0, arg1 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DRKeyGetHostHostKey", reflect.TypeOf((*MockConnector)(nil).DRKeyGetHostHostKey), arg0, arg1) } +// GetReservations mocks base method. +func (m *MockConnector) GetReservations(arg0 context.Context, arg1, arg2 addr.IA) ([]snet.Path, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetReservations", arg0, arg1, arg2) + ret0, _ := ret[0].([]snet.Path) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetReservations indicates an expected call of GetReservations. +func (mr *MockConnectorMockRecorder) GetReservations(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReservations", reflect.TypeOf((*MockConnector)(nil).GetReservations), arg0, arg1, arg2) +} + // IFInfo mocks base method. func (m *MockConnector) IFInfo(arg0 context.Context, arg1 []common.IFIDType) (map[common.IFIDType]*net.UDPAddr, error) { m.ctrl.T.Helper() diff --git a/pkg/hummingbird/BUILD.bazel b/pkg/hummingbird/BUILD.bazel new file mode 100644 index 0000000000..c702873aeb --- /dev/null +++ b/pkg/hummingbird/BUILD.bazel @@ -0,0 +1,39 @@ +load("//tools/lint:go.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "hummingbird.go", + "reservation.go", + "translate.go", + ], + importpath = "github.com/scionproto/scion/pkg/hummingbird", + visibility = ["//visibility:public"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/proto/daemon:go_default_library", + "//pkg/slayers/path/hummingbird:go_default_library", + "//pkg/slayers/path/scion:go_default_library", + "//pkg/snet:go_default_library", + "//pkg/snet/path:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "hummingbird_test.go", + "reservation_test.go", + "utils_test.go", + ], + deps = [ + ":go_default_library", + "//pkg/slayers/path:go_default_library", + "//pkg/slayers/path/hummingbird:go_default_library", + "//pkg/slayers/path/scion:go_default_library", + "//pkg/snet:go_default_library", + "//pkg/snet/path:go_default_library", + "@com_github_stretchr_testify//assert:go_default_library", + ], +) diff --git a/pkg/hummingbird/hummingbird.go b/pkg/hummingbird/hummingbird.go new file mode 100644 index 0000000000..132d6f5f1e --- /dev/null +++ b/pkg/hummingbird/hummingbird.go @@ -0,0 +1,97 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hummingbird + +import ( + "time" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" + "github.com/scionproto/scion/pkg/slayers/path/scion" + "github.com/scionproto/scion/pkg/snet" + snetpath "github.com/scionproto/scion/pkg/snet/path" +) + +// Describes a pair of Ingress and Egress interfaces in a specific AS +type BaseHop struct { + // IA denotes the IA for which a reservation is valid + IA addr.IA + // Ingress is the ingress interface for the reserved hop + Ingress uint16 + // Egress is the egress interface of the reserved hop + Egress uint16 +} + +type Flyover struct { + BaseHop + + // ResID is the reservation ID of the reservation. It is unique PER AS + ResID uint32 + // Ak is the authentication key of the reservation + Ak [16]byte + // Bw is the reserved Bandwidth + Bw uint16 + // StartTime is the unix timestamp for the start of the reservation + StartTime uint32 + // Duration is the duration of the reservation in seconds + Duration uint16 +} + +// Converts a SCiON path to a Hummingbird path without adding any reservations +// Relaces the SCiON dataplane path by a Hummingbird path +func ConvertToHbirdPath(p snet.Path, timeStamp time.Time) (snet.Path, error) { + if p == nil { + return nil, serrors.New("Cannot convert nil path") + } + dpath, ok := p.Dataplane().(snetpath.SCION) + if !ok { + return nil, serrors.New("Can only convert SCiON paths to Hummingbird") + } + dec, err := convertSCIONToHbirdDecoded(dpath.Raw) + if err != nil { + return nil, err + } + // set metaheader timestamps + secs := uint32(timeStamp.Unix()) + millis := uint32(timeStamp.Nanosecond()/1000) << 22 + dec.PathMeta.BaseTS = secs + dec.PathMeta.HighResTS = millis + + hbird, err := snetpath.NewHbirdFromDecoded(&dec) + if err != nil { + return nil, err + } + // update dataplane path + switch v := p.(type) { + case snetpath.Path: + v.DataplanePath = hbird + p = v + default: + return nil, serrors.New("Unsupported snet path struct", "path", p) + } + return p, nil +} + +func convertSCIONToHbirdDecoded(p []byte) (hummingbird.Decoded, error) { + scionDec := scion.Decoded{} + if err := scionDec.DecodeFromBytes(p); err != nil { + return hummingbird.Decoded{}, err + } + + hbirdDec := hummingbird.Decoded{} + hbirdDec.ConvertFromScionDecoded(scionDec) + return hbirdDec, nil +} diff --git a/pkg/hummingbird/hummingbird_test.go b/pkg/hummingbird/hummingbird_test.go new file mode 100644 index 0000000000..a46be88efe --- /dev/null +++ b/pkg/hummingbird/hummingbird_test.go @@ -0,0 +1,169 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hummingbird_test + +import ( + "testing" + "time" + + "github.com/scionproto/scion/pkg/hummingbird" + "github.com/stretchr/testify/assert" +) + +var testHops = []hummingbird.BaseHop{ + { + IA: interfacesTest[0].IA, + Ingress: 0, + Egress: 1, + }, + { + IA: interfacesTest[1].IA, + Ingress: 2, + Egress: 4, + }, + { + IA: interfacesTest[len(interfacesTest)-1].IA, + Ingress: 5, + Egress: 0, + }, +} + +var testFlyoversInDB = []hummingbird.Flyover{ + // For the first hop: + { + BaseHop: testHops[0], + ResID: testFlyoverFieldsReserved[0].ResID + 1, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + Bw: 16, + Duration: 70, + StartTime: uint32(fixedTime.Unix()) - 80, // expired + }, + { + BaseHop: hummingbird.BaseHop{ + IA: testHops[0].IA, + Ingress: testHops[0].Ingress, + Egress: testHops[0].Egress + 1, // not the expected egress + }, + ResID: testFlyoverFieldsReserved[0].ResID + 2, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + Bw: 16, + Duration: 120, + StartTime: uint32(fixedTime.Unix()) - 10, + }, + { + BaseHop: testHops[0], + ResID: testFlyoverFieldsReserved[0].ResID + 3, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + Bw: 16, + Duration: 120, + StartTime: uint32(fixedTime.Unix()) - 1000, // expired + }, + { + BaseHop: testHops[0], + ResID: testFlyoverFieldsReserved[0].ResID, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + Bw: 16, + Duration: 120, + StartTime: uint32(fixedTime.Unix()) - 10, + }, + + // For the second hop: + { + BaseHop: testHops[1], + ResID: testFlyoverFieldsReserved[1].ResID + 1, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 1, 0}, + Bw: 16, + Duration: 180, + StartTime: uint32(fixedTime.Unix()) - 1000, // expired + }, + { + BaseHop: testHops[1], + ResID: testFlyoverFieldsReserved[1].ResID, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 1, 0}, + Bw: 16, + Duration: 180, + StartTime: uint32(fixedTime.Unix()) - 32, + }, + + // For the third hop: + { + BaseHop: testHops[2], + ResID: testFlyoverFieldsReserved[3].ResID + 1, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}, + Bw: 20, + Duration: 150, + StartTime: uint32(fixedTime.Unix()) + 1, // not yet valid + }, + { + BaseHop: testHops[2], + ResID: testFlyoverFieldsReserved[3].ResID, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}, + Bw: testFlyoverFieldsReserved[3].Bw, + Duration: testFlyoverFieldsReserved[3].Duration, + StartTime: uint32(fixedTime.Unix()) - 80, + }, + { + BaseHop: testHops[2], + ResID: testFlyoverFieldsReserved[3].ResID + 2, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}, + Bw: testFlyoverFieldsReserved[3].Bw, + Duration: testFlyoverFieldsReserved[3].Duration, + StartTime: uint32(fixedTime.Unix()) - 10, + }, +} + +var testExpectedFlyovers = []*hummingbird.Flyover{ + // For the first hop: + { + BaseHop: testHops[0], + ResID: testFlyoverFieldsReserved[0].ResID, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + Bw: 16, + Duration: 120, + StartTime: uint32(fixedTime.Unix()) - 10, + }, + // For the second hop: + { + BaseHop: testHops[1], + ResID: testFlyoverFieldsReserved[1].ResID, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 1, 0}, + Bw: 16, + Duration: 180, + StartTime: uint32(fixedTime.Unix()) - 32, + }, + // for the third hop: + nil, + // For the fourth hop: + { + BaseHop: testHops[2], + ResID: testFlyoverFieldsReserved[3].ResID, + Ak: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}, + Bw: 20, + Duration: 150, + StartTime: uint32(fixedTime.Unix()) - 80, + }, +} + +func TestConvertToHbirdPath(t *testing.T) { + scionPath := getScionSnetPath(t) + + now := time.Now() + expectecPath, err := getHbirdNoFlyoversSnetPath(now) + assert.NoError(t, err) + + out, err := hummingbird.ConvertToHbirdPath(scionPath, now) + assert.NoError(t, err) + assert.Equal(t, expectecPath, out) +} diff --git a/pkg/hummingbird/reservation.go b/pkg/hummingbird/reservation.go new file mode 100644 index 0000000000..7d8d076af3 --- /dev/null +++ b/pkg/hummingbird/reservation.go @@ -0,0 +1,296 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hummingbird + +import ( + "encoding/binary" + "time" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" + "github.com/scionproto/scion/pkg/slayers/path/scion" + "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" +) + +// Reservation represents a possibly partially reserved path, with zero or more flyovers. +type Reservation struct { + dec *hummingbird.Decoded // caches a decoded path for multiple uses + hops []Hop // possible flyovers, one per dec.HopField that has a flyover. + now time.Time // the current time + minBW uint16 // the minimum required bandwidth + + counter uint32 // duplicate detection counter +} + +type Hop struct { + Hopfield *hummingbird.FlyoverHopField // dataplane hop field + Flyover *Flyover // flyover used to build this hop +} + +// NewReservation creates a new reservation object. The option setting functions are executed in +// the order they appear in the slice. +func NewReservation(opts ...reservationModFcn) (*Reservation, error) { + r := &Reservation{ + now: time.Now(), + } + // Run all options on this object. + for _, fcn := range opts { + if err := fcn(r); err != nil { + return nil, err + } + } + + return r, nil +} + +// reservationModFcn is a options setting function for a reservation. +type reservationModFcn func(*Reservation) error + +// WithScionPath allows to build a Reservation based on the SCION path and flyovers passed as +// arguments. +// The flyovers are chosen from the map in order of appearance iff they are suitable, i.e. if +// they have a validity period intersecting with now. +func WithScionPath(p snet.Path, flyovers FlyoverMap) reservationModFcn { + return func(r *Reservation) error { + switch p := p.Dataplane().(type) { + case path.SCION: + scion := scion.Decoded{} + if err := scion.DecodeFromBytes(p.Raw); err != nil { + return serrors.Join(err, serrors.New("Failed to Prepare Hummingbird Path")) + } + r.dec = &hummingbird.Decoded{} + r.dec.ConvertFromScionDecoded(scion) + default: + return serrors.New("Unsupported path type") + } + + // We use the path metadata to get the IA from it. This sequence of interfaces does not + // include the egress-to-ingress crossed over interfaces in the core AS. + interfaces := p.Metadata().Interfaces + + // Add the first hop of the first segment now. + r.newHopSelectFlyover( + interfaces[0].IA, + 0, + uint16(interfaces[0].ID), + 0, + flyovers, + ) + + // The dataplane path in c.dec contains inf fields and cross-over hops. + // Do each segment at a time to ignore the first hop of every segment + // (the first segment's first hop was already added above). + dpHopIdx := uint8(1) // the index of the current hop in the dataplane. + interfaceCount := 1 // the index of the interfaces (no cross overs). + for infIdx := 0; infIdx < r.dec.NumINF; infIdx, dpHopIdx = infIdx+1, dpHopIdx+1 { + // Preserve the hop count locally, as we modify the path inside the loop itself. + hopCount := int(r.dec.Base.PathMeta.SegLen[infIdx]) / hummingbird.HopLines + for i := 1; i < hopCount; i, dpHopIdx = i+1, dpHopIdx+1 { + r.newHopSelectFlyover( + interfaces[interfaceCount*2-1].IA, + uint16(interfaces[interfaceCount*2-1].ID), + egressID(interfaces, interfaceCount), + dpHopIdx, + flyovers, + ) + interfaceCount++ + } + } + + return nil + } +} + +// WithExistingHbirdPath allows to create a Reservation from an existing Hummingbird decoded +// path and its corresponding hop sequence. +func WithExistingHbirdPath(p *hummingbird.Decoded, flyovers []*Flyover) reservationModFcn { + // func WithExistingHbirdPath(p *hummingbird.Decoded, hops []Hop) reservationModFcn { + return func(r *Reservation) error { + r.dec = p + // Create as many hops as non nil flyovers. + for i, flyover := range flyovers { + if flyover == nil { + continue + } + r.newHop(flyover.IA, flyover.Ingress, flyover.Egress, uint8(i), flyover) + } + // For each hop field, clean up the ResStartTime as it's set when deriving the dataplane + // path. + for i := range r.dec.HopFields { + r.dec.HopFields[i].ResStartTime = 0 + } + + return nil + } +} + +// WithNow modifies the current point in time for this reservation. It is useful to filter +// the different flyovers that can be passed to WithScionPath. +func WithNow(now time.Time) reservationModFcn { + return func(r *Reservation) error { + r.now = now + return nil + } +} + +// WithMinBW modifies the minimum bandwidth required when filtering flyovers at the time of +// reservation creation. +func WithMinBW(bw uint16) reservationModFcn { + return func(r *Reservation) error { + r.minBW = bw + return nil + } +} + +func (r *Reservation) Destination() addr.IA { + return r.hops[len(r.hops)-1].Flyover.IA +} + +func (r *Reservation) GetHummingbirdPath() *hummingbird.Decoded { + return r.dec +} + +// FlyoverPerHopField returns a slice of pointers to flyovers, one per hop field present in the path, +// i.e. the length of the slice is the hop field count. +// If a hop field is not covered by a flyover, nil is used in its place. +func (r *Reservation) FlyoverPerHopField() []*Flyover { + flyovers := make([]*Flyover, len(r.dec.HopFields)) + for hopIdx, i := 0, 0; i < len(flyovers) && hopIdx < len(r.hops); i++ { + var flyover *Flyover + if r.hops[hopIdx].Hopfield == &r.dec.HopFields[i] { + flyover = r.hops[hopIdx].Flyover + hopIdx++ + } + flyovers[i] = flyover + } + + return flyovers +} + +func (r *Reservation) FlyoverAndHFCount() (int, int) { + return len(r.hops), len(r.dec.HopFields) +} + +// DeriveDataPlanePath sets pathmeta timestamps and increments duplicate detection counter and +// updates MACs of all flyoverfields. +func (r *Reservation) DeriveDataPlanePath( + pktLen uint16, + timeStamp time.Time, +) *hummingbird.Decoded { + + // Update timestamps + secs := uint32(timeStamp.Unix()) + millis := uint32(timeStamp.Nanosecond()/1000) << 22 + millis |= r.counter + r.dec.Base.PathMeta.BaseTS = secs + r.dec.Base.PathMeta.HighResTS = millis + //increment counter for next packet + if r.counter >= 1<<22-1 { + r.counter = 0 + } else { + r.counter += 1 + } + // compute Macs for Flyovers + var byteBuffer [hummingbird.FlyoverMacBufferSize]byte + var xkbuffer [hummingbird.XkBufferSize]uint32 + for _, h := range r.hops { + if !h.Hopfield.Flyover { + continue + } + h.Hopfield.ResStartTime = uint16(secs - h.Flyover.StartTime) + flyovermac := hummingbird.FullFlyoverMac(h.Flyover.Ak[:], r.Destination(), pktLen, + h.Hopfield.ResStartTime, millis, byteBuffer[:], xkbuffer[:]) + + binary.BigEndian.PutUint32(h.Hopfield.HopField.Mac[:4], + binary.BigEndian.Uint32(flyovermac[:4])^binary.BigEndian.Uint32(h.Hopfield.HopField.Mac[:4])) + binary.BigEndian.PutUint16(h.Hopfield.HopField.Mac[4:], + binary.BigEndian.Uint16(flyovermac[4:])^binary.BigEndian.Uint16(h.Hopfield.HopField.Mac[4:])) + } + + return r.dec +} + +func (r *Reservation) newHopSelectFlyover(ia addr.IA, in, eg uint16, + hfIdx uint8, flyoverSet FlyoverMap) { + + // Look for a valid flyover. + now := uint32(r.now.Unix()) + k := BaseHop{ + IA: ia, + Ingress: in, + Egress: eg, + } + flyovers := flyoverSet[k] + for _, flyover := range flyovers { + if flyover.StartTime <= now && uint32(flyover.Duration) >= now-flyover.StartTime && + flyover.Bw >= r.minBW { + + r.newHop(ia, in, eg, hfIdx, flyover) + break + } + } +} + +func (r *Reservation) newHop(ia addr.IA, in, eg uint16, + hfIdx uint8, flyover *Flyover) { + + // Find the hop field from its index. + hf := &r.dec.HopFields[hfIdx] + + if !hf.Flyover { + // Because we are setting a plain hop field as a flyover, it will use two more lines. + r.dec.NumLines += 2 + r.dec.PathMeta.SegLen[r.dec.InfIndexForHFIndex(hfIdx)] += 2 + hf.Flyover = true + } + + hf.Bw = flyover.Bw + hf.Duration = flyover.Duration + hf.ResID = flyover.ResID + r.hops = append(r.hops, Hop{ + Hopfield: hf, + Flyover: flyover, + }) +} + +// egressID returns the egress ID from a sequence of IDs (such as that in the metadata field +// of a snet.Path) given its index. If index is past the length of the sequence, the egress +// ID 0 is returning, meaning egress ID is that last AS. +func egressID(ifaces []snet.PathInterface, idx int) uint16 { + i := idx * 2 + if i >= len(ifaces) { + return 0 + } + return uint16(ifaces[i].ID) +} + +// FlyoverMap is a map between a flyover IA,ingress,egress and its corresponding collection of +// flyover objects (each of them can have e.g. different starting times). +type FlyoverMap map[BaseHop][]*Flyover + +func FlyoversToMap(flyovers []*Flyover) FlyoverMap { + ret := make(FlyoverMap) + for _, flyover := range flyovers { + k := BaseHop{ + IA: flyover.IA, + Ingress: flyover.Ingress, + Egress: flyover.Egress, + } + ret[k] = append(ret[k], flyover) + } + return ret +} diff --git a/pkg/hummingbird/reservation_test.go b/pkg/hummingbird/reservation_test.go new file mode 100644 index 0000000000..08e56d6c86 --- /dev/null +++ b/pkg/hummingbird/reservation_test.go @@ -0,0 +1,180 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hummingbird_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/scionproto/scion/pkg/hummingbird" + hbirddp "github.com/scionproto/scion/pkg/slayers/path/hummingbird" + "github.com/scionproto/scion/pkg/snet/path" +) + +func TestReservationWithScionPath(t *testing.T) { + scionPath := getScionSnetPath(t) + + r, err := hummingbird.NewReservation( + hummingbird.WithNow(fixedTime), + hummingbird.WithScionPath(scionPath, flyoverSliceToMap(testFlyoversInDB)), + ) + assert.NoError(t, err) + + // Check that the derived path in the reservation is the same as hbirdPath (which is the + // expected one). + hbirdPath, err := getHbirdFlyoversSnetPath(t, fixedTime) + assert.NoError(t, err) + decoded := r.DeriveDataPlanePath(16, fixedTime) + raw := path.Hummingbird{ + Raw: make([]byte, decoded.Len()), + } + err = decoded.SerializeTo(raw.Raw) + assert.NoError(t, err) + scionPath.DataplanePath = raw + + assert.Equal(t, hbirdPath, scionPath) + + // Check that the number of flyovers, nil or otherwise, is the same as hop fields. + flyovers := r.FlyoverPerHopField() + assert.Equal(t, len(decodedScionTestPath.HopFields), len(flyovers)) +} + +func TestReservationWithScionPathNoFlyovers(t *testing.T) { + scionPath := getScionSnetPath(t) + + r, err := hummingbird.NewReservation( + hummingbird.WithNow(fixedTime), + hummingbird.WithScionPath(scionPath, nil), // nil == "no flyovers" + ) + assert.NoError(t, err) + + // Should not have any flyoverCount, but the same amount of hop fields in the dataplane. + flyoverCount, hfCount := r.FlyoverAndHFCount() + assert.Equal(t, 0, flyoverCount) + assert.Equal(t, decodedScionTestPath.NumHops, hfCount) + + // All hop fields must not be flyovers. + decoded := r.DeriveDataPlanePath(16, fixedTime) + for i, hf := range decoded.HopFields { + assert.False(t, hf.Flyover, "failed at index %d", i) + } + + // Check reconstruction from parts. + // First create a reservation from no flyovers again. + rOrig, err := hummingbird.NewReservation( + hummingbird.WithNow(fixedTime), + hummingbird.WithScionPath(scionPath, nil), // nil == "no flyovers" + ) + assert.NoError(t, err) + + // Now create a reservation from an existing hummingbird path and flyovers. + flyovers := r.FlyoverPerHopField() + r2, err := hummingbird.NewReservation( + hummingbird.WithNow(fixedTime), + hummingbird.WithExistingHbirdPath(decoded, flyovers), + ) + assert.NoError(t, err) + // And derive a hummingbird path from both reservations. + expected := rOrig.DeriveDataPlanePath(16, fixedTime) + got := r2.DeriveDataPlanePath(16, fixedTime) + assert.Equal(t, expected, got) +} + +func TestReservationWithHbirdPath(t *testing.T) { + // Build a Reservation from an existing decoded hummingbird path and its associated + // flyover sequence. + r, err := hummingbird.NewReservation( + hummingbird.WithNow(fixedTime), + hummingbird.WithExistingHbirdPath( + decodedHbirdTestPathFlyovers, + testExpectedFlyovers), + ) + assert.NoError(t, err) + + // Expected: + expected, err := hummingbird.NewReservation( + hummingbird.WithNow(fixedTime), + hummingbird.WithScionPath(getScionSnetPath(t), + flyoverSliceToMap(testFlyoversInDB), + ), + ) + assert.NoError(t, err) + assert.Equal(t, expected, r) +} + +func TestDeriveDataPlanePath(t *testing.T) { + // New reservation with a scion path. + scionPath := getScionSnetPath(t) + r, err := hummingbird.NewReservation( + hummingbird.WithNow(fixedTime), + hummingbird.WithScionPath(scionPath, flyoverSliceToMap(testFlyoversInDB)), + ) + assert.NoError(t, err) + + // Run twice. + for i := 0; i < 2; i++ { + // Derive dataplane path. + decoded := r.DeriveDataPlanePath(16, fixedTime) + + // Check that it is a valid path. + buf := make([]byte, decoded.Len()) + err = decoded.SerializeTo(buf) + assert.NoError(t, err) + // Deserialize to hummingbird Decoded. + decoded = &hbirddp.Decoded{} + err = decoded.DecodeFromBytes(buf) + assert.NoError(t, err) + // Deserialize to hummingbird Raw. + hbirdRaw := hbirddp.Raw{} + err = hbirdRaw.DecodeFromBytes(buf) + assert.NoError(t, err) + } +} + +func TestDeriveDataPlanePathNoFlyovers(t *testing.T) { + // New reservation with a scion path. + scionPath := getScionSnetPath(t) + r, err := hummingbird.NewReservation( + hummingbird.WithNow(fixedTime), + hummingbird.WithScionPath(scionPath, nil), + ) + assert.NoError(t, err) + + // Derive dataplane path. + decoded := r.DeriveDataPlanePath(16, fixedTime) + + // Check that it is a valid path. + buf := make([]byte, decoded.Len()) + err = decoded.SerializeTo(buf) + assert.NoError(t, err) + // Deserialize to hummingbird Decoded. + decoded = &hbirddp.Decoded{} + err = decoded.DecodeFromBytes(buf) + assert.NoError(t, err) + // Deserialize to hummingbird Raw. + hbirdRaw := hbirddp.Raw{} + err = hbirdRaw.DecodeFromBytes(buf) + assert.NoError(t, err) +} + +func flyoverSliceToMap(flyovers []hummingbird.Flyover) hummingbird.FlyoverMap { + m := make(hummingbird.FlyoverMap) + for _, flyover := range flyovers { + clone := flyover + m[clone.BaseHop] = append(m[clone.BaseHop], &clone) + } + return m +} diff --git a/pkg/hummingbird/translate.go b/pkg/hummingbird/translate.go new file mode 100644 index 0000000000..6a3d80a6d4 --- /dev/null +++ b/pkg/hummingbird/translate.go @@ -0,0 +1,128 @@ +// 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 hummingbird + +import ( + "github.com/scionproto/scion/pkg/addr" + sdpb "github.com/scionproto/scion/pkg/proto/daemon" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" +) + +func ConvertFlyoverToPB(f *Flyover) *sdpb.Flyover { + if f == nil { + return nil + } + ret := &sdpb.Flyover{ + Ia: uint64(f.IA), + Ingress: uint32(f.Ingress), + Egress: uint32(f.Egress), + Bw: uint32(f.Bw), + ResId: f.ResID, + StartTime: f.StartTime, + Duration: uint32(f.Duration), + Ak: append([]byte{}, f.Ak[:]...), + } + + return ret +} + +func ConvertFlyoversToPB(flyovers []*Flyover) []*sdpb.Flyover { + ret := make([]*sdpb.Flyover, len(flyovers)) + for i, f := range flyovers { + ret[i] = ConvertFlyoverToPB(f) + } + return ret +} + +// protobuf field Ak of Flyover. Used to check if the serialized flyover was a nil value or not. +var akField = (&sdpb.Flyover{}).ProtoReflect().Descriptor().Fields().ByName("ak") + +// flyoverWasNil returns true if the Flyover protobuf message was nil. +// Heuristically returns true if the field `Ak` was not set. +func flyoverWasNil(f *sdpb.Flyover) bool { + return !f.ProtoReflect().Has(akField) +} + +func ConvertFlyoverFromPB(f *sdpb.Flyover) *Flyover { + if f == nil || flyoverWasNil(f) { + // No flyover. + return nil + } + + ret := &Flyover{ + BaseHop: BaseHop{ + IA: addr.IA(f.Ia), + Ingress: uint16(f.Ingress), + Egress: uint16(f.Egress), + }, + Bw: uint16(f.Bw), + ResID: f.ResId, + StartTime: f.StartTime, + Duration: uint16(f.Duration), + } + copy(ret.Ak[:], f.Ak) + return ret +} +func ConvertFlyoversFromPB(flyovers []*sdpb.Flyover) []*Flyover { + ret := make([]*Flyover, len(flyovers)) + for i, f := range flyovers { + ret[i] = ConvertFlyoverFromPB(f) + } + return ret +} + +func ConvertReservationToPB(r *Reservation) (*sdpb.Reservation, error) { + // Prepare the hummingbird path. + p := r.GetHummingbirdPath() + raw := make([]byte, p.Len()) + if err := p.SerializeTo(raw); err != nil { + return nil, err + } + + // Prepare the flyovers. + flyovers := r.FlyoverPerHopField() + numF, numHF := r.FlyoverAndHFCount() + + return &sdpb.Reservation{ + Raw: raw, + Ratio: float64(numF) / float64(numHF), + Flyovers: ConvertFlyoversToPB(flyovers), + }, nil +} + +func ConvertReservationFromPB(pb *sdpb.Reservation) (*Reservation, error) { + // Decode path. + decoded := &hummingbird.Decoded{} + err := decoded.DecodeFromBytes(pb.Raw) + if err != nil { + return nil, err + } + + // Create reservation. + flyovers := ConvertFlyoversFromPB(pb.Flyovers) + return NewReservation(WithExistingHbirdPath(decoded, flyovers)) +} + +func ConvertReservationsFromPB(pb []*sdpb.Reservation) ([]*Reservation, error) { + ret := make([]*Reservation, len(pb)) + var err error + for i, r := range pb { + ret[i], err = ConvertReservationFromPB(r) + if err != nil { + return nil, err + } + } + return ret, nil +} diff --git a/pkg/hummingbird/utils_test.go b/pkg/hummingbird/utils_test.go new file mode 100644 index 0000000000..3fd2e5fddc --- /dev/null +++ b/pkg/hummingbird/utils_test.go @@ -0,0 +1,289 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hummingbird_test + +import ( + "testing" + "time" + + "github.com/scionproto/scion/pkg/slayers/path" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" + "github.com/scionproto/scion/pkg/slayers/path/scion" + "github.com/scionproto/scion/pkg/snet" + snetpath "github.com/scionproto/scion/pkg/snet/path" + "github.com/stretchr/testify/assert" +) + +var fixedTime = time.Unix(1136239445, 432) + +var testInfoFields = []path.InfoField{ + { + Peer: false, + ConsDir: false, + SegID: 0x111, + Timestamp: 0x100, + }, + { + Peer: false, + ConsDir: true, + SegID: 0x222, + Timestamp: 0x100, + }, +} + +var testScionHopFields = []path.HopField{ + { + ExpTime: 63, + ConsIngress: 1, + ConsEgress: 0, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + { + ExpTime: 63, + ConsIngress: 3, + ConsEgress: 2, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + { + ExpTime: 63, + ConsIngress: 0, + ConsEgress: 4, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + { + ExpTime: 63, + ConsIngress: 5, + ConsEgress: 0, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, +} + +var testFlyoverFields = []hummingbird.FlyoverHopField{ + { + HopField: testScionHopFields[0], + Flyover: false, + }, + { + HopField: testScionHopFields[1], + Flyover: false, + }, + { + HopField: testScionHopFields[2], + Flyover: false, + }, + { + HopField: testScionHopFields[3], + Flyover: false, + }, +} + +var testFlyoverFieldsReserved = []hummingbird.FlyoverHopField{ + { + HopField: testScionHopFields[0], + Flyover: true, + ResID: 1234, + Bw: 16, + Duration: 120, + ResStartTime: 10, + }, + { + HopField: testScionHopFields[1], + Flyover: true, + ResID: 42, + Bw: 16, + Duration: 180, + ResStartTime: 32, + }, + { + HopField: testScionHopFields[2], + Flyover: false, + }, + { + HopField: testScionHopFields[3], + Flyover: true, + ResID: 365, + Bw: 20, + Duration: 150, + ResStartTime: 80, + }, +} + +var decodedScionTestPath = &scion.Decoded{ + Base: scion.Base{ + PathMeta: scion.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{2, 2, 0}, + }, + + NumINF: 2, + NumHops: 4, + }, + InfoFields: testInfoFields, + HopFields: testScionHopFields, +} + +var interfacesTest = []snet.PathInterface{ + { + IA: 12, + ID: 1, + }, + { + IA: 13, + ID: 2, + }, + { + IA: 13, + ID: 4, + }, + { + IA: 14, + ID: 5, + }, +} + +var decodedHbirdTestPath = &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{6, 6, 0}, + }, + NumINF: 2, + NumLines: 12, + }, + InfoFields: testInfoFields, + HopFields: testFlyoverFields, + FirstHopPerSeg: [2]uint8{2, 4}, +} + +var decodedHbirdTestPathFlyovers = &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{10, 8, 0}, // [5+5, 5+3, 0] + }, + NumINF: 2, + NumLines: 18, + }, + InfoFields: testInfoFields, + HopFields: testFlyoverFieldsReserved, + FirstHopPerSeg: [2]uint8{2, 4}, +} + +func getRawScionPath(d scion.Decoded) ([]byte, error) { + b := make([]byte, d.Len()) + err := d.SerializeTo(b) + return b, err +} + +func getScionSnetPath(t *testing.T) snetpath.Path { + t.Helper() + rawScion, err := getRawScionPath(*decodedScionTestPath) + assert.NoError(t, err) + p := snetpath.Path{ + Src: interfacesTest[0].IA, + Dst: interfacesTest[len(interfacesTest)-1].IA, + DataplanePath: snetpath.SCION{ + Raw: rawScion, + }, + Meta: snet.PathMetadata{ + Interfaces: interfacesTest, + }, + } + return p +} + +func getRawHbirdPath(h hummingbird.Decoded) ([]byte, error) { + b := make([]byte, h.Len()) + err := h.SerializeTo(b) + return b, err +} + +func getHbirdNoFlyoversSnetPath(t time.Time) (snetpath.Path, error) { + decoded := *decodedHbirdTestPath + secs := uint32(t.Unix()) + millis := uint32(t.Nanosecond()/1000) << 22 + decoded.Base.PathMeta.BaseTS = secs + decoded.Base.PathMeta.HighResTS = millis + + rawHbird, err := getRawHbirdPath(decoded) + p := snetpath.Path{ + Src: interfacesTest[0].IA, + Dst: interfacesTest[len(interfacesTest)-1].IA, + DataplanePath: snetpath.Hummingbird{ + Raw: rawHbird, + }, + Meta: snet.PathMetadata{ + Interfaces: interfacesTest, + }, + } + return p, err +} + +func getHbirdFlyoversSnetPath(t *testing.T, now time.Time) (snetpath.Path, error) { + // Fully clone from the global variable, to avoid mutating it. + serialized := make([]byte, decodedHbirdTestPathFlyovers.Len()) + err := decodedHbirdTestPathFlyovers.SerializeTo(serialized) + assert.NoError(t, err) + decoded := hummingbird.Decoded{} + err = decoded.DecodeFromBytes(serialized) + assert.NoError(t, err) + + secs := uint32(now.Unix()) + millis := uint32(now.Nanosecond()/1000) << 22 + decoded.Base.PathMeta.BaseTS = secs + decoded.Base.PathMeta.HighResTS = millis + + xkBuffer := make([]uint32, hummingbird.XkBufferSize) + macBuffer0 := make([]byte, hummingbird.FlyoverMacBufferSize) + macBuffer1 := make([]byte, hummingbird.FlyoverMacBufferSize) + macBuffer2 := make([]byte, hummingbird.FlyoverMacBufferSize) + + ak0 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + flyover0 := hummingbird.FullFlyoverMac(ak0, interfacesTest[len(interfacesTest)-1].IA, + 16, decoded.HopFields[0].ResStartTime, + millis, macBuffer0, xkBuffer) + + ak1 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 3, 2, 1, 0} + flyover1 := hummingbird.FullFlyoverMac(ak1, interfacesTest[len(interfacesTest)-1].IA, + 16, decoded.HopFields[1].ResStartTime, + millis, macBuffer1, xkBuffer) + + ak2 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7} + flyover2 := hummingbird.FullFlyoverMac(ak2, interfacesTest[len(interfacesTest)-1].IA, + 16, decoded.HopFields[3].ResStartTime, + millis, macBuffer2, xkBuffer) + + for i := 0; i < 6; i++ { + decoded.HopFields[0].HopField.Mac[i] = decoded.HopFields[0].HopField.Mac[i] ^ flyover0[i] + decoded.HopFields[1].HopField.Mac[i] = decoded.HopFields[1].HopField.Mac[i] ^ flyover1[i] + decoded.HopFields[3].HopField.Mac[i] = decoded.HopFields[3].HopField.Mac[i] ^ flyover2[i] + } + + rawHbird, err := getRawHbirdPath(decoded) + p := snetpath.Path{ + Src: interfacesTest[0].IA, + Dst: interfacesTest[len(interfacesTest)-1].IA, + DataplanePath: snetpath.Hummingbird{ + Raw: rawHbird, + }, + Meta: snet.PathMetadata{ + Interfaces: interfacesTest, + }, + } + return p, err +} diff --git a/pkg/proto/daemon/daemon.pb.go b/pkg/proto/daemon/daemon.pb.go index 24bddc037b..ee136c5a70 100644 --- a/pkg/proto/daemon/daemon.pb.go +++ b/pkg/proto/daemon/daemon.pb.go @@ -1511,183 +1511,137 @@ var file_proto_daemon_v1_daemon_proto_rawDesc = []byte{ 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2f, 0x76, 0x31, - 0x2f, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x92, 0x01, 0x0a, - 0x0c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, - 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x73, 0x64, 0x41, - 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x73, 0x64, 0x41, 0x73, 0x12, - 0x18, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x69, 0x64, - 0x64, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x69, 0x64, 0x64, 0x65, - 0x6e, 0x22, 0x3c, 0x0a, 0x0d, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x22, - 0x94, 0x04, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x66, 0x61, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x06, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, - 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x61, 0x6e, 0x64, 0x77, - 0x69, 0x64, 0x74, 0x68, 0x18, 0x07, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x62, 0x61, 0x6e, 0x64, - 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x03, 0x67, 0x65, 0x6f, 0x18, 0x08, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, - 0x74, 0x65, 0x73, 0x52, 0x03, 0x67, 0x65, 0x6f, 0x12, 0x36, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x68, 0x6f, 0x70, - 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x48, 0x6f, 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x18, 0x0b, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0a, 0x65, - 0x70, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x45, 0x70, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x52, 0x09, 0x65, 0x70, 0x69, - 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x22, 0x45, 0x0a, 0x09, 0x45, 0x70, 0x69, 0x63, 0x41, 0x75, - 0x74, 0x68, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x68, 0x76, 0x66, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x50, 0x68, 0x76, 0x66, - 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6c, 0x68, 0x76, 0x66, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x4c, 0x68, 0x76, 0x66, 0x22, 0x36, 0x0a, - 0x0d, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x15, - 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, - 0x69, 0x73, 0x64, 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x64, 0x0a, 0x0e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, - 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, - 0x75, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, - 0x75, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x22, 0x0a, 0x09, 0x41, - 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, - 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, 0x41, 0x73, 0x22, - 0x49, 0x0a, 0x0a, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, - 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, - 0x73, 0x64, 0x41, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x22, 0x13, 0x0a, 0x11, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0xc4, 0x01, 0x0a, 0x12, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x1a, 0x59, 0x0a, 0x0f, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x40, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x52, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xba, 0x01, 0x0a, 0x10, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4b, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x1a, 0x59, 0x0a, - 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x32, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0x1b, 0x0a, - 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x22, 0x24, 0x0a, 0x08, 0x55, 0x6e, - 0x64, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x22, 0x43, 0x0a, 0x1a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, - 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, - 0x69, 0x73, 0x64, 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1d, 0x0a, 0x1b, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, - 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, - 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x2f, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x68, 0x75, + 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x62, 0x69, 0x72, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0x92, 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, + 0x73, 0x64, 0x41, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x73, 0x64, + 0x41, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, + 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x68, 0x69, + 0x64, 0x64, 0x65, 0x6e, 0x22, 0x3c, 0x0a, 0x0d, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x52, 0x05, 0x70, 0x61, 0x74, + 0x68, 0x73, 0x22, 0x94, 0x04, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x72, + 0x61, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x38, 0x0a, + 0x09, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x09, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, + 0x74, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x0a, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, 0x54, 0x69, - 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x15, 0x0a, - 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, - 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x73, - 0x72, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, - 0x72, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x13, 0x44, 0x52, 0x4b, 0x65, 0x79, - 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, - 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x65, - 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, - 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x44, 0x52, 0x4b, 0x65, 0x79, - 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, - 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, - 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, - 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, - 0x08, 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x13, 0x44, 0x52, 0x4b, - 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x37, 0x0a, - 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x07, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x61, + 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x07, 0x20, 0x03, 0x28, 0x04, 0x52, 0x09, 0x62, + 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x03, 0x67, 0x65, 0x6f, 0x18, + 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, + 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x52, 0x03, 0x67, 0x65, 0x6f, 0x12, 0x36, 0x0a, 0x09, 0x6c, + 0x69, 0x6e, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x19, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, + 0x68, 0x6f, 0x70, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x48, 0x6f, 0x70, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x74, 0x65, + 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f, 0x74, 0x65, 0x73, 0x12, 0x39, + 0x0a, 0x0a, 0x65, 0x70, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x73, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x70, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x52, 0x09, + 0x65, 0x70, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x22, 0x45, 0x0a, 0x09, 0x45, 0x70, 0x69, + 0x63, 0x41, 0x75, 0x74, 0x68, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, + 0x68, 0x76, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x50, + 0x68, 0x76, 0x66, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6c, 0x68, 0x76, 0x66, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x4c, 0x68, 0x76, 0x66, + 0x22, 0x36, 0x0a, 0x0d, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, + 0x65, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x64, 0x0a, 0x0e, 0x47, 0x65, 0x6f, 0x43, + 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, + 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x6c, 0x61, + 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, + 0x75, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, + 0x74, 0x75, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x22, + 0x0a, 0x09, 0x41, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, + 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, + 0x41, 0x73, 0x22, 0x49, 0x0a, 0x0a, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x05, 0x69, 0x73, 0x64, 0x41, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, + 0x74, 0x75, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x22, 0x13, 0x0a, + 0x11, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0xc4, 0x01, 0x0a, 0x12, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x1a, 0x59, + 0x0a, 0x0f, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x40, 0x0a, 0x09, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x64, 0x65, 0x72, 0x6c, + 0x61, 0x79, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xba, + 0x01, 0x0a, 0x10, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x1a, 0x59, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x43, 0x0a, 0x0b, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x22, 0x1b, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x22, 0x24, 0x0a, + 0x08, 0x55, 0x6e, 0x64, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x22, 0x43, 0x0a, 0x1a, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x69, 0x73, 0x64, 0x41, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1d, 0x0a, 0x1b, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x44, 0x52, 0x4b, 0x65, + 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, + 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, - 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xec, 0x01, 0x0a, 0x14, 0x44, 0x52, 0x4b, - 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x07, 0x76, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, - 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, - 0x61, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, - 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x15, 0x44, 0x52, 0x4b, 0x65, - 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x76, 0x61, + 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, + 0x12, 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, + 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, + 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x73, 0x72, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x13, 0x44, 0x52, + 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, @@ -1696,63 +1650,130 @@ var file_proto_daemon_v1_daemon_proto_rawDesc = []byte{ 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x2a, 0x6c, 0x0a, 0x08, 0x4c, 0x69, 0x6e, - 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x49, - 0x52, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x48, 0x4f, 0x50, 0x10, 0x02, 0x12, - 0x16, 0x0a, 0x12, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4f, 0x50, 0x45, - 0x4e, 0x5f, 0x4e, 0x45, 0x54, 0x10, 0x03, 0x32, 0xd4, 0x05, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, - 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x05, 0x50, 0x61, 0x74, - 0x68, 0x73, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x02, 0x41, 0x53, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x53, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, - 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, - 0x65, 0x73, 0x12, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, - 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, - 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x72, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, - 0x6f, 0x73, 0x74, 0x12, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, - 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, - 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x12, - 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, - 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, - 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x0d, - 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x25, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x44, 0x52, + 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x35, 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, + 0x76, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, + 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, 0x06, 0x64, 0x73, 0x74, + 0x5f, 0x69, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, 0x73, 0x74, 0x49, 0x61, + 0x12, 0x19, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x13, + 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, 0x67, + 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, 0x6e, + 0x12, 0x37, 0x0a, 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x08, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xec, 0x01, 0x0a, 0x14, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, - 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2e, - 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x63, 0x69, - 0x6f, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x6b, - 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x07, 0x76, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x72, 0x6b, 0x65, 0x79, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x72, 0x63, 0x5f, 0x69, 0x61, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x61, 0x12, 0x15, 0x0a, + 0x06, 0x64, 0x73, 0x74, 0x5f, 0x69, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x64, + 0x73, 0x74, 0x49, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x72, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x72, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x12, + 0x19, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x64, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x22, 0x9f, 0x01, 0x0a, 0x15, 0x44, + 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x62, 0x65, + 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x65, 0x67, 0x69, + 0x6e, 0x12, 0x37, 0x0a, 0x09, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x08, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x45, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x2a, 0x6c, 0x0a, 0x08, + 0x4c, 0x69, 0x6e, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x4c, 0x49, 0x4e, 0x4b, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4c, 0x49, 0x4e, + 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x48, 0x4f, 0x50, + 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, 0x4c, 0x49, 0x4e, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x4e, 0x45, 0x54, 0x10, 0x03, 0x32, 0xfd, 0x07, 0x0a, 0x0d, 0x44, + 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x05, + 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x02, 0x41, 0x53, 0x12, 0x1a, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x53, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x53, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0a, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x66, 0x61, 0x63, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x51, 0x0a, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x20, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x72, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x2b, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, + 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, + 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x41, 0x53, + 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, + 0x4b, 0x65, 0x79, 0x41, 0x53, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0b, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, + 0x41, 0x53, 0x12, 0x23, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x41, 0x53, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, + 0x6f, 0x73, 0x74, 0x41, 0x53, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x60, 0x0a, 0x0d, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, + 0x12, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x52, 0x4b, 0x65, 0x79, 0x48, + 0x6f, 0x73, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x60, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x6c, 0x79, 0x6f, 0x76, 0x65, + 0x72, 0x73, 0x12, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x6c, 0x79, 0x6f, 0x76, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, + 0x65, 0x46, 0x6c, 0x79, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6c, 0x79, 0x6f, 0x76, + 0x65, 0x72, 0x73, 0x12, 0x24, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, + 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6c, 0x79, 0x6f, 0x76, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x46, 0x6c, 0x79, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x66, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, + 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -1800,6 +1821,12 @@ var file_proto_daemon_v1_daemon_proto_goTypes = []interface{}{ (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp (*durationpb.Duration)(nil), // 28: google.protobuf.Duration (drkey.Protocol)(0), // 29: proto.drkey.v1.Protocol + (*StoreFlyoversRequest)(nil), // 30: proto.daemon.v1.StoreFlyoversRequest + (*ListFlyoversRequest)(nil), // 31: proto.daemon.v1.ListFlyoversRequest + (*GetReservationsRequest)(nil), // 32: proto.daemon.v1.GetReservationsRequest + (*StoreFlyoversResponse)(nil), // 33: proto.daemon.v1.StoreFlyoversResponse + (*ListFlyoversResponse)(nil), // 34: proto.daemon.v1.ListFlyoversResponse + (*GetReservationsResponse)(nil), // 35: proto.daemon.v1.GetReservationsResponse } var file_proto_daemon_v1_daemon_proto_depIdxs = []int32{ 3, // 0: proto.daemon.v1.PathsResponse.paths:type_name -> proto.daemon.v1.Path @@ -1836,16 +1863,22 @@ var file_proto_daemon_v1_daemon_proto_depIdxs = []int32{ 21, // 31: proto.daemon.v1.DaemonService.DRKeyASHost:input_type -> proto.daemon.v1.DRKeyASHostRequest 19, // 32: proto.daemon.v1.DaemonService.DRKeyHostAS:input_type -> proto.daemon.v1.DRKeyHostASRequest 23, // 33: proto.daemon.v1.DaemonService.DRKeyHostHost:input_type -> proto.daemon.v1.DRKeyHostHostRequest - 2, // 34: proto.daemon.v1.DaemonService.Paths:output_type -> proto.daemon.v1.PathsResponse - 8, // 35: proto.daemon.v1.DaemonService.AS:output_type -> proto.daemon.v1.ASResponse - 10, // 36: proto.daemon.v1.DaemonService.Interfaces:output_type -> proto.daemon.v1.InterfacesResponse - 13, // 37: proto.daemon.v1.DaemonService.Services:output_type -> proto.daemon.v1.ServicesResponse - 18, // 38: proto.daemon.v1.DaemonService.NotifyInterfaceDown:output_type -> proto.daemon.v1.NotifyInterfaceDownResponse - 22, // 39: proto.daemon.v1.DaemonService.DRKeyASHost:output_type -> proto.daemon.v1.DRKeyASHostResponse - 20, // 40: proto.daemon.v1.DaemonService.DRKeyHostAS:output_type -> proto.daemon.v1.DRKeyHostASResponse - 24, // 41: proto.daemon.v1.DaemonService.DRKeyHostHost:output_type -> proto.daemon.v1.DRKeyHostHostResponse - 34, // [34:42] is the sub-list for method output_type - 26, // [26:34] is the sub-list for method input_type + 30, // 34: proto.daemon.v1.DaemonService.StoreFlyovers:input_type -> proto.daemon.v1.StoreFlyoversRequest + 31, // 35: proto.daemon.v1.DaemonService.ListFlyovers:input_type -> proto.daemon.v1.ListFlyoversRequest + 32, // 36: proto.daemon.v1.DaemonService.GetReservations:input_type -> proto.daemon.v1.GetReservationsRequest + 2, // 37: proto.daemon.v1.DaemonService.Paths:output_type -> proto.daemon.v1.PathsResponse + 8, // 38: proto.daemon.v1.DaemonService.AS:output_type -> proto.daemon.v1.ASResponse + 10, // 39: proto.daemon.v1.DaemonService.Interfaces:output_type -> proto.daemon.v1.InterfacesResponse + 13, // 40: proto.daemon.v1.DaemonService.Services:output_type -> proto.daemon.v1.ServicesResponse + 18, // 41: proto.daemon.v1.DaemonService.NotifyInterfaceDown:output_type -> proto.daemon.v1.NotifyInterfaceDownResponse + 22, // 42: proto.daemon.v1.DaemonService.DRKeyASHost:output_type -> proto.daemon.v1.DRKeyASHostResponse + 20, // 43: proto.daemon.v1.DaemonService.DRKeyHostAS:output_type -> proto.daemon.v1.DRKeyHostASResponse + 24, // 44: proto.daemon.v1.DaemonService.DRKeyHostHost:output_type -> proto.daemon.v1.DRKeyHostHostResponse + 33, // 45: proto.daemon.v1.DaemonService.StoreFlyovers:output_type -> proto.daemon.v1.StoreFlyoversResponse + 34, // 46: proto.daemon.v1.DaemonService.ListFlyovers:output_type -> proto.daemon.v1.ListFlyoversResponse + 35, // 47: proto.daemon.v1.DaemonService.GetReservations:output_type -> proto.daemon.v1.GetReservationsResponse + 37, // [37:48] is the sub-list for method output_type + 26, // [26:37] is the sub-list for method input_type 26, // [26:26] is the sub-list for extension type_name 26, // [26:26] is the sub-list for extension extendee 0, // [0:26] is the sub-list for field type_name @@ -1856,6 +1889,7 @@ func file_proto_daemon_v1_daemon_proto_init() { if File_proto_daemon_v1_daemon_proto != nil { return } + file_proto_daemon_v1_hummingbird_proto_init() if !protoimpl.UnsafeEnabled { file_proto_daemon_v1_daemon_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PathsRequest); i { @@ -2187,6 +2221,9 @@ type DaemonServiceClient interface { DRKeyASHost(ctx context.Context, in *DRKeyASHostRequest, opts ...grpc.CallOption) (*DRKeyASHostResponse, error) DRKeyHostAS(ctx context.Context, in *DRKeyHostASRequest, opts ...grpc.CallOption) (*DRKeyHostASResponse, error) DRKeyHostHost(ctx context.Context, in *DRKeyHostHostRequest, opts ...grpc.CallOption) (*DRKeyHostHostResponse, error) + StoreFlyovers(ctx context.Context, in *StoreFlyoversRequest, opts ...grpc.CallOption) (*StoreFlyoversResponse, error) + ListFlyovers(ctx context.Context, in *ListFlyoversRequest, opts ...grpc.CallOption) (*ListFlyoversResponse, error) + GetReservations(ctx context.Context, in *GetReservationsRequest, opts ...grpc.CallOption) (*GetReservationsResponse, error) } type daemonServiceClient struct { @@ -2269,6 +2306,33 @@ func (c *daemonServiceClient) DRKeyHostHost(ctx context.Context, in *DRKeyHostHo return out, nil } +func (c *daemonServiceClient) StoreFlyovers(ctx context.Context, in *StoreFlyoversRequest, opts ...grpc.CallOption) (*StoreFlyoversResponse, error) { + out := new(StoreFlyoversResponse) + err := c.cc.Invoke(ctx, "/proto.daemon.v1.DaemonService/StoreFlyovers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *daemonServiceClient) ListFlyovers(ctx context.Context, in *ListFlyoversRequest, opts ...grpc.CallOption) (*ListFlyoversResponse, error) { + out := new(ListFlyoversResponse) + err := c.cc.Invoke(ctx, "/proto.daemon.v1.DaemonService/ListFlyovers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *daemonServiceClient) GetReservations(ctx context.Context, in *GetReservationsRequest, opts ...grpc.CallOption) (*GetReservationsResponse, error) { + out := new(GetReservationsResponse) + err := c.cc.Invoke(ctx, "/proto.daemon.v1.DaemonService/GetReservations", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // DaemonServiceServer is the server API for DaemonService service. type DaemonServiceServer interface { Paths(context.Context, *PathsRequest) (*PathsResponse, error) @@ -2279,6 +2343,9 @@ type DaemonServiceServer interface { DRKeyASHost(context.Context, *DRKeyASHostRequest) (*DRKeyASHostResponse, error) DRKeyHostAS(context.Context, *DRKeyHostASRequest) (*DRKeyHostASResponse, error) DRKeyHostHost(context.Context, *DRKeyHostHostRequest) (*DRKeyHostHostResponse, error) + StoreFlyovers(context.Context, *StoreFlyoversRequest) (*StoreFlyoversResponse, error) + ListFlyovers(context.Context, *ListFlyoversRequest) (*ListFlyoversResponse, error) + GetReservations(context.Context, *GetReservationsRequest) (*GetReservationsResponse, error) } // UnimplementedDaemonServiceServer can be embedded to have forward compatible implementations. @@ -2309,6 +2376,15 @@ func (*UnimplementedDaemonServiceServer) DRKeyHostAS(context.Context, *DRKeyHost func (*UnimplementedDaemonServiceServer) DRKeyHostHost(context.Context, *DRKeyHostHostRequest) (*DRKeyHostHostResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DRKeyHostHost not implemented") } +func (*UnimplementedDaemonServiceServer) StoreFlyovers(context.Context, *StoreFlyoversRequest) (*StoreFlyoversResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StoreFlyovers not implemented") +} +func (*UnimplementedDaemonServiceServer) ListFlyovers(context.Context, *ListFlyoversRequest) (*ListFlyoversResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListFlyovers not implemented") +} +func (*UnimplementedDaemonServiceServer) GetReservations(context.Context, *GetReservationsRequest) (*GetReservationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetReservations not implemented") +} func RegisterDaemonServiceServer(s *grpc.Server, srv DaemonServiceServer) { s.RegisterService(&_DaemonService_serviceDesc, srv) @@ -2458,6 +2534,60 @@ func _DaemonService_DRKeyHostHost_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _DaemonService_StoreFlyovers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StoreFlyoversRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).StoreFlyovers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.daemon.v1.DaemonService/StoreFlyovers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).StoreFlyovers(ctx, req.(*StoreFlyoversRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DaemonService_ListFlyovers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListFlyoversRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).ListFlyovers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.daemon.v1.DaemonService/ListFlyovers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).ListFlyovers(ctx, req.(*ListFlyoversRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DaemonService_GetReservations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetReservationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DaemonServiceServer).GetReservations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.daemon.v1.DaemonService/GetReservations", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DaemonServiceServer).GetReservations(ctx, req.(*GetReservationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _DaemonService_serviceDesc = grpc.ServiceDesc{ ServiceName: "proto.daemon.v1.DaemonService", HandlerType: (*DaemonServiceServer)(nil), @@ -2494,6 +2624,18 @@ var _DaemonService_serviceDesc = grpc.ServiceDesc{ MethodName: "DRKeyHostHost", Handler: _DaemonService_DRKeyHostHost_Handler, }, + { + MethodName: "StoreFlyovers", + Handler: _DaemonService_StoreFlyovers_Handler, + }, + { + MethodName: "ListFlyovers", + Handler: _DaemonService_ListFlyovers_Handler, + }, + { + MethodName: "GetReservations", + Handler: _DaemonService_GetReservations_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/daemon/v1/daemon.proto", diff --git a/pkg/proto/daemon/hummingbird.pb.go b/pkg/proto/daemon/hummingbird.pb.go new file mode 100644 index 0000000000..5b9b694d17 --- /dev/null +++ b/pkg/proto/daemon/hummingbird.pb.go @@ -0,0 +1,693 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.15.3 +// source: proto/daemon/v1/hummingbird.proto + +package daemon + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type StoreFlyoversRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Flyovers []*Flyover `protobuf:"bytes,1,rep,name=flyovers,proto3" json:"flyovers,omitempty"` +} + +func (x *StoreFlyoversRequest) Reset() { + *x = StoreFlyoversRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StoreFlyoversRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StoreFlyoversRequest) ProtoMessage() {} + +func (x *StoreFlyoversRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StoreFlyoversRequest.ProtoReflect.Descriptor instead. +func (*StoreFlyoversRequest) Descriptor() ([]byte, []int) { + return file_proto_daemon_v1_hummingbird_proto_rawDescGZIP(), []int{0} +} + +func (x *StoreFlyoversRequest) GetFlyovers() []*Flyover { + if x != nil { + return x.Flyovers + } + return nil +} + +type StoreFlyoversResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *StoreFlyoversResponse) Reset() { + *x = StoreFlyoversResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StoreFlyoversResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StoreFlyoversResponse) ProtoMessage() {} + +func (x *StoreFlyoversResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StoreFlyoversResponse.ProtoReflect.Descriptor instead. +func (*StoreFlyoversResponse) Descriptor() ([]byte, []int) { + return file_proto_daemon_v1_hummingbird_proto_rawDescGZIP(), []int{1} +} + +type ListFlyoversRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListFlyoversRequest) Reset() { + *x = ListFlyoversRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListFlyoversRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFlyoversRequest) ProtoMessage() {} + +func (x *ListFlyoversRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListFlyoversRequest.ProtoReflect.Descriptor instead. +func (*ListFlyoversRequest) Descriptor() ([]byte, []int) { + return file_proto_daemon_v1_hummingbird_proto_rawDescGZIP(), []int{2} +} + +type ListFlyoversResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Flyovers []*Flyover `protobuf:"bytes,1,rep,name=flyovers,proto3" json:"flyovers,omitempty"` +} + +func (x *ListFlyoversResponse) Reset() { + *x = ListFlyoversResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListFlyoversResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFlyoversResponse) ProtoMessage() {} + +func (x *ListFlyoversResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListFlyoversResponse.ProtoReflect.Descriptor instead. +func (*ListFlyoversResponse) Descriptor() ([]byte, []int) { + return file_proto_daemon_v1_hummingbird_proto_rawDescGZIP(), []int{3} +} + +func (x *ListFlyoversResponse) GetFlyovers() []*Flyover { + if x != nil { + return x.Flyovers + } + return nil +} + +type GetReservationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SourceIsdAs uint64 `protobuf:"varint,1,opt,name=source_isd_as,json=sourceIsdAs,proto3" json:"source_isd_as,omitempty"` + DestinationIsdAs uint64 `protobuf:"varint,2,opt,name=destination_isd_as,json=destinationIsdAs,proto3" json:"destination_isd_as,omitempty"` + Refresh bool `protobuf:"varint,3,opt,name=refresh,proto3" json:"refresh,omitempty"` + MinBandwidth uint32 `protobuf:"varint,4,opt,name=min_bandwidth,json=minBandwidth,proto3" json:"min_bandwidth,omitempty"` +} + +func (x *GetReservationsRequest) Reset() { + *x = GetReservationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetReservationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetReservationsRequest) ProtoMessage() {} + +func (x *GetReservationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetReservationsRequest.ProtoReflect.Descriptor instead. +func (*GetReservationsRequest) Descriptor() ([]byte, []int) { + return file_proto_daemon_v1_hummingbird_proto_rawDescGZIP(), []int{4} +} + +func (x *GetReservationsRequest) GetSourceIsdAs() uint64 { + if x != nil { + return x.SourceIsdAs + } + return 0 +} + +func (x *GetReservationsRequest) GetDestinationIsdAs() uint64 { + if x != nil { + return x.DestinationIsdAs + } + return 0 +} + +func (x *GetReservationsRequest) GetRefresh() bool { + if x != nil { + return x.Refresh + } + return false +} + +func (x *GetReservationsRequest) GetMinBandwidth() uint32 { + if x != nil { + return x.MinBandwidth + } + return 0 +} + +type GetReservationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Reservations []*Reservation `protobuf:"bytes,1,rep,name=reservations,proto3" json:"reservations,omitempty"` +} + +func (x *GetReservationsResponse) Reset() { + *x = GetReservationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetReservationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetReservationsResponse) ProtoMessage() {} + +func (x *GetReservationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetReservationsResponse.ProtoReflect.Descriptor instead. +func (*GetReservationsResponse) Descriptor() ([]byte, []int) { + return file_proto_daemon_v1_hummingbird_proto_rawDescGZIP(), []int{5} +} + +func (x *GetReservationsResponse) GetReservations() []*Reservation { + if x != nil { + return x.Reservations + } + return nil +} + +type Flyover struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ia uint64 `protobuf:"varint,1,opt,name=ia,proto3" json:"ia,omitempty"` + Ingress uint32 `protobuf:"varint,2,opt,name=ingress,proto3" json:"ingress,omitempty"` + Egress uint32 `protobuf:"varint,3,opt,name=egress,proto3" json:"egress,omitempty"` + Bw uint32 `protobuf:"varint,4,opt,name=bw,proto3" json:"bw,omitempty"` + StartTime uint32 `protobuf:"varint,5,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"` + Duration uint32 `protobuf:"varint,6,opt,name=duration,proto3" json:"duration,omitempty"` + ResId uint32 `protobuf:"varint,7,opt,name=res_id,json=resId,proto3" json:"res_id,omitempty"` + Ak []byte `protobuf:"bytes,8,opt,name=ak,proto3" json:"ak,omitempty"` +} + +func (x *Flyover) Reset() { + *x = Flyover{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Flyover) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Flyover) ProtoMessage() {} + +func (x *Flyover) ProtoReflect() protoreflect.Message { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Flyover.ProtoReflect.Descriptor instead. +func (*Flyover) Descriptor() ([]byte, []int) { + return file_proto_daemon_v1_hummingbird_proto_rawDescGZIP(), []int{6} +} + +func (x *Flyover) GetIa() uint64 { + if x != nil { + return x.Ia + } + return 0 +} + +func (x *Flyover) GetIngress() uint32 { + if x != nil { + return x.Ingress + } + return 0 +} + +func (x *Flyover) GetEgress() uint32 { + if x != nil { + return x.Egress + } + return 0 +} + +func (x *Flyover) GetBw() uint32 { + if x != nil { + return x.Bw + } + return 0 +} + +func (x *Flyover) GetStartTime() uint32 { + if x != nil { + return x.StartTime + } + return 0 +} + +func (x *Flyover) GetDuration() uint32 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *Flyover) GetResId() uint32 { + if x != nil { + return x.ResId + } + return 0 +} + +func (x *Flyover) GetAk() []byte { + if x != nil { + return x.Ak + } + return nil +} + +type Reservation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Raw []byte `protobuf:"bytes,1,opt,name=raw,proto3" json:"raw,omitempty"` + Flyovers []*Flyover `protobuf:"bytes,2,rep,name=flyovers,proto3" json:"flyovers,omitempty"` + Ratio float64 `protobuf:"fixed64,3,opt,name=ratio,proto3" json:"ratio,omitempty"` +} + +func (x *Reservation) Reset() { + *x = Reservation{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Reservation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Reservation) ProtoMessage() {} + +func (x *Reservation) ProtoReflect() protoreflect.Message { + mi := &file_proto_daemon_v1_hummingbird_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Reservation.ProtoReflect.Descriptor instead. +func (*Reservation) Descriptor() ([]byte, []int) { + return file_proto_daemon_v1_hummingbird_proto_rawDescGZIP(), []int{7} +} + +func (x *Reservation) GetRaw() []byte { + if x != nil { + return x.Raw + } + return nil +} + +func (x *Reservation) GetFlyovers() []*Flyover { + if x != nil { + return x.Flyovers + } + return nil +} + +func (x *Reservation) GetRatio() float64 { + if x != nil { + return x.Ratio + } + return 0 +} + +var File_proto_daemon_v1_hummingbird_proto protoreflect.FileDescriptor + +var file_proto_daemon_v1_hummingbird_proto_rawDesc = []byte{ + 0x0a, 0x21, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, + 0x31, 0x2f, 0x68, 0x75, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x62, 0x69, 0x72, 0x64, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x22, 0x4c, 0x0a, 0x14, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x6c, 0x79, + 0x6f, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x08, + 0x66, 0x6c, 0x79, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x46, 0x6c, 0x79, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x08, 0x66, 0x6c, 0x79, 0x6f, 0x76, 0x65, + 0x72, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x46, 0x6c, 0x79, 0x6f, 0x76, + 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x4c, + 0x69, 0x73, 0x74, 0x46, 0x6c, 0x79, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x4c, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6c, 0x79, 0x6f, 0x76, 0x65, + 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x66, 0x6c, + 0x79, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x46, + 0x6c, 0x79, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x08, 0x66, 0x6c, 0x79, 0x6f, 0x76, 0x65, 0x72, 0x73, + 0x22, 0xa9, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x73, 0x64, 0x41, 0x73, 0x12, + 0x2c, 0x0a, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x73, 0x64, 0x5f, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x64, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x73, 0x64, 0x41, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x62, + 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, + 0x6d, 0x69, 0x6e, 0x42, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x22, 0x5b, 0x0a, 0x17, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x72, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xbd, 0x01, 0x0a, 0x07, 0x46, 0x6c, + 0x79, 0x6f, 0x76, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x02, 0x69, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x06, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x62, 0x77, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x02, 0x62, 0x77, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x05, 0x72, 0x65, 0x73, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x61, 0x6b, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x61, 0x6b, 0x22, 0x6b, 0x0a, 0x0b, 0x52, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x61, 0x77, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x72, 0x61, 0x77, 0x12, 0x34, 0x0a, 0x08, 0x66, 0x6c, + 0x79, 0x6f, 0x76, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x46, + 0x6c, 0x79, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x08, 0x66, 0x6c, 0x79, 0x6f, 0x76, 0x65, 0x72, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x05, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x73, 0x63, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_daemon_v1_hummingbird_proto_rawDescOnce sync.Once + file_proto_daemon_v1_hummingbird_proto_rawDescData = file_proto_daemon_v1_hummingbird_proto_rawDesc +) + +func file_proto_daemon_v1_hummingbird_proto_rawDescGZIP() []byte { + file_proto_daemon_v1_hummingbird_proto_rawDescOnce.Do(func() { + file_proto_daemon_v1_hummingbird_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_daemon_v1_hummingbird_proto_rawDescData) + }) + return file_proto_daemon_v1_hummingbird_proto_rawDescData +} + +var file_proto_daemon_v1_hummingbird_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_proto_daemon_v1_hummingbird_proto_goTypes = []interface{}{ + (*StoreFlyoversRequest)(nil), // 0: proto.daemon.v1.StoreFlyoversRequest + (*StoreFlyoversResponse)(nil), // 1: proto.daemon.v1.StoreFlyoversResponse + (*ListFlyoversRequest)(nil), // 2: proto.daemon.v1.ListFlyoversRequest + (*ListFlyoversResponse)(nil), // 3: proto.daemon.v1.ListFlyoversResponse + (*GetReservationsRequest)(nil), // 4: proto.daemon.v1.GetReservationsRequest + (*GetReservationsResponse)(nil), // 5: proto.daemon.v1.GetReservationsResponse + (*Flyover)(nil), // 6: proto.daemon.v1.Flyover + (*Reservation)(nil), // 7: proto.daemon.v1.Reservation +} +var file_proto_daemon_v1_hummingbird_proto_depIdxs = []int32{ + 6, // 0: proto.daemon.v1.StoreFlyoversRequest.flyovers:type_name -> proto.daemon.v1.Flyover + 6, // 1: proto.daemon.v1.ListFlyoversResponse.flyovers:type_name -> proto.daemon.v1.Flyover + 7, // 2: proto.daemon.v1.GetReservationsResponse.reservations:type_name -> proto.daemon.v1.Reservation + 6, // 3: proto.daemon.v1.Reservation.flyovers:type_name -> proto.daemon.v1.Flyover + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_proto_daemon_v1_hummingbird_proto_init() } +func file_proto_daemon_v1_hummingbird_proto_init() { + if File_proto_daemon_v1_hummingbird_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_daemon_v1_hummingbird_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StoreFlyoversRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_daemon_v1_hummingbird_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StoreFlyoversResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_daemon_v1_hummingbird_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFlyoversRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_daemon_v1_hummingbird_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFlyoversResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_daemon_v1_hummingbird_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetReservationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_daemon_v1_hummingbird_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetReservationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_daemon_v1_hummingbird_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Flyover); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_daemon_v1_hummingbird_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Reservation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_daemon_v1_hummingbird_proto_rawDesc, + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto_daemon_v1_hummingbird_proto_goTypes, + DependencyIndexes: file_proto_daemon_v1_hummingbird_proto_depIdxs, + MessageInfos: file_proto_daemon_v1_hummingbird_proto_msgTypes, + }.Build() + File_proto_daemon_v1_hummingbird_proto = out.File + file_proto_daemon_v1_hummingbird_proto_rawDesc = nil + file_proto_daemon_v1_hummingbird_proto_goTypes = nil + file_proto_daemon_v1_hummingbird_proto_depIdxs = nil +} diff --git a/pkg/slayers/BUILD.bazel b/pkg/slayers/BUILD.bazel index c375215d35..32a47bdd03 100644 --- a/pkg/slayers/BUILD.bazel +++ b/pkg/slayers/BUILD.bazel @@ -22,6 +22,7 @@ go_library( "//pkg/slayers/path:go_default_library", "//pkg/slayers/path/empty:go_default_library", "//pkg/slayers/path/epic:go_default_library", + "//pkg/slayers/path/hummingbird:go_default_library", "//pkg/slayers/path/onehop:go_default_library", "//pkg/slayers/path/scion:go_default_library", "@com_github_google_gopacket//:go_default_library", diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index c4415ce671..3ac1425505 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -111,7 +111,7 @@ func (h *HopField) DecodeFromBytes(raw []byte) (err error) { // @ decreases func (h *HopField) SerializeTo(b []byte) (err error) { if len(b) < HopLen { - return serrors.New("buffer for HopField too short", "expected", MacLen, "actual", len(b)) + return serrors.New("buffer for HopField too short", "expected", HopLen, "actual", len(b)) } b[0] = 0 if h.EgressRouterAlert { diff --git a/pkg/slayers/path/hummingbird/BUILD.bazel b/pkg/slayers/path/hummingbird/BUILD.bazel new file mode 100644 index 0000000000..416de18608 --- /dev/null +++ b/pkg/slayers/path/hummingbird/BUILD.bazel @@ -0,0 +1,55 @@ +load("//tools/lint:go.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "asm_amd64.s", + "asm_arm64.s", + "asm_ppc64x.s", + "base.go", + "decoded.go", + "flyoverhopfield.go", + "mac.go", + "raw.go", + ], + importpath = "github.com/scionproto/scion/pkg/slayers/path/hummingbird", + visibility = ["//visibility:public"], + deps = [ + "//pkg/private/serrors:go_default_library", + "//pkg/slayers/path:go_default_library", + "//pkg/slayers/path/scion:go_default_library", + ] + select({ + "@io_bazel_rules_go//go/platform:amd64": [ + "//pkg/addr:go_default_library", + ], + "@io_bazel_rules_go//go/platform:arm64": [ + "//pkg/addr:go_default_library", + ], + "@io_bazel_rules_go//go/platform:ppc64": [ + "//pkg/addr:go_default_library", + ], + "@io_bazel_rules_go//go/platform:ppc64le": [ + "//pkg/addr:go_default_library", + ], + "//conditions:default": [], + }), +) + +go_test( + name = "go_default_test", + srcs = [ + "base_test.go", + "decoded_test.go", + "flyoverhopfield_test.go", + "mac_test.go", + "raw_test.go", + ], + deps = [ + ":go_default_library", + "//pkg/addr:go_default_library", + "//pkg/slayers/path:go_default_library", + "//pkg/slayers/path/scion:go_default_library", + "@com_github_stretchr_testify//assert:go_default_library", + "@com_github_stretchr_testify//require:go_default_library", + ], +) diff --git a/pkg/slayers/path/hummingbird/asm_amd64.s b/pkg/slayers/path/hummingbird/asm_amd64.s new file mode 100644 index 0000000000..c8884cd346 --- /dev/null +++ b/pkg/slayers/path/hummingbird/asm_amd64.s @@ -0,0 +1,260 @@ +// This file is mostly a copy of the file of the same name in the crypto/aes go package +// The key expansion for the decryption keys has been removed in this file + +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" + +// func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) +TEXT ·encryptBlockAsm(SB),NOSPLIT,$0 + MOVQ nr+0(FP), CX + MOVQ xk+8(FP), AX + MOVQ dst+16(FP), DX + MOVQ src+24(FP), BX + MOVUPS 0(AX), X1 + MOVUPS 0(BX), X0 + ADDQ $16, AX + PXOR X1, X0 + SUBQ $12, CX + JE Lenc192 + JB Lenc128 +Lenc256: + MOVUPS 0(AX), X1 + AESENC X1, X0 + MOVUPS 16(AX), X1 + AESENC X1, X0 + ADDQ $32, AX +Lenc192: + MOVUPS 0(AX), X1 + AESENC X1, X0 + MOVUPS 16(AX), X1 + AESENC X1, X0 + ADDQ $32, AX +Lenc128: + MOVUPS 0(AX), X1 + AESENC X1, X0 + MOVUPS 16(AX), X1 + AESENC X1, X0 + MOVUPS 32(AX), X1 + AESENC X1, X0 + MOVUPS 48(AX), X1 + AESENC X1, X0 + MOVUPS 64(AX), X1 + AESENC X1, X0 + MOVUPS 80(AX), X1 + AESENC X1, X0 + MOVUPS 96(AX), X1 + AESENC X1, X0 + MOVUPS 112(AX), X1 + AESENC X1, X0 + MOVUPS 128(AX), X1 + AESENC X1, X0 + MOVUPS 144(AX), X1 + AESENCLAST X1, X0 + MOVUPS X0, 0(DX) + RET + +// func decryptBlockAsm(nr int, xk *uint32, dst, src *byte) +TEXT ·decryptBlockAsm(SB),NOSPLIT,$0 + MOVQ nr+0(FP), CX + MOVQ xk+8(FP), AX + MOVQ dst+16(FP), DX + MOVQ src+24(FP), BX + MOVUPS 0(AX), X1 + MOVUPS 0(BX), X0 + ADDQ $16, AX + PXOR X1, X0 + SUBQ $12, CX + JE Ldec192 + JB Ldec128 +Ldec256: + MOVUPS 0(AX), X1 + AESDEC X1, X0 + MOVUPS 16(AX), X1 + AESDEC X1, X0 + ADDQ $32, AX +Ldec192: + MOVUPS 0(AX), X1 + AESDEC X1, X0 + MOVUPS 16(AX), X1 + AESDEC X1, X0 + ADDQ $32, AX +Ldec128: + MOVUPS 0(AX), X1 + AESDEC X1, X0 + MOVUPS 16(AX), X1 + AESDEC X1, X0 + MOVUPS 32(AX), X1 + AESDEC X1, X0 + MOVUPS 48(AX), X1 + AESDEC X1, X0 + MOVUPS 64(AX), X1 + AESDEC X1, X0 + MOVUPS 80(AX), X1 + AESDEC X1, X0 + MOVUPS 96(AX), X1 + AESDEC X1, X0 + MOVUPS 112(AX), X1 + AESDEC X1, X0 + MOVUPS 128(AX), X1 + AESDEC X1, X0 + MOVUPS 144(AX), X1 + AESDECLAST X1, X0 + MOVUPS X0, 0(DX) + RET + +// func expandKeyAsm(nr int, key *byte, enc) { +// Note that round keys are stored in uint128 format, not uint32 +TEXT ·expandKeyAsm(SB),NOSPLIT,$0 + MOVQ nr+0(FP), CX + MOVQ key+8(FP), AX + MOVQ enc+16(FP), BX + MOVUPS (AX), X0 + // enc + MOVUPS X0, (BX) + ADDQ $16, BX + PXOR X4, X4 // _expand_key_* expect X4 to be zero + CMPL CX, $12 + JE Lexp_enc192 + JB Lexp_enc128 +Lexp_enc256: + MOVUPS 16(AX), X2 + MOVUPS X2, (BX) + ADDQ $16, BX + AESKEYGENASSIST $0x01, X2, X1 + CALL _expand_key_256a<>(SB) + AESKEYGENASSIST $0x01, X0, X1 + CALL _expand_key_256b<>(SB) + AESKEYGENASSIST $0x02, X2, X1 + CALL _expand_key_256a<>(SB) + AESKEYGENASSIST $0x02, X0, X1 + CALL _expand_key_256b<>(SB) + AESKEYGENASSIST $0x04, X2, X1 + CALL _expand_key_256a<>(SB) + AESKEYGENASSIST $0x04, X0, X1 + CALL _expand_key_256b<>(SB) + AESKEYGENASSIST $0x08, X2, X1 + CALL _expand_key_256a<>(SB) + AESKEYGENASSIST $0x08, X0, X1 + CALL _expand_key_256b<>(SB) + AESKEYGENASSIST $0x10, X2, X1 + CALL _expand_key_256a<>(SB) + AESKEYGENASSIST $0x10, X0, X1 + CALL _expand_key_256b<>(SB) + AESKEYGENASSIST $0x20, X2, X1 + CALL _expand_key_256a<>(SB) + AESKEYGENASSIST $0x20, X0, X1 + CALL _expand_key_256b<>(SB) + AESKEYGENASSIST $0x40, X2, X1 + CALL _expand_key_256a<>(SB) + RET +Lexp_enc192: + MOVQ 16(AX), X2 + AESKEYGENASSIST $0x01, X2, X1 + CALL _expand_key_192a<>(SB) + AESKEYGENASSIST $0x02, X2, X1 + CALL _expand_key_192b<>(SB) + AESKEYGENASSIST $0x04, X2, X1 + CALL _expand_key_192a<>(SB) + AESKEYGENASSIST $0x08, X2, X1 + CALL _expand_key_192b<>(SB) + AESKEYGENASSIST $0x10, X2, X1 + CALL _expand_key_192a<>(SB) + AESKEYGENASSIST $0x20, X2, X1 + CALL _expand_key_192b<>(SB) + AESKEYGENASSIST $0x40, X2, X1 + CALL _expand_key_192a<>(SB) + AESKEYGENASSIST $0x80, X2, X1 + CALL _expand_key_192b<>(SB) + RET +Lexp_enc128: + AESKEYGENASSIST $0x01, X0, X1 + CALL _expand_key_128<>(SB) + AESKEYGENASSIST $0x02, X0, X1 + CALL _expand_key_128<>(SB) + AESKEYGENASSIST $0x04, X0, X1 + CALL _expand_key_128<>(SB) + AESKEYGENASSIST $0x08, X0, X1 + CALL _expand_key_128<>(SB) + AESKEYGENASSIST $0x10, X0, X1 + CALL _expand_key_128<>(SB) + AESKEYGENASSIST $0x20, X0, X1 + CALL _expand_key_128<>(SB) + AESKEYGENASSIST $0x40, X0, X1 + CALL _expand_key_128<>(SB) + AESKEYGENASSIST $0x80, X0, X1 + CALL _expand_key_128<>(SB) + AESKEYGENASSIST $0x1b, X0, X1 + CALL _expand_key_128<>(SB) + AESKEYGENASSIST $0x36, X0, X1 + CALL _expand_key_128<>(SB) + RET + +TEXT _expand_key_128<>(SB),NOSPLIT,$0 + PSHUFD $0xff, X1, X1 + SHUFPS $0x10, X0, X4 + PXOR X4, X0 + SHUFPS $0x8c, X0, X4 + PXOR X4, X0 + PXOR X1, X0 + MOVUPS X0, (BX) + ADDQ $16, BX + RET + +TEXT _expand_key_192a<>(SB),NOSPLIT,$0 + PSHUFD $0x55, X1, X1 + SHUFPS $0x10, X0, X4 + PXOR X4, X0 + SHUFPS $0x8c, X0, X4 + PXOR X4, X0 + PXOR X1, X0 + + MOVAPS X2, X5 + MOVAPS X2, X6 + PSLLDQ $0x4, X5 + PSHUFD $0xff, X0, X3 + PXOR X3, X2 + PXOR X5, X2 + + MOVAPS X0, X1 + SHUFPS $0x44, X0, X6 + MOVUPS X6, (BX) + SHUFPS $0x4e, X2, X1 + MOVUPS X1, 16(BX) + ADDQ $32, BX + RET + +TEXT _expand_key_192b<>(SB),NOSPLIT,$0 + PSHUFD $0x55, X1, X1 + SHUFPS $0x10, X0, X4 + PXOR X4, X0 + SHUFPS $0x8c, X0, X4 + PXOR X4, X0 + PXOR X1, X0 + + MOVAPS X2, X5 + PSLLDQ $0x4, X5 + PSHUFD $0xff, X0, X3 + PXOR X3, X2 + PXOR X5, X2 + + MOVUPS X0, (BX) + ADDQ $16, BX + RET + +TEXT _expand_key_256a<>(SB),NOSPLIT,$0 + JMP _expand_key_128<>(SB) + +TEXT _expand_key_256b<>(SB),NOSPLIT,$0 + PSHUFD $0xaa, X1, X1 + SHUFPS $0x10, X2, X4 + PXOR X4, X2 + SHUFPS $0x8c, X2, X4 + PXOR X4, X2 + PXOR X1, X2 + + MOVUPS X2, (BX) + ADDQ $16, BX + RET diff --git a/pkg/slayers/path/hummingbird/asm_arm64.s b/pkg/slayers/path/hummingbird/asm_arm64.s new file mode 100644 index 0000000000..517074ddf7 --- /dev/null +++ b/pkg/slayers/path/hummingbird/asm_arm64.s @@ -0,0 +1,217 @@ +// This file is mostly a copy of the file of the same name in the crypto/aes go package +// The key expansion for the decryption keys has been removed in this file + +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "textflag.h" +DATA rotInvSRows<>+0x00(SB)/8, $0x080f0205040b0e01 +DATA rotInvSRows<>+0x08(SB)/8, $0x00070a0d0c030609 +GLOBL rotInvSRows<>(SB), (NOPTR+RODATA), $16 +DATA invSRows<>+0x00(SB)/8, $0x0b0e0104070a0d00 +DATA invSRows<>+0x08(SB)/8, $0x0306090c0f020508 +GLOBL invSRows<>(SB), (NOPTR+RODATA), $16 +// func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) +TEXT ·encryptBlockAsm(SB),NOSPLIT,$0 + MOVD nr+0(FP), R9 + MOVD xk+8(FP), R10 + MOVD dst+16(FP), R11 + MOVD src+24(FP), R12 + + VLD1 (R12), [V0.B16] + + CMP $12, R9 + BLT enc128 + BEQ enc196 +enc256: + VLD1.P 32(R10), [V1.B16, V2.B16] + AESE V1.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V2.B16, V0.B16 + AESMC V0.B16, V0.B16 +enc196: + VLD1.P 32(R10), [V3.B16, V4.B16] + AESE V3.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V4.B16, V0.B16 + AESMC V0.B16, V0.B16 +enc128: + VLD1.P 64(R10), [V5.B16, V6.B16, V7.B16, V8.B16] + VLD1.P 64(R10), [V9.B16, V10.B16, V11.B16, V12.B16] + VLD1.P 48(R10), [V13.B16, V14.B16, V15.B16] + AESE V5.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V6.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V7.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V8.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V9.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V10.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V11.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V12.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V13.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V14.B16, V0.B16 + VEOR V0.B16, V15.B16, V0.B16 + VST1 [V0.B16], (R11) + RET + +// func decryptBlockAsm(nr int, xk *uint32, dst, src *byte) +TEXT ·decryptBlockAsm(SB),NOSPLIT,$0 + MOVD nr+0(FP), R9 + MOVD xk+8(FP), R10 + MOVD dst+16(FP), R11 + MOVD src+24(FP), R12 + + VLD1 (R12), [V0.B16] + + CMP $12, R9 + BLT dec128 + BEQ dec196 +dec256: + VLD1.P 32(R10), [V1.B16, V2.B16] + AESD V1.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V2.B16, V0.B16 + AESIMC V0.B16, V0.B16 +dec196: + VLD1.P 32(R10), [V3.B16, V4.B16] + AESD V3.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V4.B16, V0.B16 + AESIMC V0.B16, V0.B16 +dec128: + VLD1.P 64(R10), [V5.B16, V6.B16, V7.B16, V8.B16] + VLD1.P 64(R10), [V9.B16, V10.B16, V11.B16, V12.B16] + VLD1.P 48(R10), [V13.B16, V14.B16, V15.B16] + AESD V5.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V6.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V7.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V8.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V9.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V10.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V11.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V12.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V13.B16, V0.B16 + AESIMC V0.B16, V0.B16 + AESD V14.B16, V0.B16 + VEOR V0.B16, V15.B16, V0.B16 + VST1 [V0.B16], (R11) + RET + +// func expandKeyAsm(nr int, key *byte, enc) { +// Note that round keys are stored in uint128 format, not uint32 +TEXT ·expandKeyAsm(SB),NOSPLIT,$0 + MOVD nr+0(FP), R8 + MOVD key+8(FP), R9 + MOVD enc+16(FP), R10 + LDP rotInvSRows<>(SB), (R0, R1) + VMOV R0, V3.D[0] + VMOV R1, V3.D[1] + VEOR V0.B16, V0.B16, V0.B16 // All zeroes + MOVW $1, R13 + TBZ $1, R8, ks192 + TBNZ $2, R8, ks256 + LDPW (R9), (R4, R5) + LDPW 8(R9), (R6, R7) + STPW.P (R4, R5), 8(R10) + STPW.P (R6, R7), 8(R10) + MOVW $0x1b, R14 +ks128Loop: + VMOV R7, V2.S[0] + WORD $0x4E030042 // TBL V3.B16, [V2.B16], V2.B16 + AESE V0.B16, V2.B16 // Use AES to compute the SBOX + EORW R13, R4 + LSLW $1, R13 // Compute next Rcon + ANDSW $0x100, R13, ZR + CSELW NE, R14, R13, R13 // Fake modulo + SUBS $1, R8 + VMOV V2.S[0], R0 + EORW R0, R4 + EORW R4, R5 + EORW R5, R6 + EORW R6, R7 + STPW.P (R4, R5), 8(R10) + STPW.P (R6, R7), 8(R10) + BNE ks128Loop + B ksDone // If dec is nil we are done +ks192: + LDPW (R9), (R2, R3) + LDPW 8(R9), (R4, R5) + LDPW 16(R9), (R6, R7) + STPW.P (R2, R3), 8(R10) + STPW.P (R4, R5), 8(R10) + SUB $4, R8 +ks192Loop: + STPW.P (R6, R7), 8(R10) + VMOV R7, V2.S[0] + WORD $0x4E030042 //TBL V3.B16, [V2.B16], V2.B16 + AESE V0.B16, V2.B16 + EORW R13, R2 + LSLW $1, R13 + SUBS $1, R8 + VMOV V2.S[0], R0 + EORW R0, R2 + EORW R2, R3 + EORW R3, R4 + EORW R4, R5 + EORW R5, R6 + EORW R6, R7 + STPW.P (R2, R3), 8(R10) + STPW.P (R4, R5), 8(R10) + BNE ks192Loop + B ksDone +ks256: + LDP invSRows<>(SB), (R0, R1) + VMOV R0, V4.D[0] + VMOV R1, V4.D[1] + LDPW (R9), (R0, R1) + LDPW 8(R9), (R2, R3) + LDPW 16(R9), (R4, R5) + LDPW 24(R9), (R6, R7) + STPW.P (R0, R1), 8(R10) + STPW.P (R2, R3), 8(R10) + SUB $7, R8 +ks256Loop: + STPW.P (R4, R5), 8(R10) + STPW.P (R6, R7), 8(R10) + VMOV R7, V2.S[0] + WORD $0x4E030042 //TBL V3.B16, [V2.B16], V2.B16 + AESE V0.B16, V2.B16 + EORW R13, R0 + LSLW $1, R13 + SUBS $1, R8 + VMOV V2.S[0], R9 + EORW R9, R0 + EORW R0, R1 + EORW R1, R2 + EORW R2, R3 + VMOV R3, V2.S[0] + WORD $0x4E040042 //TBL V3.B16, [V2.B16], V2.B16 + AESE V0.B16, V2.B16 + VMOV V2.S[0], R9 + EORW R9, R4 + EORW R4, R5 + EORW R5, R6 + EORW R6, R7 + STPW.P (R0, R1), 8(R10) + STPW.P (R2, R3), 8(R10) + BNE ks256Loop + B ksDone +ksDone: + RET diff --git a/pkg/slayers/path/hummingbird/asm_ppc64x.s b/pkg/slayers/path/hummingbird/asm_ppc64x.s new file mode 100644 index 0000000000..80fe2cfda0 --- /dev/null +++ b/pkg/slayers/path/hummingbird/asm_ppc64x.s @@ -0,0 +1,657 @@ +// This file is mostly a copy of the file of the same name in the crypto/aes go package +// The key expansion for the decryption keys has been removed in this file + +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ppc64 || ppc64le + +// Based on CRYPTOGAMS code with the following comment: +// # ==================================================================== +// # Written by Andy Polyakov for the OpenSSL +// # project. The module is, however, dual licensed under OpenSSL and +// # CRYPTOGAMS licenses depending on where you obtain it. For further +// # details see http://www.openssl.org/~appro/cryptogams/. +// # ==================================================================== + +// Original code can be found at the link below: +// https://github.com/dot-asm/cryptogams/blob/master/ppc/aesp8-ppc.pl + +// Some function names were changed to be consistent with Go function +// names. For instance, function aes_p8_set_{en,de}crypt_key become +// set{En,De}cryptKeyAsm. I also split setEncryptKeyAsm in two parts +// and a new session was created (doEncryptKeyAsm). This was necessary to +// avoid arguments overwriting when setDecryptKeyAsm calls setEncryptKeyAsm. +// There were other modifications as well but kept the same functionality. + +#include "textflag.h" + +// For expandKeyAsm +#define INP R3 +#define BITS R4 +#define OUTENC R5 // Pointer to next expanded encrypt key +#define PTR R6 +#define CNT R7 +#define ROUNDS R8 +#define OUTDEC R9 // Pointer to next expanded decrypt key +#define TEMP R19 +#define ZERO V0 +#define IN0 V1 +#define IN1 V2 +#define KEY V3 +#define RCON V4 +#define MASK V5 +#define TMP V6 +#define STAGE V7 +#define OUTPERM V8 +#define OUTMASK V9 +#define OUTHEAD V10 +#define OUTTAIL V11 + +// For P9 instruction emulation +#define ESPERM V21 // Endian swapping permute into BE +#define TMP2 V22 // Temporary for P8_STXVB16X/P8_STXV + +// For {en,de}cryptBlockAsm +#define BLK_INP R3 +#define BLK_OUT R4 +#define BLK_KEY R5 +#define BLK_ROUNDS R6 +#define BLK_IDX R7 + +DATA ·rcon+0x00(SB)/8, $0x0f0e0d0c0b0a0908 // Permute for vector doubleword endian swap +DATA ·rcon+0x08(SB)/8, $0x0706050403020100 +DATA ·rcon+0x10(SB)/8, $0x0100000001000000 // RCON +DATA ·rcon+0x18(SB)/8, $0x0100000001000000 // RCON +DATA ·rcon+0x20(SB)/8, $0x1b0000001b000000 +DATA ·rcon+0x28(SB)/8, $0x1b0000001b000000 +DATA ·rcon+0x30(SB)/8, $0x0d0e0f0c0d0e0f0c // MASK +DATA ·rcon+0x38(SB)/8, $0x0d0e0f0c0d0e0f0c // MASK +DATA ·rcon+0x40(SB)/8, $0x0000000000000000 +DATA ·rcon+0x48(SB)/8, $0x0000000000000000 +GLOBL ·rcon(SB), RODATA, $80 + +// Emulate unaligned BE vector load/stores on LE targets +#ifdef GOARCH_ppc64le +#define P8_LXVB16X(RA,RB,VT) \ + LXVD2X (RA+RB), VT \ + VPERM VT, VT, ESPERM, VT + +#define P8_STXVB16X(VS,RA,RB) \ + VPERM VS, VS, ESPERM, TMP2 \ + STXVD2X TMP2, (RA+RB) + +#define LXSDX_BE(RA,RB,VT) \ + LXSDX (RA+RB), VT \ + VPERM VT, VT, ESPERM, VT +#else +#define P8_LXVB16X(RA,RB,VT) \ + LXVD2X (RA+RB), VT + +#define P8_STXVB16X(VS,RA,RB) \ + STXVD2X VS, (RA+RB) + +#define LXSDX_BE(RA,RB,VT) \ + LXSDX (RA+RB), VT +#endif + +// func setEncryptKeyAsm(nr int, key *byte, enc *uint32) +TEXT ·expandKeyAsm(SB), NOSPLIT|NOFRAME, $0 + // Load the arguments inside the registers + MOVD nr+0(FP), ROUNDS + MOVD key+8(FP), INP + MOVD enc+16(FP), OUTENC +// MOVD dec+24(FP), OUTDEC + +#ifdef GOARCH_ppc64le + MOVD $·rcon(SB), PTR // PTR point to rcon addr + LVX (PTR), ESPERM + ADD $0x10, PTR +#else + MOVD $·rcon+0x10(SB), PTR // PTR point to rcon addr (skipping permute vector) +#endif + + // Get key from memory and write aligned into VR + P8_LXVB16X(INP, R0, IN0) + ADD $0x10, INP, INP + MOVD $0x20, TEMP + + CMPW ROUNDS, $12 + LVX (PTR)(R0), RCON // lvx 4,0,6 Load first 16 bytes into RCON + LVX (PTR)(TEMP), MASK + ADD $0x10, PTR, PTR // addi 6,6,0x10 PTR to next 16 bytes of RCON + MOVD $8, CNT // li 7,8 CNT = 8 + VXOR ZERO, ZERO, ZERO // vxor 0,0,0 Zero to be zero :) + MOVD CNT, CTR // mtctr 7 Set the counter to 8 (rounds) + + // The expanded decrypt key is the expanded encrypt key stored in reverse order. + // Move OUTDEC to the last key location, and store in descending order. +// ADD $160, OUTDEC, OUTDEC + BLT loop128 +// ADD $32, OUTDEC, OUTDEC + BEQ l192 +// ADD $32, OUTDEC, OUTDEC + JMP l256 + +loop128: + // Key schedule (Round 1 to 8) + VPERM IN0, IN0, MASK, KEY // vperm 3,1,1,5 Rotate-n-splat + VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 + STXVD2X IN0, (R0+OUTENC) +// STXVD2X IN0, (R0+OUTDEC) + VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 + ADD $16, OUTENC, OUTENC +// ADD $-16, OUTDEC, OUTDEC + + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VADDUWM RCON, RCON, RCON // vadduwm 4,4,4 + VXOR IN0, KEY, IN0 // vxor 1,1,3 + BC 0x10, 0, loop128 // bdnz .Loop128 + + LVX (PTR)(R0), RCON // lvx 4,0,6 Last two round keys + + // Key schedule (Round 9) + VPERM IN0, IN0, MASK, KEY // vperm 3,1,1,5 Rotate-n-spat + VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 + STXVD2X IN0, (R0+OUTENC) +// STXVD2X IN0, (R0+OUTDEC) + VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 + ADD $16, OUTENC, OUTENC +// ADD $-16, OUTDEC, OUTDEC + + // Key schedule (Round 10) + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VADDUWM RCON, RCON, RCON // vadduwm 4,4,4 + VXOR IN0, KEY, IN0 // vxor 1,1,3 + + VPERM IN0, IN0, MASK, KEY // vperm 3,1,1,5 Rotate-n-splat + VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 + STXVD2X IN0, (R0+OUTENC) +// STXVD2X IN0, (R0+OUTDEC) + VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 + ADD $16, OUTENC, OUTENC +// ADD $-16, OUTDEC, OUTDEC + + // Key schedule (Round 11) + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VXOR IN0, KEY, IN0 // vxor 1,1,3 + STXVD2X IN0, (R0+OUTENC) +// STXVD2X IN0, (R0+OUTDEC) + + RET + +l192: + LXSDX_BE(INP, R0, IN1) // Load next 8 bytes into upper half of VSR in BE order. + MOVD $4, CNT // li 7,4 + STXVD2X IN0, (R0+OUTENC) +// STXVD2X IN0, (R0+OUTDEC) + ADD $16, OUTENC, OUTENC +// ADD $-16, OUTDEC, OUTDEC + VSPLTISB $8, KEY // vspltisb 3,8 + MOVD CNT, CTR // mtctr 7 + VSUBUBM MASK, KEY, MASK // vsububm 5,5,3 + +loop192: + VPERM IN1, IN1, MASK, KEY // vperm 3,2,2,5 + VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 + VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 + + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + + VSLDOI $8, ZERO, IN1, STAGE // vsldoi 7,0,2,8 + VSPLTW $3, IN0, TMP // vspltw 6,1,3 + VXOR TMP, IN1, TMP // vxor 6,6,2 + VSLDOI $12, ZERO, IN1, IN1 // vsldoi 2,0,2,12 + VADDUWM RCON, RCON, RCON // vadduwm 4,4,4 + VXOR IN1, TMP, IN1 // vxor 2,2,6 + VXOR IN0, KEY, IN0 // vxor 1,1,3 + VXOR IN1, KEY, IN1 // vxor 2,2,3 + VSLDOI $8, STAGE, IN0, STAGE // vsldoi 7,7,1,8 + + VPERM IN1, IN1, MASK, KEY // vperm 3,2,2,5 + VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 + STXVD2X STAGE, (R0+OUTENC) +// STXVD2X STAGE, (R0+OUTDEC) + VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 + ADD $16, OUTENC, OUTENC +// ADD $-16, OUTDEC, OUTDEC + + VSLDOI $8, IN0, IN1, STAGE // vsldoi 7,1,2,8 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + STXVD2X STAGE, (R0+OUTENC) +// STXVD2X STAGE, (R0+OUTDEC) + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + ADD $16, OUTENC, OUTENC +// ADD $-16, OUTDEC, OUTDEC + + VSPLTW $3, IN0, TMP // vspltw 6,1,3 + VXOR TMP, IN1, TMP // vxor 6,6,2 + VSLDOI $12, ZERO, IN1, IN1 // vsldoi 2,0,2,12 + VADDUWM RCON, RCON, RCON // vadduwm 4,4,4 + VXOR IN1, TMP, IN1 // vxor 2,2,6 + VXOR IN0, KEY, IN0 // vxor 1,1,3 + VXOR IN1, KEY, IN1 // vxor 2,2,3 + STXVD2X IN0, (R0+OUTENC) +// STXVD2X IN0, (R0+OUTDEC) + ADD $16, OUTENC, OUTENC +// ADD $-16, OUTDEC, OUTDEC + BC 0x10, 0, loop192 // bdnz .Loop192 + + RET + +l256: + P8_LXVB16X(INP, R0, IN1) + MOVD $7, CNT // li 7,7 + STXVD2X IN0, (R0+OUTENC) +// STXVD2X IN0, (R0+OUTDEC) + ADD $16, OUTENC, OUTENC +// ADD $-16, OUTDEC, OUTDEC + MOVD CNT, CTR // mtctr 7 + +loop256: + VPERM IN1, IN1, MASK, KEY // vperm 3,2,2,5 + VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 + STXVD2X IN1, (R0+OUTENC) +// STXVD2X IN1, (R0+OUTDEC) + VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 + ADD $16, OUTENC, OUTENC +// ADD $-16, OUTDEC, OUTDEC + + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN0, TMP, IN0 // vxor 1,1,6 + VADDUWM RCON, RCON, RCON // vadduwm 4,4,4 + VXOR IN0, KEY, IN0 // vxor 1,1,3 + STXVD2X IN0, (R0+OUTENC) +// STXVD2X IN0, (R0+OUTDEC) + ADD $16, OUTENC, OUTENC +// ADD $-16, OUTDEC, OUTDEC + BC 0x12, 0, done // bdz .Ldone + + VSPLTW $3, IN0, KEY // vspltw 3,1,3 + VSLDOI $12, ZERO, IN1, TMP // vsldoi 6,0,2,12 + VSBOX KEY, KEY // vsbox 3,3 + + VXOR IN1, TMP, IN1 // vxor 2,2,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN1, TMP, IN1 // vxor 2,2,6 + VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 + VXOR IN1, TMP, IN1 // vxor 2,2,6 + + VXOR IN1, KEY, IN1 // vxor 2,2,3 + JMP loop256 // b .Loop256 + +done: + RET + +// func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) +TEXT ·encryptBlockAsm(SB), NOSPLIT|NOFRAME, $0 + MOVD nr+0(FP), R6 // Round count/Key size + MOVD xk+8(FP), R5 // Key pointer + MOVD dst+16(FP), R3 // Dest pointer + MOVD src+24(FP), R4 // Src pointer +#ifdef GOARCH_ppc64le + MOVD $·rcon(SB), R7 + LVX (R7), ESPERM // Permute value for P8_ macros. +#endif + + // Set CR{1,2,3}EQ to hold the key size information. + CMPU R6, $10, CR1 + CMPU R6, $12, CR2 + CMPU R6, $14, CR3 + + MOVD $16, R6 + MOVD $32, R7 + MOVD $48, R8 + MOVD $64, R9 + MOVD $80, R10 + MOVD $96, R11 + MOVD $112, R12 + + // Load text in BE order + P8_LXVB16X(R4, R0, V0) + + // V1, V2 will hold keys, V0 is a temp. + // At completion, V2 will hold the ciphertext. + // Load xk[0:3] and xor with text + LXVD2X (R0+R5), V1 + VXOR V0, V1, V0 + + // Load xk[4:11] and cipher + LXVD2X (R6+R5), V1 + LXVD2X (R7+R5), V2 + VCIPHER V0, V1, V0 + VCIPHER V0, V2, V0 + + // Load xk[12:19] and cipher + LXVD2X (R8+R5), V1 + LXVD2X (R9+R5), V2 + VCIPHER V0, V1, V0 + VCIPHER V0, V2, V0 + + // Load xk[20:27] and cipher + LXVD2X (R10+R5), V1 + LXVD2X (R11+R5), V2 + VCIPHER V0, V1, V0 + VCIPHER V0, V2, V0 + + // Increment xk pointer to reuse constant offsets in R6-R12. + ADD $112, R5 + + // Load xk[28:35] and cipher + LXVD2X (R0+R5), V1 + LXVD2X (R6+R5), V2 + VCIPHER V0, V1, V0 + VCIPHER V0, V2, V0 + + // Load xk[36:43] and cipher + LXVD2X (R7+R5), V1 + LXVD2X (R8+R5), V2 + BEQ CR1, Ldec_tail // Key size 10? + VCIPHER V0, V1, V0 + VCIPHER V0, V2, V0 + + // Load xk[44:51] and cipher + LXVD2X (R9+R5), V1 + LXVD2X (R10+R5), V2 + BEQ CR2, Ldec_tail // Key size 12? + VCIPHER V0, V1, V0 + VCIPHER V0, V2, V0 + + // Load xk[52:59] and cipher + LXVD2X (R11+R5), V1 + LXVD2X (R12+R5), V2 + BNE CR3, Linvalid_key_len // Not key size 14? + // Fallthrough to final cipher + +Ldec_tail: + // Cipher last two keys such that key information is + // cleared from V1 and V2. + VCIPHER V0, V1, V1 + VCIPHERLAST V1, V2, V2 + + // Store the result in BE order. + P8_STXVB16X(V2, R3, R0) + RET + +Linvalid_key_len: + // Segfault, this should never happen. Only 3 keys sizes are created/used. + MOVD R0, 0(R0) + RET + +// func decryptBlockAsm(nr int, xk *uint32, dst, src *byte) +TEXT ·decryptBlockAsm(SB), NOSPLIT|NOFRAME, $0 + MOVD nr+0(FP), R6 // Round count/Key size + MOVD xk+8(FP), R5 // Key pointer + MOVD dst+16(FP), R3 // Dest pointer + MOVD src+24(FP), R4 // Src pointer +#ifdef GOARCH_ppc64le + MOVD $·rcon(SB), R7 + LVX (R7), ESPERM // Permute value for P8_ macros. +#endif + + // Set CR{1,2,3}EQ to hold the key size information. + CMPU R6, $10, CR1 + CMPU R6, $12, CR2 + CMPU R6, $14, CR3 + + MOVD $16, R6 + MOVD $32, R7 + MOVD $48, R8 + MOVD $64, R9 + MOVD $80, R10 + MOVD $96, R11 + MOVD $112, R12 + + // Load text in BE order + P8_LXVB16X(R4, R0, V0) + + // V1, V2 will hold keys, V0 is a temp. + // At completion, V2 will hold the text. + // Load xk[0:3] and xor with ciphertext + LXVD2X (R0+R5), V1 + VXOR V0, V1, V0 + + // Load xk[4:11] and cipher + LXVD2X (R6+R5), V1 + LXVD2X (R7+R5), V2 + VNCIPHER V0, V1, V0 + VNCIPHER V0, V2, V0 + + // Load xk[12:19] and cipher + LXVD2X (R8+R5), V1 + LXVD2X (R9+R5), V2 + VNCIPHER V0, V1, V0 + VNCIPHER V0, V2, V0 + + // Load xk[20:27] and cipher + LXVD2X (R10+R5), V1 + LXVD2X (R11+R5), V2 + VNCIPHER V0, V1, V0 + VNCIPHER V0, V2, V0 + + // Increment xk pointer to reuse constant offsets in R6-R12. + ADD $112, R5 + + // Load xk[28:35] and cipher + LXVD2X (R0+R5), V1 + LXVD2X (R6+R5), V2 + VNCIPHER V0, V1, V0 + VNCIPHER V0, V2, V0 + + // Load xk[36:43] and cipher + LXVD2X (R7+R5), V1 + LXVD2X (R8+R5), V2 + BEQ CR1, Ldec_tail // Key size 10? + VNCIPHER V0, V1, V0 + VNCIPHER V0, V2, V0 + + // Load xk[44:51] and cipher + LXVD2X (R9+R5), V1 + LXVD2X (R10+R5), V2 + BEQ CR2, Ldec_tail // Key size 12? + VNCIPHER V0, V1, V0 + VNCIPHER V0, V2, V0 + + // Load xk[52:59] and cipher + LXVD2X (R11+R5), V1 + LXVD2X (R12+R5), V2 + BNE CR3, Linvalid_key_len // Not key size 14? + // Fallthrough to final cipher + +Ldec_tail: + // Cipher last two keys such that key information is + // cleared from V1 and V2. + VNCIPHER V0, V1, V1 + VNCIPHERLAST V1, V2, V2 + + // Store the result in BE order. + P8_STXVB16X(V2, R3, R0) + RET + +Linvalid_key_len: + // Segfault, this should never happen. Only 3 keys sizes are created/used. + MOVD R0, 0(R0) + RET + +// Remove defines from above so they can be defined here +#undef INP +#undef OUTENC +#undef ROUNDS +#undef KEY +#undef TMP + +// CBC encrypt or decrypt +// R3 src +// R4 dst +// R5 len +// R6 key +// R7 iv +// R8 enc=1 dec=0 +// Ported from: aes_p8_cbc_encrypt +// Register usage: +// R9: ROUNDS +// R10: Index +// V4: IV +// V5: SRC +// V7: DST + +#define INP R3 +#define OUT R4 +#define LEN R5 +#define KEY R6 +#define IVP R7 +#define ENC R8 +#define ROUNDS R9 +#define IDX R10 + +#define RNDKEY0 V0 +#define INOUT V2 +#define TMP V3 + +#define IVEC V4 + +// Vector loads are done using LVX followed by +// a VPERM using mask generated from previous +// LVSL or LVSR instruction, to obtain the correct +// bytes if address is unaligned. + +// Encryption is done with VCIPHER and VCIPHERLAST +// Decryption is done with VNCIPHER and VNCIPHERLAST + +// Encrypt and decypt is done as follows: +// - INOUT value is initialized in outer loop. +// - ROUNDS value is adjusted for loop unrolling. +// - Encryption/decryption is done in loop based on +// adjusted ROUNDS value. +// - Final INOUT value is encrypted/decrypted and stored. + +// Note: original implementation had an 8X version +// for decryption which was omitted to avoid the +// complexity. + +// func cryptBlocksChain(src, dst *byte, length int, key *uint32, iv *byte, enc int, nr int) +TEXT ·cryptBlocksChain(SB), NOSPLIT|NOFRAME, $0 + MOVD src+0(FP), INP + MOVD dst+8(FP), OUT + MOVD length+16(FP), LEN + MOVD key+24(FP), KEY + MOVD iv+32(FP), IVP + MOVD enc+40(FP), ENC + MOVD nr+48(FP), ROUNDS + +#ifdef GOARCH_ppc64le + MOVD $·rcon(SB), R11 + LVX (R11), ESPERM // Permute value for P8_ macros. +#endif + + CMPU LEN, $16 // cmpldi r5,16 + BC 14, 0, LR // bltlr-, return if len < 16. + CMPW ENC, $0 // cmpwi r8,0 + + P8_LXVB16X(IVP, R0, IVEC) // load ivec in BE register order + + SRW $1, ROUNDS // rlwinm r9,r9,31,1,31 + MOVD $0, IDX // li r10,0 + ADD $-1, ROUNDS // addi r9,r9,-1 + BEQ Lcbc_dec // beq + PCALIGN $16 + + // Outer loop: initialize encrypted value (INOUT) + // Load input (INPTAIL) ivec (IVEC) +Lcbc_enc: + P8_LXVB16X(INP, R0, INOUT) // load text in BE vreg order + ADD $16, INP // addi r3,r3,16 + MOVD ROUNDS, CTR // mtctr r9 + ADD $-16, LEN // addi r5,r5,-16 + LXVD2X (KEY+IDX), RNDKEY0 // load first xkey + ADD $16, IDX // addi r10,r10,16 + VXOR INOUT, RNDKEY0, INOUT // vxor v2,v2,v0 + VXOR INOUT, IVEC, INOUT // vxor v2,v2,v4 + + // Encryption loop of INOUT using RNDKEY0 +Loop_cbc_enc: + LXVD2X (KEY+IDX), RNDKEY0 // load next xkey + VCIPHER INOUT, RNDKEY0, INOUT // vcipher v2,v2,v1 + ADD $16, IDX // addi r10,r10,16 + LXVD2X (KEY+IDX), RNDKEY0 // load next xkey + VCIPHER INOUT, RNDKEY0, INOUT // vcipher v2,v2,v1 + ADD $16, IDX // addi r10,r10,16 + BDNZ Loop_cbc_enc + + // Encrypt tail values and store INOUT + LXVD2X (KEY+IDX), RNDKEY0 // load next xkey + VCIPHER INOUT, RNDKEY0, INOUT // vcipher v2,v2,v1 + ADD $16, IDX // addi r10,r10,16 + LXVD2X (KEY+IDX), RNDKEY0 // load final xkey + VCIPHERLAST INOUT, RNDKEY0, IVEC // vcipherlast v4,v2,v0 + MOVD $0, IDX // reset key index for next block + CMPU LEN, $16 // cmpldi r5,16 + P8_STXVB16X(IVEC, OUT, R0) // store ciphertext in BE order + ADD $16, OUT // addi r4,r4,16 + BGE Lcbc_enc // bge Lcbc_enc + BR Lcbc_done // b Lcbc_done + + // Outer loop: initialize decrypted value (INOUT) + // Load input (INPTAIL) ivec (IVEC) +Lcbc_dec: + P8_LXVB16X(INP, R0, TMP) // load ciphertext in BE vreg order + ADD $16, INP // addi r3,r3,16 + MOVD ROUNDS, CTR // mtctr r9 + ADD $-16, LEN // addi r5,r5,-16 + LXVD2X (KEY+IDX), RNDKEY0 // load first xkey + ADD $16, IDX // addi r10,r10,16 + VXOR TMP, RNDKEY0, INOUT // vxor v2,v3,v0 + PCALIGN $16 + + // Decryption loop of INOUT using RNDKEY0 +Loop_cbc_dec: + LXVD2X (KEY+IDX), RNDKEY0 // load next xkey + ADD $16, IDX // addi r10,r10,16 + VNCIPHER INOUT, RNDKEY0, INOUT // vncipher v2,v2,v1 + LXVD2X (KEY+IDX), RNDKEY0 // load next xkey + ADD $16, IDX // addi r10,r10,16 + VNCIPHER INOUT, RNDKEY0, INOUT // vncipher v2,v2,v0 + BDNZ Loop_cbc_dec + + // Decrypt tail values and store INOUT + LXVD2X (KEY+IDX), RNDKEY0 // load next xkey + ADD $16, IDX // addi r10,r10,16 + VNCIPHER INOUT, RNDKEY0, INOUT // vncipher v2,v2,v1 + LXVD2X (KEY+IDX), RNDKEY0 // load final xkey + MOVD $0, IDX // li r10,0 + VNCIPHERLAST INOUT, RNDKEY0, INOUT // vncipherlast v2,v2,v0 + CMPU LEN, $16 // cmpldi r5,16 + VXOR INOUT, IVEC, INOUT // vxor v2,v2,v4 + VOR TMP, TMP, IVEC // vor v4,v3,v3 + P8_STXVB16X(INOUT, OUT, R0) // store text in BE order + ADD $16, OUT // addi r4,r4,16 + BGE Lcbc_dec // bge + +Lcbc_done: + VXOR RNDKEY0, RNDKEY0, RNDKEY0 // clear key register + P8_STXVB16X(IVEC, R0, IVP) // Save ivec in BE order for next round. + RET // bclr 20,lt,0 + diff --git a/pkg/slayers/path/hummingbird/base.go b/pkg/slayers/path/hummingbird/base.go new file mode 100644 index 0000000000..379023a9e2 --- /dev/null +++ b/pkg/slayers/path/hummingbird/base.go @@ -0,0 +1,158 @@ +package hummingbird + +import ( + "encoding/binary" + "fmt" + + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/slayers/path" +) + +const MetaLen = 12 + +func RegisterPath() { + path.RegisterPath(path.Metadata{ + Type: PathType, + Desc: "Hummingbird", + New: func() path.Path { + return &Raw{} + }, + }) +} + +// Base holds the basic information that is used by both raw and fully decoded paths. +type Base struct { + // PathMeta is the Hummingbird path meta header. It is always instantiated when + // decoding a path from bytes. + PathMeta MetaHdr + // NumINF is the number of InfoFields in the path. + NumINF int + // NumLines is the number of 4 bytes lines in the path. NumLines = SegLen[i] for 0<=i<=2. + NumLines int +} + +// DecodeFromBytes populates the fields from a raw buffer. The buffer must be of length >= +// hummingbird.MetaLen. +func (s *Base) DecodeFromBytes(data []byte) error { + // PathMeta takes care of bounds check. + err := s.PathMeta.DecodeFromBytes(data) + if err != nil { + return err + } + s.NumINF = 0 + s.NumLines = 0 + for i := 2; i >= 0; i-- { + if s.PathMeta.SegLen[i] == 0 && s.NumINF > 0 { + return serrors.New( + fmt.Sprintf("Meta.SegLen[%d] == 0, but Meta.SegLen[%d] > 0", i, s.NumINF-1)) + } + if s.PathMeta.SegLen[i] > 0 && s.NumINF == 0 { + s.NumINF = i + 1 + } + s.NumLines += int(s.PathMeta.SegLen[i]) + } + return nil +} + +// IncPath increases the currHF index by n and the currINF index if appropriate. +func (s *Base) IncPath(n int) error { + if s.NumINF == 0 { + return serrors.New("empty path cannot be increased") + } + if int(s.PathMeta.CurrHF) >= s.NumLines-n { + // s.PathMeta.CurrHF = uint8(s.NumLines - n) + return serrors.New("Incrementing path over end") + } + s.PathMeta.CurrHF += uint8(n) + s.PathMeta.CurrINF = s.InfIndexForHF(s.PathMeta.CurrHF) + return nil +} + +// IsXover returns whether we are at a crossover point. +func (s *Base) IsXover() bool { + return s.PathMeta.CurrHF+FlyoverLines < uint8(s.NumLines) && + (s.PathMeta.CurrINF != s.InfIndexForHF(s.PathMeta.CurrHF+HopLines) || + s.PathMeta.CurrINF != s.InfIndexForHF(s.PathMeta.CurrHF+FlyoverLines)) + +} + +// IsFirstHopAfterXover returns whether this is the first hop field after a crossover point. +func (s *Base) IsFirstHopAfterXover() bool { + return s.PathMeta.CurrINF > 0 && s.PathMeta.CurrHF > 0 && + s.PathMeta.CurrINF-1 == s.InfIndexForHF(s.PathMeta.CurrHF-1) +} + +// InfIndexForHF returns the segment to which the HopField hf belongs +// The argument hfLines is the line count until the first line of this hop field. +func (s *Base) InfIndexForHF(hfLines uint8) uint8 { + switch { + case hfLines < s.PathMeta.SegLen[0]: + return 0 + case hfLines < s.PathMeta.SegLen[0]+s.PathMeta.SegLen[1]: + return 1 + default: + return 2 + } +} + +// Len returns the length of the path in bytes. +func (s *Base) Len() int { + return MetaLen + s.NumINF*path.InfoLen + s.NumLines*LineLen +} + +// Type returns the type of the path. +func (s *Base) Type() path.Type { + return PathType +} + +// MetaHdr is the PathMetaHdr of a Hummingbird (data-plane) path type. +type MetaHdr struct { + CurrINF uint8 // Index of the current info field. + CurrHF uint8 // Index of the current hop field. + SegLen [3]uint8 // Length in bytes / 4 of each segment. + BaseTS uint32 + HighResTS uint32 +} + +// DecodeFromBytes populates the fields from a raw buffer. The buffer must be of length >= +// hummingbird.MetaLen. +func (m *MetaHdr) DecodeFromBytes(raw []byte) error { + if len(raw) < MetaLen { + return serrors.New("MetaHdr raw too short", "expected", MetaLen, "actual", len(raw)) + } + line := binary.BigEndian.Uint32(raw[0:4]) + m.CurrINF = uint8(line >> 30) + m.CurrHF = uint8(line >> 22) + m.SegLen[0] = uint8(line>>14) & 0x7F + m.SegLen[1] = uint8(line>>7) & 0x7F + m.SegLen[2] = uint8(line) & 0x7F + + m.BaseTS = binary.BigEndian.Uint32(raw[4:8]) + m.HighResTS = binary.BigEndian.Uint32(raw[8:12]) + + return nil +} + +// SerializeTo writes the fields into the provided buffer. The buffer must be of length >= +// hummingbird.MetaLen. +func (m *MetaHdr) SerializeTo(b []byte) error { + if len(b) < MetaLen { + return serrors.New("buffer for MetaHdr too short", "expected", MetaLen, "actual", len(b)) + } + line := uint32(m.CurrINF)<<30 | uint32(m.CurrHF)<<22 + line |= uint32(m.SegLen[0]&0x7F) << 14 + line |= uint32(m.SegLen[1]&0x7F) << 7 + line |= uint32(m.SegLen[2] & 0x7F) + binary.BigEndian.PutUint32(b[0:4], line) + + binary.BigEndian.PutUint32(b[4:8], m.BaseTS) + binary.BigEndian.PutUint32(b[8:12], m.HighResTS) + + return nil +} + +func (m MetaHdr) String() string { + return fmt.Sprintf( + "{CurrInf: %d, CurrHF: %d, SegLen: %v, BaseTimestamp: %v, HighResTimestamp: %v}", + m.CurrINF, m.CurrHF, m.SegLen, m.BaseTS, m.HighResTS) +} diff --git a/pkg/slayers/path/hummingbird/base_test.go b/pkg/slayers/path/hummingbird/base_test.go new file mode 100644 index 0000000000..41b7c4ff01 --- /dev/null +++ b/pkg/slayers/path/hummingbird/base_test.go @@ -0,0 +1,147 @@ +package hummingbird_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" +) + +func TestIncPath(t *testing.T) { + testCases := map[string]struct { + nsegs, nhops int + segLens [3]uint8 + inIdxs, wantIdxs [][2]int + }{ + "1 segment, 2 hops": { + nsegs: 1, + nhops: 6, + segLens: [3]uint8{6, 0, 0}, + inIdxs: [][2]int{{0, 0}, {0, 3}}, + wantIdxs: [][2]int{{0, 3}, {0, 0}}, + }, + "1 segment, 5 hops": { + nsegs: 1, + nhops: 15, + segLens: [3]uint8{15, 0, 0}, + inIdxs: [][2]int{{0, 0}, {0, 3}, {0, 6}, {0, 9}, {0, 12}}, + wantIdxs: [][2]int{{0, 3}, {0, 6}, {0, 9}, {0, 12}, {0, 0}}, + }, + "2 segments, 5 hops": { + nsegs: 2, + nhops: 15, + segLens: [3]uint8{6, 9, 0}, + inIdxs: [][2]int{{0, 0}, {0, 3}, {1, 6}, {1, 9}, {1, 12}}, + wantIdxs: [][2]int{{0, 3}, {1, 6}, {1, 9}, {1, 12}, {0, 0}}, + }, + "3 segments, 9 hops": { + nsegs: 3, + nhops: 27, + segLens: [3]uint8{6, 12, 9}, + inIdxs: [][2]int{ + {0, 0}, {0, 3}, {1, 6}, {1, 9}, {1, 12}, {1, 15}, {2, 18}, {2, 21}, {2, 24}, + }, + wantIdxs: [][2]int{ + {0, 3}, {1, 6}, {1, 9}, {1, 12}, {1, 15}, {2, 18}, {2, 21}, {2, 24}, {0, 0}, + }, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + for i := range tc.inIdxs { + i := i + t.Run(fmt.Sprintf("%s case %d", name, i+1), func(t *testing.T) { + t.Parallel() + s := hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: uint8(tc.inIdxs[i][0]), + CurrHF: uint8(tc.inIdxs[i][1]), + SegLen: tc.segLens, + }, + NumINF: tc.nsegs, + NumLines: tc.nhops, + } + err := s.IncPath(3) + if tc.wantIdxs[i][0] == 0 && tc.wantIdxs[i][1] == 0 { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, uint8(tc.wantIdxs[i][0]), s.PathMeta.CurrINF, "CurrINF") + assert.Equal(t, uint8(tc.wantIdxs[i][1]), s.PathMeta.CurrHF, "CurrHF") + } + + }) + } + } +} + +func TestBaseIsXOver(t *testing.T) { + testCases := map[string]struct { + nsegs, nhops int + segLens [3]uint8 + inIdxs [][2]int + xover []bool + }{ + "1 segment, 2 hops": { + nsegs: 1, + nhops: 6, + segLens: [3]uint8{6, 0, 0}, + inIdxs: [][2]int{{0, 0}, {0, 3}}, + xover: []bool{false, false}, + }, + "1 segment, 5 hops": { + nsegs: 1, + nhops: 19, + segLens: [3]uint8{19, 0, 0}, + inIdxs: [][2]int{{0, 0}, {0, 3}, {0, 8}, {0, 13}, {0, 16}}, + xover: []bool{false, false, false, false, false}, + }, + "2 segments, 5 hops": { + nsegs: 2, + nhops: 17, + segLens: [3]uint8{8, 9, 0}, + inIdxs: [][2]int{{0, 0}, {0, 3}, {1, 8}, {1, 11}, {1, 14}}, + xover: []bool{false, true, false, false, false}, + }, + "3 segments, 9 hops": { + nsegs: 3, + nhops: 37, + segLens: [3]uint8{6, 16, 15}, + inIdxs: [][2]int{ + {0, 0}, {0, 3}, {1, 6}, {1, 11}, {1, 14}, {1, 19}, {2, 22}, {2, 27}, {2, 32}, + }, + xover: []bool{false, true, false, false, false, true, false, false, false}, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + for i := range tc.xover { + i := i + s := hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: uint8(tc.inIdxs[i][0]), + CurrHF: uint8(tc.inIdxs[i][1]), + SegLen: tc.segLens, + }, + NumINF: tc.nsegs, + NumLines: tc.nhops, + } + t.Run(fmt.Sprintf("%s case %d", name, i+1), func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.xover[i], s.IsXover()) + }) + t.Run(fmt.Sprintf("%s case %d IsFirstAfterXover", name, i+1), func(t *testing.T) { + t.Parallel() + firstHopAfterXover := false + if i > 0 { + firstHopAfterXover = tc.xover[i-1] + } + assert.Equal(t, firstHopAfterXover, s.IsFirstHopAfterXover()) + }) + } + } +} diff --git a/pkg/slayers/path/hummingbird/decoded.go b/pkg/slayers/path/hummingbird/decoded.go new file mode 100644 index 0000000000..4005de8b05 --- /dev/null +++ b/pkg/slayers/path/hummingbird/decoded.go @@ -0,0 +1,245 @@ +package hummingbird + +import ( + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/slayers/path" + "github.com/scionproto/scion/pkg/slayers/path/scion" +) + +const ( + // MaxINFs is the maximum number of info fields in a Hummingbird path. + MaxINFs = 3 + // MaxHops is the maximum number of hop fields in a Hummingbird path. + MaxHops = 85 +) + +// Decoded implements the Hummingbird (data-plane) path type. Decoded is intended to be used in +// non-performance critical code paths, where the convenience of having a fully parsed path trumps +// the loss of performance. +type Decoded struct { + Base + // InfoFields contains all the InfoFields of the path. + InfoFields []path.InfoField + // HopFields contains all the HopFields of the path. + HopFields []FlyoverHopField + // FirstHopPerSeg notes the index of the first hopfield of the second and third segment + FirstHopPerSeg [2]uint8 +} + +// DecodeFromBytes fully decodes the Hummingbird path into the corresponding fields. +func (s *Decoded) DecodeFromBytes(data []byte) error { + if err := s.Base.DecodeFromBytes(data); err != nil { + return err + } + if minLen := s.Len(); len(data) < minLen { + return serrors.New("DecodedPath raw too short", "expected", minLen, "actual", len(data)) + } + + offset := MetaLen + s.InfoFields = make([]path.InfoField, s.NumINF) + for i := 0; i < s.NumINF; i++ { + if err := s.InfoFields[i].DecodeFromBytes(data[offset : offset+path.InfoLen]); err != nil { + return err + } + offset += path.InfoLen + } + + // Allocate maximum number of possible hopfields based on length + s.HopFields = make([]FlyoverHopField, s.NumLines/HopLines) + i, j := 0, 0 + // If last hop is not a flyover hop, decode it with only 12 bytes slice + for ; j < s.NumLines-HopLines; i++ { + if err := s.HopFields[i].DecodeFromBytes(data[offset : offset+FlyoverLen]); err != nil { + return err + } + // Set FirstHopPerSeg + if j == int(s.PathMeta.SegLen[0]) { + s.FirstHopPerSeg[0] = uint8(i) + } else if j == int(s.PathMeta.SegLen[0])+int(s.PathMeta.SegLen[1]) { + s.FirstHopPerSeg[1] = uint8(i) + } + + if s.HopFields[i].Flyover { + offset += FlyoverLen + j += FlyoverLines + } else { + offset += HopLen + j += HopLines + } + } + if j == s.NumLines-HopLines { + if err := s.HopFields[i].DecodeFromBytes(data[offset : offset+HopLen]); err != nil { + return err + } + i++ + } + s.HopFields = s.HopFields[:i] + if s.PathMeta.SegLen[1] == 0 { + s.FirstHopPerSeg[0] = uint8(i) + s.FirstHopPerSeg[1] = uint8(i) + } else if s.PathMeta.SegLen[2] == 0 { + s.FirstHopPerSeg[1] = uint8(i) + } + + return nil +} + +// SerializeTo writes the path to a slice. The slice must be big enough to hold the entire data, +// otherwise an error is returned. +func (s *Decoded) SerializeTo(b []byte) error { + if len(b) < s.Len() { + return serrors.New("buffer too small to serialize path.", "expected", s.Len(), + "actual", len(b)) + } + var offset int + + offset = MetaLen + if err := s.PathMeta.SerializeTo(b[:MetaLen]); err != nil { + return err + } + + for _, info := range s.InfoFields { + if err := info.SerializeTo(b[offset : offset+path.InfoLen]); err != nil { + return err + } + offset += path.InfoLen + } + for _, hop := range s.HopFields { + if hop.Flyover { + if err := hop.SerializeTo(b[offset : offset+FlyoverLen]); err != nil { + return err + } + offset += FlyoverLen + } else { + if err := hop.SerializeTo(b[offset : offset+HopLen]); err != nil { + return err + } + offset += HopLen + } + } + return nil +} + +// Reverse reverses a hummingbird path. +// Removes all reservations from a Hummingbird path, as these are not bidirectional +func (s *Decoded) Reverse() (path.Path, error) { + if s.NumINF == 0 { + return nil, serrors.New("empty decoded path is invalid and cannot be reversed") + } + + if err := s.removeFlyovers(); err != nil { + return nil, err + } + // Reverse order of InfoFields and SegLens + for i, j := 0, s.NumINF-1; i < j; i, j = i+1, j-1 { + s.InfoFields[i], s.InfoFields[j] = s.InfoFields[j], s.InfoFields[i] + s.PathMeta.SegLen[i], s.PathMeta.SegLen[j] = s.PathMeta.SegLen[j], s.PathMeta.SegLen[i] + } + // Reverse cons dir flags + for i := 0; i < s.NumINF; i++ { + info := &s.InfoFields[i] + info.ConsDir = !info.ConsDir + } + // Reverse order of hop fields + for i, j := 0, len(s.HopFields)-1; i < j; i, j = i+1, j-1 { + s.HopFields[i], s.HopFields[j] = s.HopFields[j], s.HopFields[i] + } + // Update CurrINF and CurrHF and SegLens + s.PathMeta.CurrINF = uint8(s.NumINF) - s.PathMeta.CurrINF - 1 + s.PathMeta.CurrHF = uint8(s.NumLines) - s.PathMeta.CurrHF - HopLines + + return s, nil +} + +// RemoveFlyovers removes all reservations from a decoded path +// Corrects SegLen and CurrHF accordingly +// Does not affect MACs +func (s *Decoded) removeFlyovers() error { + var idxInf uint8 = 0 + var offset uint8 = 0 + var segCount uint8 = 0 + + for i, hop := range s.HopFields { + if idxInf > 2 { + return serrors.New("path appears to have more than 3 segments during flyover removal") + } + if hop.Flyover { + s.HopFields[i].Flyover = false + + if s.PathMeta.CurrHF > offset { + s.PathMeta.CurrHF -= 2 + } + s.Base.NumLines -= 2 + s.PathMeta.SegLen[idxInf] -= 2 + } + segCount += HopLines + if s.PathMeta.SegLen[idxInf] == segCount { + segCount = 0 + idxInf += 1 + } else if s.PathMeta.SegLen[idxInf] < segCount { + return serrors.New( + "New hopfields boundaries do not match new segment lengths after flyover removal") + } + offset += HopLines + } + return nil +} + +// ToRaw tranforms hummingbird.Decoded into hummingbird.Raw. +func (s *Decoded) ToRaw() (*Raw, error) { + b := make([]byte, s.Len()) + if err := s.SerializeTo(b); err != nil { + return nil, err + } + raw := &Raw{} + if err := raw.DecodeFromBytes(b); err != nil { + return nil, err + } + return raw, nil +} + +// InfIndexForHFIndex takes the index of the hop field in the HopFields slice and returns its +// corresponding info field index in the InfoFields slice. Expected 0 <= hfIdx < len(HopFields). +func (s *Decoded) InfIndexForHFIndex(hfIdx uint8) uint8 { + lineCount := uint8(0) + for i := uint8(0); i < hfIdx; i++ { + if s.HopFields[i].Flyover { + lineCount += FlyoverLines + } else { + lineCount += HopLines + } + } + return s.InfIndexForHF(lineCount) +} + +// Converts a SCiON decoded path to a hummingbird decoded path +// Does NOT perform a deep copy of hop and info fields. +// Does NOT set the PathMeta Timestamps and counter +func (s *Decoded) ConvertFromScionDecoded(d scion.Decoded) { + // convert Base + s.convertBaseFromScion(d.Base) + // transfer Infofields + s.InfoFields = d.InfoFields + // convert HopFields + s.HopFields = make([]FlyoverHopField, d.NumHops) + for i, hop := range d.HopFields { + s.HopFields[i] = FlyoverHopField{ + HopField: hop, + Flyover: false, + } + } + s.FirstHopPerSeg[0] = d.Base.PathMeta.SegLen[0] + s.FirstHopPerSeg[1] = d.Base.PathMeta.SegLen[0] + d.Base.PathMeta.SegLen[1] +} + +func (s *Decoded) convertBaseFromScion(d scion.Base) { + s.Base.NumINF = d.NumINF + s.Base.PathMeta.CurrINF = d.PathMeta.CurrINF + + s.Base.NumLines = d.NumHops * HopLines + s.Base.PathMeta.CurrHF = d.PathMeta.CurrHF * HopLines + + s.Base.PathMeta.SegLen[0] = d.PathMeta.SegLen[0] * HopLines + s.Base.PathMeta.SegLen[1] = d.PathMeta.SegLen[1] * HopLines + s.Base.PathMeta.SegLen[2] = d.PathMeta.SegLen[2] * HopLines +} diff --git a/pkg/slayers/path/hummingbird/decoded_test.go b/pkg/slayers/path/hummingbird/decoded_test.go new file mode 100644 index 0000000000..7463636e94 --- /dev/null +++ b/pkg/slayers/path/hummingbird/decoded_test.go @@ -0,0 +1,446 @@ +package hummingbird_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/scionproto/scion/pkg/slayers/path" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" + "github.com/scionproto/scion/pkg/slayers/path/scion" +) + +var testInfoFields = []path.InfoField{ + { + Peer: false, + ConsDir: false, + SegID: 0x111, + Timestamp: 0x100, + }, + { + Peer: false, + ConsDir: true, + SegID: 0x222, + Timestamp: 0x100, + }, +} + +var testFlyoverFields = []hummingbird.FlyoverHopField{ + { + HopField: path.HopField{ + ExpTime: 63, + ConsIngress: 1, + ConsEgress: 0, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + Flyover: true, + ResID: 0, + Bw: 4, + ResStartTime: 2, + Duration: 1, + }, + { + HopField: path.HopField{ + ExpTime: 63, + ConsIngress: 3, + ConsEgress: 2, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + }, + { + HopField: path.HopField{ + ExpTime: 63, + ConsIngress: 0, + ConsEgress: 2, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + }, + { + HopField: path.HopField{ + ExpTime: 63, + ConsIngress: 1, + ConsEgress: 0, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + Flyover: true, + ResID: 0, + Bw: 4, + ResStartTime: 0, + Duration: 1, + }, +} + +var otherTestFlyoverFields = []hummingbird.FlyoverHopField{ + { + HopField: path.HopField{ + ExpTime: 63, + ConsIngress: 1, + ConsEgress: 0, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + Flyover: true, + ResID: 0, + Bw: 4, + ResStartTime: 2, + Duration: 1, + }, + { + HopField: path.HopField{ + ExpTime: 63, + ConsIngress: 3, + ConsEgress: 2, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + }, + { + HopField: path.HopField{ + ExpTime: 63, + ConsIngress: 0, + ConsEgress: 2, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + }, + { + HopField: path.HopField{ + ExpTime: 63, + ConsIngress: 1, + ConsEgress: 0, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + }, +} + +var decodedHbirdTestPath = &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{8, 8, 0}, + BaseTS: 808, + HighResTS: 1234, + }, + NumINF: 2, + NumLines: 16, + }, + InfoFields: testInfoFields, + HopFields: testFlyoverFields, + FirstHopPerSeg: [2]uint8{2, 4}, +} + +var otherDecodedHbirdTestPath = &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{8, 6, 0}, + BaseTS: 808, + HighResTS: 1234, + }, + NumINF: 2, + NumLines: 14, + }, + InfoFields: testInfoFields, + HopFields: otherTestFlyoverFields, + FirstHopPerSeg: [2]uint8{2, 4}, +} + +var emptyDecodedTestPath = &hummingbird.Decoded{ + Base: hummingbird.Base{}, + InfoFields: []path.InfoField{}, + HopFields: []hummingbird.FlyoverHopField{}, +} + +var rawHbirdPath = []byte("\x00\x02\x04\x00\x00\x00\x03\x28\x00\x00\x04\xd2" + + "\x00\x00\x01\x11\x00\x00\x01\x00\x01\x00\x02\x22\x00\x00\x01\x00" + + "\x80\x3f\x00\x01\x00\x00\x01\x02\x03\x04\x05\x06\x00\x00\x00\x04\x00\x02\x00\x01" + + "\x00\x3f\x00\x03\x00\x02\x01\x02\x03\x04\x05\x06" + + "\x00\x3f\x00\x00\x00\x02\x01\x02\x03\x04\x05\x06" + + "\x80\x3f\x00\x01\x00\x00\x01\x02\x03\x04\x05\x06\x00\x00\x00\x04\x00\x00\x00\x01") + +var otherRawHbirdPath = []byte("\x00\x02\x03\x00\x00\x00\x03\x28\x00\x00\x04\xd2" + + "\x00\x00\x01\x11\x00\x00\x01\x00\x01\x00\x02\x22\x00\x00\x01\x00" + + "\x80\x3f\x00\x01\x00\x00\x01\x02\x03\x04\x05\x06\x00\x00\x00\x04\x00\x02\x00\x01" + + "\x00\x3f\x00\x03\x00\x02\x01\x02\x03\x04\x05\x06" + + "\x00\x3f\x00\x00\x00\x02\x01\x02\x03\x04\x05\x06" + + "\x00\x3f\x00\x01\x00\x00\x01\x02\x03\x04\x05\x06") + +type hbirdPathCase struct { + infos []bool + hops [][][]uint16 +} + +var pathReverseCasesHbird = map[string]struct { + input hbirdPathCase + want hbirdPathCase + inIdxs [][2]int + wantIdxs [][2]int +}{ + "1 segment, 2 hops": { + input: hbirdPathCase{[]bool{true}, [][][]uint16{{{11, 0}, {12, 1}}}}, + want: hbirdPathCase{[]bool{false}, [][][]uint16{{{12, 0}, {11, 0}}}}, + inIdxs: [][2]int{{0, 0}, {0, 3}}, + wantIdxs: [][2]int{{0, 3}, {0, 0}}, + }, + "1 segment, 5 hops": { + input: hbirdPathCase{[]bool{true}, + [][][]uint16{{{11, 1}, {12, 1}, {13, 0}, {14, 1}, {15, 0}}}}, + want: hbirdPathCase{[]bool{false}, + [][][]uint16{{{15, 0}, {14, 0}, {13, 0}, {12, 0}, {11, 0}}}}, + inIdxs: [][2]int{{0, 0}, {0, 5}, {0, 10}, {0, 13}, {0, 18}}, + wantIdxs: [][2]int{{0, 12}, {0, 9}, {0, 6}, {0, 3}, {0, 0}}, + }, + "2 segments, 5 hops": { + input: hbirdPathCase{[]bool{true, false}, + [][][]uint16{{{11, 0}, {12, 0}}, {{13, 1}, {14, 1}, {15, 0}}}}, + want: hbirdPathCase{[]bool{true, false}, + [][][]uint16{{{15, 0}, {14, 0}, {13, 0}}, {{12, 0}, {11, 0}}}}, + inIdxs: [][2]int{{0, 0}, {0, 3}, {1, 6}, {1, 11}, {1, 16}}, + wantIdxs: [][2]int{{1, 12}, {1, 9}, {0, 6}, {0, 3}, {0, 0}}, + }, + "3 segments, 9 hops": { + input: hbirdPathCase{ + []bool{true, false, false}, + [][][]uint16{ + {{11, 1}, {12, 0}}, + {{13, 0}, {14, 1}, {15, 1}, {16, 0}}, + {{17, 0}, {18, 1}, {19, 1}}, + }, + }, + want: hbirdPathCase{ + []bool{true, true, false}, + [][][]uint16{ + {{19, 0}, {18, 0}, {17, 0}}, + {{16, 0}, {15, 0}, {14, 0}, {13, 0}}, + {{12, 0}, {11, 0}}, + }, + }, + inIdxs: [][2]int{ + {0, 0}, {0, 5}, {1, 8}, {1, 11}, {1, 16}, {1, 21}, {2, 24}, {2, 27}, {2, 32}, + }, + wantIdxs: [][2]int{ + {2, 24}, {2, 21}, {1, 18}, {1, 15}, {1, 12}, {1, 9}, {0, 6}, {0, 3}, {0, 0}, + }, + }, +} + +func TestDecodedSerializeHbird(t *testing.T) { + b := make([]byte, decodedHbirdTestPath.Len()) + assert.NoError(t, decodedHbirdTestPath.SerializeTo(b)) + assert.Equal(t, rawHbirdPath, b) +} + +func TestDecodedDecodeFromBytesHbird(t *testing.T) { + s := &hummingbird.Decoded{} + assert.NoError(t, s.DecodeFromBytes(rawHbirdPath)) + assert.Equal(t, decodedHbirdTestPath, s) +} + +func TestDecodedDecodeFromBytesNoFlyovers(t *testing.T) { + // p is the scion decoded path we would observe using the Tiny topology of the + // topology generator, when going from 111 to 112. This is one up segment with 2 hops, followed + // by a down segment with two hops as well. There is a cross over at core 110 gluing both. + p := &scion.Decoded{ + Base: scion.Base{ + PathMeta: scion.MetaHdr{ + SegLen: [3]uint8{2, 2, 0}, + }, + NumINF: 2, + NumHops: 4, + }, + InfoFields: []path.InfoField{ + {}, // up + {}, // down + }, + HopFields: []path.HopField{ + {}, // 111: 0->41 up + {}, // 110: 1->0 up + {}, // 110: 0->2 down + {}, // 112: 1->0 down + }, + } + + // Create a hummingbird path from the scion one. + hbird := &hummingbird.Decoded{} + hbird.ConvertFromScionDecoded(*p) // SegLen will be [6,6,0] after this + + // Check the hummingbird path is correct by serializing and deserializing it. + buf := make([]byte, hbird.Len()) + err := hbird.SerializeTo(buf) + assert.NoError(t, err) + // Deserialize. + hbird = &hummingbird.Decoded{} + err = hbird.DecodeFromBytes(buf) + assert.NoError(t, err) +} + +func TestDecodedSerializeDecodeHbird(t *testing.T) { + b := make([]byte, decodedHbirdTestPath.Len()) + assert.NoError(t, decodedHbirdTestPath.SerializeTo(b)) + s := &hummingbird.Decoded{} + assert.NoError(t, s.DecodeFromBytes(b)) + assert.Equal(t, decodedHbirdTestPath, s) +} + +func TestOtherDecodedSerializeHbird(t *testing.T) { + b := make([]byte, otherDecodedHbirdTestPath.Len()) + assert.NoError(t, otherDecodedHbirdTestPath.SerializeTo(b)) + assert.Equal(t, otherRawHbirdPath, b) +} + +func TestOtherDecodedDecodeFromBytesHbird(t *testing.T) { + s := &hummingbird.Decoded{} + assert.NoError(t, s.DecodeFromBytes(otherRawHbirdPath)) + assert.Equal(t, otherDecodedHbirdTestPath, s) +} + +func TestOtherDecodedSerializeDecodeHbird(t *testing.T) { + b := make([]byte, otherDecodedHbirdTestPath.Len()) + assert.NoError(t, otherDecodedHbirdTestPath.SerializeTo(b)) + s := &hummingbird.Decoded{} + assert.NoError(t, s.DecodeFromBytes(b)) + assert.Equal(t, otherDecodedHbirdTestPath, s) +} + +func TestDecodedReverseHbird(t *testing.T) { + for name, tc := range pathReverseCasesHbird { + name, tc := name, tc + for i := range tc.inIdxs { + i := i + t.Run(fmt.Sprintf("%s case %d", name, i+1), func(t *testing.T) { + t.Parallel() + inputPath := mkDecodedHbirdPath(t, tc.input, uint8(tc.inIdxs[i][0]), + uint8(tc.inIdxs[i][1])) + wantPath := mkDecodedHbirdPath(t, tc.want, uint8(tc.wantIdxs[i][0]), + uint8(tc.wantIdxs[i][1])) + revPath, err := inputPath.Reverse() + assert.NoError(t, err) + assert.Equal(t, wantPath, revPath) + }) + } + } +} + +func TestEmptyDecodedReverse(t *testing.T) { + _, err := emptyDecodedTestPath.Reverse() + assert.Error(t, err) +} + +func TestDecodedToRawHbird(t *testing.T) { + raw, err := decodedHbirdTestPath.ToRaw() + assert.NoError(t, err) + assert.Equal(t, rawHbirdTestPath, raw) +} + +func TestInfIndexForHFIndex(t *testing.T) { + cases := map[string]struct { + path hummingbird.Decoded + expected []uint8 // the INF indices of each hop field in the test case + }{ + "empty": { + path: hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + SegLen: [3]uint8{0, 0, 0}, + }, + }, + }, + }, + "one_segment_o": { + path: hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + SegLen: [3]uint8{3, 0, 0}, + }, + }, + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: false}, + }, + }, + expected: []uint8{0}, + }, + // one_segment_oxx means there is one segment with three hops, first is not flyover, + // second and third are. + "one_segment_oxx": { + path: hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + SegLen: [3]uint8{13, 0, 0}, + }, + }, + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: false}, + {Flyover: true}, + {Flyover: true}, + }, + }, + expected: []uint8{0, 0, 0}, + }, + "two_segments_o_oxx": { + path: hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + SegLen: [3]uint8{3, 13, 0}, + }, + }, + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: false}, + {Flyover: false}, + {Flyover: true}, + {Flyover: true}, + }, + }, + expected: []uint8{0, 1, 1, 1}, + }, + } + for name, tc := range cases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + for i := range tc.path.HopFields { + got := tc.path.InfIndexForHFIndex(uint8(i)) + assert.Equal(t, tc.expected[i], got) + } + assert.Panics(t, func() { + tc.path.InfIndexForHFIndex(uint8(len(tc.path.HopFields)) + 1) + }) + }) + } +} + +func mkDecodedHbirdPath(t *testing.T, pcase hbirdPathCase, + infIdx, hopIdx uint8) *hummingbird.Decoded { + t.Helper() + s := &hummingbird.Decoded{} + meta := hummingbird.MetaHdr{ + CurrINF: infIdx, + CurrHF: hopIdx, + BaseTS: 14, + HighResTS: 15, + } + for _, dir := range pcase.infos { + s.InfoFields = append(s.InfoFields, path.InfoField{ConsDir: dir}) + } + i := 0 + for j, hops := range pcase.hops { + for _, hop := range hops { + f := hop[1] == 1 + s.HopFields = append(s.HopFields, hummingbird.FlyoverHopField{ + HopField: path.HopField{ConsIngress: hop[0], ConsEgress: hop[0], + Mac: [6]byte{1, 2, 3, 4, 5, 6}}, + Flyover: f, + Duration: 2}) + if f { + i += 5 + meta.SegLen[j] += 5 + } else { + i += 3 + meta.SegLen[j] += 3 + } + } + } + s.PathMeta = meta + s.NumINF = len(pcase.infos) + s.NumLines = i + + return s +} diff --git a/pkg/slayers/path/hummingbird/flyoverhopfield.go b/pkg/slayers/path/hummingbird/flyoverhopfield.go new file mode 100644 index 0000000000..160e6d08f1 --- /dev/null +++ b/pkg/slayers/path/hummingbird/flyoverhopfield.go @@ -0,0 +1,93 @@ +package hummingbird + +import ( + "encoding/binary" + + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/slayers/path" +) + +const ( + // MacOffset is the offset of the MAC field from the beginning of the HopField + MacOffset = 6 + + // LineLen is the number of bytes in a line as considered by CurrHF in the PathMEtaHeader + LineLen = 4 + + // The number of lines in a hopfield + HopLines = 3 + // The number of lines in a flyoverhopfield + FlyoverLines = 5 + + // HopLen is the size of a HopField in bytes. + HopLen = LineLen * HopLines + // HopLen is the size of a FlyoverHopField in bytes. + FlyoverLen = LineLen * FlyoverLines +) + +type FlyoverHopField struct { + // SCiON Hopfield part of the FlyoverHopField + HopField path.HopField + // True if flyover is present + Flyover bool + // ResID is the Reservation ID of the flyover + ResID uint32 + // Bw is the reserved banwidth of the flyover + Bw uint16 + // ResStartTime is the start time of the reservation + // As a negative offset from the BaseTimeStamp in the PathMetaHdr + ResStartTime uint16 + // Duration is the duration of the reservation + Duration uint16 +} + +// DecodeFromBytes populates the fields from a raw buffer. +// The buffer must be of length >= HopLen if the Flyover bit is false +// The buffer must be of length >= FlyoverLen if the Flyover bit is set +// DecodeFromBytes modifies the fields of *h and reads (but does not modify) the contents of raw. +// When a call that satisfies the precondition (len(raw) >= HopLen) is made, +// the return value is guaranteed to be nil. +// Calls to DecodeFromBytes are always guaranteed to terminate. +func (h *FlyoverHopField) DecodeFromBytes(raw []byte) (err error) { + if err := h.HopField.DecodeFromBytes(raw); err != nil { + return err + } + h.Flyover = raw[0]&0x80 == 0x80 + if h.Flyover { + if len(raw) < FlyoverLen { + return serrors.New("FlyoverHopField raw too short", "expected", + FlyoverLen, "actual", len(raw)) + } + h.ResID = binary.BigEndian.Uint32(raw[12:16]) >> 10 + h.Bw = binary.BigEndian.Uint16(raw[14:16]) & 0x03ff + h.ResStartTime = binary.BigEndian.Uint16(raw[16:18]) + h.Duration = binary.BigEndian.Uint16(raw[18:20]) + } + return nil +} + +// SerializeTo writes the fields into the provided buffer. +// The buffer must be of length >= HopLen if the Flyover bit is false +// The buffer must be of length >= FlyoverLen if the Flyover bit is set +// SerializeTo reads (but does not modify) the fields of *h and writes to the contents of b. +// When a call that satisfies the precondition (len(b) >= HopLen) is made, +// the return value is guaranteed to be nil. +// Calls to SerializeTo are guaranteed to terminate. +func (h *FlyoverHopField) SerializeTo(b []byte) (err error) { + if err := h.HopField.SerializeTo(b); err != nil { + return err + } + + if h.Flyover { + if len(b) < FlyoverLen { + return serrors.New("buffer for FlyoverHopField too short", "expected", + FlyoverLen, "actual", len(b)) + } + b[0] |= 0x80 + binary.BigEndian.PutUint32(b[12:16], h.ResID<<10+uint32(h.Bw)) + binary.BigEndian.PutUint16(b[16:18], h.ResStartTime) + binary.BigEndian.PutUint16(b[18:20], h.Duration) + } + + return nil +} diff --git a/pkg/slayers/path/hummingbird/flyoverhopfield_test.go b/pkg/slayers/path/hummingbird/flyoverhopfield_test.go new file mode 100644 index 0000000000..804fbb797d --- /dev/null +++ b/pkg/slayers/path/hummingbird/flyoverhopfield_test.go @@ -0,0 +1,58 @@ +package hummingbird_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/scionproto/scion/pkg/slayers/path" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" +) + +func TestFlyoverHopSerializeDecodeFlyover(t *testing.T) { + want := &hummingbird.FlyoverHopField{ + HopField: path.HopField{ + IngressRouterAlert: true, + EgressRouterAlert: true, + ExpTime: 63, + ConsIngress: 1, + ConsEgress: 0, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + Flyover: true, + ResID: 782, + Bw: 23, + ResStartTime: 233, + Duration: 11, + } + b := make([]byte, hummingbird.FlyoverLen) + assert.NoError(t, want.SerializeTo(b)) + + got := &hummingbird.FlyoverHopField{} + assert.NoError(t, got.DecodeFromBytes(b)) + assert.Equal(t, want, got) +} + +func TestFlyoverHopSerializeDecode(t *testing.T) { + want := &hummingbird.FlyoverHopField{ + HopField: path.HopField{ + IngressRouterAlert: true, + EgressRouterAlert: false, + ExpTime: 63, + ConsIngress: 1, + ConsEgress: 0, + Mac: [path.MacLen]byte{1, 2, 3, 4, 5, 6}, + }, + Flyover: false, + ResID: 0, + Bw: 0, + ResStartTime: 0, + Duration: 0, + } + b := make([]byte, hummingbird.FlyoverLen) + assert.NoError(t, want.SerializeTo(b)) + + got := &hummingbird.FlyoverHopField{} + assert.NoError(t, got.DecodeFromBytes(b)) + assert.Equal(t, want, got) +} diff --git a/pkg/slayers/path/hummingbird/mac.go b/pkg/slayers/path/hummingbird/mac.go new file mode 100644 index 0000000000..7fe8ec944b --- /dev/null +++ b/pkg/slayers/path/hummingbird/mac.go @@ -0,0 +1,89 @@ +//go:build amd64 || arm64 || ppc64 || ppc64le + +package hummingbird + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/binary" + + "github.com/scionproto/scion/pkg/addr" +) + +// The FullFlyoverMac makes use of the assembly code in the asm_* files +// There are two main, related, reasons for that. +// First, the aes key expansion performed by these assembly files is +// much faster than what the library code does. +// BenchmarkFlyoverMac and BenchmarkFlyoverMacLib in mac_test.go show the difference +// +// Second, the library implementation of the aes key expansion performs calls to make() +// and allocates memory, which we would like to avoid +// This is also the main reason why the direct call to assembly is much faster +// +// A full implementation of aes written in go only without memory allocations +// has been attempted, but turned out to not be much more efficient than +// the library implementation. +// This is expectedt to be due to the fact that a go only implementation of aes +// is unable to make use of hardware accelerated aes instructions. + +// defined in asm_* assembly files + +//go:noescape +func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) + +// TODO: test expandKeyAsm on arm64 and ppc64 machines. +// Compare with code in go/src/crypto/aes/asm_* if necessary + +//go:noescape +func expandKeyAsm(nr int, key *byte, enc *uint32) + +const AkBufferSize = 16 +const FlyoverMacBufferSize = 32 +const XkBufferSize = 44 +const PathType = 5 + +var ZeroBlock [aes.BlockSize]byte + +// Derive authentication key A_k +// block is expected to be initialized beforehand with aes.NewCipher(sv), +// where sv is this AS' secret value +// Requires buffer to be of size at least AkBufferSize +func DeriveAuthKey(block cipher.Block, resId uint32, bw, in, eg uint16, + startTime uint32, resDuration uint16, buffer []byte) []byte { + + _ = buffer[AkBufferSize-1] + + //prepare input + binary.BigEndian.PutUint16(buffer[0:2], in) + binary.BigEndian.PutUint16(buffer[2:4], eg) + binary.BigEndian.PutUint32(buffer[4:8], resId<<10|uint32(bw)) + binary.BigEndian.PutUint32(buffer[8:12], startTime) + binary.BigEndian.PutUint16(buffer[12:14], resDuration) + binary.BigEndian.PutUint16(buffer[14:16], 0) //padding + + // should xor input with iv, but we use iv = 0 => identity + block.Encrypt(buffer[0:16], buffer[0:16]) + return buffer[0:16] +} + +// Computes full flyover mac vk +// Requires buffer to be of size at least FlyoverMacBufferSize +// Requires xkbuffer to be of size at least XkBufferSize. +// It is used to store the aes expanded keys +func FullFlyoverMac(ak []byte, dstIA addr.IA, pktlen uint16, resStartTime uint16, + highResTime uint32, buffer []byte, xkbuffer []uint32) []byte { + + _ = buffer[FlyoverMacBufferSize-1] + _ = xkbuffer[XkBufferSize-1] + + binary.BigEndian.PutUint64(buffer[0:8], uint64(dstIA)) + binary.BigEndian.PutUint16(buffer[8:10], pktlen) + binary.BigEndian.PutUint16(buffer[10:12], resStartTime) + binary.BigEndian.PutUint32(buffer[12:16], highResTime) + + expandKeyAsm(10, &ak[0], &xkbuffer[0]) + + encryptBlockAsm(10, &xkbuffer[0], &buffer[0], &buffer[0]) + + return buffer[0:16] +} diff --git a/pkg/slayers/path/hummingbird/mac_test.go b/pkg/slayers/path/hummingbird/mac_test.go new file mode 100644 index 0000000000..a7a1e88b52 --- /dev/null +++ b/pkg/slayers/path/hummingbird/mac_test.go @@ -0,0 +1,228 @@ +package hummingbird_test + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/binary" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" +) + +func TestDeriveAuthKey(t *testing.T) { + sv := []byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7} + var resId uint32 = 0x40 + var bw uint16 = 0x0203 + buffer := make([]byte, 16) + var in uint16 = 2 + var eg uint16 = 5 + var start uint32 = 0x0030001 + var duration uint16 = 0x0203 + + block, err := aes.NewCipher(sv) + if err != nil { + require.Fail(t, err.Error()) + } + + // Compute expected result with library CBC + expected := make([]byte, 16) + binary.BigEndian.PutUint16(expected[0:2], in) + binary.BigEndian.PutUint16(expected[2:4], eg) + binary.BigEndian.PutUint32(expected[4:8], resId<<10) + expected[6] |= byte(bw >> 8) + expected[7] = byte(bw) + binary.BigEndian.PutUint32(expected[8:12], start) + binary.BigEndian.PutUint16(expected[12:14], duration) + binary.BigEndian.PutUint16(expected[14:16], 0) + + var ZeroBlock [aes.BlockSize]byte + mode := cipher.NewCBCEncrypter(block, ZeroBlock[:]) + mode.CryptBlocks(expected, expected) + + // Run DeriveAuthKey Function + block, err = aes.NewCipher(sv) + if err != nil { + require.Fail(t, err.Error()) + } + + key := hummingbird.DeriveAuthKey(block, resId, bw, in, eg, start, duration, buffer) + require.Equal(t, expected, key) + + key = hummingbird.DeriveAuthKey(block, resId, bw, in, eg, start, duration, buffer) + require.Equal(t, expected, key) +} + +// Golden Data test used for cross verification with other implementations +func TestDeriveAuthKeyGoldenData(t *testing.T) { + sv := []byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7} + var resId uint32 = 0x40 + var bw uint16 = 0x0203 + buffer := make([]byte, 16) + var in uint16 = 2 + var eg uint16 = 5 + var start uint32 = 0x0030001 + var duration uint16 = 0x0203 + + expected := [16]byte{0x25, 0xe6, 0xb9, 0x75, 0x96, 0x7, 0x3, 0x93, 0xef, 0x54, 0x73, 0x67, + 0x1b, 0xf6, 0x3a, 0x9a} + + // Run DeriveAuthKey Function + block, err := aes.NewCipher(sv) + if err != nil { + require.Fail(t, err.Error()) + } + + key := hummingbird.DeriveAuthKey(block, resId, bw, in, eg, start, duration, buffer) + require.Equal(t, expected[:], key) + + key = hummingbird.DeriveAuthKey(block, resId, bw, in, eg, start, duration, buffer) + require.Equal(t, expected[:], key) +} + +func BenchmarkDeriveAuthKey(b *testing.B) { + sv := []byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7} + + var resId uint32 = 0x40 + var bw uint16 = 0x0203 + buffer := make([]byte, 16) + var in uint16 = 2 + var eg uint16 = 5 + var start uint32 = 0x0030001 + var duration uint16 = 0x0203 + + block, err := aes.NewCipher(sv) + if err != nil { + require.Fail(b, err.Error()) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + hummingbird.DeriveAuthKey(block, resId, bw, in, eg, start, duration, buffer) + } +} + +// BenchmarkDeriveAuthKeyManually benchmarks obtaining Ak by just using the stdlib. +// Results in my machine of 5.987 ns/op. +// Does not take into account the process of moving data into the buffer +func BenchmarkDeriveAuthKeyManually(b *testing.B) { + sv := []byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7} + var resId uint32 = 0x40 + var bw uint16 = 0x0203 + var in uint16 = 2 + var eg uint16 = 5 + var start uint16 = 0x0001 + var end uint16 = 0x0203 + + src := make([]byte, hummingbird.AkBufferSize) + binary.BigEndian.PutUint32(src[0:4], resId<<10) + src[2] |= byte(bw >> 8) + src[3] = byte(bw) + binary.BigEndian.PutUint16(src[4:6], in) + binary.BigEndian.PutUint16(src[6:8], eg) + binary.BigEndian.PutUint16(src[8:10], start) + binary.BigEndian.PutUint16(src[10:12], end) + binary.BigEndian.PutUint32(src[12:16], 0) //padding + + buffer := make([]byte, 16) + block, err := aes.NewCipher(sv) + require.NoError(b, err) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + block.Encrypt(buffer[:], src) + } +} + +// We use CBC-MAC using aes for the flyover mac. +func TestFlyoverMac(t *testing.T) { + ak := []byte{0x7e, 0x61, 0x4, 0x91, 0x30, 0x6b, 0x95, 0xec, 0xb5, 0x75, 0xc6, 0xe9, + 0x4c, 0x5a, 0x89, 0x84} + var dstIA addr.IA = 326 + var pktlen uint16 = 23 + var resStartTs uint16 = 1234 + var highResTs uint32 = 4321 + buffer := make([]byte, 32) + xkbuffer := make([]uint32, 44) + + // Compute expected output based on library cbc-mac implementation + expected := make([]byte, 16) + binary.BigEndian.PutUint64(expected[0:8], uint64(dstIA)) + binary.BigEndian.PutUint16(expected[8:10], pktlen) + binary.BigEndian.PutUint16(expected[10:12], resStartTs) + binary.BigEndian.PutUint32(expected[12:16], highResTs) + block, err := aes.NewCipher(ak) + if err != nil { + require.Fail(t, err.Error()) + } + block.Encrypt(expected[:], expected[:]) + + mac := hummingbird.FullFlyoverMac(ak, dstIA, pktlen, resStartTs, highResTs, buffer, xkbuffer) + require.Equal(t, expected, mac) + mac = hummingbird.FullFlyoverMac(ak, dstIA, pktlen, resStartTs, highResTs, buffer, xkbuffer) + require.Equal(t, expected, mac) +} + +// Golden data test used for cross verification with other implementations +func TestFlyoverMacGoldenData(t *testing.T) { + ak := []byte{0x7e, 0x61, 0x4, 0x91, 0x30, 0x6b, 0x95, 0xec, 0xb5, 0x75, 0xc6, 0xe9, + 0x4c, 0x5a, 0x89, 0x84} + var dstIA addr.IA = 326 + var pktlen uint16 = 23 + var resStartTs uint16 = 1234 + var highResTs uint32 = 4321 + buffer := make([]byte, 32) + xkbuffer := make([]uint32, 44) + + expected := [16]byte{0xbe, 0xad, 0xcf, 0x70, 0xf, 0x75, 0xdf, 0x8, 0xde, 0x91, 0xe9, 0xda, + 0xf5, 0xcb, 0x9f, 0x74} + + mac := hummingbird.FullFlyoverMac(ak, dstIA, pktlen, resStartTs, highResTs, buffer, xkbuffer) + require.Equal(t, expected[:], mac) + mac = hummingbird.FullFlyoverMac(ak, dstIA, pktlen, resStartTs, highResTs, buffer, xkbuffer) + require.Equal(t, expected[:], mac) +} + +func BenchmarkFlyoverMac(b *testing.B) { + ak := []byte{0x7e, 0x61, 0x4, 0x91, 0x30, 0x6b, 0x95, 0xec, 0xb5, 0x75, 0xc6, 0xe9, + 0x4c, 0x5a, 0x89, 0x84} + var dstIA addr.IA = 326 + var pktlen uint16 = 23 + var resStartTs uint16 = 1234 + var highResTs uint32 = 4321 + buffer := make([]byte, 32) + xkbuffer := make([]uint32, 44) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + hummingbird.FullFlyoverMac(ak, dstIA, pktlen, resStartTs, highResTs, buffer, xkbuffer) + } +} + +// Benchmark of the FLyover Mac if we use library code only +// Without using the assembly code in the asm_* files +func BenchmarkFlyoverMacLib(b *testing.B) { + ak := []byte{0x7e, 0x61, 0x4, 0x91, 0x30, 0x6b, 0x95, 0xec, 0xb5, 0x75, 0xc6, 0xe9, + 0x4c, 0x5a, 0x89, 0x84} + var dstIA addr.IA = 326 + var pktlen uint16 = 23 + var resStartTs uint16 = 1234 + var highResTs uint32 = 4321 + buffer := make([]byte, 16) + + b.ResetTimer() + // Compute expected output based on library cbc-mac implementation + for i := 0; i < b.N; i++ { + binary.BigEndian.PutUint64(buffer[0:8], uint64(dstIA)) + binary.BigEndian.PutUint16(buffer[8:10], pktlen) + binary.BigEndian.PutUint16(buffer[10:12], resStartTs) + binary.BigEndian.PutUint32(buffer[12:16], highResTs) + block, err := aes.NewCipher(ak) + if err != nil { + require.Fail(b, err.Error()) + } + block.Encrypt(buffer[:], buffer[:]) + } +} diff --git a/pkg/slayers/path/hummingbird/raw.go b/pkg/slayers/path/hummingbird/raw.go new file mode 100644 index 0000000000..0140a08630 --- /dev/null +++ b/pkg/slayers/path/hummingbird/raw.go @@ -0,0 +1,346 @@ +package hummingbird + +import ( + "encoding/binary" + + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/slayers/path" +) + +// Raw is a raw representation of the Hummingbird (data-plane) path type. It is designed to parse as +// little as possible and should be used if performance matters. +type Raw struct { + Base + Raw []byte +} + +// DecodeFromBytes only decodes the PathMetaHeader. Otherwise the nothing is decoded and simply kept +// as raw bytes. +func (s *Raw) DecodeFromBytes(data []byte) error { + if err := s.Base.DecodeFromBytes(data); err != nil { + return err + } + pathLen := s.Len() + if len(data) < pathLen { + return serrors.New("RawPath raw too short", "expected", pathLen, "actual", len(data)) + } + s.Raw = data[:pathLen] + return nil +} + +// SerializeTo writes the path to a slice. The slice must be big enough to hold the entire data, +// otherwise an error is returned. +func (s *Raw) SerializeTo(b []byte) error { + if s.Raw == nil { + return serrors.New("raw is nil") + } + if minLen := s.Len(); len(b) < minLen { + return serrors.New("buffer too small", "expected", minLen, "actual", len(b)) + } + // XXX(roosd): This modifies the underlying buffer. Consider writing to data + // directly. + if err := s.PathMeta.SerializeTo(s.Raw[:MetaLen]); err != nil { + return err + } + + copy(b, s.Raw) + return nil +} + +// Reverse reverses the path such that it can be used in the reverse direction. +// Removes all flyovers in the process +func (s *Raw) Reverse() (path.Path, error) { + // XXX(shitz): The current implementation is not the most performant, since it parses the entire + // path first. If this becomes a performance bottleneck, the implementation should be changed to + // work directly on the raw representation. + + decoded, err := s.ToDecoded() + if err != nil { + return nil, err + } + reversed, err := decoded.Reverse() + if err != nil { + return nil, err + } + if err := reversed.SerializeTo(s.Raw); err != nil { + return nil, err + } + err = s.DecodeFromBytes(s.Raw) + return s, err +} + +// ToDecoded transforms a hummingbird.Raw to a hummingbird.Decoded. +func (s *Raw) ToDecoded() (*Decoded, error) { + // Serialize PathMeta to ensure potential changes are reflected Raw. + + if err := s.PathMeta.SerializeTo(s.Raw[:MetaLen]); err != nil { + return nil, err + } + + decoded := &Decoded{} + if err := decoded.DecodeFromBytes(s.Raw); err != nil { + return nil, err + } + return decoded, nil +} + +// IncPath increments the path by n and writes it to the buffer. +func (s *Raw) IncPath(n int) error { + if err := s.Base.IncPath(n); err != nil { + return err + } + + return s.PathMeta.SerializeTo(s.Raw[:MetaLen]) +} + +// GetInfoField returns the InfoField at a given index. +func (s *Raw) GetInfoField(idx int) (path.InfoField, error) { + if idx >= s.NumINF { + return path.InfoField{}, + serrors.New("InfoField index out of bounds", "max", s.NumINF-1, "actual", idx) + } + infOffset := MetaLen + idx*path.InfoLen + info := path.InfoField{} + if err := info.DecodeFromBytes(s.Raw[infOffset : infOffset+path.InfoLen]); err != nil { + return path.InfoField{}, err + } + return info, nil +} + +// GetCurrentInfoField is a convenience method that returns the current info field pointed to by the +// CurrINF index in the path meta header. +func (s *Raw) GetCurrentInfoField() (path.InfoField, error) { + return s.GetInfoField(int(s.PathMeta.CurrINF)) +} + +// Returns whether the hopfield at the given index is in construction direction +func (s *Raw) isConsdir(idx uint8) bool { + hopIdx := s.Base.InfIndexForHF(idx) + infOffset := MetaLen + hopIdx*path.InfoLen + return s.Raw[infOffset]&0x1 == 0x1 + +} + +// SetInfoField updates the InfoField at a given index. +func (s *Raw) SetInfoField(info path.InfoField, idx int) error { + if idx >= s.NumINF { + return serrors.New("InfoField index out of bounds", "max", s.NumINF-1, "actual", idx) + } + infOffset := MetaLen + idx*path.InfoLen + return info.SerializeTo(s.Raw[infOffset : infOffset+path.InfoLen]) +} + +// GetHopField returns the HopField beginninrg at a given index. +// Does NOT check whether the given index is the first line of a hopfield +// Responsibility to check that falls to the caller +func (s *Raw) GetHopField(idx int) (FlyoverHopField, error) { + if idx >= s.NumLines-HopLines+1 { + return FlyoverHopField{}, + serrors.New("HopField index out of bounds", "max", s.NumLines-HopLines, "actual", idx) + } + hopOffset := MetaLen + s.NumINF*path.InfoLen + idx*LineLen + hop := FlyoverHopField{} + // Let the decoder read a big enough slice in case it is a FlyoverHopField + maxHopLen := FlyoverLen + if idx > s.NumLines-FlyoverLines { + if idx == s.NumLines-HopLines { + maxHopLen = HopLen + } else { + return FlyoverHopField{}, serrors.New( + "Invalid hopfield index", "NumHops", s.NumLines, "index", idx) + } + } + if err := hop.DecodeFromBytes(s.Raw[hopOffset : hopOffset+maxHopLen]); err != nil { + return FlyoverHopField{}, err + } + return hop, nil +} + +// GetCurrentHopField is a convenience method that returns the current hop field pointed to by the +// CurrHF index in the path meta header. +func (s *Raw) GetCurrentHopField() (FlyoverHopField, error) { + return s.GetHopField(int(s.PathMeta.CurrHF)) +} + +// ReplaceMac replaces the Mac of the hopfield at the given index with a new mac +func (s *Raw) ReplacMac(idx int, mac []byte) error { + if idx >= s.NumLines-HopLines+1 { + return serrors.New("HopField index out of bounds", "max", + s.NumLines-HopLines, "actual", idx) + } + offset := s.NumINF*path.InfoLen + MetaLen + idx*LineLen + MacOffset + if n := copy(s.Raw[offset:offset+path.MacLen], mac[:path.MacLen]); n != path.MacLen { + return serrors.New("copied worng number of bytes for mac replacement", + "expected", path.MacLen, "actual", n) + } + return nil +} + +// SetCurrentMac replaces the Mac of the current hopfield by a new mac +func (s *Raw) ReplaceCurrentMac(mac []byte) error { + return s.ReplacMac(int(s.PathMeta.CurrHF), mac) +} + +// Returns a slice of the MAC of the hopfield starting at index idx +// It is the caller's responsibility to make sure line idx is the beginning of a hopfield. +func (s *Raw) GetMac(idx int) ([]byte, error) { + if idx >= s.NumLines-HopLines+1 { + return nil, serrors.New("HopField index out of bounds", + "max", s.NumLines-HopLines, "actual", idx) + } + offset := s.NumINF*path.InfoLen + MetaLen + idx*LineLen + MacOffset + return s.Raw[offset : offset+path.MacLen], nil +} + +// SetHopField updates the HopField at a given index. +// For Hummingbird paths the index is the offset in 4 byte lines +// +// If replacing a FlyoverHopField with a Hopfield, +// it is replaced by a FlyoverHopField with dummy values. +// This works for SCMP packets as Flyover hops are removed later +// in the process of building a SCMP packet. +// +// Does not allow replacing a normal hopfield with a FlyoverHopField +func (s *Raw) SetHopField(hop FlyoverHopField, idx int) error { + if idx >= s.NumLines-HopLines+1 { + return serrors.New("HopField index out of bounds", + "max", s.NumLines-HopLines, "actual", idx) + } + hopOffset := MetaLen + s.NumINF*path.InfoLen + idx*LineLen + if s.Raw[hopOffset]&0x80 == 0x80 { + // IF the current hop is a flyover, the flyover bit of the new hop is set to 1 + // in order to preserve correctness of the path + // + // The reservation data of the new hop is dummy data and invalid. + // This works because SetHopField is currently only used to prepare a SCMP packet, + // and all flyovers are removed later in that process + // + // IF this is ever used for something else, this function needs to be re-written + hop.Flyover = true + } + if hop.Flyover { + if idx >= s.NumLines-FlyoverLines+1 { + return serrors.New("FlyoverHopField index out of bounds", + "max", s.NumLines-FlyoverLines, "actual", idx) + } + hopOffset := MetaLen + s.NumINF*path.InfoLen + idx*LineLen + if s.Raw[hopOffset]&0x80 == 0x00 { + return serrors.New( + "Setting FlyoverHopField over Hopfield with setHopField not supported") + } + return hop.SerializeTo(s.Raw[hopOffset : hopOffset+FlyoverLen]) + } + return hop.SerializeTo(s.Raw[hopOffset : hopOffset+HopLen]) +} + +// IsFirstHop returns whether the current hop is the first hop on the path. +func (s *Raw) IsFirstHop() bool { + return s.PathMeta.CurrHF == 0 +} + +// IsLastHop returns whether the current hop is the last hop on the path. +func (s *Raw) IsLastHop() bool { + return int(s.PathMeta.CurrHF) == (s.NumLines-HopLines) || + int(s.PathMeta.CurrHF) == (s.NumLines-FlyoverLines) +} + +// Returns the egress interface of the next hop +func (s *Raw) GetNextEgress() (uint16, error) { + idx := int(s.Base.PathMeta.CurrHF) + hopOffset := MetaLen + s.NumINF*path.InfoLen + idx*LineLen + if s.Raw[hopOffset]&0x80 == 0x80 { + idx += FlyoverLines + hopOffset += FlyoverLines * LineLen + } else { + idx += HopLines + hopOffset += HopLines * LineLen + } + if idx >= s.NumLines-2 { + return 0, serrors.New("HopField index out of bounds", "max", + s.NumLines-HopLines, "actual", idx) + } + if s.isConsdir(uint8(idx)) { + return binary.BigEndian.Uint16(s.Raw[hopOffset+4 : hopOffset+6]), nil + } + return binary.BigEndian.Uint16(s.Raw[hopOffset+2 : hopOffset+4]), nil +} + +// Returns the ingress interface of the previous hop +// Does NOT work if the previous hop is a flyover hop +func (s *Raw) GetPreviousIngress() (uint16, error) { + idx := int(s.Base.PathMeta.CurrHF) - HopLines + if idx < 0 { + return 0, serrors.New("HopField index out of bounds", "min", 0, "actual", idx) + } + hopOffset := MetaLen + s.NumINF*path.InfoLen + idx*LineLen + if s.isConsdir(uint8(idx)) { + return binary.BigEndian.Uint16(s.Raw[hopOffset+2 : hopOffset+4]), nil + } + return binary.BigEndian.Uint16(s.Raw[hopOffset+4 : hopOffset+6]), nil +} + +// Attaches previous flyoverfield to current hopfield. +// DOES NOT adapt MACS. +// Assumes previous hopfield has a flyover +// Assumes to be the first hop of the second or third segment +func (s *Raw) DoFlyoverXover() error { + idx := int(s.Base.PathMeta.CurrHF) + if idx >= s.NumLines-2 { + return serrors.New("CurrHF out of bounds", + "max", s.NumLines-2, "actual", idx) + } + hopOffset := MetaLen + s.NumINF*path.InfoLen + idx*LineLen - FlyoverLen + if s.Raw[hopOffset+FlyoverLen]&0x80 == 0x80 { + return serrors.New("Current hop does already have a Flyover") + } + // buffer flyover and copy data + var t [2 * LineLen]byte + copy(t[:], s.Raw[hopOffset+HopLen:hopOffset+FlyoverLen]) + copy(s.Raw[hopOffset+HopLen:hopOffset+2*HopLen], + s.Raw[hopOffset+FlyoverLen:hopOffset+FlyoverLen+HopLen]) + copy(s.Raw[hopOffset+2*HopLen:hopOffset+HopLen+FlyoverLen], t[:]) + + // Unset and Set Flyoverbits + s.Raw[hopOffset] &= 0x7f + s.Raw[hopOffset+HopLen] |= 0x80 + // Adatp seglens + s.Base.PathMeta.CurrHF -= 2 + s.Base.PathMeta.SegLen[s.PathMeta.CurrINF-1] -= 2 + s.Base.PathMeta.SegLen[s.PathMeta.CurrINF] += 2 + return s.Base.PathMeta.SerializeTo(s.Raw[:]) +} + +// Attaches current flyoverfield to previous hopfield +// DOES NOT adapt MACs +// It is assumed that the previous hopfield does NOT already have a flyover +func (s *Raw) ReverseFlyoverXover() error { + idx := int(s.Base.PathMeta.CurrHF) + if idx < 6 { + return serrors.New("CurrHF too small for reversing flyover crossover", + "min", 6, "actual", idx) + } + if s.PathMeta.CurrINF == 0 { + return serrors.New("Cannot reverse Flyover Xover when CurrINF = 0") + } + hopOffset := MetaLen + s.NumINF*path.InfoLen + idx*LineLen + if s.Raw[hopOffset]&0x80 == 0x00 { + return serrors.New("Current hop does not have a Flyover") + } + if s.Raw[hopOffset-HopLen]&0x80 != 0x00 { + return serrors.New( + "Cannot Reverse Flyover Crossover, flyover bit set where previous hop should be") + } + var t [FlyoverLen - HopLen]byte + copy(t[:], s.Raw[hopOffset+HopLen:hopOffset+FlyoverLen]) + copy(s.Raw[hopOffset+FlyoverLen-HopLen:hopOffset+FlyoverLen], + s.Raw[hopOffset:hopOffset+HopLen]) + copy(s.Raw[hopOffset:hopOffset+FlyoverLen-HopLen], t[:]) + // Set and Unset Flyoverbits + s.Raw[hopOffset-HopLen] |= 0x80 + s.Raw[hopOffset+FlyoverLen-HopLen] &= 0x7f + // Adapt Seglens and CurrHF + s.Base.PathMeta.SegLen[s.PathMeta.CurrINF] -= 2 + s.Base.PathMeta.SegLen[s.PathMeta.CurrINF-1] += 2 + s.Base.PathMeta.CurrHF += 2 + return s.Base.PathMeta.SerializeTo(s.Raw[:]) +} diff --git a/pkg/slayers/path/hummingbird/raw_test.go b/pkg/slayers/path/hummingbird/raw_test.go new file mode 100644 index 0000000000..f66a9e8392 --- /dev/null +++ b/pkg/slayers/path/hummingbird/raw_test.go @@ -0,0 +1,247 @@ +package hummingbird_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/scionproto/scion/pkg/slayers/path" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" +) + +var emptyRawTestPath = &hummingbird.Raw{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{0, 0, 0}, + }, + NumINF: 0, + NumLines: 0, + }, + Raw: make([]byte, hummingbird.MetaLen), +} + +var rawHbirdTestPath = &hummingbird.Raw{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{8, 8, 0}, + BaseTS: 808, + HighResTS: 1234, + }, + NumINF: 2, + NumLines: 16, + }, + Raw: rawHbirdPath, +} + +func TestRawSerializeHbird(t *testing.T) { + b := make([]byte, rawHbirdTestPath.Len()) + assert.NoError(t, rawHbirdTestPath.SerializeTo(b)) + assert.Equal(t, rawHbirdPath, b) +} + +func TestRawDecodeFromBytesHbird(t *testing.T) { + s := &hummingbird.Raw{} + assert.NoError(t, s.DecodeFromBytes(rawHbirdPath)) + assert.Equal(t, rawHbirdTestPath, s) +} + +func TestRawSerializeDecodeHbird(t *testing.T) { + b := make([]byte, rawHbirdTestPath.Len()) + assert.NoError(t, rawHbirdTestPath.SerializeTo(b)) + s := &hummingbird.Raw{} + assert.NoError(t, s.DecodeFromBytes(b)) + assert.Equal(t, rawHbirdTestPath, s) +} + +func TestRawReverseHbird(t *testing.T) { + for name, tc := range pathReverseCasesHbird { + name, tc := name, tc + for i := range tc.inIdxs { + i := i + t.Run(fmt.Sprintf("%s case %d", name, i+1), func(t *testing.T) { + t.Parallel() + input := mkRawHbirdPath(t, tc.input, uint8(tc.inIdxs[i][0]), + uint8(tc.inIdxs[i][1])) + want := mkRawHbirdPath(t, tc.want, uint8(tc.wantIdxs[i][0]), + uint8(tc.wantIdxs[i][1])) + revPath, err := input.Reverse() + assert.NoError(t, err) + assert.Equal(t, want, revPath) + }) + } + } +} + +func TestEmptyRawReverse(t *testing.T) { + _, err := emptyRawTestPath.Reverse() + assert.Error(t, err) +} + +func TestRawToDecodedHbird(t *testing.T) { + decoded, err := rawHbirdTestPath.ToDecoded() + assert.NoError(t, err) + assert.Equal(t, decodedHbirdTestPath, decoded) +} + +func TestGetInfoField(t *testing.T) { + testCases := map[string]struct { + idx int + want path.InfoField + errorFunc assert.ErrorAssertionFunc + }{ + "first info": { + idx: 0, + want: testInfoFields[0], + errorFunc: assert.NoError, + }, + "second info": { + idx: 1, + want: testInfoFields[1], + errorFunc: assert.NoError, + }, + "out of bounds": { + idx: 2, + want: path.InfoField{}, + errorFunc: assert.Error, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + + t.Run(name+" hummingbird", func(t *testing.T) { + t.Parallel() + got, err := rawHbirdTestPath.GetInfoField(tc.idx) + tc.errorFunc(t, err) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestGetHbirdHopField(t *testing.T) { + testCases := map[string]struct { + idx int + want hummingbird.FlyoverHopField + errorFunc assert.ErrorAssertionFunc + }{ + "first hop": { + idx: 0, + want: testFlyoverFields[0], + errorFunc: assert.NoError, + }, + "fourth hop": { + idx: 11, + want: testFlyoverFields[3], + errorFunc: assert.NoError, + }, + "invalid index": { + idx: 12, + errorFunc: assert.Error, + }, + "out of bounds": { + idx: 14, + errorFunc: assert.Error, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + got, err := rawHbirdTestPath.GetHopField(tc.idx) + tc.errorFunc(t, err) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestLastHop(t *testing.T) { + testCases := map[*hummingbird.Raw]bool{ + createHbirdPath(3, 9): false, + createHbirdPath(3, 11): false, + createHbirdPath(3, 12): false, + createHbirdPath(6, 9): true, + createHbirdPath(6, 11): true, + } + for scionRaw, want := range testCases { + got := scionRaw.IsLastHop() + assert.Equal(t, want, got) + } +} + +func TestSetHopfield(t *testing.T) { + hop1 := hummingbird.FlyoverHopField{ + HopField: path.HopField{ + ConsIngress: 0, + ConsEgress: 1, + }, + } + hop2 := hummingbird.FlyoverHopField{ + HopField: path.HopField{ + ConsIngress: 2, + ConsEgress: 3, + }, + } + hop3 := hummingbird.FlyoverHopField{ + Flyover: true, + HopField: path.HopField{ + ConsIngress: 1, + ConsEgress: 0, + }, + ResID: 13, + Bw: 6, + ResStartTime: 0, + Duration: 45, + } + expected := decodedHbirdTestPath + expected.HopFields[0] = hop1 + expected.HopFields[0].Flyover = true + expected.HopFields[1] = hop2 + expected.HopFields[3] = hop3 + + buffer := make([]byte, expected.Len()) + err := expected.SerializeTo(buffer) + assert.NoError(t, err) + + testPath := rawHbirdTestPath + + err = testPath.SetHopField(hop1, 0) + assert.NoError(t, err) + + err = testPath.SetHopField(hop2, 5) + assert.NoError(t, err) + + err = testPath.SetHopField(hop3, 11) + assert.NoError(t, err) + + result, err := testPath.ToDecoded() + + assert.NoError(t, err) + require.Equal(t, expected, result) +} + +func mkRawHbirdPath(t *testing.T, pcase hbirdPathCase, infIdx, hopIdx uint8) *hummingbird.Raw { + t.Helper() + decoded := mkDecodedHbirdPath(t, pcase, infIdx, hopIdx) + raw, err := decoded.ToRaw() + require.NoError(t, err) + return raw +} + +func createHbirdPath(currHF uint8, numHops int) *hummingbird.Raw { + hbirdRaw := &hummingbird.Raw{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: currHF, + }, + NumLines: numHops, + }, + } + return hbirdRaw +} diff --git a/pkg/slayers/scion.go b/pkg/slayers/scion.go index 66ff20c755..ba8ca2843d 100644 --- a/pkg/slayers/scion.go +++ b/pkg/slayers/scion.go @@ -25,6 +25,7 @@ import ( "github.com/scionproto/scion/pkg/slayers/path" "github.com/scionproto/scion/pkg/slayers/path/empty" "github.com/scionproto/scion/pkg/slayers/path/epic" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" "github.com/scionproto/scion/pkg/slayers/path/onehop" "github.com/scionproto/scion/pkg/slayers/path/scion" ) @@ -46,6 +47,7 @@ func init() { scion.RegisterPath() onehop.RegisterPath() epic.RegisterPath() + hummingbird.RegisterPath() } // AddrType indicates the type of a host address in the SCION header. @@ -262,10 +264,11 @@ func (s *SCION) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { func (s *SCION) RecyclePaths() { if s.pathPool == nil { s.pathPool = []path.Path{ - empty.PathType: empty.Path{}, - onehop.PathType: &onehop.Path{}, - scion.PathType: &scion.Raw{}, - epic.PathType: &epic.Path{}, + empty.PathType: empty.Path{}, + onehop.PathType: &onehop.Path{}, + scion.PathType: &scion.Raw{}, + epic.PathType: &epic.Path{}, + hummingbird.PathType: &hummingbird.Raw{}, } s.pathPoolRaw = path.NewRawPath() } diff --git a/pkg/slayers/scmp_typecode.go b/pkg/slayers/scmp_typecode.go index eb61d1dcfc..564b7b0fc0 100644 --- a/pkg/slayers/scmp_typecode.go +++ b/pkg/slayers/scmp_typecode.go @@ -72,6 +72,8 @@ const ( SCMPCodeInvalidExtensionHeader SCMPCode = 64 SCMPCodeUnknownHopByHopOption SCMPCode = 65 SCMPCodeUnknownEndToEndOption SCMPCode = 66 + + SCMPCodeReservationExpired SCMPCode = 71 ) // SCMP informational messages. diff --git a/pkg/snet/path/BUILD.bazel b/pkg/snet/path/BUILD.bazel index 351e08c2e3..8d07e84076 100644 --- a/pkg/snet/path/BUILD.bazel +++ b/pkg/snet/path/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "empty.go", "epic.go", + "hummingbird.go", "onehop.go", "path.go", "scion.go", @@ -20,6 +21,7 @@ go_library( "//pkg/slayers/path:go_default_library", "//pkg/slayers/path/empty:go_default_library", "//pkg/slayers/path/epic:go_default_library", + "//pkg/slayers/path/hummingbird:go_default_library", "//pkg/slayers/path/onehop:go_default_library", "//pkg/slayers/path/scion:go_default_library", "//pkg/snet:go_default_library", diff --git a/pkg/snet/path/hummingbird.go b/pkg/snet/path/hummingbird.go new file mode 100644 index 0000000000..00d3b3b9f7 --- /dev/null +++ b/pkg/snet/path/hummingbird.go @@ -0,0 +1,30 @@ +package path + +import ( + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" +) + +type Hummingbird struct { + // Raw is the raw representation of this path. This data should not be + // modified because it is potentially shared. + Raw []byte +} + +func NewHbirdFromDecoded(d *hummingbird.Decoded) (Hummingbird, error) { + buf := make([]byte, d.Len()) + if err := d.SerializeTo(buf); err != nil { + return Hummingbird{}, serrors.WrapStr("serializing decoded Hummingbird path", err) + } + return Hummingbird{Raw: buf}, nil +} + +func (p Hummingbird) SetPath(s *slayers.SCION) error { + var sp hummingbird.Raw + if err := sp.DecodeFromBytes(p.Raw); err != nil { + return err + } + s.Path, s.PathType = &sp, sp.Type() + return nil +} diff --git a/private/hummingbirddb/BUILD.bazel b/private/hummingbirddb/BUILD.bazel new file mode 100644 index 0000000000..7edb99b21b --- /dev/null +++ b/private/hummingbirddb/BUILD.bazel @@ -0,0 +1,12 @@ +load("//tools/lint:go.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["db.go"], + importpath = "github.com/scionproto/scion/private/hummingbirddb", + visibility = ["//visibility:public"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/hummingbird:go_default_library", + ], +) diff --git a/private/hummingbirddb/db.go b/private/hummingbirddb/db.go new file mode 100644 index 0000000000..4193aa6b81 --- /dev/null +++ b/private/hummingbirddb/db.go @@ -0,0 +1,47 @@ +// 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 hummingbirddb + +import ( + "context" + "database/sql" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/hummingbird" +) + +type DB interface { + ReadWrite + BeginTransaction(ctx context.Context, opts *sql.TxOptions) (Transaction, error) +} + +type Read interface { + GetFlyovers(ctx context.Context, IAs []addr.IA) ([]*hummingbird.Flyover, error) +} +type Write interface { + StoreFlyovers(ctx context.Context, flyovers []*hummingbird.Flyover) error + DeleteExpiredFlyovers(ctx context.Context) (int, error) +} + +type ReadWrite interface { + Read + Write +} + +type Transaction interface { + ReadWrite + Commit() error + Rollback() error +} diff --git a/private/hummingbirddb/mock_hummingbirddb/BUILD.bazel b/private/hummingbirddb/mock_hummingbirddb/BUILD.bazel new file mode 100644 index 0000000000..882ddc5478 --- /dev/null +++ b/private/hummingbirddb/mock_hummingbirddb/BUILD.bazel @@ -0,0 +1,23 @@ +load("//tools/lint:go.bzl", "go_library") +load("@io_bazel_rules_go//go:def.bzl", "gomock") + +gomock( + name = "go_default_mock", + out = "mock.go", + interfaces = ["DB"], + library = "//private/hummingbirddb:go_default_library", + package = "mock_hummingbirddb", +) + +go_library( + name = "go_default_library", + srcs = ["mock.go"], + importpath = "github.com/scionproto/scion/private/hummingbirddb/mock_hummingbirddb", + visibility = ["//visibility:public"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/hummingbird:go_default_library", + "//private/hummingbirddb:go_default_library", + "@com_github_golang_mock//gomock:go_default_library", + ], +) diff --git a/private/hummingbirddb/mock_hummingbirddb/mock.go b/private/hummingbirddb/mock_hummingbirddb/mock.go new file mode 100644 index 0000000000..ec6dab2ab6 --- /dev/null +++ b/private/hummingbirddb/mock_hummingbirddb/mock.go @@ -0,0 +1,69 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/scionproto/scion/private/hummingbirddb (interfaces: DB) + +// Package mock_hummingbirddb is a generated GoMock package. +package mock_hummingbirddb + +import ( + context "context" + sql "database/sql" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + addr "github.com/scionproto/scion/pkg/addr" + hummingbird "github.com/scionproto/scion/pkg/hummingbird" + hummingbirddb "github.com/scionproto/scion/private/hummingbirddb" +) + +// MockDB is a mock of DB interface. +type MockDB struct { + ctrl *gomock.Controller + recorder *MockDBMockRecorder +} + +// MockDBMockRecorder is the mock recorder for MockDB. +type MockDBMockRecorder struct { + mock *MockDB +} + +// NewMockDB creates a new mock instance. +func NewMockDB(ctrl *gomock.Controller) *MockDB { + mock := &MockDB{ctrl: ctrl} + mock.recorder = &MockDBMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDB) EXPECT() *MockDBMockRecorder { + return m.recorder +} + +// BeginTransaction mocks base method. +func (m *MockDB) BeginTransaction(arg0 context.Context, arg1 *sql.TxOptions) (hummingbirddb.Transaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BeginTransaction", arg0, arg1) + ret0, _ := ret[0].(hummingbirddb.Transaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BeginTransaction indicates an expected call of BeginTransaction. +func (mr *MockDBMockRecorder) BeginTransaction(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginTransaction", reflect.TypeOf((*MockDB)(nil).BeginTransaction), arg0, arg1) +} + +// ListFlyovers mocks base method. +func (m *MockDB) ListFlyovers(arg0 context.Context, arg1 []addr.IA) ([]*hummingbird.Flyover, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListFlyovers", arg0, arg1) + ret0, _ := ret[0].([]*hummingbird.Flyover) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListFlyovers indicates an expected call of ListFlyovers. +func (mr *MockDBMockRecorder) ListFlyovers(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListFlyovers", reflect.TypeOf((*MockDB)(nil).ListFlyovers), arg0, arg1) +} diff --git a/private/storage/BUILD.bazel b/private/storage/BUILD.bazel index b2a9d0176d..e8caf464d0 100644 --- a/private/storage/BUILD.bazel +++ b/private/storage/BUILD.bazel @@ -14,6 +14,7 @@ go_library( "//pkg/drkey:go_default_library", "//pkg/log:go_default_library", "//private/config:go_default_library", + "//private/hummingbirddb:go_default_library", "//private/pathdb:go_default_library", "//private/periodic:go_default_library", "//private/revcache:go_default_library", @@ -25,6 +26,7 @@ go_library( "//private/storage/drkey/level1/sqlite:go_default_library", "//private/storage/drkey/level2/sqlite:go_default_library", "//private/storage/drkey/secret/sqlite:go_default_library", + "//private/storage/hummingbird/sqlite:go_default_library", "//private/storage/path/sqlite:go_default_library", "//private/storage/trust:go_default_library", "//private/storage/trust/sqlite:go_default_library", diff --git a/private/storage/hummingbird/sqlite/BUILD.bazel b/private/storage/hummingbird/sqlite/BUILD.bazel new file mode 100644 index 0000000000..e93ee151ba --- /dev/null +++ b/private/storage/hummingbird/sqlite/BUILD.bazel @@ -0,0 +1,32 @@ +load("//tools/lint:go.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "schema.go", + "sqlite.go", + ], + importpath = "github.com/scionproto/scion/private/storage/hummingbird/sqlite", + visibility = ["//visibility:public"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/hummingbird:go_default_library", + "//pkg/private/serrors:go_default_library", + "//private/hummingbirddb:go_default_library", + "//private/storage/db:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["sqlite_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/hummingbird:go_default_library", + "//pkg/private/util:go_default_library", + "//pkg/private/xtest:go_default_library", + "@com_github_stretchr_testify//assert:go_default_library", + "@com_github_stretchr_testify//require:go_default_library", + ], +) diff --git a/private/storage/hummingbird/sqlite/schema.go b/private/storage/hummingbird/sqlite/schema.go new file mode 100644 index 0000000000..5eeedaf5f3 --- /dev/null +++ b/private/storage/hummingbird/sqlite/schema.go @@ -0,0 +1,38 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains an SQLite backend for the HummingbirdDB. + +package sqlite + +const ( + // SchemaVersion is the version of the SQLite schema understood by this backend. + // Whenever changes to the schema are made, this version number should be increased + // to prevent data corruption between incompatible database schemas. + SchemaVersion = 1 + // Schema is the SQLite database layout. + Schema = `CREATE TABLE Flyovers( + RowID INTEGER PRIMARY KEY, + ia INTEGER NOT NULL, + ingress INTEGER NOT NULL, + egress INTEGER NOT NULL, + resID INTEGER NOT NULL, + bw INTEGER NOT NULL, + notBefore INTEGER NOT NULL, + notAfter INTEGER NOT NULL, + ak BLOB NOT NULL, + UNIQUE(ia,resID) ON CONFLICT REPLACE + ); + ` +) diff --git a/private/storage/hummingbird/sqlite/sqlite.go b/private/storage/hummingbird/sqlite/sqlite.go new file mode 100644 index 0000000000..4011956cec --- /dev/null +++ b/private/storage/hummingbird/sqlite/sqlite.go @@ -0,0 +1,257 @@ +// Copyright 2017 ETH Zurich +// Copyright 2018 ETH Zurich, Anapaya Systems +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains an SQLite backend for the PathDB. + +package sqlite + +import ( + "context" + "database/sql" + "strings" + "sync" + "time" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/hummingbird" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/private/hummingbirddb" + "github.com/scionproto/scion/private/storage/db" +) + +var _ hummingbirddb.DB = (*Backend)(nil) + +type Backend struct { + db *sql.DB + *executor +} + +// New returns a new SQLite backend opening a database at the given path. If +// no database exists a new database is be created. If the schema version of the +// stored database is different from the one in schema.go, an error is returned. +func New(path string) (*Backend, error) { + db, err := db.NewSqlite(path, Schema, SchemaVersion) + if err != nil { + return nil, serrors.WrapStr("opening db", err, "schema", Schema) + } + return &Backend{ + executor: &executor{ + db: db, + }, + db: db, + }, nil +} + +func (b *Backend) Close() error { + return b.db.Close() +} + +func (b *Backend) SetMaxOpenConns(maxOpenConns int) { + b.db.SetMaxOpenConns(maxOpenConns) +} +func (b *Backend) SetMaxIdleConns(maxIdleConns int) { + b.db.SetMaxIdleConns(maxIdleConns) +} + +func (b *Backend) BeginTransaction(ctx context.Context, + opts *sql.TxOptions) (hummingbirddb.Transaction, error) { + + b.Lock() + defer b.Unlock() + tx, err := b.db.BeginTx(ctx, opts) + if err != nil { + return nil, serrors.WrapStr("Failed to create transaction", err) + } + return &transaction{ + executor: &executor{ + db: tx, + }, + tx: tx, + }, nil +} + +var _ (hummingbirddb.Transaction) = (*transaction)(nil) + +type transaction struct { + *executor + tx *sql.Tx +} + +func (tx *transaction) Commit() error { + tx.Lock() + defer tx.Unlock() + return tx.tx.Commit() +} + +func (tx *transaction) Rollback() error { + tx.Lock() + defer tx.Unlock() + return tx.tx.Rollback() +} + +var _ (hummingbirddb.ReadWrite) = (*executor)(nil) + +type executor struct { + sync.RWMutex + db db.Sqler +} + +type IASet map[addr.IA]struct{} + +func (e *executor) GetFlyovers(ctx context.Context, IAs []addr.IA) ([]*hummingbird.Flyover, error) { + var flyovers []*hummingbird.Flyover + err := db.DoInTx(ctx, e.db, func(ctx context.Context, tx *sql.Tx) error { + var err error + flyovers, err = getFlyovers(ctx, tx, IAs) + return err + }) + return flyovers, err +} + +func (e *executor) StoreFlyovers(ctx context.Context, flyovers []*hummingbird.Flyover, +) error { + + err := db.DoInTx(ctx, e.db, func(ctx context.Context, tx *sql.Tx) error { + return storeFlyovers(ctx, tx, flyovers) + }) + return err +} + +func (e *executor) DeleteExpiredFlyovers(ctx context.Context) (n int, err error) { + return e.deleteExpiredFlyovers(ctx, time.Now()) +} + +func (e *executor) deleteExpiredFlyovers( + ctx context.Context, + notAfter time.Time, +) (n int, err error) { + + db.DoInTx(ctx, e.db, func(ctx context.Context, tx *sql.Tx) error { + n, err = deleteExpired(ctx, tx, notAfter) + return err + }) + // n and err have been already set in the closure, just return. + return +} + +func getFlyovers(ctx context.Context, tx *sql.Tx, IAs []addr.IA) ([]*hummingbird.Flyover, error) { + query := "SELECT ia,ingress,egress,resID,bw,notBefore,notAfter,ak FROM flyovers" + queryParams := []any{} + // If there are any parameters, format them for SQL. + if len(IAs) > 0 { + query += " WHERE IA in (" + + strings.Repeat("?,", len(IAs)-1) + query += "?)" + for _, ia := range IAs { + queryParams = append(queryParams, uint64(ia)) + } + } + // Query with or without query parameters. + rows, err := tx.QueryContext(ctx, query, queryParams...) + if err != nil { + return nil, serrors.WrapStr("looking for flyovers in DB", err, "q", query) + } + flyovers := make([]*hummingbird.Flyover, 0) + for rows.Next() { + var ia uint64 + var ingress uint16 + var egress uint16 + var resID uint32 + var bw uint16 + var notBefore uint32 + var notAfter uint32 + var ak []byte + if err := rows.Scan(&ia, &ingress, &egress, &resID, &bw, ¬Before, ¬After, + &ak); err != nil { + + return nil, serrors.WrapStr("error reading flyover from DB", err) + } + // Convert ak from slice to array. + var akArray [16]byte + copy(akArray[:], ak) + // Add the flyover. + flyovers = append(flyovers, &hummingbird.Flyover{ + BaseHop: hummingbird.BaseHop{ + IA: addr.IA(ia), + Ingress: ingress, + Egress: egress, + }, + ResID: resID, + Bw: bw, + StartTime: notBefore, + Duration: uint16(notAfter - notBefore), + Ak: akArray, + }) + } + return flyovers, nil +} + +func storeFlyovers( + ctx context.Context, + tx *sql.Tx, + flyovers []*hummingbird.Flyover, +) error { + + const numCols = 8 // 8 columns + rowParams := "(" + strings.Repeat("?,", numCols-1) + "?)" // => (?,?,?,?,?,?,?,?) + // query has the form: + // INSERT INTO flyovers (ia,ingress,egress,resID,bw,notBefore,notAfter,ak) VALUES + // (?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?) + // for a total of number of flyovers. + query := "INSERT INTO flyovers (ia,ingress,egress,resID,bw,notBefore,notAfter,ak) VALUES " + + strings.Repeat(rowParams+",", len(flyovers)-1) + rowParams + + params := make([]any, 0, len(flyovers)*numCols) + for _, f := range flyovers { + params = append(params, + uint64(f.IA), + uint16(f.Ingress), + uint16(f.Egress), + uint32(f.ResID), + uint16(f.Bw), + f.StartTime, + f.StartTime+uint32(f.Duration), + f.Ak[:], + ) + } + + res, err := tx.ExecContext(ctx, query, params...) + if err != nil { + return serrors.WrapStr("storing flyovers", err, "query", query) + } + n, err := res.RowsAffected() + if err != nil { + return serrors.WrapStr("checking inserted flyovers, cannot retrieve num. affected rows", + err) + } + if int(n) != len(flyovers) { + return serrors.New("storing flyovers: affected rows differs from request", + "affected", n, "requested", len(flyovers)) + } + return nil +} + +func deleteExpired(ctx context.Context, tx *sql.Tx, now time.Time) (int, error) { + query := "DELETE FROM flyovers WHERE notAfter < ?" + res, err := tx.ExecContext(ctx, query, now.Unix()) + if err != nil { + return 0, serrors.WrapStr("deleting expired flyovers", err) + } + n, err := res.RowsAffected() + if err != nil { + return 0, serrors.WrapStr("computing affected expired flyovers", err) + } + return int(n), nil +} diff --git a/private/storage/hummingbird/sqlite/sqlite_test.go b/private/storage/hummingbird/sqlite/sqlite_test.go new file mode 100644 index 0000000000..d7e70631cf --- /dev/null +++ b/private/storage/hummingbird/sqlite/sqlite_test.go @@ -0,0 +1,282 @@ +// Copyright 2017 ETH Zurich +// Copyright 2018 ETH Zurich, Anapaya Systems +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqlite + +import ( + "context" + "fmt" + "os" + "path" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/hummingbird" + "github.com/scionproto/scion/pkg/private/util" + "github.com/scionproto/scion/pkg/private/xtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + timeout = time.Second + flyover1 = &hummingbird.Flyover{ + BaseHop: hummingbird.BaseHop{ + IA: xtest.MustParseIA("1-ff00:0:111"), + Ingress: 42, + Egress: 43, + }, + ResID: 12345, + Bw: 666, + Ak: [16]byte{1, 2, 3, 3, 3, 3}, + StartTime: 100, + Duration: 10, + } + flyover2 = &hummingbird.Flyover{ + BaseHop: hummingbird.BaseHop{ + IA: xtest.MustParseIA("1-ff00:0:112"), + Ingress: 42, + Egress: 43, + }, + ResID: 12346, + Bw: 666, + Ak: [16]byte{1, 2, 3, 3, 3, 3}, + StartTime: 333, + Duration: 10, + } +) + +// TestOpenExisting tests that New does not overwrite an existing database if +// versions match. +func TestOpenExisting(t *testing.T) { + ctx, cancelF := context.WithTimeout(context.Background(), timeout) + defer cancelF() + + b, tmpF := setupDB(t) + defer cleanup(tmpF) + + // Insert a new flyover. + f := &hummingbird.Flyover{ + BaseHop: hummingbird.BaseHop{ + IA: xtest.MustParseIA("1-ff00:0:111"), + Ingress: 42, + Egress: 43, + }, + ResID: 12345, + Bw: 666, + Ak: [16]byte{1, 2, 3, 3, 3, 3}, + StartTime: 333, + Duration: 10, + } + err := b.StoreFlyovers(ctx, []*hummingbird.Flyover{f}) + require.NoError(t, err) + b.db.Close() + + // Open again. + b, err = New(tmpF) + require.NoError(t, err) + // Check the flyover is present + flyovers, err := b.GetFlyovers(ctx, nil) + require.NoError(t, err) + require.Equal(t, 1, len(flyovers)) + require.Equal(t, f, flyovers[0]) +} + +// TestOpenNewer tests that New does not overwrite an existing database if it's +// of a newer version. +func TestOpenNewer(t *testing.T) { + b, tmpF := setupDB(t) + defer cleanup(tmpF) + // Write a newer version + _, err := b.db.Exec(fmt.Sprintf("PRAGMA user_version = %d", SchemaVersion+1)) + require.NoError(t, err) + b.db.Close() + // Call + b, err = New(tmpF) + // Test + assert.Error(t, err) + assert.Nil(t, b) +} + +func TestGetFlyovers(t *testing.T) { + allFlyovers := []*hummingbird.Flyover{ + flyover1, + flyover2, + } + testCases := map[string]struct { + IAs []string + expected []*hummingbird.Flyover + }{ + "nil-filter": { + IAs: nil, + expected: []*hummingbird.Flyover{flyover1, flyover2}, + }, + "all": { + IAs: []string{}, + expected: []*hummingbird.Flyover{flyover1, flyover2}, + }, + "one": { + IAs: []string{"1-ff00:0:112"}, + expected: []*hummingbird.Flyover{flyover2}, + }, + "two": { + IAs: []string{"1-ff00:0:111", "1-ff00:0:112"}, + expected: []*hummingbird.Flyover{flyover1, flyover2}, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + ctx, cancelF := context.WithTimeout(context.Background(), timeout) + defer cancelF() + // Create new DB + b, tmpF := setupDB(t) + defer b.Close() + defer cleanup(tmpF) + + // Insert all flyovers. + err := b.StoreFlyovers(ctx, allFlyovers) + require.NoError(t, err) + + // Retrieve. + IAs := make([]addr.IA, len(tc.IAs)) + for i, ia := range tc.IAs { + IAs[i] = xtest.MustParseIA(ia) + } + flyovers, err := b.GetFlyovers(ctx, IAs) + require.NoError(t, err) + // Check. + require.EqualValues(t, tc.expected, flyovers) + }) + } +} + +func TestInsertUniqueFlyovers(t *testing.T) { + // All flyovers have a unique (IA,resID) + flyovers := []*hummingbird.Flyover{ + flyover1, + flyover2, + } + + ctx, cancelF := context.WithTimeout(context.Background(), timeout) + defer cancelF() + + b, tmpF := setupDB(t) + defer b.Close() + defer cleanup(tmpF) + + // Store a sequence of flyovers. + err := b.StoreFlyovers(ctx, flyovers) + require.NoError(t, err) + + // Check stored flyovers. + expected := flyovers + flyovers, err = b.GetFlyovers(ctx, nil) + require.NoError(t, err) + require.Equal(t, expected, flyovers) +} + +func TestInsertNonUniqueFlyovers(t *testing.T) { + // All flyovers have a unique (IA,resID) + flyovers := []*hummingbird.Flyover{ + flyover1, + flyover1, + } + + ctx, cancelF := context.WithTimeout(context.Background(), timeout) + defer cancelF() + + b, tmpF := setupDB(t) + defer b.Close() + defer cleanup(tmpF) + + // Store a sequence of flyovers. + err := b.StoreFlyovers(ctx, flyovers) + require.NoError(t, err) + + // Check stored flyovers. + expected := []*hummingbird.Flyover{flyover1} + flyovers, err = b.GetFlyovers(ctx, nil) + require.NoError(t, err) + require.Equal(t, expected, flyovers) +} + +func TestDeleteExpired(t *testing.T) { + ctx, cancelF := context.WithTimeout(context.Background(), timeout) + defer cancelF() + + b, tmpF := setupDB(t) + defer cleanup(tmpF) + + // Insert two flyovers. They (start,end) at (100,110)(333,343) respectively. + allFlyovers := []*hummingbird.Flyover{flyover1, flyover2} + err := b.StoreFlyovers(ctx, allFlyovers) + require.NoError(t, err) + + // Expire as if time was now 10. + n, err := b.deleteExpiredFlyovers(ctx, util.SecsToTime(0)) + require.NoError(t, err) + require.Equal(t, 0, n) + + // Expire as if time was now 101. + n, err = b.deleteExpiredFlyovers(ctx, util.SecsToTime(101)) + require.NoError(t, err) + require.Equal(t, 0, n) + + // Expire as if time was now 111. + n, err = b.deleteExpiredFlyovers(ctx, util.SecsToTime(111)) + require.NoError(t, err) + require.Equal(t, 1, n) // one has been deleted + // Check we still have flyover2. + flyovers, err := b.GetFlyovers(ctx, nil) + require.NoError(t, err) + require.Len(t, flyovers, 1) + require.Equal(t, flyover2, flyovers[0]) + + // Expire as if time was now 500. + n, err = b.deleteExpiredFlyovers(ctx, util.SecsToTime(500)) + require.NoError(t, err) + require.Equal(t, 1, n) // one has been deleted + // Check we don't have flyovers. + flyovers, err = b.GetFlyovers(ctx, nil) + require.NoError(t, err) + require.Len(t, flyovers, 0) +} + +func setupDB(t *testing.T) (*Backend, string) { + tmpFile := tempFilename(t) + b, err := New(tmpFile) + // b, err := New("file::memory:") + require.NoError(t, err, "Failed to open DB") + return b, tmpFile +} + +func tempFilename(t *testing.T) string { + dir, err := os.MkdirTemp("", "hummingbirddb-sqlite") + require.NoError(t, err) + n := t.Name() + n = strings.ReplaceAll(n, "/", "-") + return path.Join(dir, n) +} + +func cleanup(tmpFile string) { + os.RemoveAll(filepath.Dir(tmpFile)) +} diff --git a/private/storage/storage.go b/private/storage/storage.go index bc400dd462..093d70cb41 100644 --- a/private/storage/storage.go +++ b/private/storage/storage.go @@ -26,6 +26,7 @@ import ( "github.com/scionproto/scion/pkg/drkey" "github.com/scionproto/scion/pkg/log" "github.com/scionproto/scion/private/config" + "github.com/scionproto/scion/private/hummingbirddb" "github.com/scionproto/scion/private/pathdb" "github.com/scionproto/scion/private/periodic" "github.com/scionproto/scion/private/revcache" @@ -37,9 +38,11 @@ import ( sqlitelevel1 "github.com/scionproto/scion/private/storage/drkey/level1/sqlite" sqlitelevel2 "github.com/scionproto/scion/private/storage/drkey/level2/sqlite" sqlitesecret "github.com/scionproto/scion/private/storage/drkey/secret/sqlite" + sqlitehbirddb "github.com/scionproto/scion/private/storage/hummingbird/sqlite" sqlitepathdb "github.com/scionproto/scion/private/storage/path/sqlite" truststorage "github.com/scionproto/scion/private/storage/trust" sqlitetrustdb "github.com/scionproto/scion/private/storage/trust/sqlite" + "github.com/scionproto/scion/private/trust" ) @@ -56,6 +59,7 @@ const ( DefaultDRKeyLevel1DBPath = "/share/cache/%s.drkey_level1.db" DefaultDRKeyLevel2DBPath = "/share/cache/%s.drkey_level2.db" DefaultDRKeySVDBPath = "/share/cache/%s.drkey_secret_value.db" + DefaultHbirdDBPath = "/share/cache/%s.hbird.db" ) // Default samples for various databases. @@ -78,6 +82,9 @@ var ( SampleDRKeySecretValueDB = DBConfig{ Connection: DefaultDRKeySVDBPath, } + SampleHbirdPathDB = DBConfig{ + Connection: DefaultHbirdDBPath, + } ) // SetID returns a clone of the configuration that has the ID set on the connection string. @@ -105,6 +112,12 @@ type PathDB interface { pathdb.DB } +type HbirdDB interface { + io.Closer + hummingbirddb.DB + GetCleaner() *periodic.Runner +} + var _ (config.Config) = (*DBConfig)(nil) // DBConfig is the configuration for the connection to a database. @@ -236,6 +249,49 @@ func (b pathDBWithCleaner) Close() error { return b.dbCloser.Close() } +func NewHummingbirdStorage(c DBConfig) (HbirdDB, error) { + log.Info("Connecting HummingbirdDB", "backend", BackendSqlite, "connection", c.Connection) + db, err := sqlitehbirddb.New(c.Connection) + if err != nil { + return nil, err + } + SetConnLimits(db, c) + + // Start a periodic task that cleans up the expired path segments. + cleaner := periodic.Start( + cleaner.New( + func(ctx context.Context) (int, error) { + return db.DeleteExpiredFlyovers(ctx) + }, + "hbirdstorage_cleaner", + ), + 30*time.Second, + 30*time.Second, + ) + return hbirdDBWithCleaner{ + DB: db, + cleaner: cleaner, + dbCloser: db, + }, nil +} + +// pathDBWithCleaner implements the path DB interface and stops both the +// database and the cleanup task on Close. +type hbirdDBWithCleaner struct { + hummingbirddb.DB + cleaner *periodic.Runner + dbCloser io.Closer +} + +func (b hbirdDBWithCleaner) Close() error { + b.cleaner.Kill() + return b.dbCloser.Close() +} + +func (b hbirdDBWithCleaner) GetCleaner() *periodic.Runner { + return b.cleaner +} + func NewRevocationStorage() revcache.RevCache { return memrevcache.New() } diff --git a/proto/daemon/v1/BUILD.bazel b/proto/daemon/v1/BUILD.bazel index cf258adada..4a2a8968ea 100644 --- a/proto/daemon/v1/BUILD.bazel +++ b/proto/daemon/v1/BUILD.bazel @@ -4,6 +4,7 @@ proto_library( name = "daemon", srcs = [ "daemon.proto", + "hummingbird.proto", ], visibility = ["//visibility:public"], deps = [ diff --git a/proto/daemon/v1/daemon.proto b/proto/daemon/v1/daemon.proto index e8b2758b2b..f97d655eb4 100644 --- a/proto/daemon/v1/daemon.proto +++ b/proto/daemon/v1/daemon.proto @@ -21,6 +21,7 @@ package proto.daemon.v1; import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; import "proto/drkey/v1/drkey.proto"; +import "proto/daemon/v1/hummingbird.proto"; service DaemonService { // Return a set of paths to the requested destination. @@ -41,6 +42,14 @@ service DaemonService { rpc DRKeyHostAS (DRKeyHostASRequest) returns (DRKeyHostASResponse) {} // DRKeyHostHost returns a key that matches the request. rpc DRKeyHostHost (DRKeyHostHostRequest) returns (DRKeyHostHostResponse) {} + + // StoreFlyovers stores the flyovers in the DB. + rpc StoreFlyovers(StoreFlyoversRequest) returns (StoreFlyoversResponse) {} + // ListFlyovers lists all stored flyovers in this DB. + rpc ListFlyovers(ListFlyoversRequest) returns (ListFlyoversResponse) {} + // GetReservations finds paths from source to destination, and then tries to assign flyovers to + // as many hops as possible. + rpc GetReservations(GetReservationsRequest) returns (GetReservationsResponse) {} } message PathsRequest { diff --git a/proto/daemon/v1/hummingbird.proto b/proto/daemon/v1/hummingbird.proto new file mode 100644 index 0000000000..561789477a --- /dev/null +++ b/proto/daemon/v1/hummingbird.proto @@ -0,0 +1,82 @@ +// 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. + +syntax = "proto3"; + +option go_package = "github.com/scionproto/scion/pkg/proto/daemon"; + +package proto.daemon.v1; + + +message StoreFlyoversRequest { + repeated Flyover flyovers = 1; +} + +message StoreFlyoversResponse {} // empty + +message ListFlyoversRequest {} // empty + +message ListFlyoversResponse { + repeated Flyover flyovers = 1; +} + + +message GetReservationsRequest { + // ISD-AS of the source of the path request. + uint64 source_isd_as = 1; + // ISD-AS of the destination of the path request. + uint64 destination_isd_as = 2; + // Choose to fetch fresh paths for this request instead + // of having the server reply from its cache. + bool refresh = 3; + // The minimum bandwidth for the obtained reservations, 16 bits. + uint32 min_bandwidth = 4; +} + +message GetReservationsResponse { + repeated Reservation reservations = 1; +} + +message Flyover { + // The AS ID in ISD-AS form (64 bits). + uint64 ia = 1; + // Ingress ID. 16 bits. + uint32 ingress = 2; + // Egress ID. 16 bits. + uint32 egress = 3; + + // Bandwidth. 16 bits. + uint32 bw = 4; + // The unix timestamp when the reservation starts. + uint32 start_time = 5; + // Duration in seconds. 16 bits. + uint32 duration = 6; + + // Reservation ID, unique per AS. + uint32 res_id = 7; + // This Ak is the derived key for this flyover, that is used to derive the per-host key. + // It is 16 bytes long. + bytes ak = 8; +} + +message Reservation { + // The raw Hummingbird path. + bytes raw = 1; + // The hop-sequence of flyovers. There are no crossovers in this sequence, + // i.e. for each xover in the hummingbird path, only one hop is present. + // Hops in the path without corresponding flyover have a nil entry. + repeated Flyover flyovers = 2; + // Ratio # flyovers / # hops. + double ratio = 3; +} diff --git a/router/BUILD.bazel b/router/BUILD.bazel index d7ebcbc60e..75b7a1f165 100644 --- a/router/BUILD.bazel +++ b/router/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "connector.go", "dataplane.go", + "dataplane_hbird.go", "metrics.go", "svc.go", ], @@ -23,6 +24,7 @@ go_library( "//pkg/slayers/path:go_default_library", "//pkg/slayers/path/empty:go_default_library", "//pkg/slayers/path/epic:go_default_library", + "//pkg/slayers/path/hummingbird:go_default_library", "//pkg/slayers/path/onehop:go_default_library", "//pkg/slayers/path/scion:go_default_library", "//pkg/spao:go_default_library", @@ -31,6 +33,7 @@ go_library( "//private/underlay/conn:go_default_library", "//router/bfd:go_default_library", "//router/control:go_default_library", + "//router/tokenbucket:go_default_library", "@com_github_google_gopacket//:go_default_library", "@com_github_google_gopacket//layers:go_default_library", "@com_github_prometheus_client_golang//prometheus:go_default_library", @@ -42,6 +45,8 @@ go_library( go_test( name = "go_default_test", srcs = [ + "dataplane_benchmark_test.go", + "dataplane_hbird_test.go", "dataplane_internal_test.go", "dataplane_test.go", "export_test.go", @@ -59,6 +64,7 @@ go_test( "//pkg/slayers/path:go_default_library", "//pkg/slayers/path/empty:go_default_library", "//pkg/slayers/path/epic:go_default_library", + "//pkg/slayers/path/hummingbird:go_default_library", "//pkg/slayers/path/onehop:go_default_library", "//pkg/slayers/path/scion:go_default_library", "//private/topology:go_default_library", diff --git a/router/connector.go b/router/connector.go index 7b744b2c41..ffac6e821c 100644 --- a/router/connector.go +++ b/router/connector.go @@ -180,6 +180,20 @@ func (c *Connector) SetKey(ia addr.IA, index int, key []byte) error { return c.DataPlane.SetKey(key) } +// SetHbirdKey sets the hummingbird key for the given ISD-AS at the given indey +func (c *Connector) SetHbirdKey(ia addr.IA, index int, sv []byte) error { + c.mtx.Lock() + defer c.mtx.Unlock() + log.Debug("Setting secret value", "isd_as", ia, "index", index) + if !c.ia.Equal(ia) { + return serrors.WithCtx(errMultiIA, "current", c.ia, "new", ia) + } + if index != 0 { + return serrors.New("currently only index 0 secret value is supported") + } + return c.DataPlane.SetHbirdKey(sv) +} + func (c *Connector) ListInternalInterfaces() ([]control.InternalInterface, error) { c.mtx.Lock() defer c.mtx.Unlock() diff --git a/router/control/conf.go b/router/control/conf.go index d5cf15a676..0498927181 100644 --- a/router/control/conf.go +++ b/router/control/conf.go @@ -37,6 +37,7 @@ type Dataplane interface { AddSvc(ia addr.IA, svc addr.SVC, ip net.IP) error DelSvc(ia addr.IA, svc addr.SVC, ip net.IP) error SetKey(ia addr.IA, index int, key []byte) error + SetHbirdKey(ia addr.IA, index int, key []byte) error } // LinkInfo contains the information about a link between an internal and @@ -124,6 +125,10 @@ func ConfigDataplane(dp Dataplane, cfg *Config) error { if err := dp.SetKey(cfg.IA, 0, key0); err != nil { return err } + keySv := DeriveHbirdSecretValue(cfg.MasterKeys.Key0) + if err := dp.SetHbirdKey(cfg.IA, 0, keySv); err != nil { + return err + } } // Add internal interfaces if cfg.BR != nil { @@ -156,6 +161,17 @@ func DeriveHFMacKey(k []byte) []byte { return pbkdf2.Key(k, hfMacSalt, 1000, 16, sha256.New) } +// DeriveHbirdSecretValue derives hummingbird AS secret value from the given key +func DeriveHbirdSecretValue(k []byte) []byte { + if len(k) == 0 { + panic("empty key") + } + hbirdSalt := []byte("Derive hbird sv") + // This uses 16B keys with 1000 hash iterations, which is the same as the + // defaults used by pycrypto. + return pbkdf2.Key(k, hbirdSalt, 1000, 16, sha256.New) +} + func confExternalInterfaces(dp Dataplane, cfg *Config) error { // Sort out keys/ifids to get deterministic order for unit testing infoMap := cfg.Topo.IFInfoMap() diff --git a/router/dataplane.go b/router/dataplane.go index 362c298c34..dfd7228052 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -18,6 +18,7 @@ package router import ( "bytes" "context" + "crypto/cipher" "crypto/rand" "crypto/subtle" "errors" @@ -47,6 +48,7 @@ import ( "github.com/scionproto/scion/pkg/slayers/path" "github.com/scionproto/scion/pkg/slayers/path/empty" "github.com/scionproto/scion/pkg/slayers/path/epic" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" "github.com/scionproto/scion/pkg/slayers/path/onehop" "github.com/scionproto/scion/pkg/slayers/path/scion" "github.com/scionproto/scion/pkg/spao" @@ -105,6 +107,7 @@ type DataPlane struct { internalNextHops map[uint16]*net.UDPAddr svc *services macFactory func() hash.Hash + prfFactory func() cipher.Block bfdSessions map[uint16]bfdSession localIA addr.IA mtx sync.Mutex @@ -117,6 +120,9 @@ type DataPlane struct { // The pool that stores all the packet buffers as described in the design document. See // https://github.com/scionproto/scion/blob/master/doc/dev/design/BorderRouter.rst packetPool chan []byte + + // Contains the token buckets for hummingbird bandwidth check + tokenBuckets sync.Map } var ( @@ -988,10 +994,14 @@ type processResult struct { func newPacketProcessor(d *DataPlane) *scionPacketProcessor { p := &scionPacketProcessor{ - d: d, - buffer: gopacket.NewSerializeBuffer(), - mac: d.macFactory(), - macInputBuffer: make([]byte, max(path.MACBufferSize, libepic.MACBufferSize)), + d: d, + buffer: gopacket.NewSerializeBuffer(), + prf: d.prfFactory(), + mac: d.macFactory(), + macInputBuffer: make([]byte, + max(path.MACBufferSize+hummingbird.FlyoverMacBufferSize+hummingbird.AkBufferSize, + libepic.MACBufferSize)), + hbirdXkbuffer: make([]uint32, hummingbird.XkBufferSize), } p.scionLayer.RecyclePaths() return p @@ -1007,6 +1017,11 @@ func (p *scionPacketProcessor) reset() error { p.infoField = path.InfoField{} p.effectiveXover = false p.peering = false + p.hbirdPath = nil + p.flyoverField = hummingbird.FlyoverHopField{} + p.hasPriority = false + p.isFlyoverXover = false + if err := p.buffer.Clear(); err != nil { return serrors.WrapStr("Failed to clear buffer", err) } @@ -1058,6 +1073,8 @@ func (p *scionPacketProcessor) processPkt(rawPkt []byte, return p.processSCION() case epic.PathType: return p.processEPIC() + case hummingbird.PathType: + return p.processHBIRD() default: return processResult{}, serrors.WithCtx(unsupportedPathType, "type", pathType) } @@ -1183,7 +1200,6 @@ type scionPacketProcessor struct { buffer gopacket.SerializeBuffer // mac is the hasher for the MAC computation. mac hash.Hash - // scionLayer is the SCION gopacket layer. scionLayer slayers.SCION hbhLayer slayers.HopByHopExtnSkipper @@ -1208,6 +1224,19 @@ type scionPacketProcessor struct { // macInputBuffer avoid allocating memory during processing. macInputBuffer []byte + // prf is the keyed PRF for the hummingbird auth key computation + prf cipher.Block + // hbirdPath is the raw Hummingbird path. Will be set during processing + hbirdPath *hummingbird.Raw + // flyoverField is the flyoverfield containing the current hopfield for hummingbird packets + flyoverField hummingbird.FlyoverHopField + // hasPriority indicates whether this packet has forwarding priority + hasPriority bool + // isFlyoverXover is true if the current hop has a flyover and a cross-over is performed + isFlyoverXover bool + // hbirdXkbuffer avoid allocating memory during aes computation for hummingbird flyover mac + hbirdXkbuffer []uint32 + // bfdLayer is reusable buffer for parsing BFD messages bfdLayer layers.BFD } @@ -1505,12 +1534,20 @@ func (p *scionPacketProcessor) updateNonConsDirIngressSegID() error { return nil } +// TODO: write PR for scionproto for this case distinction here (juagargi) func (p *scionPacketProcessor) currentInfoPointer() uint16 { + if p.scionLayer.PathType == hummingbird.PathType { + return p.currentHbirdInfoPointer() + } return uint16(slayers.CmnHdrLen + p.scionLayer.AddrHdrLen() + scion.MetaLen + path.InfoLen*int(p.path.PathMeta.CurrINF)) } +// TODO: write PR for scionproto for this case distinction here (juagargi) func (p *scionPacketProcessor) currentHopPointer() uint16 { + if p.scionLayer.PathType == hummingbird.PathType { + return p.currentHbirdHopPointer() + } return uint16(slayers.CmnHdrLen + p.scionLayer.AddrHdrLen() + scion.MetaLen + path.InfoLen*p.path.NumINF + path.HopLen*int(p.path.PathMeta.CurrHF)) } @@ -1532,8 +1569,8 @@ func (p *scionPacketProcessor) verifyCurrentMAC() (processResult, error) { } return processResult{SlowPathRequest: slowPathRequest}, slowPathRequired } - // Add the full MAC to the SCION packet processor, - // such that EPIC does not need to recalculate it. + // Add the full MAC to the SCION packet processor + // such that EPIC does not need to recalculate it p.cachedMac = fullMac return processResult{}, nil @@ -1581,6 +1618,7 @@ func (p *scionPacketProcessor) doXover() (processResult, error) { // TODO parameter problem invalid path return processResult{}, serrors.WrapStr("incrementing path", err) } + var err error if p.hopField, err = p.path.GetCurrentHopField(); err != nil { // TODO parameter problem invalid path @@ -2090,6 +2128,8 @@ func (p *slowPathPacketProcessor) prepareSCMP( "path type", pathType) } path = epicPath.ScionPath + case hummingbird.PathType: + return p.prepareHbirdSCMP(typ, code, scmpP, cause) default: return nil, serrors.WithCtx(cannotRoute, "details", "unsupported path type", "path type", pathType) diff --git a/router/dataplane_benchmark_test.go b/router/dataplane_benchmark_test.go new file mode 100644 index 0000000000..7f72cb8bac --- /dev/null +++ b/router/dataplane_benchmark_test.go @@ -0,0 +1,670 @@ +// Copyright 2020 Anapaya Systems +// Copyright 2023 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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 router_test + +import ( + "crypto/aes" + "net" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/google/gopacket" + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/private/util" + "github.com/scionproto/scion/pkg/private/xtest" + "github.com/scionproto/scion/pkg/scrypto" + "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/path" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" + "github.com/scionproto/scion/pkg/slayers/path/scion" + "github.com/scionproto/scion/private/topology" + "github.com/scionproto/scion/router" + "github.com/scionproto/scion/router/mock_router" + "github.com/stretchr/testify/require" + "golang.org/x/net/ipv4" +) + +const ( + benchmarkPayloadLen = 10 +) + +// standard SCION benchmark for reference +func BenchmarkProcessScion(b *testing.B) { + // prepare Dataplane + ctrl := gomock.NewController(b) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + + dp := router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Core, + 31: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + // prepare PacketProcessor + pp := dp.NewBenchmarkPP() + + // prepare packet + now := time.Now() + + spkt := &slayers.SCION{ + Version: 0, + TrafficClass: 0xb8, + FlowID: 0xdead, + NextHdr: slayers.L4UDP, + PathType: scion.PathType, + DstIA: xtest.MustParseIA("4-ff00:0:411"), + SrcIA: xtest.MustParseIA("2-ff00:0:222"), + Path: &scion.Raw{}, + PayloadLen: benchmarkPayloadLen, + } + + dpath := &scion.Decoded{ + Base: scion.Base{ + PathMeta: scion.MetaHdr{ + CurrHF: 1, + SegLen: [3]uint8{3, 3, 0}, + }, + NumINF: 2, + NumHops: 6, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []path.HopField{ + {ConsIngress: 0, ConsEgress: 2}, + {ConsIngress: 7, ConsEgress: 31}, + {ConsIngress: 3, ConsEgress: 0}, + {ConsIngress: 0, ConsEgress: 6}, + {ConsIngress: 8, ConsEgress: 9}, + {ConsIngress: 11, ConsEgress: 0}, + }, + } + + dpath.HopFields[1].Mac = benchmarkScionMac(b, key, dpath.InfoFields[0], dpath.HopFields[1]) + msg := toBenchmarkMsg(b, spkt, dpath) + + backup := make([]byte, len(msg.Buffers[0])) + copy(backup, msg.Buffers[0]) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + copy(msg.Buffers[0], backup) + pp.ProcessPkt(7, msg) + // DO NOT check for errors when getting actual numbers from benchmark + // require.NoError(b, err) // verify no failures on repeated usage of same packet + } +} + +func BenchmarkProcessScionXover(b *testing.B) { + // prepare Dataplane + ctrl := gomock.NewController(b) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + + dp := router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Core, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + // prepare PacketProcessor + pp := dp.NewBenchmarkPP() + + // prepare packet + now := time.Now() + + spkt := &slayers.SCION{ + Version: 0, + TrafficClass: 0xb8, + FlowID: 0xdead, + NextHdr: slayers.L4UDP, + PathType: scion.PathType, + DstIA: xtest.MustParseIA("4-ff00:0:411"), + SrcIA: xtest.MustParseIA("2-ff00:0:222"), + Path: &scion.Raw{}, + PayloadLen: benchmarkPayloadLen, + } + + dpath := &scion.Decoded{ + Base: scion.Base{ + PathMeta: scion.MetaHdr{ + CurrHF: 2, + SegLen: [3]uint8{3, 3, 0}, + }, + NumINF: 2, + NumHops: 6, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []path.HopField{ + {ConsIngress: 0, ConsEgress: 2}, + {ConsIngress: 3, ConsEgress: 4}, + {ConsIngress: 7, ConsEgress: 0}, + {ConsIngress: 0, ConsEgress: 31}, + {ConsIngress: 8, ConsEgress: 9}, + {ConsIngress: 11, ConsEgress: 0}, + }, + } + + dpath.HopFields[2].Mac = benchmarkScionMac(b, key, dpath.InfoFields[0], dpath.HopFields[2]) + dpath.HopFields[3].Mac = benchmarkScionMac(b, key, dpath.InfoFields[1], dpath.HopFields[3]) + msg := toBenchmarkMsg(b, spkt, dpath) + + backup := make([]byte, len(msg.Buffers[0])) + copy(backup, msg.Buffers[0]) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + copy(msg.Buffers[0], backup) + pp.ProcessPkt(7, msg) + // DO NOT check for errors when getting actual numbers from benchmark + // require.NoError(b, err) // verify no failures on repeated usage of same packet + } +} + +// standard Hbird packet, no flyover +func BenchmarkProcessHbirdFlyoverless(b *testing.B) { + // prepare Dataplane + ctrl := gomock.NewController(b) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + + dp := router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Core, + 31: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + // prepare PacketProcessor + pp := dp.NewBenchmarkPP() + + // prepare packet + now := time.Now() + + spkt := &slayers.SCION{ + Version: 0, + TrafficClass: 0xb8, + FlowID: 0xdead, + NextHdr: slayers.L4UDP, + PathType: hummingbird.PathType, + DstIA: xtest.MustParseIA("4-ff00:0:411"), + SrcIA: xtest.MustParseIA("2-ff00:0:222"), + Path: &hummingbird.Raw{}, + PayloadLen: benchmarkPayloadLen, + } + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + SegLen: [3]uint8{9, 9, 0}, + }, + NumINF: 2, + NumLines: 18, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 7, ConsEgress: 31}}, + {HopField: path.HopField{ConsIngress: 3, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 6}}, + {HopField: path.HopField{ConsIngress: 8, ConsEgress: 9}}, + {HopField: path.HopField{ConsIngress: 11, ConsEgress: 0}}, + }, + } + + dpath.HopFields[1].HopField.Mac = benchmarkScionMac(b, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + msg := toBenchmarkMsg(b, spkt, dpath) + + backup := make([]byte, len(msg.Buffers[0])) + copy(backup, msg.Buffers[0]) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + copy(msg.Buffers[0], backup) + pp.ProcessPkt(7, msg) + // DO NOT check for errors when getting actual numbers from benchmark + //require.NoError(b, err) // verify no failures on repeated usage of same packet + } +} + +func BenchmarkProcessHbirdFlyoverlessXover(b *testing.B) { + // prepare Dataplane + ctrl := gomock.NewController(b) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + + dp := router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Core, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + // prepare PacketProcessor + pp := dp.NewBenchmarkPP() + + // prepare packet + now := time.Now() + + spkt := &slayers.SCION{ + Version: 0, + TrafficClass: 0xb8, + FlowID: 0xdead, + NextHdr: slayers.L4UDP, + PathType: hummingbird.PathType, + DstIA: xtest.MustParseIA("4-ff00:0:411"), + SrcIA: xtest.MustParseIA("2-ff00:0:222"), + Path: &hummingbird.Raw{}, + PayloadLen: benchmarkPayloadLen, + } + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 6, + SegLen: [3]uint8{9, 9, 0}, + }, + NumINF: 2, + NumLines: 18, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 3, ConsEgress: 4}}, + {HopField: path.HopField{ConsIngress: 7, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 31}}, + {HopField: path.HopField{ConsIngress: 8, ConsEgress: 9}}, + {HopField: path.HopField{ConsIngress: 11, ConsEgress: 0}}, + }, + } + + dpath.HopFields[2].HopField.Mac = benchmarkScionMac(b, key, dpath.InfoFields[0], + dpath.HopFields[2].HopField) + dpath.HopFields[3].HopField.Mac = benchmarkScionMac(b, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + msg := toBenchmarkMsg(b, spkt, dpath) + + backup := make([]byte, len(msg.Buffers[0])) + copy(backup, msg.Buffers[0]) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + copy(msg.Buffers[0], backup) + pp.ProcessPkt(7, msg) + // DO NOT check for errors when getting actual numbers from benchmark + // require.NoError(b, err) // verify no failures on repeated usage of same packet + } +} + +// To run any benchmark containing flyovers, temporarily modify +// dataplane_hbird.go:checkReservationBandwidth() such that it never fails, +// but still performs all operations +func BenchmarkProcessHbirdFlyover(b *testing.B) { + // prepare Dataplane + ctrl := gomock.NewController(b) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + + dp := router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Core, + 31: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + // prepare PacketProcessor + pp := dp.NewBenchmarkPP() + + // prepare packet + now := time.Now() + + spkt := &slayers.SCION{ + Version: 0, + TrafficClass: 0xb8, + FlowID: 0xdead, + NextHdr: slayers.L4UDP, + PathType: hummingbird.PathType, + DstIA: xtest.MustParseIA("4-ff00:0:411"), + SrcIA: xtest.MustParseIA("2-ff00:0:222"), + Path: &hummingbird.Raw{}, + PayloadLen: benchmarkPayloadLen, + } + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + SegLen: [3]uint8{11, 9, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 20, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 2}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 7, ConsEgress: 31}, + ResStartTime: 10, Duration: 180, Bw: 777}, + {HopField: path.HopField{ConsIngress: 3, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 6}}, + {HopField: path.HopField{ConsIngress: 8, ConsEgress: 9}}, + {HopField: path.HopField{ConsIngress: 11, ConsEgress: 0}}, + }, + } + + dpath.HopFields[1].HopField.Mac = benchmarkAggregateMac(b, key, sv, spkt.DstIA, + benchmarkPayloadLen, 7, 31, dpath.InfoFields[0], dpath.HopFields[1], dpath.PathMeta) + msg := toBenchmarkMsg(b, spkt, dpath) + + backup := make([]byte, len(msg.Buffers[0])) + copy(backup, msg.Buffers[0]) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + copy(msg.Buffers[0], backup) + pp.ProcessPkt(7, msg) + // DO NOT check for errors when getting actual numbers from benchmark + // require.NoError(b, err) // verify no failures on repeated usage of same packet + } +} + +// To run any benchmark containing flyovers, temporarily modify +// dataplane_hbird.go:checkReservationBandwidth() such that it never fails, +// but still performs all operations +func BenchmarkProcessHbirdFlyoverXoverBrtransit(b *testing.B) { + // prepare Dataplane + ctrl := gomock.NewController(b) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + + dp := router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Core, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + // prepare PacketProcessor + pp := dp.NewBenchmarkPP() + + // prepare packet + now := time.Now() + + spkt := &slayers.SCION{ + Version: 0, + TrafficClass: 0xb8, + FlowID: 0xdead, + NextHdr: slayers.L4UDP, + PathType: hummingbird.PathType, + DstIA: xtest.MustParseIA("4-ff00:0:411"), + SrcIA: xtest.MustParseIA("2-ff00:0:222"), + Path: &hummingbird.Raw{}, + PayloadLen: benchmarkPayloadLen, + } + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 6, + SegLen: [3]uint8{11, 9, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 20, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 4, ConsEgress: 5}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 7, ConsEgress: 0}, + ResID: 2345, ResStartTime: 10, Duration: 180, Bw: 777}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 31}}, + {HopField: path.HopField{ConsIngress: 8, ConsEgress: 9}}, + {HopField: path.HopField{ConsIngress: 11, ConsEgress: 0}}, + }, + } + + dpath.HopFields[2].HopField.Mac = benchmarkAggregateMac(b, key, sv, spkt.DstIA, + benchmarkPayloadLen, 7, 31, dpath.InfoFields[0], dpath.HopFields[2], dpath.PathMeta) + dpath.HopFields[3].HopField.Mac = benchmarkScionMac(b, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + msg := toBenchmarkMsg(b, spkt, dpath) + + backup := make([]byte, len(msg.Buffers[0])) + copy(backup, msg.Buffers[0]) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + copy(msg.Buffers[0], backup) + pp.ProcessPkt(7, msg) + // DO NOT check for errors when getting actual numbers from benchmark + // require.NoError(b, err) // verify no failures on repeated usage of same packet + } +} + +func BenchmarkProcessHbirdFlyoverXoverAstransit(b *testing.B) { + // prepare Dataplane + ctrl := gomock.NewController(b) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + + dp := router.NewDP( + map[uint16]router.BatchConn{ + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Core, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(31): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + // prepare PacketProcessor + pp := dp.NewBenchmarkPP() + + // prepare packet + now := time.Now() + + spkt := &slayers.SCION{ + Version: 0, + TrafficClass: 0xb8, + FlowID: 0xdead, + NextHdr: slayers.L4UDP, + PathType: hummingbird.PathType, + DstIA: xtest.MustParseIA("4-ff00:0:411"), + SrcIA: xtest.MustParseIA("2-ff00:0:222"), + Path: &hummingbird.Raw{}, + PayloadLen: benchmarkPayloadLen, + } + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 6, + SegLen: [3]uint8{11, 9, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 20, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 4, ConsEgress: 5}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 7, ConsEgress: 0}, + ResID: 2345, ResStartTime: 10, Duration: 180, Bw: 777}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 31}}, + {HopField: path.HopField{ConsIngress: 8, ConsEgress: 9}}, + {HopField: path.HopField{ConsIngress: 11, ConsEgress: 0}}, + }, + } + + dpath.HopFields[2].HopField.Mac = benchmarkAggregateMac(b, key, sv, spkt.DstIA, + benchmarkPayloadLen, 7, 31, dpath.InfoFields[0], dpath.HopFields[2], dpath.PathMeta) + dpath.HopFields[3].HopField.Mac = benchmarkScionMac(b, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + msg := toBenchmarkMsg(b, spkt, dpath) + + backup := make([]byte, len(msg.Buffers[0])) + copy(backup, msg.Buffers[0]) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + copy(msg.Buffers[0], backup) + pp.ProcessPkt(7, msg) + // DO NOT check for errors when getting actual numbers from benchmark + // require.NoError(b, err) // verify no failures on repeated usage of same packet + } +} + +// Helper Functions for Benchmarking + +func toBenchmarkMsg(b *testing.B, spkt *slayers.SCION, dpath path.Path) *ipv4.Message { + b.Helper() + ret := &ipv4.Message{} + spkt.Path = dpath + buffer := gopacket.NewSerializeBuffer() + spkt.PayloadLen = benchmarkPayloadLen + payload := [benchmarkPayloadLen]byte{} + err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{FixLengths: true}, + spkt, gopacket.Payload(payload[:])) + require.NoError(b, err) + raw := buffer.Bytes() + ret.Buffers = make([][]byte, 1) + ret.Buffers[0] = make([]byte, 1500) + copy(ret.Buffers[0], raw) + ret.N = len(raw) + ret.Buffers[0] = ret.Buffers[0][:ret.N] + return ret +} + +func benchmarkScionMac(b *testing.B, key []byte, info path.InfoField, + hf path.HopField) [path.MacLen]byte { + mac, err := scrypto.InitMac(key) + require.NoError(b, err) + buffer := [path.MacLen]byte{} + return path.MAC(mac, info, hf, buffer[:]) +} + +func benchmarkAggregateMac(b *testing.B, key, sv []byte, dst addr.IA, l, ingress, egress uint16, + info path.InfoField, hf hummingbird.FlyoverHopField, + meta hummingbird.MetaHdr) [path.MacLen]byte { + + scionMac := benchmarkScionMac(b, key, info, hf.HopField) + block, err := aes.NewCipher(sv) + require.NoError(b, err) + + akBuffer := make([]byte, hummingbird.AkBufferSize) + macBuffer := make([]byte, hummingbird.FlyoverMacBufferSize) + xkBuffer := make([]uint32, hummingbird.XkBufferSize) + + ak := hummingbird.DeriveAuthKey(block, hf.ResID, hf.Bw, ingress, egress, + meta.BaseTS-uint32(hf.ResStartTime), hf.Duration, akBuffer) + flyoverMac := hummingbird.FullFlyoverMac(ak, dst, l, hf.ResStartTime, + meta.HighResTS, macBuffer, xkBuffer) + + for i, b := range scionMac { + scionMac[i] = b ^ flyoverMac[i] + } + return scionMac +} diff --git a/router/dataplane_hbird.go b/router/dataplane_hbird.go new file mode 100644 index 0000000000..ef44acdada --- /dev/null +++ b/router/dataplane_hbird.go @@ -0,0 +1,991 @@ +// Copyright 2020 Anapaya Systems +// Copyright 2023 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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 router + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/subtle" + "errors" + "fmt" + "time" + + "github.com/google/gopacket" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/private/util" + "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/path" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" + "github.com/scionproto/scion/pkg/spao" + "github.com/scionproto/scion/router/tokenbucket" +) + +var ( + reservationExpired = errors.New("current time is outside of reservation validity") +) + +// SetHbirdKey sets the key for the PRF function used to compute the Hummingbird Auth Key +func (d *DataPlane) SetHbirdKey(key []byte) error { + d.mtx.Lock() + defer d.mtx.Unlock() + if d.running { + return modifyExisting + } + if len(key) == 0 { + return emptyValue + } + if d.prfFactory != nil { + return alreadySet + } + // First check for cipher creation errors + if _, err := aes.NewCipher(key); err != nil { + return err + } + d.prfFactory = func() cipher.Block { + prf, _ := aes.NewCipher(key) + return prf + } + return nil +} + +func (p *scionPacketProcessor) parseHbirdPath() (processResult, error) { + var err error + p.flyoverField, err = p.hbirdPath.GetCurrentHopField() + if err != nil { + return processResult{}, err + } + p.hopField = p.flyoverField.HopField + p.infoField, err = p.hbirdPath.GetCurrentInfoField() + if err != nil { + // TODO(lukedirtwalker) parameter problem invalid path? + return processResult{}, err + } + if p.flyoverField.Flyover { + p.hasPriority = true + } + + return processResult{}, nil +} + +func determinePeerHbird(pathMeta hummingbird.MetaHdr, inf path.InfoField) (bool, error) { + if !inf.Peer { + return false, nil + } + + if pathMeta.SegLen[0] == 0 { + return false, errPeeringEmptySeg0 + } + if pathMeta.SegLen[1] == 0 { + return false, errPeeringEmptySeg1 + + } + if pathMeta.SegLen[2] != 0 { + return false, errPeeringNonemptySeg2 + } + + // The peer hop fields are the last hop field on the first path + // segment (at SegLen[0] - 1) and the first hop field of the second + // path segment (at SegLen[0]). The below check applies only + // because we already know this is a well-formed peering path. + currHF := pathMeta.CurrHF + segLen := pathMeta.SegLen[0] + peer := currHF == segLen-hummingbird.HopLines || currHF == segLen-hummingbird.FlyoverLines || + currHF == segLen + return peer, nil +} + +func (p *scionPacketProcessor) determinePeerHbird() (processResult, error) { + peer, err := determinePeerHbird(p.hbirdPath.PathMeta, p.infoField) + p.peering = peer + return processResult{}, err +} + +func (p *scionPacketProcessor) validateHopExpiryHbird() (processResult, error) { + expiration := util.SecsToTime(p.infoField.Timestamp). + Add(path.ExpTimeToDuration(p.hopField.ExpTime)) + expired := expiration.Before(time.Now()) + if !expired { + return processResult{}, nil + } + log.Debug("SCMP: expired hop", "cons_dir", p.infoField.ConsDir, "if_id", p.ingressID, + "curr_inf", p.hbirdPath.PathMeta.CurrINF, "curr_hf", p.hbirdPath.PathMeta.CurrHF) + slowPathRequest := slowPathRequest{ + scmpType: slayers.SCMPTypeParameterProblem, + code: slayers.SCMPCodePathExpired, + pointer: p.currentHopPointer(), + cause: expiredHop, + } + return processResult{SlowPathRequest: slowPathRequest}, slowPathRequired +} + +func (p *scionPacketProcessor) validateReservationExpiry() (processResult, error) { + startTime := util.SecsToTime(p.hbirdPath.PathMeta.BaseTS - uint32(p.flyoverField.ResStartTime)) + endTime := startTime.Add(time.Duration(p.flyoverField.Duration) * time.Second) + now := time.Now() + if startTime.Before(now) && now.Before(endTime) { + return processResult{}, nil + } + log.Debug("SCMP: Reservation is not valid at current time", "reservation start", startTime, + "reservation end", endTime, "now", now) + slowPathRequest := slowPathRequest{ + scmpType: slayers.SCMPTypeParameterProblem, + code: slayers.SCMPCodeReservationExpired, + pointer: p.currentHopPointer(), + cause: reservationExpired, + } + return processResult{SlowPathRequest: slowPathRequest}, slowPathRequired +} + +func (p *scionPacketProcessor) currentHbirdInfoPointer() uint16 { + return uint16(slayers.CmnHdrLen + p.scionLayer.AddrHdrLen() + + hummingbird.MetaLen + path.InfoLen*int(p.hbirdPath.PathMeta.CurrINF)) +} + +func (p *scionPacketProcessor) currentHbirdHopPointer() uint16 { + return uint16(slayers.CmnHdrLen + p.scionLayer.AddrHdrLen() + + hummingbird.MetaLen + path.InfoLen*p.hbirdPath.NumINF + + hummingbird.LineLen*int(p.hbirdPath.PathMeta.CurrHF)) +} + +// Returns the ingress and egress through which the current packet enters and leves the AS +func (p *scionPacketProcessor) getFlyoverInterfaces() (uint16, uint16, error) { + ingress := p.hopField.ConsIngress + egress := p.hopField.ConsEgress + // Reservations are not bidirectional, + // reservation ingress and egress are always real ingress and egress + if !p.infoField.ConsDir { + ingress, egress = egress, ingress + } + // On crossovers, A Reservation goes from the ingress of the incoming hop to + // the egress of the outgoing one + var err error + if p.hbirdPath.IsXover() && !p.peering { + egress, err = p.hbirdPath.GetNextEgress() + if err != nil { + return 0, 0, err + } + } else if p.hbirdPath.IsFirstHopAfterXover() && !p.peering { + ingress, err = p.hbirdPath.GetPreviousIngress() + if err != nil { + return 0, 0, err + } + } + return ingress, egress, nil +} + +func (p *scionPacketProcessor) verifyHbirdScionMac() (processResult, error) { + scionMac := path.FullMAC(p.mac, p.infoField, p.hopField, p.macInputBuffer[:path.MACBufferSize]) + verified := subtle.ConstantTimeCompare(p.hopField.Mac[:path.MacLen], scionMac[:path.MacLen]) + if verified == 0 { + log.Debug("SCMP: MAC verification failed", "expected", fmt.Sprintf( + "%x", scionMac[:path.MacLen]), + "actual", fmt.Sprintf("%x", p.hopField.Mac[:path.MacLen]), + "cons_dir", p.infoField.ConsDir, + "if_id", p.ingressID, "curr_inf", p.hbirdPath.PathMeta.CurrINF, + "curr_hf", p.hbirdPath.PathMeta.CurrHF, "seg_id", p.infoField.SegID) + slowPathRequest := slowPathRequest{ + scmpType: slayers.SCMPTypeParameterProblem, + code: slayers.SCMPCodeInvalidHopFieldMAC, + pointer: p.currentHopPointer(), + cause: macVerificationFailed, + } + return processResult{SlowPathRequest: slowPathRequest}, slowPathRequired + } + return processResult{}, nil +} + +func (p *scionPacketProcessor) verifyHbirdFlyoverMac() (processResult, error) { + var flyoverMac []byte + var verified int + + ingress, egress, err := p.getFlyoverInterfaces() + if err != nil { + return processResult{}, err + } + + ak := hummingbird.DeriveAuthKey(p.prf, p.flyoverField.ResID, p.flyoverField.Bw, + ingress, egress, p.hbirdPath.PathMeta.BaseTS-uint32(p.flyoverField.ResStartTime), + p.flyoverField.Duration, + p.macInputBuffer[path.MACBufferSize+hummingbird.FlyoverMacBufferSize:]) + flyoverMac = hummingbird.FullFlyoverMac(ak, p.scionLayer.DstIA, p.scionLayer.PayloadLen, + p.flyoverField.ResStartTime, p.hbirdPath.PathMeta.HighResTS, + p.macInputBuffer[path.MACBufferSize:], p.hbirdXkbuffer) + + if !p.hbirdPath.IsFirstHopAfterXover() { + err := p.updateHbirdNonConsDirIngressSegIDFlyover(flyoverMac) + if err != nil { + return processResult{}, err + } + } + scionMac := path.FullMAC(p.mac, p.infoField, p.hopField, p.macInputBuffer[:path.MACBufferSize]) + + macXor(flyoverMac[:], scionMac[:], flyoverMac[:]) + verified = subtle.ConstantTimeCompare(p.hopField.Mac[:path.MacLen], flyoverMac[:path.MacLen]) + if verified == 0 { + log.Debug("SCMP: Aggregate MAC verification failed", + "expected", fmt.Sprintf("%x", flyoverMac[:path.MacLen]), + "actual", fmt.Sprintf("%x", p.hopField.Mac[:path.MacLen]), + "cons_dir", p.infoField.ConsDir, + "scionMac", fmt.Sprintf("%x", scionMac[:path.MacLen]), + "if_id", p.ingressID, "curr_inf", p.hbirdPath.PathMeta.CurrINF, + "curr_hf", p.hbirdPath.PathMeta.CurrHF, "seg_id", p.infoField.SegID, + "packet length", p.scionLayer.PayloadLen, + "dest", p.scionLayer.DstIA, "startTime", p.flyoverField.ResStartTime, + "highResTS", p.hbirdPath.PathMeta.HighResTS, + "ResID", p.flyoverField.ResID, "Bw", p.flyoverField.Bw, + "in", p.hopField.ConsIngress, "Eg", p.hopField.ConsEgress, + "start ak", p.hbirdPath.PathMeta.BaseTS-uint32(p.flyoverField.ResStartTime), + "Duration", p.flyoverField.Duration) + } + + // Add the full MAC to the SCION packet processor, + // such that hummingbird mac de-aggregation do not need to recalculate it. + // Do not overwrite cachedmac after doing xover, as it may contain a flyovermac + // This function is currently not called after a xover, so no need to check + // Keep in mind for future changes + p.cachedMac = scionMac + + if verified == 0 { + slowPathRequest := slowPathRequest{ + scmpType: slayers.SCMPTypeParameterProblem, + code: slayers.SCMPCodeInvalidHopFieldMAC, + pointer: p.currentHopPointer(), + cause: macVerificationFailed, + } + return processResult{SlowPathRequest: slowPathRequest}, slowPathRequired + } + return processResult{}, nil +} + +func (p *scionPacketProcessor) validateHbirdSrcDstIA() (processResult, error) { + srcIsLocal := (p.scionLayer.SrcIA == p.d.localIA) + dstIsLocal := (p.scionLayer.DstIA == p.d.localIA) + if p.ingressID == 0 { + // Outbound + // Only check SrcIA if first hop, for transit this already checked by ingress router. + // Note: SCMP error messages triggered by the sibling router may use paths that + // don't start with the first hop. + if p.hbirdPath.IsFirstHop() && !srcIsLocal { + return p.invalidSrcIA() + } + if dstIsLocal { + return p.invalidDstIA() + } + } else { + // Inbound + if srcIsLocal { + return p.invalidSrcIA() + } + if p.hbirdPath.IsLastHop() != dstIsLocal { + return p.invalidDstIA() + } + } + return processResult{}, nil +} + +func (p *scionPacketProcessor) ingressInterfaceHbird() uint16 { + info := p.infoField + hop := p.flyoverField + if !p.peering && p.hbirdPath.IsFirstHopAfterXover() { + var err error + info, err = p.hbirdPath.GetInfoField(int(p.hbirdPath.PathMeta.CurrINF) - 1) + if err != nil { // cannot be out of range + panic(err) + } + // Previous hop should always be a non-flyover field, + // as flyover is transferred to second hop on xover + hop, err = p.hbirdPath.GetHopField(int(p.hbirdPath.PathMeta.CurrHF) - hummingbird.HopLines) + if err != nil { // cannot be out of range + panic(err) + } + } + if info.ConsDir { + return hop.HopField.ConsIngress + } + return hop.HopField.ConsEgress +} + +// validateTransitUnderlaySrc checks that the source address of transit packets +// matches the expected sibling router. +// Provided that underlying network infrastructure prevents address spoofing, +// this check prevents malicious end hosts in the local AS from bypassing the +// SrcIA checks by disguising packets as transit traffic. +func (p *scionPacketProcessor) validateHbirdTransitUnderlaySrc() (processResult, error) { + if p.hbirdPath.IsFirstHop() || p.ingressID != 0 { + // not a transit packet, nothing to check + return processResult{}, nil + } + pktIngressID := p.ingressInterfaceHbird() + expectedSrc, ok := p.d.internalNextHops[pktIngressID] + if !ok || !expectedSrc.IP.Equal(p.srcAddr.IP) { + // Drop + return processResult{}, invalidSrcAddrForTransit + } + return processResult{}, nil +} + +// Verifies the PathMetaHeader timestamp is recent +// Current implementation works with a nanosecond granularity HighResTS +func (p *scionPacketProcessor) validatePathMetaTimestamp() { + timestamp := util.SecsToTime(p.hbirdPath.PathMeta.BaseTS).Add( + time.Duration(p.hbirdPath.PathMeta.HighResTS>>22) * time.Millisecond) + // TODO: make a configurable value instead of using a flat 1 seconds + if time.Until(timestamp).Abs() > time.Duration(1)*time.Second { + // Hummingbird specification explicitly says to forward best-effort is timestamp too old + p.hasPriority = false + } +} + +// Converts a flyover bandwidth value to bytes per second +func convertResBw(bw uint16) float64 { + + // In this implementation, we choose to allow reservations up to 64 kBps + // Since the bandwidth field has 10 bits, we multiply by 64 to reach the target range + return float64(bw * 64) +} + +func (p *scionPacketProcessor) checkReservationBandwidth() (processResult, error) { + + // Only check bandwidth if packet is given priority + // Bandwidth check is NOT performed for late packets that have flyover but no priority + if !p.hasPriority { + return processResult{}, nil + } + // resID only has to be unique per interface pair + // key for the tokenbuckets map is based on flyover resID, ingress and egress + ingress, egress, err := p.getFlyoverInterfaces() + if err != nil { + return processResult{}, err + } + resKey := uint64(p.flyoverField.ResID) + uint64(ingress)<<22 + uint64(egress)<<38 + v, ok := p.d.tokenBuckets.Load(resKey) + if ok { + // Check bandwidth + tb, ok := v.(*tokenbucket.TokenBucket) + if !ok { + log.Error("Non-tokenbucket value found in tokenbucket map") + panic("tokenbucket map contains value of different type") + } + resBw := convertResBw(p.flyoverField.Bw) + if tb.CIR != resBw { + // It is possible for different reservations to share a resID + // if they do not overlap in time + tb.SetRate(resBw) + tb.SetBurstSize(resBw) + } + if tb.Apply(int(p.scionLayer.PayloadLen), time.Now()) { + return processResult{}, nil + } + // TODO: return scmp packet for reservation overuse + return processResult{}, serrors.New("Reservation bandwidth overuse", + "ResID", p.flyoverField.ResID, "Authorized Bandwidth", p.flyoverField.Bw) + } + // Initialize token bucket for given reservation + resBw := convertResBw(p.flyoverField.Bw) + now := time.Now() + tb := tokenbucket.NewTokenBucket(now, resBw, resBw) + r, _ := p.d.tokenBuckets.LoadOrStore(resKey, tb) + + tb, ok = r.(*tokenbucket.TokenBucket) + if !ok { + log.Error("Non-tokenbucket value found in tokenbucket map") + panic("tokenbucket map contains value of different type") + } + + if tb.Apply(int(p.scionLayer.PayloadLen), time.Now()) { + return processResult{}, nil + } + // TODO: return scmp packet for reservation overuse + return processResult{}, serrors.New("Reservation bandwidth overuse", + "ResID", p.flyoverField.ResID, "Authorized Bandwidth", p.flyoverField.Bw) +} + +func (p *scionPacketProcessor) handleHbirdIngressRouterAlert() (processResult, error) { + if p.ingressID == 0 { + return processResult{}, nil + } + alert := p.ingressRouterAlertFlag() + if !*alert { + return processResult{}, nil + } + *alert = false + err := p.hbirdPath.SetHopField(p.flyoverField, int(p.hbirdPath.PathMeta.CurrHF)) + if err != nil { + return processResult{}, serrors.WrapStr("update hop field", err) + } + slowPathRequest := slowPathRequest{ + typ: slowPathRouterAlert, + interfaceId: p.ingressID, + } + return processResult{SlowPathRequest: slowPathRequest}, slowPathRequired +} + +func (p *scionPacketProcessor) handleHbirdEgressRouterAlert() (processResult, error) { + alert := p.egressRouterAlertFlag() + if !*alert { + return processResult{}, nil + } + egressID := p.egressInterface() + if _, ok := p.d.external[egressID]; !ok { + return processResult{}, nil + } + *alert = false + err := p.hbirdPath.SetHopField(p.flyoverField, int(p.hbirdPath.PathMeta.CurrHF)) + if err != nil { + return processResult{}, serrors.WrapStr("update hop field", err) + } + slowPathRequest := slowPathRequest{ + typ: slowPathRouterAlert, + interfaceId: egressID, + } + return processResult{SlowPathRequest: slowPathRequest}, slowPathRequired +} + +func (p *scionPacketProcessor) updateHbirdNonConsDirIngressSegIDFlyover(flyoverMac []byte) error { + // against construction dir the ingress router updates the SegID, ifID == 0 + // means this comes from this AS itself, so nothing has to be done. + // If a flyover is present, need to first de-aggregate the first two bytes of the mac + // before updating SegID + if !p.infoField.ConsDir && p.ingressID != 0 && !p.peering { + // de-aggregate first two bytes of mac + p.hopField.Mac[0] ^= flyoverMac[0] + p.hopField.Mac[1] ^= flyoverMac[1] + p.infoField.UpdateSegID(p.hopField.Mac) + // restore correct state of MAC field, even if error + p.hopField.Mac[0] ^= flyoverMac[0] + p.hopField.Mac[1] ^= flyoverMac[1] + err := p.hbirdPath.SetInfoField(p.infoField, int(p.hbirdPath.PathMeta.CurrINF)) + if err != nil { + return serrors.WrapStr("update info field", err) + } + } + return nil +} + +func (p *scionPacketProcessor) updateHbirdNonConsDirIngressSegID() error { + // against construction dir the ingress router updates the SegID, ifID == 0 + // means this comes from this AS itself, so nothing has to be done. + if !p.infoField.ConsDir && p.ingressID != 0 && !p.peering { + p.infoField.UpdateSegID(p.hopField.Mac) + err := p.hbirdPath.SetInfoField(p.infoField, int(p.hbirdPath.PathMeta.CurrINF)) + if err != nil { + return serrors.WrapStr("update info field", err) + } + } + return nil +} + +// Xors a and b and writes the result into d. +// +// Expects all arguments to have a length of macLen +func macXor(d, a, b []byte) { + for i := 0; i < path.MacLen; i++ { + d[i] = a[i] ^ b[i] + } +} + +func (p *scionPacketProcessor) deAggregateMac() (processResult, error) { + if !p.flyoverField.Flyover { + return processResult{}, nil + } + copy(p.hopField.Mac[:], p.cachedMac[:path.MacLen]) + if err := p.hbirdPath.ReplaceCurrentMac(p.cachedMac); err != nil { + log.Debug("Failed to replace MAC after de-aggregation", "error", err.Error()) + return processResult{}, serrors.Join(err, serrors.New("Mac replacement failed")) + } + return processResult{}, nil +} + +// de-aggregates mac and stores the flyovermac part of the mac in cachedMac +func (p *scionPacketProcessor) deAggregateAndCacheMac() (processResult, error) { + if !p.flyoverField.Flyover { + return processResult{}, nil + } + // obtain flyoverMac and buffer in macInputBuffer + // such that it is not overwritten by the following standard mac computation + macXor(p.macInputBuffer[path.MACBufferSize:], p.cachedMac, p.hopField.Mac[:]) + // deaggregate Mac + copy(p.hopField.Mac[:], p.cachedMac[:path.MacLen]) + if err := p.hbirdPath.ReplaceCurrentMac(p.cachedMac); err != nil { + log.Debug("Failed to replace MAC after de-aggregation", "error", err.Error()) + return processResult{}, serrors.Join(err, serrors.New("Mac replacement failed")) + } + // set cachedMac to the buffered flyoverMac + p.cachedMac = p.macInputBuffer[path.MACBufferSize : path.MACBufferSize+path.MacLen] + return processResult{}, nil +} + +func (p *scionPacketProcessor) doFlyoverXover() error { + // Move flyoverhopfield to next hop for benefit of egress router + if err := p.hbirdPath.DoFlyoverXover(); err != nil { + return err + } + + // Aggregate mac of current hopfield with buffered flyoverMac + mac, err := p.hbirdPath.GetMac(int(p.hbirdPath.PathMeta.CurrHF)) + if err != nil { + return err + } + macXor(mac, mac, p.cachedMac) + return nil +} + +func (p *scionPacketProcessor) reverseFlyoverXover() error { + if err := p.hbirdPath.ReverseFlyoverXover(); err != nil { + return err + } + // No MAC aggregation/de-aggregation, as these are already performed + p.flyoverField.Flyover = false + return nil +} + +func (p *scionPacketProcessor) doHbirdXoverFlyover() (processResult, error) { + p.effectiveXover = true + p.isFlyoverXover = true + + if r, err := p.deAggregateAndCacheMac(); err != nil { + return r, err + } + + if err := p.hbirdPath.IncPath(hummingbird.FlyoverLines); err != nil { + return processResult{}, serrors.WrapStr("incrementing path", err) + } + + var err error + if p.flyoverField, err = p.hbirdPath.GetCurrentHopField(); err != nil { + return processResult{}, err + } + if p.infoField, err = p.hbirdPath.GetCurrentInfoField(); err != nil { + return processResult{}, err + } + p.hopField = p.flyoverField.HopField + return processResult{}, nil +} + +func (p *scionPacketProcessor) doHbirdXoverBestEffort() (processResult, error) { + p.effectiveXover = true + + if err := p.hbirdPath.IncPath(hummingbird.HopLines); err != nil { + // TODO parameter problem invalid path + return processResult{}, serrors.WrapStr("incrementing path", err) + } + + var err error + if p.flyoverField, err = p.hbirdPath.GetCurrentHopField(); err != nil { + // TODO parameter problem invalid path + return processResult{}, err + } + if p.infoField, err = p.hbirdPath.GetCurrentInfoField(); err != nil { + // TODO parameter problem invalid path + return processResult{}, err + } + p.hopField = p.flyoverField.HopField + return processResult{}, nil +} + +func (p *scionPacketProcessor) processHbirdEgress() error { + // we are the egress router and if we go in construction direction we + // need to update the SegID. + if p.infoField.ConsDir && !p.peering { + p.infoField.UpdateSegID(p.hopField.Mac) + err := p.hbirdPath.SetInfoField(p.infoField, int(p.hbirdPath.PathMeta.CurrINF)) + if err != nil { + // TODO parameter problem invalid path + return serrors.WrapStr("update info field", err) + } + } + + n := hummingbird.HopLines + if p.flyoverField.Flyover { + n = hummingbird.FlyoverLines + } + if err := p.hbirdPath.IncPath(n); err != nil { + // TODO parameter problem invalid path + return serrors.WrapStr("incrementing path", err) + } + return nil +} + +func (p *scionPacketProcessor) processHBIRD() (processResult, error) { + var ok bool + p.hbirdPath, ok = p.scionLayer.Path.(*hummingbird.Raw) + if !ok { + // TODO(lukedirtwalker) parameter problem invalid path? + return processResult{}, malformedPath + } + if r, err := p.parseHbirdPath(); err != nil { + return r, err + } + if r, err := p.determinePeerHbird(); err != nil { + return r, err + } + if r, err := p.validateHopExpiryHbird(); err != nil { + return r, err + } + if r, err := p.validateIngressID(); err != nil { + return r, err + } + if r, err := p.validatePktLen(); err != nil { + return r, err + } + if r, err := p.validateHbirdTransitUnderlaySrc(); err != nil { + return r, err + } + if r, err := p.validateHbirdSrcDstIA(); err != nil { + return r, err + } + if p.flyoverField.Flyover { + return p.processHBIRDFlyover() + } + return p.processHBIRDBestEffort() +} + +func (p *scionPacketProcessor) processHBIRDFlyover() (processResult, error) { + + if r, err := p.validateReservationExpiry(); err != nil { + return r, err + } + if r, err := p.verifyHbirdFlyoverMac(); err != nil { + return r, err + } + p.validatePathMetaTimestamp() + if r, err := p.checkReservationBandwidth(); err != nil { + return r, err + } + if r, err := p.handleHbirdIngressRouterAlert(); err != nil { + return r, err + } + // Inbound: pkts destined to the local IA. + if p.scionLayer.DstIA == p.d.localIA { + + if r, err := p.deAggregateMac(); err != nil { + return r, err + } + a, r, err := p.resolveInbound() + if err != nil { + return r, err + } + return processResult{OutAddr: a, OutPkt: p.rawPkt}, nil + } + + // Outbound: pkts leaving the local IA. + // BRTransit: pkts leaving from the same BR different interface. + if !p.peering && p.hbirdPath.IsXover() { + if r, err := p.doHbirdXoverFlyover(); err != nil { + return r, err + } + if r, err := p.validateHopExpiry(); err != nil { + return r, serrors.WithCtx(err, "info", "after xover") + } + // verify the new hopField + if r, err := p.verifyHbirdScionMac(); err != nil { + return r, err + } + } + if r, err := p.validateEgressID(); err != nil { + return r, err + } + // handle egress router alert before we check if it's up because we want to + // send the reply anyway, so that trace route can pinpoint the exact link + // that failed. + if r, err := p.handleHbirdEgressRouterAlert(); err != nil { + return r, err + } + if r, err := p.validateEgressUp(); err != nil { + return r, err + } + + egressID := p.egressInterface() + if _, ok := p.d.external[egressID]; ok { + if r, err := p.deAggregateMac(); err != nil { + return r, err + } + if p.hbirdPath.IsFirstHopAfterXover() && !p.effectiveXover && !p.peering { + if err := p.reverseFlyoverXover(); err != nil { + return processResult{}, err + } + } + if err := p.processHbirdEgress(); err != nil { + return processResult{}, err + } + return processResult{EgressID: egressID, OutPkt: p.rawPkt}, nil + } + // ASTransit: pkts leaving from another AS BR. + if a, ok := p.d.internalNextHops[egressID]; ok { + if p.isFlyoverXover { + if err := p.doFlyoverXover(); err != nil { + return processResult{}, err + } + } + return processResult{OutAddr: a, OutPkt: p.rawPkt}, nil + } + errCode := slayers.SCMPCodeUnknownHopFieldEgress + if !p.infoField.ConsDir { + errCode = slayers.SCMPCodeUnknownHopFieldIngress + } + log.Debug("SCMP: cannot route") + slowPathRequest := slowPathRequest{ + scmpType: slayers.SCMPTypeParameterProblem, + code: errCode, + pointer: p.currentHopPointer(), + cause: cannotRoute, + } + return processResult{SlowPathRequest: slowPathRequest}, slowPathRequired +} + +func (p *scionPacketProcessor) processHBIRDBestEffort() (processResult, error) { + + if err := p.updateHbirdNonConsDirIngressSegID(); err != nil { + return processResult{}, err + } + if r, err := p.verifyHbirdScionMac(); err != nil { + return r, err + } + if r, err := p.handleHbirdIngressRouterAlert(); err != nil { + return r, err + } + // Inbound: pkts destined to the local IA. + if p.scionLayer.DstIA == p.d.localIA { + a, r, err := p.resolveInbound() + if err != nil { + return r, err + } + return processResult{OutAddr: a, OutPkt: p.rawPkt}, nil + } + + // Outbound: pkts leaving the local IA. + // BRTransit: pkts leaving from the same BR different interface. + if !p.peering && p.hbirdPath.IsXover() { + if r, err := p.doHbirdXoverBestEffort(); err != nil { + return r, err + } + if r, err := p.validateHopExpiryHbird(); err != nil { + return r, serrors.WithCtx(err, "info", "after xover") + } + // verify the new hopField + if r, err := p.verifyHbirdScionMac(); err != nil { + return r, err + } + } + if r, err := p.validateEgressID(); err != nil { + return r, err + } + // handle egress router alert before we check if it's up because we want to + // send the reply anyway, so that trace route can pinpoint the exact link + // that failed. + if r, err := p.handleHbirdEgressRouterAlert(); err != nil { + return r, err + } + if r, err := p.validateEgressUp(); err != nil { + return r, err + } + + egressID := p.egressInterface() + if _, ok := p.d.external[egressID]; ok { + if err := p.processHbirdEgress(); err != nil { + return processResult{}, err + } + return processResult{EgressID: egressID, OutPkt: p.rawPkt}, nil + } + // ASTransit: pkts leaving from another AS BR. + if a, ok := p.d.internalNextHops[egressID]; ok { + return processResult{OutAddr: a, OutPkt: p.rawPkt}, nil + } + errCode := slayers.SCMPCodeUnknownHopFieldEgress + if !p.infoField.ConsDir { + errCode = slayers.SCMPCodeUnknownHopFieldIngress + } + log.Debug("SCMP: cannot route") + slowPathRequest := slowPathRequest{ + scmpType: slayers.SCMPTypeParameterProblem, + code: errCode, + pointer: p.currentHopPointer(), + cause: cannotRoute, + } + return processResult{SlowPathRequest: slowPathRequest}, slowPathRequired +} + +// Functions for SCMP packets preparation + +func (p *slowPathPacketProcessor) prepareHbirdSCMP( + typ slayers.SCMPType, + code slayers.SCMPCode, + scmpP gopacket.SerializableLayer, + cause error, +) ([]byte, error) { + + path, ok := p.scionLayer.Path.(*hummingbird.Raw) + if !ok { + return nil, serrors.WithCtx(cannotRoute, "details", "unsupported path type", + "path type", hummingbird.PathType) + } + decPath, err := path.ToDecoded() + if err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "decoding raw path") + } + revPathTmp, err := decPath.Reverse() + if err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "reversing path for SCMP") + } + revPath := revPathTmp.(*hummingbird.Decoded) + + peering, err := determinePeerHbird(revPath.PathMeta, + revPath.InfoFields[revPath.PathMeta.CurrINF]) + if err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "peering cannot be determined") + } + + // Revert potential path segment switches that were done during processing. + if revPath.IsXover() && !peering { + // An effective cross-over is a change of segment other than at + // a peering hop. + if err := revPath.IncPath(hummingbird.HopLines); err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "reverting cross over for SCMP") + } + } + + // If the packet is sent to an external router, we need to increment the + // path to prepare it for the next hop. + _, external := p.d.external[p.ingressID] + if external { + infoField := &revPath.InfoFields[revPath.PathMeta.CurrINF] + if infoField.ConsDir && !peering { + hopField := revPath.HopFields[revPath.PathMeta.CurrHF] + infoField.UpdateSegID(hopField.HopField.Mac) + } + if err := revPath.IncPath(hummingbird.HopLines); err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "incrementing path for SCMP") + } + } //TODO else, make sure MAC is deaggregated? + + var scionL slayers.SCION + scionL.FlowID = p.scionLayer.FlowID + scionL.TrafficClass = p.scionLayer.TrafficClass + scionL.PathType = revPath.Type() + scionL.Path = revPath + scionL.DstIA = p.scionLayer.SrcIA + scionL.SrcIA = p.d.localIA + srcA, err := p.scionLayer.SrcAddr() + if err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "extracting src addr") + } + if err := scionL.SetDstAddr(srcA); err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "setting dest addr") + } + if err := scionL.SetSrcAddr(addr.HostIP(p.d.internalIP)); err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "setting src addr") + } + scionL.NextHdr = slayers.L4SCMP + + typeCode := slayers.CreateSCMPTypeCode(typ, code) + scmpH := slayers.SCMP{TypeCode: typeCode} + scmpH.SetNetworkLayerForChecksum(&scionL) + + needsAuth := false + if p.d.ExperimentalSCMPAuthentication { + // Error messages must be authenticated. + // Traceroute are OPTIONALLY authenticated ONLY IF the request + // was authenticated. + // TODO(JordiSubira): Reuse the key computed in p.hasValidAuth + // if SCMPTypeTracerouteReply to create the response. + needsAuth = cause != nil || + (scmpH.TypeCode.Type() == slayers.SCMPTypeTracerouteReply && + p.hasValidAuth(time.Now())) + } + + var quote []byte + if cause != nil { + // add quote for errors. + hdrLen := slayers.CmnHdrLen + scionL.AddrHdrLen() + scionL.Path.Len() + if needsAuth { + hdrLen += e2eAuthHdrLen + } + switch scmpH.TypeCode.Type() { + case slayers.SCMPTypeExternalInterfaceDown: + hdrLen += 20 + case slayers.SCMPTypeInternalConnectivityDown: + hdrLen += 28 + default: + hdrLen += 8 + } + quote = p.rawPkt + maxQuoteLen := slayers.MaxSCMPPacketLen - hdrLen + if len(quote) > maxQuoteLen { + quote = quote[:maxQuoteLen] + } + } + + if err := p.buffer.Clear(); err != nil { + return nil, err + } + sopts := gopacket.SerializeOptions{ + ComputeChecksums: true, + FixLengths: true, + } + // First write the SCMP message only without the SCION header(s) to get a buffer that we + // can (re-)use as input in the MAC computation. + // XXX(matzf) could we use iovec gather to avoid copying quote? + err = gopacket.SerializeLayers(p.buffer, sopts, &scmpH, scmpP, gopacket.Payload(quote)) + if err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "serializing SCMP message") + } + + if needsAuth { + var e2e slayers.EndToEndExtn + scionL.NextHdr = slayers.End2EndClass + + now := time.Now() + // srcA == scionL.DstAddr + key, err := p.drkeyProvider.GetASHostKey(now, scionL.DstIA, srcA) + if err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "retrieving DRKey") + } + if err := p.resetSPAOMetadata(key, now); err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "resetting SPAO header") + } + + e2e.Options = []*slayers.EndToEndOption{p.optAuth.EndToEndOption} + e2e.NextHdr = slayers.L4SCMP + _, err = spao.ComputeAuthCMAC( + spao.MACInput{ + Key: key.Key[:], + Header: p.optAuth, + ScionLayer: &scionL, + PldType: slayers.L4SCMP, + Pld: p.buffer.Bytes(), + }, + p.macInputBuffer, + p.optAuth.Authenticator(), + ) + if err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "computing CMAC") + } + if err := e2e.SerializeTo(p.buffer, sopts); err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "serializing SCION E2E headers") + } + } else { + scionL.NextHdr = slayers.L4SCMP + } + if err := scionL.SerializeTo(p.buffer, sopts); err != nil { + return nil, serrors.Wrap(cannotRoute, err, "details", "serializing SCION header") + } + + log.Debug("scmp", "typecode", scmpH.TypeCode, "cause", cause) + return p.buffer.Bytes(), nil +} diff --git a/router/dataplane_hbird_test.go b/router/dataplane_hbird_test.go new file mode 100644 index 0000000000..c1a20bfcfe --- /dev/null +++ b/router/dataplane_hbird_test.go @@ -0,0 +1,3953 @@ +// Copyright 2020 Anapaya Systems +// Copyright 2023 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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 router_test + +import ( + "crypto/aes" + "net" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/google/gopacket" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/net/ipv4" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/private/util" + "github.com/scionproto/scion/pkg/private/xtest" + "github.com/scionproto/scion/pkg/slayers" + "github.com/scionproto/scion/pkg/slayers/path" + "github.com/scionproto/scion/pkg/slayers/path/hummingbird" + "github.com/scionproto/scion/private/topology" + "github.com/scionproto/scion/router" + "github.com/scionproto/scion/router/mock_router" +) + +func TestDataPlaneSetSecretValue(t *testing.T) { + t.Run("fails after serve", func(t *testing.T) { + d := &router.DataPlane{} + d.FakeStart() + assert.Error(t, d.SetHbirdKey([]byte("dummy"))) + }) + t.Run("setting nil value is not allowed", func(t *testing.T) { + d := &router.DataPlane{} + d.FakeStart() + assert.Error(t, d.SetHbirdKey(nil)) + }) + t.Run("single set works", func(t *testing.T) { + d := &router.DataPlane{} + assert.NoError(t, d.SetHbirdKey([]byte("dummy key xxxxxx"))) + }) + t.Run("double set fails", func(t *testing.T) { + d := &router.DataPlane{} + assert.NoError(t, d.SetHbirdKey([]byte("dummy key xxxxxx"))) + assert.Error(t, d.SetHbirdKey([]byte("dummy key xxxxxx"))) + }) +} + +func TestProcessHbirdPacket(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + now := time.Now() + + testCases := map[string]struct { + mockMsg func(bool) *ipv4.Message + prepareDP func(*gomock.Controller) *router.DataPlane + srcInterface uint16 + egressInterface uint16 + assertFunc assert.ErrorAssertionFunc + }{ + "inbound": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, + nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + spkt.DstIA = xtest.MustParseIA("1-ff00:0:110") + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 41, ConsEgress: 40}}, + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {HopField: path.HopField{ConsIngress: 01, ConsEgress: 0}}, + } + dpath.Base.PathMeta.CurrHF = 6 + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[2].HopField) + ret := toMsg(t, spkt, dpath) + if afterProcessing { + ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: topology.EndhostPort} + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + + } + return ret + }, + srcInterface: 1, + egressInterface: 0, + assertFunc: assert.NoError, + }, + "outbound": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + spkt.SrcIA = xtest.MustParseIA("1-ff00:0:110") + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 1}}, + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {HopField: path.HopField{ConsIngress: 41, ConsEgress: 40}}, + } + dpath.Base.PathMeta.CurrHF = 0 + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + if !afterProcessing { + return toMsg(t, spkt, dpath) + } + _ = dpath.IncPath(hummingbird.HopLines) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 0, + egressInterface: 1, + assertFunc: assert.NoError, + }, + "brtransit": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Parent, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + } + dpath.Base.PathMeta.CurrHF = 3 + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + if !afterProcessing { + return toMsg(t, spkt, dpath) + } + _ = dpath.IncPath(hummingbird.HopLines) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 1, + egressInterface: 2, + assertFunc: assert.NoError, + }, + "brtransit non consdir": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 2: topology.Parent, + 1: topology.Child, + }, nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {HopField: path.HopField{ConsIngress: 2, ConsEgress: 1}}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + } + dpath.Base.PathMeta.CurrHF = 3 + dpath.InfoFields[0].ConsDir = false + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + if !afterProcessing { + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + return toMsg(t, spkt, dpath) + } + require.NoError(t, dpath.IncPath(hummingbird.HopLines)) + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 1, + egressInterface: 2, + assertFunc: assert.NoError, + }, + "astransit direct": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP(nil, + map[uint16]topology.LinkType{ + 1: topology.Core, + 3: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(3): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 3}}, + {HopField: path.HopField{ConsIngress: 50, ConsEgress: 51}}, + } + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + ret := toMsg(t, spkt, dpath) + if afterProcessing { + ret.Addr = &net.UDPAddr{IP: net.ParseIP("10.0.200.200").To4(), Port: 30043} + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + } + return ret + }, + srcInterface: 1, + egressInterface: 0, + assertFunc: assert.NoError, + }, + "astransit xover": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP(nil, + map[uint16]topology.LinkType{ + 51: topology.Child, + 31: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(51): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, _ := prepHbirdMsg(now) + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + SegLen: [3]uint8{6, 6, 0}, + }, + NumINF: 2, + NumLines: 12, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + // core seg + {SegID: 0x222, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 31}}, + {HopField: path.HopField{ConsIngress: 51, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 3}}, + }, + } + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + + if !afterProcessing { + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + return toMsg(t, spkt, dpath) + } + require.NoError(t, dpath.IncPath(hummingbird.HopLines)) + ret := toMsg(t, spkt, dpath) + ret.Addr = &net.UDPAddr{IP: net.ParseIP("10.0.200.200").To4(), Port: 30043} + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 31, + egressInterface: 0, + assertFunc: assert.NoError, + }, + "invalid dest": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, + map[addr.SVC][]*net.UDPAddr{}, + xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + spkt.DstIA = xtest.MustParseIA("1-ff00:0:f1") + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 41, ConsEgress: 40}}, + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 404}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 0}}, + } + dpath.Base.PathMeta.CurrHF = 6 + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + ret := toMsg(t, spkt, dpath) + return ret + }, + srcInterface: 1, + assertFunc: assert.Error, + }, + "brtransit peering consdir": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + // Story: the packet just left segment 0 which ends at + // (peering) hop 0 and is landing on segment 1 which + // begins at (peering) hop 1. We do not care what hop 0 + // looks like. The forwarding code is looking at hop 1 and + // should leave the message in shape to be processed at hop 2. + spkt, _ := prepHbirdMsg(now) + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + CurrINF: 1, + SegLen: [3]uint8{3, 6, 0}, + }, + NumINF: 2, + NumLines: 9, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + // core seg + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + }, + } + + // Make obvious the unusual aspect of the path: two + // hopfield MACs (1 and 2) derive from the same SegID + // accumulator value. However, the forwarding code isn't + // supposed to even look at the second one. The SegID + // accumulator value can be anything (it comes from the + // parent hop of HF[1] in the original beaconned segment, + // which is not in the path). So, we use one from an + // info field because computeMAC makes that easy. + dpath.HopFields[1].HopField.Mac = computeMAC( + t, key, dpath.InfoFields[1], dpath.HopFields[1].HopField) + dpath.HopFields[2].HopField.Mac = computeMAC( + t, sv, dpath.InfoFields[1], dpath.HopFields[2].HopField) + if !afterProcessing { + return toMsg(t, spkt, dpath) + } + _ = dpath.IncPath(hummingbird.HopLines) + + // ... The SegID accumulator wasn't updated from HF[1], + // it is still the same. That is the key behavior. + + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 1, // from peering link + egressInterface: 2, + assertFunc: assert.NoError, + }, + "brtransit peering non consdir": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + // Story: the packet lands on the last (peering) hop of + // segment 0. After processing, the packet is ready to + // be processed by the first (peering) hop of segment 1. + spkt, _ := prepHbirdMsg(now) + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + CurrINF: 0, + SegLen: [3]uint8{6, 3, 0}, + }, + NumINF: 2, + NumLines: 9, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now), Peer: true}, + // down seg + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + }, + } + + // Make obvious the unusual aspect of the path: two + // hopfield MACs (0 and 1) derive from the same SegID + // accumulator value. However, the forwarding code isn't + // supposed to even look at the first one. The SegID + // accumulator value can be anything (it comes from the + // parent hop of HF[1] in the original beaconned segment, + // which is not in the path). So, we use one from an + // info field because computeMAC makes that easy. + dpath.HopFields[0].HopField.Mac = computeMAC( + t, sv, dpath.InfoFields[0], dpath.HopFields[0].HopField) + dpath.HopFields[1].HopField.Mac = computeMAC( + t, key, dpath.InfoFields[0], dpath.HopFields[1].HopField) + + // We're going against construction order, so the accumulator + // value is that of the previous hop in traversal order. The + // story starts with the packet arriving at hop 1, so the + // accumulator value must match hop field 0. In this case, + // it is identical to that for hop field 1, which we made + // identical to the original SegID. So, we're all set. + if !afterProcessing { + return toMsg(t, spkt, dpath) + } + + _ = dpath.IncPath(hummingbird.HopLines) + + // The SegID should not get updated on arrival. If it is, then MAC validation + // of HF1 will fail. Otherwise, this isn't visible because we changed segment. + + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 2, // from child link + egressInterface: 1, + assertFunc: assert.NoError, + }, + "peering consdir downstream": { + // Similar to previous test case but looking at what + // happens on the next hop. + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + // Story: the packet just left hop 1 (the first hop + // of peering down segment 1) and is processed at hop 2 + // which is not a peering hop. + spkt, _ := prepHbirdMsg(now) + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 6, + CurrINF: 1, + SegLen: [3]uint8{3, 9, 0}, + }, + NumINF: 2, + NumLines: 12, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + // core seg + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 50, ConsEgress: 51}}, + // There has to be a 4th hop to make + // the 3rd router agree that the packet + // is not at destination yet. + }, + } + + // Make obvious the unusual aspect of the path: two + // hopfield MACs (1 and 2) derive from the same SegID + // accumulator value. The router shouldn't need to + // know this or do anything special. The SegID + // accumulator value can be anything (it comes from the + // parent hop of HF[1] in the original beaconned segment, + // which is not in the path). So, we use one from an + // info field because computeMAC makes that easy. + dpath.HopFields[1].HopField.Mac = computeMAC( + t, sv, dpath.InfoFields[1], dpath.HopFields[1].HopField) + dpath.HopFields[2].HopField.Mac = computeMAC( + t, key, dpath.InfoFields[1], dpath.HopFields[2].HopField) + if !afterProcessing { + // The SegID we provide is that of HF[2] which happens to be SEG[1]'s SegID, + // so, already set. + return toMsg(t, spkt, dpath) + } + _ = dpath.IncPath(hummingbird.HopLines) + + // ... The SegID accumulator should have been updated. + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[2].HopField.Mac) + + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 1, + egressInterface: 2, + assertFunc: assert.NoError, + }, + "peering non consdir upstream": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + // Story: the packet lands on the second (non-peering) hop of + // segment 0 (a peering segment). After processing, the packet + // is ready to be processed by the third (peering) hop of segment 0. + spkt, _ := prepHbirdMsg(now) + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + CurrINF: 0, + SegLen: [3]uint8{9, 3, 0}, + }, + NumINF: 2, + NumLines: 12, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now), Peer: true}, + // down seg + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + {HopField: path.HopField{ConsIngress: 50, ConsEgress: 51}}, + // The second segment (4th hop) has to be + // there but the packet isn't processed + // at that hop for this test. + }, + } + + // Make obvious the unusual aspect of the path: two + // hopfield MACs (1 and 2) derive from the same SegID + // accumulator value. The SegID accumulator value can + // be anything (it comes from the parent hop of HF[1] + // in the original beaconned segment, which is not in + // the path). So, we use one from an info field because + // computeMAC makes that easy. + dpath.HopFields[1].HopField.Mac = computeMAC( + t, key, dpath.InfoFields[0], dpath.HopFields[1].HopField) + dpath.HopFields[2].HopField.Mac = computeMAC( + t, sv, dpath.InfoFields[0], dpath.HopFields[2].HopField) + + if !afterProcessing { + // We're going against construction order, so the + // before-processing accumulator value is that of + // the previous hop in traversal order. The story + // starts with the packet arriving at hop 1, so the + // accumulator value must match hop field 0, which + // derives from hop field[1]. HopField[0]'s MAC is + // not checked during this test. + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + + return toMsg(t, spkt, dpath) + } + + _ = dpath.IncPath(hummingbird.HopLines) + + // After-processing, the SegID should have been updated + // (on ingress) to be that of HF[1], which happens to be + // the Segment's SegID. That is what we already have as + // we only change it in the before-processing version + // of the packet. + + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 2, // from child link + egressInterface: 1, + assertFunc: assert.NoError, + }, + "inbound flyover": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, + nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + spkt.DstIA = xtest.MustParseIA("1-ff00:0:110") + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 41, ConsEgress: 40}}, + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 01, ConsEgress: 0}, + ResStartTime: 123, Duration: 304, Bw: 16}, + } + dpath.Base.PathMeta.SegLen[0] = 11 + dpath.Base.NumLines = 11 + dpath.Base.PathMeta.CurrHF = 6 + dpath.HopFields[2].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[2], dpath.PathMeta) + if afterProcessing { + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[2].HopField) + ret := toMsg(t, spkt, dpath) + ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: topology.EndhostPort} + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + } + return toMsg(t, spkt, dpath) + }, + srcInterface: 1, + egressInterface: 0, + assertFunc: assert.NoError, + }, + "outbound flyover": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + spkt.SrcIA = xtest.MustParseIA("1-ff00:0:110") + dpath.HopFields = []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 1}, + ResStartTime: 123, Duration: 304, Bw: 16}, + {Flyover: true, HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}, + ResStartTime: 123, Duration: 304, Bw: 16}, + {Flyover: true, HopField: path.HopField{ConsIngress: 41, ConsEgress: 40}, + ResStartTime: 123, Duration: 304, Bw: 16}, + } + dpath.Base.PathMeta.CurrHF = 0 + dpath.Base.PathMeta.SegLen[0] = 15 + dpath.NumLines = 15 + dpath.HopFields[0].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[0], dpath.Base.PathMeta) + if !afterProcessing { + return toMsg(t, spkt, dpath) + } + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + _ = dpath.IncPath(hummingbird.FlyoverLines) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 0, + egressInterface: 1, + assertFunc: assert.NoError, + }, + "reservation expired": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, + nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + spkt.DstIA = xtest.MustParseIA("1-ff00:0:110") + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 41, ConsEgress: 40}}, + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 01, ConsEgress: 0}, + ResStartTime: 5, Duration: 2}, + } + dpath.Base.PathMeta.SegLen[0] = 11 + dpath.Base.NumLines = 11 + dpath.Base.PathMeta.CurrHF = 6 + dpath.HopFields[2].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[2], dpath.PathMeta) + ret := toMsg(t, spkt, dpath) + if afterProcessing { + ret.Addr = &net.UDPAddr{IP: dst.IP().AsSlice(), Port: topology.EndhostPort} + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + } + return ret + }, + srcInterface: 1, + assertFunc: assert.Error, + }, + "brtransit flyover": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Parent, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + } + dpath.Base.PathMeta.SegLen[0] = 11 + dpath.Base.PathMeta.CurrHF = 3 + dpath.Base.NumLines = 11 + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.Base.PathMeta) + if !afterProcessing { + return toMsg(t, spkt, dpath) + } + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + _ = dpath.IncPath(hummingbird.FlyoverLines) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 1, + egressInterface: 2, + assertFunc: assert.NoError, + }, + "brtransit non consdir flyover": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 2: topology.Parent, + 1: topology.Child, + }, nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 2, ConsEgress: 1}, + ResID: 42, ResStartTime: 5, Duration: 301, Bw: 16}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + } + dpath.Base.PathMeta.CurrHF = 3 + dpath.Base.NumLines = 11 + dpath.Base.PathMeta.SegLen[0] = 11 + + dpath.InfoFields[0].ConsDir = false + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.PathMeta) + if !afterProcessing { + dpath.InfoFields[0].UpdateSegID(computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField)) + return toMsg(t, spkt, dpath) + } + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + require.NoError(t, dpath.IncPath(hummingbird.FlyoverLines)) + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 1, + egressInterface: 2, + assertFunc: assert.NoError, + }, + "astransit direct flyover": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP(nil, + map[uint16]topology.LinkType{ + 1: topology.Core, + 3: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(3): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, dpath := prepHbirdMsg(now) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 3}, + ResID: 42, ResStartTime: 5, Duration: 301, Bw: 16}, + {HopField: path.HopField{ConsIngress: 50, ConsEgress: 51}}, + } + dpath.Base.PathMeta.SegLen[0] = 11 + dpath.Base.NumLines = 11 + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.Base.PathMeta) + if afterProcessing { + ret := toMsg(t, spkt, dpath) + ret.Addr = &net.UDPAddr{IP: net.ParseIP("10.0.200.200").To4(), Port: 30043} + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + } + return toMsg(t, spkt, dpath) + }, + srcInterface: 1, + egressInterface: 0, + assertFunc: assert.NoError, + }, + "astransit xover flyover ingress": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP(nil, + map[uint16]topology.LinkType{ + 51: topology.Child, + 31: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(31): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, _ := prepHbirdMsg(now) + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + SegLen: [3]uint8{8, 6, 0}, + BaseTS: util.TimeToSecs(now), + }, + NumINF: 2, + NumLines: 14, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + // core seg + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 1}}, // IA 110 + {Flyover: true, HopField: path.HopField{ConsIngress: 51, ConsEgress: 0}, + Bw: 5, ResStartTime: 5, Duration: 310}, // Src + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 31}}, // Dst + {HopField: path.HopField{ConsIngress: 3, ConsEgress: 0}}, // IA 110 + }, + } + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + dpath.HopFields[1].HopField.Mac = computeAggregateMacXover(t, key, sv, + spkt.DstIA, spkt.PayloadLen, 51, 31, + dpath.InfoFields[0], dpath.HopFields[1], dpath.PathMeta) + if !afterProcessing { + + return toMsg(t, spkt, dpath) + } + dpath.HopFields[1].Flyover = false + dpath.HopFields[2].Flyover = true + dpath.HopFields[2].Bw = 5 + dpath.HopFields[2].ResStartTime = 5 + dpath.HopFields[2].Duration = 310 + + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + dpath.HopFields[2].HopField.Mac = computeAggregateMacXover(t, key, sv, + spkt.DstIA, spkt.PayloadLen, 51, 31, + dpath.InfoFields[1], dpath.HopFields[2], dpath.PathMeta) + dpath.PathMeta.SegLen[0] -= 2 + dpath.PathMeta.SegLen[1] += 2 + require.NoError(t, dpath.IncPath(hummingbird.HopLines)) + ret := toMsg(t, spkt, dpath) + ret.Addr = &net.UDPAddr{IP: net.ParseIP("10.0.200.200").To4(), Port: 30043} + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 51, + egressInterface: 0, + assertFunc: assert.NoError, + }, + "astransit xover flyover egress": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 51: topology.Child, + 31: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(51): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + spkt, _ := prepHbirdMsg(now) + spkt.SrcIA = 109 + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 6, + CurrINF: 1, + SegLen: [3]uint8{6, 8, 0}, + BaseTS: util.TimeToSecs(now), + }, + NumINF: 2, + NumLines: 14, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + // core seg + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 1}}, // Src + {HopField: path.HopField{ConsIngress: 51, ConsEgress: 0}}, // IA 110 + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 31}, + Bw: 5, ResStartTime: 5, Duration: 310}, // IA 110 + {HopField: path.HopField{ConsIngress: 3, ConsEgress: 0}}, // Dst + }, + } + dpath.HopFields[2].HopField.Mac = computeAggregateMacXover(t, key, sv, + spkt.DstIA, spkt.PayloadLen, 51, 31, + dpath.InfoFields[1], dpath.HopFields[2], dpath.PathMeta) + if !afterProcessing { + ret := toMsg(t, spkt, dpath) + ret.Addr = &net.UDPAddr{IP: net.ParseIP("10.0.200.200").To4(), Port: 30043} + return ret + } + dpath.HopFields[2].Flyover = false + dpath.HopFields[1].Flyover = true + dpath.HopFields[1].Bw = 5 + dpath.HopFields[1].ResStartTime = 5 + dpath.HopFields[1].Duration = 310 + + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[2].HopField.Mac) + require.NoError(t, dpath.IncPath(hummingbird.FlyoverLines)) + dpath.PathMeta.SegLen[0] += 2 + dpath.PathMeta.SegLen[1] -= 2 + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 0, + egressInterface: 31, + assertFunc: assert.NoError, + }, + "brtransit peering consdir flyovers": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + // Story: the packet just left segment 0 which ends at + // (peering) hop 0 and is landing on segment 1 which + // begins at (peering) hop 1. We do not care what hop 0 + // looks like. The forwarding code is looking at hop 1 and + // should leave the message in shape to be processed at hop 2. + spkt, _ := prepHbirdMsg(now) + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + CurrINF: 1, + SegLen: [3]uint8{3, 8, 0}, + BaseTS: util.TimeToSecs(now), + }, + NumINF: 2, + NumLines: 11, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + // core seg + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + }, + } + + // Make obvious the unusual aspect of the path: two + // hopfield MACs (1 and 2) derive from the same SegID + // accumulator value. However, the forwarding code isn't + // supposed to even look at the second one. The SegID + // accumulator value can be anything (it comes from the + // parent hop of HF[1] in the original beaconned segment, + // which is not in the path). So, we use one from an + // info field because computeMAC makes that easy. + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[1], dpath.HopFields[1], dpath.PathMeta) + dpath.HopFields[2].HopField.Mac = computeMAC( + t, sv, dpath.InfoFields[1], dpath.HopFields[2].HopField) + if !afterProcessing { + return toMsg(t, spkt, dpath) + } + _ = dpath.IncPath(hummingbird.FlyoverLines) + // deaggregate MAC + dpath.HopFields[1].HopField.Mac = computeMAC( + t, key, dpath.InfoFields[1], dpath.HopFields[1].HopField) + // ... The SegID accumulator wasn't updated from HF[1], + // it is still the same. That is the key behavior. + + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 1, // from peering link + egressInterface: 2, + assertFunc: assert.NoError, + }, + "brtransit peering non consdir flyovers": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + // Story: the packet lands on the last (peering) hop of + // segment 0. After processing, the packet is ready to + // be processed by the first (peering) hop of segment 1. + spkt, _ := prepHbirdMsg(now) + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + CurrINF: 0, + SegLen: [3]uint8{8, 3, 0}, + BaseTS: util.TimeToSecs(now), + }, + NumINF: 2, + NumLines: 11, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now), Peer: true}, + // down seg + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + }, + } + + // Make obvious the unusual aspect of the path: two + // hopfield MACs (0 and 1) derive from the same SegID + // accumulator value. However, the forwarding code isn't + // supposed to even look at the first one. The SegID + // accumulator value can be anything (it comes from the + // parent hop of HF[1] in the original beaconned segment, + // which is not in the path). So, we use one from an + // info field because computeMAC makes that easy. + dpath.HopFields[0].HopField.Mac = computeMAC( + t, sv, dpath.InfoFields[0], dpath.HopFields[0].HopField) + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.PathMeta) + + // We're going against construction order, so the accumulator + // value is that of the previous hop in traversal order. The + // story starts with the packet arriving at hop 1, so the + // accumulator value must match hop field 0. In this case, + // it is identical to that for hop field 1, which we made + // identical to the original SegID. So, we're all set. + if !afterProcessing { + return toMsg(t, spkt, dpath) + } + + _ = dpath.IncPath(hummingbird.FlyoverLines) + // deaggregate MAc + dpath.HopFields[1].HopField.Mac = computeMAC( + t, key, dpath.InfoFields[0], dpath.HopFields[1].HopField) + + // The SegID should not get updated on arrival. If it is, then MAC validation + // of HF1 will fail. Otherwise, this isn't visible because we changed segment. + + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 2, // from child link + egressInterface: 1, + assertFunc: assert.NoError, + }, + "peering consdir downstream flyovers": { + // Similar to previous test case but looking at what + // happens on the next hop. + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + // Story: the packet just left hop 1 (the first hop + // of peering down segment 1) and is processed at hop 2 + // which is not a peering hop. + spkt, _ := prepHbirdMsg(now) + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 6, + CurrINF: 1, + SegLen: [3]uint8{3, 11, 0}, + BaseTS: util.TimeToSecs(now), + }, + NumINF: 2, + NumLines: 14, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + // core seg + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 50, ConsEgress: 51}}, + // There has to be a 4th hop to make + // the 3rd router agree that the packet + // is not at destination yet. + }, + } + + // Make obvious the unusual aspect of the path: two + // hopfield MACs (1 and 2) derive from the same SegID + // accumulator value. The router shouldn't need to + // know this or do anything special. The SegID + // accumulator value can be anything (it comes from the + // parent hop of HF[1] in the original beaconned segment, + // which is not in the path). So, we use one from an + // info field because computeMAC makes that easy. + dpath.HopFields[1].HopField.Mac = computeMAC( + t, sv, dpath.InfoFields[1], dpath.HopFields[1].HopField) + dpath.HopFields[2].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[1], dpath.HopFields[2], dpath.PathMeta) + if !afterProcessing { + // The SegID we provide is that of HF[2] which happens to be SEG[1]'s SegID, + // so, already set. + return toMsg(t, spkt, dpath) + } + _ = dpath.IncPath(hummingbird.FlyoverLines) + // mac should be deaggregated, and used for updateSegID + dpath.HopFields[2].HopField.Mac = computeMAC( + t, key, dpath.InfoFields[1], dpath.HopFields[2].HopField) + + // ... The SegID accumulator should have been updated. + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[2].HopField.Mac) + + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 1, + egressInterface: 2, + assertFunc: assert.NoError, + }, + "peering non consdir upstream flyovers": { + prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { + return router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + }, + mockMsg: func(afterProcessing bool) *ipv4.Message { + // Story: the packet lands on the second (non-peering) hop of + // segment 0 (a peering segment). After processing, the packet + // is ready to be processed by the third (peering) hop of segment 0. + spkt, _ := prepHbirdMsg(now) + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + CurrINF: 0, + SegLen: [3]uint8{11, 3, 0}, + BaseTS: util.TimeToSecs(now), + }, + NumINF: 2, + NumLines: 14, + }, + InfoFields: []path.InfoField{ + // up seg + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now), Peer: true}, + // down seg + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now), Peer: true}, + }, + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + {HopField: path.HopField{ConsIngress: 50, ConsEgress: 51}}, + // The second segment (4th hop) has to be + // there but the packet isn't processed + // at that hop for this test. + }, + } + + // Make obvious the unusual aspect of the path: two + // hopfield MACs (1 and 2) derive from the same SegID + // accumulator value. The SegID accumulator value can + // be anything (it comes from the parent hop of HF[1] + // in the original beaconned segment, which is not in + // the path). So, we use one from an info field because + // computeMAC makes that easy. + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.PathMeta) + dpath.HopFields[2].HopField.Mac = computeMAC( + t, sv, dpath.InfoFields[0], dpath.HopFields[2].HopField) + + if !afterProcessing { + // We're going against construction order, so the + // before-processing accumulator value is that of + // the previous hop in traversal order. The story + // starts with the packet arriving at hop 1, so the + // accumulator value must match hop field 0, which + // derives from hop field[1]. HopField[0]'s MAC is + // not checked during this test. + // Use de-aggregated MAC value for segID update + scionMac := computeMAC( + t, key, dpath.InfoFields[0], dpath.HopFields[1].HopField) + dpath.InfoFields[0].UpdateSegID(scionMac) + + return toMsg(t, spkt, dpath) + } + + _ = dpath.IncPath(hummingbird.FlyoverLines) + // deaggregate MAC) + dpath.HopFields[1].HopField.Mac = computeMAC( + t, key, dpath.InfoFields[0], dpath.HopFields[1].HopField) + + // After-processing, the SegID should have been updated + // (on ingress) to be that of HF[1], which happens to be + // the Segment's SegID. That is what we already have as + // we only change it in the before-processing version + // of the packet. + + ret := toMsg(t, spkt, dpath) + ret.Addr = nil + ret.Flags, ret.NN, ret.N, ret.OOB = 0, 0, 0, nil + return ret + }, + srcInterface: 2, // from child link + egressInterface: 1, + assertFunc: assert.NoError, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + dp := tc.prepareDP(ctrl) + input, want := tc.mockMsg(false), tc.mockMsg(true) + result, err := dp.ProcessPkt(tc.srcInterface, input) + tc.assertFunc(t, err) + if err != nil { + return + } + outPkt := &ipv4.Message{ + Buffers: [][]byte{result.OutPkt}, + Addr: result.OutAddr, + } + if result.OutAddr == nil { + outPkt.Addr = nil + } + assert.Equal(t, want, outPkt) + assert.Equal(t, tc.egressInterface, result.EgressID) + }) + } +} + +func TestHbirdPacketPath(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + now := time.Now() + + testCases := map[string]struct { + mockMsg func() *ipv4.Message + prepareDPs func(*gomock.Controller) []*router.DataPlane + srcInterfaces []uint16 + }{ + "two hops consdir": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:111"), + xtest.MustParseIA("1-ff00:0:110")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{6, 0, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 1, + NumLines: 6, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}}, + {HopField: path.HopField{ConsIngress: 01, ConsEgress: 0}}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + // Reset SegID to original value + dpath.InfoFields[0].SegID = 0x111 + ret := toMsg(t, spkt, dpath) + return ret + + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [2]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(01): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 01: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + + return dps[:] + }, + srcInterfaces: []uint16{0, 01}, + }, + "two hops non consdir": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:111")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{6, 0, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 1, + NumLines: 6, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 01, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + //dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + + ret := toMsg(t, spkt, dpath) + return ret + + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [2]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(01): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 01: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + return dps[:] + }, + srcInterfaces: []uint16{0, 40}, + }, + "six hops astransit xover consdir": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:111"), + xtest.MustParseIA("3-ff00:0:333")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{9, 9, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 18, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 31}}, + {HopField: path.HopField{ConsIngress: 5, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 7}}, + {HopField: path.HopField{ConsIngress: 11, ConsEgress: 8}}, + {HopField: path.HopField{ConsIngress: 3, ConsEgress: 0}}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[2].HopField) + + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[3].HopField.Mac) + dpath.HopFields[4].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[4].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[4].HopField.Mac) + dpath.HopFields[5].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[5].HopField) + // Reset SegID to original value + dpath.InfoFields[0].SegID = 0x111 + dpath.InfoFields[1].SegID = 0x222 + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [7]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 31: topology.Parent, + 1: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Child, + 7: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Child, + 7: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(5): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(11): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Core, + 11: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(8): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("2-ff00:0:222"), nil, key, sv) + + dps[5] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(8): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Core, + 11: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(11): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("2-ff00:0:222"), nil, key, sv) + + dps[6] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(3): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 3: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("3-ff00:0:333"), nil, key, sv) + + return dps[:] + }, // middle hop of second segment is astransit + srcInterfaces: []uint16{0, 1, 5, 0, 11, 0, 3}, + }, + "six hops astransit xover non consdir": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:111"), + xtest.MustParseIA("3-ff00:0:333")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{9, 9, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 18, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 1}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 5}}, + {HopField: path.HopField{ConsIngress: 7, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 8, ConsEgress: 11}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 3}}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[2].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[2].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.HopFields[5].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[5].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[5].HopField.Mac) + dpath.HopFields[4].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[4].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[4].HopField.Mac) + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + // Reset SegID to original value + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [7]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 31: topology.Parent, + 1: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Child, + 7: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Child, + 7: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(5): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(11): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Core, + 11: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(8): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("2-ff00:0:222"), nil, key, sv) + + dps[5] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(8): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Core, + 11: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(11): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("2-ff00:0:222"), nil, key, sv) + + dps[6] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(3): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 3: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("3-ff00:0:333"), nil, key, sv) + + return dps[:] + }, // middle hop of second segment is astransit + srcInterfaces: []uint16{0, 1, 5, 0, 11, 0, 3}, + }, + "six hops brtransit xover mixed consdir": { + // up segment non consdir, down segment consdir + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:111"), + xtest.MustParseIA("3-ff00:0:333")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{9, 9, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 18, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 1}}, + {HopField: path.HopField{ConsIngress: 7, ConsEgress: 5}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 7}}, + {HopField: path.HopField{ConsIngress: 11, ConsEgress: 8}}, + {HopField: path.HopField{ConsIngress: 3, ConsEgress: 0}}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[2].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[2].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[3].HopField.Mac) + dpath.HopFields[4].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[4].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[4].HopField.Mac) + dpath.HopFields[5].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[5].HopField) + // Reset SegID to original value + dpath.InfoFields[1].SegID = 0x222 + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [5]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 31: topology.Parent, + 1: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Child, + 7: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(8): mock_router.NewMockBatchConn(ctrl), + uint16(11): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Child, + 11: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("2-ff00:0:222"), nil, key, sv) + + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(3): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 3: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("3-ff00:0:333"), nil, key, sv) + + return dps[:] + }, // middle hop of second segment is astransit + srcInterfaces: []uint16{0, 1, 5, 11, 3}, + }, + "six hops three segs mixed consdir": { + // two crossovers, first crossover is brtransit, second one is astransit + // core segment is non consdir + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:113")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{6, 6, 6}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 3, + NumLines: 18, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x333, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 5, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 31}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 8}}, + {HopField: path.HopField{ConsIngress: 3, ConsEgress: 0}}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[3].HopField.Mac) + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + + dpath.HopFields[4].HopField.Mac = computeMAC(t, key, dpath.InfoFields[2], + dpath.HopFields[4].HopField) + dpath.InfoFields[2].UpdateSegID(dpath.HopFields[4].HopField.Mac) + dpath.HopFields[5].HopField.Mac = computeMAC(t, key, dpath.InfoFields[2], + dpath.HopFields[5].HopField) + // Reset SegID to original value + dpath.InfoFields[0].SegID = 0x111 + dpath.InfoFields[2].SegID = 0x333 + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [5]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Child, + 5: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Child, + 31: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(8): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(8): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Child, + 31: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(31): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(3): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 3: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + return dps[:] + }, + srcInterfaces: []uint16{0, 1, 31, 0, 3}, + }, + "three hops peering brtransit consdir": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:113")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{3, 6}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 9, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, Peer: true, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, Peer: true, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 5, ConsEgress: 0}}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[1].HopField) + // No Segment update here as the second hop of a peering path + // Uses the same segID as it's following hop + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [3]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Peer, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + return dps[:] + }, + srcInterfaces: []uint16{0, 1, 5}, + }, + "three hops peering brtransit non consdir": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:113")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{3, 6}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 9, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, Peer: true, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, Peer: true, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 2, ConsEgress: 1}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 5}}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[2].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[1].HopField) + // No Segment update here as the second hop of a peering path + // Uses the same segID as it's following hop + + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [3]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Peer, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + return dps[:] + }, + srcInterfaces: []uint16{0, 1, 5}, + }, + "four hops peering astransit consdir": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:113")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{6, 6}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 12, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, Peer: true, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, Peer: true, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}}, + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 7}}, + {HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}}, + {HopField: path.HopField{ConsIngress: 5, ConsEgress: 0}}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + // No Segment update here + // the second hop of a peering path uses the same segID as it's following hop + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + // reset segID + dpath.InfoFields[0].SegID = 0x111 + + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [6]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Peer, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Peer, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(31): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(2): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(1): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[5] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + return dps[:] + }, + srcInterfaces: []uint16{0, 31, 0, 1, 0, 5}, + }, + "four hops peering astransit non consdir": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:113")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{6, 6}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 12, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, Peer: true, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, Peer: true, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 0}}, + {HopField: path.HopField{ConsIngress: 7, ConsEgress: 31}}, + {HopField: path.HopField{ConsIngress: 2, ConsEgress: 1}}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 5}}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + // No Segment update here + // the second hop of a peering path uses the same segID as it's following hop + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[3].HopField.Mac) + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [6]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Peer, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Peer, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(31): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(2): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(1): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[5] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + return dps[:] + }, + srcInterfaces: []uint16{0, 31, 0, 1, 0, 5}, + }, + "two hops consdir flyovers": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:111"), + xtest.MustParseIA("1-ff00:0:110")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{10, 0, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 1, + NumLines: 10, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 01, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + // add flyover macs + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 0, 40, + dpath.InfoFields[0], &dpath.HopFields[0], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 1, 0, + dpath.InfoFields[0], &dpath.HopFields[1], dpath.PathMeta) + // Reset SegID to original value + dpath.InfoFields[0].SegID = 0x111 + ret := toMsg(t, spkt, dpath) + return ret + + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [2]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(01): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 01: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + + return dps[:] + }, + srcInterfaces: []uint16{0, 01}, + }, + "two hops non consdir flyovers": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:111")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{10, 0, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 1, + NumLines: 10, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 01, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}, + Bw: 5, ResStartTime: 123, Duration: 304}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + // aggregate macs + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 0, 1, + dpath.InfoFields[0], &dpath.HopFields[0], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 40, 0, + dpath.InfoFields[0], &dpath.HopFields[1], dpath.PathMeta) + + ret := toMsg(t, spkt, dpath) + return ret + + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [2]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(01): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 01: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + return dps[:] + }, + srcInterfaces: []uint16{0, 40}, + }, + "six hops astransit xover consdir flyovers": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:111"), + xtest.MustParseIA("3-ff00:0:333")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{15, 13, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 28, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 31}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 5, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 7}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 11, ConsEgress: 8}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 3, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[2].HopField) + + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[3].HopField.Mac) + dpath.HopFields[4].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[4].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[4].HopField.Mac) + dpath.HopFields[5].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[5].HopField) + // Reset SegID to original value + dpath.InfoFields[0].SegID = 0x111 + dpath.InfoFields[1].SegID = 0x222 + // aggregate flyover macs + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 0, 40, + dpath.InfoFields[0], &dpath.HopFields[0], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 1, 31, + dpath.InfoFields[0], &dpath.HopFields[1], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 5, 7, + dpath.InfoFields[0], &dpath.HopFields[2], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 11, 8, + dpath.InfoFields[1], &dpath.HopFields[4], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 3, 0, + dpath.InfoFields[1], &dpath.HopFields[5], dpath.PathMeta) + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [7]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 31: topology.Parent, + 1: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Child, + 7: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Child, + 7: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(5): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(11): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Core, + 11: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(8): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("2-ff00:0:222"), nil, key, sv) + + dps[5] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(8): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Core, + 11: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(11): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("2-ff00:0:222"), nil, key, sv) + + dps[6] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(3): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 3: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("3-ff00:0:333"), nil, key, sv) + + return dps[:] + }, // middle hop of second segment is astransit + srcInterfaces: []uint16{0, 1, 5, 0, 11, 0, 3}, + }, + "six hops astransit xover non consdir flyovers": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:111"), + xtest.MustParseIA("3-ff00:0:333")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{15, 13, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 28, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 40, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 31, ConsEgress: 1}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 5}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 7, ConsEgress: 0}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 8, ConsEgress: 11}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 3}, + Bw: 5, ResStartTime: 123, Duration: 304}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[2].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[2].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.HopFields[5].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[5].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[5].HopField.Mac) + dpath.HopFields[4].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[4].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[4].HopField.Mac) + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + // aggregate with flyover macs + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 0, 40, + dpath.InfoFields[0], &dpath.HopFields[0], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 1, 31, + dpath.InfoFields[0], &dpath.HopFields[1], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 5, 7, + dpath.InfoFields[0], &dpath.HopFields[2], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 11, 8, + dpath.InfoFields[1], &dpath.HopFields[4], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 3, 0, + dpath.InfoFields[1], &dpath.HopFields[5], dpath.PathMeta) + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [7]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 31: topology.Parent, + 1: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Child, + 7: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Child, + 7: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(5): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(11): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Core, + 11: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(8): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("2-ff00:0:222"), nil, key, sv) + + dps[5] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(8): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Core, + 11: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(11): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("2-ff00:0:222"), nil, key, sv) + + dps[6] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(3): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 3: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("3-ff00:0:333"), nil, key, sv) + + return dps[:] + }, // middle hop of second segment is astransit + srcInterfaces: []uint16{0, 1, 5, 0, 11, 0, 3}, + }, + "six hops brtransit xover mixed consdir flyovers": { + // up segment non consdir, down segment consdir + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:111"), + xtest.MustParseIA("3-ff00:0:333")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{15, 13, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 28, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 40, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 31, ConsEgress: 1}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 5}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 7}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 11, ConsEgress: 8}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 3, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[2].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[2].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[1].HopField.Mac) + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[3].HopField.Mac) + dpath.HopFields[4].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[4].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[4].HopField.Mac) + dpath.HopFields[5].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[5].HopField) + // Reset SegID to original value + dpath.InfoFields[1].SegID = 0x222 + + //aggregate MACs + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 0, 40, + dpath.InfoFields[0], &dpath.HopFields[0], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 1, 31, + dpath.InfoFields[0], &dpath.HopFields[1], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 5, 7, + dpath.InfoFields[0], &dpath.HopFields[2], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 11, 8, + dpath.InfoFields[1], &dpath.HopFields[4], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 3, 0, + dpath.InfoFields[1], &dpath.HopFields[5], dpath.PathMeta) + + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [5]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 31: topology.Parent, + 1: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Child, + 7: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(8): mock_router.NewMockBatchConn(ctrl), + uint16(11): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Child, + 11: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("2-ff00:0:222"), nil, key, sv) + + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(3): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 3: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("3-ff00:0:333"), nil, key, sv) + + return dps[:] + }, // middle hop of second segment is astransit + srcInterfaces: []uint16{0, 1, 5, 11, 3}, + }, + "six hops three segs mixed consdir flyovers": { + // two crossovers, first crossover is brtransit, second one is astransit + // core segment is non consdir + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:113")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{10, 8, 8}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 3, + NumLines: 26, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x333, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 5, ConsEgress: 0}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 31}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 0, ConsEgress: 8}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 3, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[3].HopField.Mac) + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + + dpath.HopFields[4].HopField.Mac = computeMAC(t, key, dpath.InfoFields[2], + dpath.HopFields[4].HopField) + dpath.InfoFields[2].UpdateSegID(dpath.HopFields[4].HopField.Mac) + dpath.HopFields[5].HopField.Mac = computeMAC(t, key, dpath.InfoFields[2], + dpath.HopFields[5].HopField) + // Reset SegID to original value + dpath.InfoFields[0].SegID = 0x111 + dpath.InfoFields[2].SegID = 0x333 + // aggregate flyover macs + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 0, 40, + dpath.InfoFields[0], &dpath.HopFields[0], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 1, 5, + dpath.InfoFields[0], &dpath.HopFields[1], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 31, 8, + dpath.InfoFields[1], &dpath.HopFields[3], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 3, 0, + dpath.InfoFields[2], &dpath.HopFields[5], dpath.PathMeta) + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [5]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Child, + 5: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Child, + 31: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(8): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(8): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 8: topology.Child, + 31: topology.Core, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(31): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(3): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 3: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + return dps[:] + }, + srcInterfaces: []uint16{0, 1, 31, 0, 3}, + }, + "three hops peering brtransit consdir flyovers": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:113")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{5, 10}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 15, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, Peer: true, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, Peer: true, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 5, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[1].HopField) + // No Segment update here + // The second hop of a peering path uses the same segID as it's following hop + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 0, 40, + dpath.InfoFields[0], &dpath.HopFields[0], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 1, 2, + dpath.InfoFields[1], &dpath.HopFields[1], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 5, 0, + dpath.InfoFields[1], &dpath.HopFields[2], dpath.PathMeta) + + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [3]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Peer, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + return dps[:] + }, + srcInterfaces: []uint16{0, 1, 5}, + }, + "three hops peering brtransit non consdir flyovers": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:113")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{5, 10}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 15, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, Peer: true, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, Peer: true, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 40, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 2, ConsEgress: 1}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 5}, + Bw: 5, ResStartTime: 123, Duration: 304}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[2].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[1].HopField) + // No Segment update here + // The second hop of a peering path uses the same segID as it's following hop + + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 0, 40, + dpath.InfoFields[0], &dpath.HopFields[0], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 1, 2, + dpath.InfoFields[1], &dpath.HopFields[1], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 5, 0, + dpath.InfoFields[1], &dpath.HopFields[2], dpath.PathMeta) + + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [3]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Peer, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + return dps[:] + }, + srcInterfaces: []uint16{0, 1, 5}, + }, + "four hops peering astransit consdir flyovers": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:113")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{10, 10}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 20, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, Peer: true, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, Peer: true, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 40}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 31, ConsEgress: 7}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 5, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + dpath.InfoFields[0].UpdateSegID(dpath.HopFields[0].HopField.Mac) + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + // No Segment update here + // The second hop of a peering path uses the same segID as it's following hop + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + // reset segID + dpath.InfoFields[0].SegID = 0x111 + + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 0, 40, + dpath.InfoFields[0], &dpath.HopFields[0], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 31, 7, + dpath.InfoFields[0], &dpath.HopFields[1], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 1, 2, + dpath.InfoFields[1], &dpath.HopFields[2], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 5, 0, + dpath.InfoFields[1], &dpath.HopFields[3], dpath.PathMeta) + + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [6]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Peer, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Peer, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(31): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(2): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(1): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[5] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + return dps[:] + }, + srcInterfaces: []uint16{0, 31, 0, 1, 0, 5}, + }, + "four hops peering astransit non consdir flyovers": { + mockMsg: func() *ipv4.Message { + spkt := prepHbirdSlayers(xtest.MustParseIA("1-ff00:0:110"), + xtest.MustParseIA("1-ff00:0:113")) + dst := addr.MustParseHost("10.0.100.100") + _ = spkt.SetDstAddr(dst) + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrINF: 0, + CurrHF: 0, + SegLen: [3]uint8{10, 10}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 2, + NumLines: 20, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, Peer: true, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + {SegID: 0x222, Peer: true, ConsDir: false, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{ + {Flyover: true, HopField: path.HopField{ConsIngress: 40, ConsEgress: 0}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 7, ConsEgress: 31}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 2, ConsEgress: 1}, + Bw: 5, ResStartTime: 123, Duration: 304}, + {Flyover: true, HopField: path.HopField{ConsIngress: 0, ConsEgress: 5}, + Bw: 5, ResStartTime: 123, Duration: 304}, + }, + } + // Compute MACs and increase SegID while doing so + dpath.HopFields[1].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[1].HopField) + // No Segment update here + // The second hop of a peering path uses the same segID as it's following hop + dpath.HopFields[0].HopField.Mac = computeMAC(t, key, dpath.InfoFields[0], + dpath.HopFields[0].HopField) + + dpath.HopFields[3].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[3].HopField) + dpath.InfoFields[1].UpdateSegID(dpath.HopFields[3].HopField.Mac) + dpath.HopFields[2].HopField.Mac = computeMAC(t, key, dpath.InfoFields[1], + dpath.HopFields[2].HopField) + + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 0, 40, + dpath.InfoFields[0], &dpath.HopFields[0], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 31, 7, + dpath.InfoFields[0], &dpath.HopFields[1], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 1, 2, + dpath.InfoFields[1], &dpath.HopFields[2], dpath.PathMeta) + aggregateOntoScionMac(t, sv, spkt.DstIA, spkt.PayloadLen, 5, 0, + dpath.InfoFields[1], &dpath.HopFields[3], dpath.PathMeta) + + ret := toMsg(t, spkt, dpath) + return ret + }, + prepareDPs: func(*gomock.Controller) []*router.DataPlane { + var dps [6]*router.DataPlane + dps[0] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(40): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 40: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + dps[1] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(31): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Peer, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(7): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[2] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(7): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 7: topology.Peer, + 31: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(31): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:111"), nil, key, sv) + dps[3] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(1): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(2): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[4] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Peer, + 2: topology.Child, + }, + mock_router.NewMockBatchConn(ctrl), + map[uint16]*net.UDPAddr{ + uint16(1): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, + }, nil, xtest.MustParseIA("1-ff00:0:112"), nil, key, sv) + dps[5] = router.NewDP( + map[uint16]router.BatchConn{ + uint16(5): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 5: topology.Parent, + }, + mock_router.NewMockBatchConn(ctrl), + nil, nil, xtest.MustParseIA("1-ff00:0:113"), nil, key, sv) + return dps[:] + }, + srcInterfaces: []uint16{0, 31, 0, 1, 0, 5}, + }, + } + + for name, tc := range testCases { + name, tc := name, tc + t.Run(name, func(t *testing.T) { + t.Parallel() + dps := tc.prepareDPs(ctrl) + input := tc.mockMsg() + for i, dp := range dps { + result, err := dp.ProcessPkt(tc.srcInterfaces[i], input) + assert.NoError(t, err) + + input = &ipv4.Message{ + Buffers: [][]byte{result.OutPkt}, + Addr: result.OutAddr, + N: len(result.OutPkt), + } + } + }) + } +} + +// TODO(juagargi): write test for concurrent bandwidth check calls + +func TestBandwidthCheck(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + now := time.Now() + + dp := router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Parent, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + + spkt, dpath := prepHbirdMsg(now) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}, ResID: 42, + Bw: 2, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + } + dpath.Base.PathMeta.SegLen[0] = 11 + dpath.Base.PathMeta.CurrHF = 3 + dpath.Base.NumLines = 11 + + spkt.PayloadLen = 120 + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.Base.PathMeta) + + msg := toLongMsg(t, spkt, dpath) + + _, err := dp.ProcessPkt(1, msg) + assert.NoError(t, err) + + msg = toLongMsg(t, spkt, dpath) + _, err = dp.ProcessPkt(1, msg) + assert.Error(t, err) + + time.Sleep(time.Duration(1) * time.Second) + + msg = toLongMsg(t, spkt, dpath) + _, err = dp.ProcessPkt(1, msg) + assert.NoError(t, err) +} + +func TestBandwidthCheckDifferentResID(t *testing.T) { + // Verifies that packets of one reservation do not affect + // available bandwidth of another reservation + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + now := time.Now() + + dp := router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Parent, + 2: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + + spkt, dpath := prepHbirdMsg(now) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}, ResID: 24, + Bw: 2, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + } + dpath.Base.PathMeta.SegLen[0] = 11 + dpath.Base.PathMeta.CurrHF = 3 + dpath.Base.NumLines = 11 + + spkt.PayloadLen = 120 + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.Base.PathMeta) + + msg := toLongMsg(t, spkt, dpath) + + _, err := dp.ProcessPkt(1, msg) + assert.NoError(t, err) + + dpath.HopFields[1].ResID = 32 + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.Base.PathMeta) + + msg = toLongMsg(t, spkt, dpath) + _, err = dp.ProcessPkt(1, msg) + assert.NoError(t, err) + + dpath.HopFields[1].ResID = 42 + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.Base.PathMeta) + + msg = toLongMsg(t, spkt, dpath) + _, err = dp.ProcessPkt(1, msg) + assert.NoError(t, err) +} + +func TestBandwidthCheckDifferentEgress(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + key := []byte("testkey_xxxxxxxx") + sv := []byte("test_secretvalue") + now := time.Now() + + dp := router.NewDP( + map[uint16]router.BatchConn{ + uint16(2): mock_router.NewMockBatchConn(ctrl), + uint16(3): mock_router.NewMockBatchConn(ctrl), + }, + map[uint16]topology.LinkType{ + 1: topology.Parent, + 2: topology.Child, + 3: topology.Child, + }, + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, sv) + + spkt, dpath := prepHbirdMsg(now) + dpath.HopFields = []hummingbird.FlyoverHopField{ + {HopField: path.HopField{ConsIngress: 31, ConsEgress: 30}}, + {Flyover: true, HopField: path.HopField{ConsIngress: 1, ConsEgress: 2}, ResID: 42, + Bw: 2, ResStartTime: 123, Duration: 304}, + {HopField: path.HopField{ConsIngress: 40, ConsEgress: 41}}, + } + dpath.Base.PathMeta.SegLen[0] = 11 + dpath.Base.PathMeta.CurrHF = 3 + dpath.Base.NumLines = 11 + + spkt.PayloadLen = 120 + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.Base.PathMeta) + + msg := toLongMsg(t, spkt, dpath) + + _, err := dp.ProcessPkt(1, msg) + assert.NoError(t, err) + + msg = toLongMsg(t, spkt, dpath) + _, err = dp.ProcessPkt(1, msg) + assert.Error(t, err) + + // Reservation with same resID but different Ingress/Egress pair is a different reservation + dpath.HopFields[1].HopField.ConsEgress = 3 + spkt.PayloadLen = 120 + dpath.HopFields[1].HopField.Mac = computeAggregateMac(t, key, sv, spkt.DstIA, + spkt.PayloadLen, dpath.InfoFields[0], dpath.HopFields[1], dpath.Base.PathMeta) + msg = toLongMsg(t, spkt, dpath) + _, err = dp.ProcessPkt(1, msg) + assert.NoError(t, err) +} + +func toLongMsg(t *testing.T, spkt *slayers.SCION, dpath path.Path) *ipv4.Message { + t.Helper() + ret := &ipv4.Message{} + spkt.Path = dpath + buffer := gopacket.NewSerializeBuffer() + payload := [120]byte{} + err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{FixLengths: true}, + spkt, gopacket.Payload(payload[:])) + require.NoError(t, err) + raw := buffer.Bytes() + ret.Buffers = make([][]byte, 1) + ret.Buffers[0] = make([]byte, 1500) + copy(ret.Buffers[0], raw) + ret.N = len(raw) + ret.Buffers[0] = ret.Buffers[0][:ret.N] + return ret +} + +func prepHbirdMsg(now time.Time) (*slayers.SCION, *hummingbird.Decoded) { + spkt := &slayers.SCION{ + Version: 0, + TrafficClass: 0xb8, + FlowID: 0xdead, + NextHdr: slayers.L4UDP, + PathType: hummingbird.PathType, + DstIA: xtest.MustParseIA("4-ff00:0:411"), + SrcIA: xtest.MustParseIA("2-ff00:0:222"), + Path: &hummingbird.Raw{}, + PayloadLen: 18, + } + + dpath := &hummingbird.Decoded{ + Base: hummingbird.Base{ + PathMeta: hummingbird.MetaHdr{ + CurrHF: 3, + SegLen: [3]uint8{9, 0, 0}, + BaseTS: util.TimeToSecs(now), + HighResTS: 500 << 22, + }, + NumINF: 1, + NumLines: 9, + }, + InfoFields: []path.InfoField{ + {SegID: 0x111, ConsDir: true, Timestamp: util.TimeToSecs(now)}, + }, + + HopFields: []hummingbird.FlyoverHopField{}, + } + return spkt, dpath +} + +func prepHbirdSlayers(src, dst addr.IA) *slayers.SCION { + spkt := &slayers.SCION{ + Version: 0, + TrafficClass: 0xb8, + FlowID: 0xdead, + NextHdr: slayers.L4UDP, + PathType: hummingbird.PathType, + DstIA: dst, + SrcIA: src, + Path: &hummingbird.Raw{}, + PayloadLen: 18, + } + return spkt +} + +func computeAggregateMac(t *testing.T, key, sv []byte, dst addr.IA, l uint16, info path.InfoField, + hf hummingbird.FlyoverHopField, meta hummingbird.MetaHdr) [path.MacLen]byte { + + scionMac := computeMAC(t, key, info, hf.HopField) + + block, err := aes.NewCipher(sv) + require.NoError(t, err) + + ingress, egress := hf.HopField.ConsIngress, hf.HopField.ConsEgress + if !info.ConsDir { + ingress, egress = egress, ingress + } + akBuffer := make([]byte, hummingbird.AkBufferSize) + macBuffer := make([]byte, hummingbird.FlyoverMacBufferSize) + xkBuffer := make([]uint32, hummingbird.XkBufferSize) + + ak := hummingbird.DeriveAuthKey(block, hf.ResID, hf.Bw, ingress, egress, + meta.BaseTS-uint32(hf.ResStartTime), hf.Duration, akBuffer) + flyoverMac := hummingbird.FullFlyoverMac(ak, dst, l, hf.ResStartTime, + meta.HighResTS, macBuffer, xkBuffer) + + for i, b := range scionMac { + scionMac[i] = b ^ flyoverMac[i] + } + return scionMac +} + +func computeAggregateMacXover(t *testing.T, key, sv []byte, dst addr.IA, l, hin, heg uint16, + info path.InfoField, hf hummingbird.FlyoverHopField, + meta hummingbird.MetaHdr) [path.MacLen]byte { + + scionMac := computeMAC(t, key, info, hf.HopField) + + block, err := aes.NewCipher(sv) + require.NoError(t, err) + ingress, egress := hin, heg + if !info.ConsDir { + ingress, egress = egress, ingress + } + + akBuffer := make([]byte, hummingbird.AkBufferSize) + macBuffer := make([]byte, hummingbird.FlyoverMacBufferSize) + xkBuffer := make([]uint32, hummingbird.XkBufferSize) + + ak := hummingbird.DeriveAuthKey(block, hf.ResID, hf.Bw, ingress, egress, + meta.BaseTS-uint32(hf.ResStartTime), hf.Duration, akBuffer) + flyoverMac := hummingbird.FullFlyoverMac(ak, dst, l, hf.ResStartTime, + meta.HighResTS, macBuffer, xkBuffer) + + for i, b := range scionMac { + scionMac[i] = b ^ flyoverMac[i] + } + return scionMac +} + +// Computes flyovermac and aggregates it to existing mac in hopfield +func aggregateOntoScionMac(t *testing.T, sv []byte, dst addr.IA, l, hin, heg uint16, + info path.InfoField, hf *hummingbird.FlyoverHopField, meta hummingbird.MetaHdr) { + block, err := aes.NewCipher(sv) + require.NoError(t, err) + ingress, egress := hin, heg + + akBuffer := make([]byte, hummingbird.AkBufferSize) + macBuffer := make([]byte, hummingbird.FlyoverMacBufferSize) + xkBuffer := make([]uint32, hummingbird.XkBufferSize) + + ak := hummingbird.DeriveAuthKey(block, hf.ResID, hf.Bw, ingress, egress, + meta.BaseTS-uint32(hf.ResStartTime), hf.Duration, akBuffer) + flyoverMac := hummingbird.FullFlyoverMac(ak, dst, l, hf.ResStartTime, + meta.HighResTS, macBuffer, xkBuffer) + + for i := range hf.HopField.Mac { + hf.HopField.Mac[i] ^= flyoverMac[i] + } +} diff --git a/router/dataplane_internal_test.go b/router/dataplane_internal_test.go index 6e92a8219d..e3c75430c8 100644 --- a/router/dataplane_internal_test.go +++ b/router/dataplane_internal_test.go @@ -432,7 +432,7 @@ func TestSlowPathProcessing(t *testing.T) { prepareDP: func(ctrl *gomock.Controller) *DataPlane { return NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, map[addr.SVC][]*net.UDPAddr{}, - xtest.MustParseIA("1-ff00:0:110"), nil, testKey) + xtest.MustParseIA("1-ff00:0:110"), nil, testKey, testKey) }, mockMsg: func() []byte { spkt := prepBaseMsg(t, payload, 0) @@ -454,7 +454,7 @@ func TestSlowPathProcessing(t *testing.T) { prepareDP: func(ctrl *gomock.Controller) *DataPlane { return NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, map[addr.SVC][]*net.UDPAddr{}, - xtest.MustParseIA("1-ff00:0:110"), nil, testKey) + xtest.MustParseIA("1-ff00:0:110"), nil, testKey, testKey) }, mockMsg: func() []byte { spkt := prepBaseMsg(t, payload, 0) @@ -476,7 +476,7 @@ func TestSlowPathProcessing(t *testing.T) { prepareDP: func(ctrl *gomock.Controller) *DataPlane { return NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, map[addr.SVC][]*net.UDPAddr{}, - xtest.MustParseIA("1-ff00:0:110"), nil, testKey) + xtest.MustParseIA("1-ff00:0:110"), nil, testKey, testKey) }, mockMsg: func() []byte { spkt := prepBaseMsg(t, payload, 0) diff --git a/router/dataplane_test.go b/router/dataplane_test.go index 57f3b20ddf..4e98a8c45b 100644 --- a/router/dataplane_test.go +++ b/router/dataplane_test.go @@ -261,6 +261,7 @@ func TestDataPlaneRun(t *testing.T) { _ = ret.SetIA(local) _ = ret.SetKey(key) + _ = ret.SetHbirdKey(key) return ret }, }, @@ -336,6 +337,7 @@ func TestDataPlaneRun(t *testing.T) { local := &net.UDPAddr{IP: net.ParseIP("10.0.200.100").To4()} _ = ret.SetKey([]byte("randomkeyformacs")) + _ = ret.SetHbirdKey([]byte("randomsvformacss")) _ = ret.AddInternalInterface(mInternal, net.IP{}) for remote, ifIDs := range routers { for _, ifID := range ifIDs { @@ -392,6 +394,7 @@ func TestDataPlaneRun(t *testing.T) { mInternal.EXPECT().ReadBatch(gomock.Any()).Return(0, nil).AnyTimes() _ = ret.SetKey([]byte("randomkeyformacs")) + _ = ret.SetHbirdKey([]byte("randomsvformacss")) _ = ret.AddInternalInterface(mInternal, net.IP{}) _ = ret.AddNextHop(3, localAddr) _ = ret.AddNextHopBFD(3, localAddr, remoteAddr, bfd(), "") @@ -446,6 +449,7 @@ func TestDataPlaneRun(t *testing.T) { Addr: &net.UDPAddr{IP: net.ParseIP("10.0.0.200")}, } _ = ret.SetKey([]byte("randomkeyformacs")) + _ = ret.SetHbirdKey([]byte("randomsvformacss")) _ = ret.AddInternalInterface(mInternal, net.IP{}) _ = ret.AddExternalInterface(ifID, mExternal) _ = ret.AddExternalInterfaceBFD(ifID, mExternal, local, remote, bfd()) @@ -526,6 +530,7 @@ func TestDataPlaneRun(t *testing.T) { Addr: &net.UDPAddr{IP: net.ParseIP("10.0.0.200")}, } _ = ret.SetKey([]byte("randomkeyformacs")) + _ = ret.SetHbirdKey([]byte("randomsvformacss")) _ = ret.AddInternalInterface(mInternal, net.IP{}) _ = ret.AddExternalInterface(1, mExternal) _ = ret.AddExternalInterfaceBFD(1, mExternal, local, remote, bfd()) @@ -586,7 +591,7 @@ func TestProcessPkt(t *testing.T) { "inbound": { prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { return router.NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, - nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -620,7 +625,7 @@ func TestProcessPkt(t *testing.T) { map[uint16]topology.LinkType{ 1: topology.Child, }, - nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -656,7 +661,7 @@ func TestProcessPkt(t *testing.T) { 1: topology.Parent, 2: topology.Child, }, - nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -690,7 +695,7 @@ func TestProcessPkt(t *testing.T) { map[uint16]topology.LinkType{ 2: topology.Parent, 1: topology.Child, - }, nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + }, nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -726,7 +731,7 @@ func TestProcessPkt(t *testing.T) { 1: topology.Peer, 2: topology.Child, }, - nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { // Story: the packet just left segment 0 which ends at @@ -797,7 +802,7 @@ func TestProcessPkt(t *testing.T) { 1: topology.Peer, 2: topology.Child, }, - nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { // Story: the packet lands on the last (peering) hop of @@ -876,7 +881,7 @@ func TestProcessPkt(t *testing.T) { 1: topology.Peer, 2: topology.Child, }, - nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { // Story: the packet just left hop 1 (the first hop @@ -951,7 +956,7 @@ func TestProcessPkt(t *testing.T) { 1: topology.Peer, 2: topology.Child, }, - nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, nil, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { // Story: the packet lands on the second (non-peering) hop of @@ -1037,7 +1042,7 @@ func TestProcessPkt(t *testing.T) { mock_router.NewMockBatchConn(ctrl), map[uint16]*net.UDPAddr{ uint16(3): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, - }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -1068,7 +1073,7 @@ func TestProcessPkt(t *testing.T) { mock_router.NewMockBatchConn(ctrl), map[uint16]*net.UDPAddr{ uint16(3): {IP: net.ParseIP("10.0.200.200").To4(), Port: 30043}, - }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + }, nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, _ := prepBaseMsg(now) @@ -1122,7 +1127,7 @@ func TestProcessPkt(t *testing.T) { }, }, }, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, dpath := prepBaseMsg(now) @@ -1162,7 +1167,7 @@ func TestProcessPkt(t *testing.T) { xtest.MustParseIA("1-ff00:0:110"), map[uint16]addr.IA{ uint16(1): xtest.MustParseIA("1-ff00:0:111"), - }, key) + }, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, _ := prepBaseMsg(now) @@ -1217,7 +1222,7 @@ func TestProcessPkt(t *testing.T) { xtest.MustParseIA("1-ff00:0:110"), map[uint16]addr.IA{ uint16(1): xtest.MustParseIA("1-ff00:0:111"), - }, key) + }, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, _ := prepBaseMsg(now) @@ -1261,7 +1266,7 @@ func TestProcessPkt(t *testing.T) { Port: topology.EndhostPort, }}, }, - xtest.MustParseIA("1-ff00:0:110"), nil, key) + xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, _ := prepBaseMsg(now) @@ -1319,7 +1324,7 @@ func TestProcessPkt(t *testing.T) { xtest.MustParseIA("1-ff00:0:110"), map[uint16]addr.IA{ uint16(2): xtest.MustParseIA("1-ff00:0:111"), - }, key) + }, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, _ := prepBaseMsg(now) @@ -1357,7 +1362,7 @@ func TestProcessPkt(t *testing.T) { "epic inbound": { prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { return router.NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, - nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, epicpath, dpath := prepEpicMsg(t, @@ -1373,7 +1378,7 @@ func TestProcessPkt(t *testing.T) { "epic malformed path": { prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { return router.NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, - nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, epicpath, dpath := prepEpicMsg(t, @@ -1390,7 +1395,7 @@ func TestProcessPkt(t *testing.T) { "epic invalid timestamp": { prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { return router.NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, - nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, epicpath, dpath := prepEpicMsg(t, @@ -1409,7 +1414,7 @@ func TestProcessPkt(t *testing.T) { "epic invalid LHVF": { prepareDP: func(ctrl *gomock.Controller) *router.DataPlane { return router.NewDP(nil, nil, mock_router.NewMockBatchConn(ctrl), nil, - nil, xtest.MustParseIA("1-ff00:0:110"), nil, key) + nil, xtest.MustParseIA("1-ff00:0:110"), nil, key, key) }, mockMsg: func(afterProcessing bool) *ipv4.Message { spkt, epicpath, dpath := prepEpicMsg(t, diff --git a/router/export_test.go b/router/export_test.go index a82d01e5a0..011c5195d8 100644 --- a/router/export_test.go +++ b/router/export_test.go @@ -47,7 +47,7 @@ func NewDP( svc map[addr.SVC][]*net.UDPAddr, local addr.IA, neighbors map[uint16]addr.IA, - key []byte) *DataPlane { + key []byte, sv []byte) *DataPlane { dp := &DataPlane{ localIA: local, @@ -62,6 +62,9 @@ func NewDP( if err := dp.SetKey(key); err != nil { panic(err) } + if err := dp.SetHbirdKey(sv); err != nil { + panic(err) + } return dp } @@ -85,3 +88,16 @@ func (d *DataPlane) ProcessPkt(ifID uint16, m *ipv4.Message) (ProcessResult, err func ExtractServices(s *services) map[addr.SVC][]*net.UDPAddr { return s.m } + +type BenchmarkPacketProcessor struct { + p *scionPacketProcessor +} + +func (d *DataPlane) NewBenchmarkPP() BenchmarkPacketProcessor { + return BenchmarkPacketProcessor{newPacketProcessor(d)} +} + +func (p BenchmarkPacketProcessor) ProcessPkt(ifID uint16, m *ipv4.Message) error { + _, err := p.p.processPkt(m.Buffers[0], nil, ifID) + return err +} diff --git a/router/tokenbucket/BUILD.bazel b/router/tokenbucket/BUILD.bazel new file mode 100644 index 0000000000..2ef5049512 --- /dev/null +++ b/router/tokenbucket/BUILD.bazel @@ -0,0 +1,17 @@ +load("//tools/lint:go.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["tokenbucket.go"], + importpath = "github.com/scionproto/scion/router/tokenbucket", + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["tokenbucket_test.go"], + deps = [ + ":go_default_library", + "@com_github_stretchr_testify//assert:go_default_library", + ], +) diff --git a/router/tokenbucket/tokenbucket.go b/router/tokenbucket/tokenbucket.go new file mode 100644 index 0000000000..aa9f45198a --- /dev/null +++ b/router/tokenbucket/tokenbucket.go @@ -0,0 +1,73 @@ +package tokenbucket + +import ( + "sync" + "time" +) + +type TokenBucket struct { + CurrentTokens float64 + LastTimeApplied time.Time + + //Burst Size + CBS float64 + + //In bytes per second + CIR float64 + + // Lock + lock sync.Mutex +} + +// Initializes a new tockenbucket for the given burstSize and rate +func NewTokenBucket(initialTime time.Time, burstSize float64, rate float64) *TokenBucket { + return &TokenBucket{ + CurrentTokens: rate, + CIR: rate, + CBS: burstSize, + LastTimeApplied: initialTime, + } +} + +// Sets a new rate for the token bucket +func (t *TokenBucket) SetRate(rate float64) { + t.lock.Lock() + defer t.lock.Unlock() + t.CIR = rate +} + +// Sets a new burst size for the token bucket +func (t *TokenBucket) SetBurstSize(burstSize float64) { + t.lock.Lock() + defer t.lock.Unlock() + t.CBS = burstSize +} + +// Apply calculates the current available tokens and checks whether there +// are enough tokens available. The success is indicated by a bool. +func (t *TokenBucket) Apply(size int, now time.Time) bool { + t.lock.Lock() + defer t.lock.Unlock() + // Increase available tokens according to time passed since last call + // Apply() is expected to be called from different threads + // As a consequence, it is possible for now to be older than LastTimeApplied + if !now.Before(t.LastTimeApplied) { + t.CurrentTokens += now.Sub(t.LastTimeApplied).Seconds() * t.CIR + t.CurrentTokens = min(t.CurrentTokens, t.CBS) + t.LastTimeApplied = now + } + if t.CurrentTokens >= float64(size) { + t.CurrentTokens -= float64(size) + return true + } + return false +} + +// This function calculates the minimal value of two float64. +func min(a float64, b float64) float64 { + if a > b { + return b + } else { + return a + } +} diff --git a/router/tokenbucket/tokenbucket_test.go b/router/tokenbucket/tokenbucket_test.go new file mode 100644 index 0000000000..fa9740351c --- /dev/null +++ b/router/tokenbucket/tokenbucket_test.go @@ -0,0 +1,128 @@ +package tokenbucket_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/scionproto/scion/router/tokenbucket" +) + +type entry struct { + length int + arrivalTime time.Time + result bool +} +type test struct { + name string + entries []entry + bucket *tokenbucket.TokenBucket +} + +// Tests the Token Bucket Algorithm by running subtests. +func TestGroupForTokenBucketAlgorithm(t *testing.T) { + + var startTime = time.Unix(0, 0) + + tests := []test{ + { + name: "TestApplyDoesAllowArrivalBehindTheLastArrival", + bucket: tokenbucket.NewTokenBucket(startTime.Add(1), 1024, 1024), + entries: []entry{ + { + length: 1, + arrivalTime: startTime, + result: true, + }, + }, + }, + { + name: "FullBandwidthCanBeConsumedAtOnce", + bucket: tokenbucket.NewTokenBucket(startTime, 1024, 1024), + entries: []entry{ + { + length: 1024, + arrivalTime: startTime, + result: true, + }, + { + length: 1, + arrivalTime: startTime, + result: false, + }, + }, + }, + { + name: "FullBandwidthCanBeConsumedOverMultiplePackets", + bucket: tokenbucket.NewTokenBucket(startTime, 1024, 1024), + entries: []entry{ + { + length: 512, + arrivalTime: startTime, + result: true, + }, + { + length: 512, + arrivalTime: startTime, + result: true, + }, + { + length: 1, + arrivalTime: startTime, + result: false, + }, + }, + }, + { + name: "CurrentTokensRegenerate", + bucket: tokenbucket.NewTokenBucket(startTime, 1024, 1024), + entries: []entry{ + { + length: 1024, + arrivalTime: startTime, + result: true, + }, + { + length: 512, + arrivalTime: startTime.Add(500 * time.Millisecond), + result: true, + }, + { + length: 1, + arrivalTime: startTime.Add(500 * time.Millisecond), + result: false, + }, + }, + }, + { + name: "CurrentTokensIsLimitedByCBS", + bucket: tokenbucket.NewTokenBucket(startTime, 2048, 1024), + entries: []entry{ + { + length: 2049, + arrivalTime: startTime.Add(1 * time.Second), + result: false, + }, + { + length: 2048, + arrivalTime: startTime.Add(1 * time.Second), + result: true, + }, + { + length: 1, + arrivalTime: startTime.Add(1 * time.Second), + result: false, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + for _, en := range tc.entries { + assert.Equal(t, en.result, tc.bucket.Apply(en.length, en.arrivalTime), tc.name) + } + }) + } +} diff --git a/tools/end2end_hbird/BUILD.bazel b/tools/end2end_hbird/BUILD.bazel new file mode 100644 index 0000000000..6d3c3a2927 --- /dev/null +++ b/tools/end2end_hbird/BUILD.bazel @@ -0,0 +1,34 @@ +load("//tools/lint:go.bzl", "go_library") +load("//:scion.bzl", "scion_go_binary") + +go_library( + name = "go_default_library", + srcs = ["main.go"], + importpath = "github.com/scionproto/scion/tools/end2end_hbird", + visibility = ["//visibility:private"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/daemon:go_default_library", + "//pkg/hummingbird:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/common:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/private/util:go_default_library", + "//pkg/snet:go_default_library", + "//pkg/snet/metrics:go_default_library", + "//pkg/snet/path:go_default_library", + "//pkg/sock/reliable:go_default_library", + "//private/topology:go_default_library", + "//private/tracing:go_default_library", + "//tools/integration:go_default_library", + "//tools/integration/integrationlib:go_default_library", + "@com_github_opentracing_opentracing_go//:go_default_library", + "@com_github_opentracing_opentracing_go//ext:go_default_library", + ], +) + +scion_go_binary( + name = "end2end_hbird", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/tools/end2end_hbird/main.go b/tools/end2end_hbird/main.go new file mode 100644 index 0000000000..a68cbfe017 --- /dev/null +++ b/tools/end2end_hbird/main.go @@ -0,0 +1,509 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "net" + "net/netip" + "os" + "time" + + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/daemon" + "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/common" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/private/util" + "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/metrics" + snetpath "github.com/scionproto/scion/pkg/snet/path" + "github.com/scionproto/scion/pkg/sock/reliable" + "github.com/scionproto/scion/private/topology" + "github.com/scionproto/scion/private/tracing" + libint "github.com/scionproto/scion/tools/integration" + integration "github.com/scionproto/scion/tools/integration/integrationlib" +) + +const ( + ping = "ping" + pong = "pong" + pingPayloadLen = 113 +) + +type Ping struct { + Server addr.IA `json:"server"` + Message string `json:"message"` + Trace []byte `json:"trace"` +} + +type Pong struct { + Client addr.IA `json:"client"` + Server addr.IA `json:"server"` + Message string `json:"message"` + Trace []byte `json:"trace"` +} + +var ( + remote snet.UDPAddr + timeout = &util.DurWrap{Duration: 10 * time.Second} + scionPacketConnMetrics = metrics.NewSCIONPacketConnMetrics() + scmpErrorsCounter = scionPacketConnMetrics.SCMPErrors +) + +func main() { + os.Exit(realMain()) +} + +func realMain() int { + defer log.HandlePanic() + defer log.Flush() + addFlags() + err := integration.Setup() + if err != nil { + log.Error("Parsing common flags failed", "err", err) + return 1 + } + validateFlags() + + closeTracer, err := integration.InitTracer("end2end_hbird-" + integration.Mode) + if err != nil { + log.Error("Tracer initialization failed", "err", err) + return 1 + } + defer closeTracer() + + if integration.Mode == integration.ModeServer { + server{}.run() + return 0 + } + c := client{} + return c.run() +} + +func addFlags() { + flag.Var(&remote, "remote", "(Mandatory for clients) address to connect to") + flag.Var(timeout, "timeout", "The timeout for each attempt") +} + +func validateFlags() { + if integration.Mode == integration.ModeClient { + if remote.Host == nil { + integration.LogFatal("Missing remote address") + } + if remote.Host.Port == 0 { + integration.LogFatal("Invalid remote port", "remote port", remote.Host.Port) + } + if timeout.Duration == 0 { + integration.LogFatal("Invalid timeout provided", "timeout", timeout) + } + } +} + +type server struct{} + +func (s server) run() { + log.Info("Starting server", "isd_as", integration.Local.IA) + defer log.Info("Finished server", "isd_as", integration.Local.IA) + + sdConn := integration.SDConn() + defer sdConn.Close() + connFactory := &snet.DefaultPacketDispatcherService{ + Dispatcher: reliable.NewDispatcher(""), + SCMPHandler: snet.DefaultSCMPHandler{ + RevocationHandler: daemon.RevHandler{Connector: sdConn}, + SCMPErrors: scmpErrorsCounter, + }, + SCIONPacketConnMetrics: scionPacketConnMetrics, + } + conn, port, err := connFactory.Register(context.Background(), integration.Local.IA, + integration.Local.Host, addr.SvcNone) + if err != nil { + integration.LogFatal("Error listening", "err", err) + } + defer conn.Close() + if len(os.Getenv(libint.GoIntegrationEnv)) > 0 { + // Needed for integration test ready signal. + fmt.Printf("Port=%d\n", port) + fmt.Printf("%s%s\n\n", libint.ReadySignal, integration.Local.IA) + } + log.Info("Listening", "local", fmt.Sprintf("%v:%d", integration.Local.Host, port)) + + // Receive ping message + for { + if err := s.handlePing(conn); err != nil { + log.Error("Error handling ping", "err", err) + } + } +} + +func (s server) handlePing(conn snet.PacketConn) error { + var p snet.Packet + var ov net.UDPAddr + if err := readFrom(conn, &p, &ov); err != nil { + return serrors.WrapStr("reading packet", err) + } + udp, ok := p.Payload.(snet.UDPPayload) + if !ok { + return serrors.New("unexpected payload received", + "source", p.Source, + "destination", p.Destination, + "type", common.TypeOf(p.Payload), + ) + } + var pld Ping + if err := json.Unmarshal(udp.Payload, &pld); err != nil { + 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, + )) + } + log.Info(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, + } + // reverse path + rpath, ok := p.Path.(snet.RawPath) + if !ok { + return serrors.New("unecpected 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)) + } + log.Info("Sent pong to", "client", p.Destination) + return nil +} + +type client struct { + conn snet.PacketConn + port uint16 + sdConn daemon.Connector + + errorPaths map[snet.PathFingerprint]struct{} +} + +func (c *client) run() int { + pair := fmt.Sprintf("%s -> %s", integration.Local.IA, remote.IA) + log.Info("Starting", "pair", pair) + defer log.Info("Finished", "pair", pair) + defer integration.Done(integration.Local.IA, remote.IA) + + connFactory := &snet.DefaultPacketDispatcherService{ + Dispatcher: reliable.NewDispatcher(""), + SCMPHandler: snet.DefaultSCMPHandler{ + RevocationHandler: daemon.RevHandler{Connector: integration.SDConn()}, + SCMPErrors: scmpErrorsCounter, + }, + SCIONPacketConnMetrics: scionPacketConnMetrics, + } + + var err error + c.conn, c.port, err = connFactory.Register(context.Background(), integration.Local.IA, + integration.Local.Host, addr.SvcNone) + if err != nil { + integration.LogFatal("Unable to listen", "err", err) + } + log.Info("Send on", "local", + fmt.Sprintf("%v,[%v]:%d", integration.Local.IA, integration.Local.Host.IP, c.port)) + c.sdConn = integration.SDConn() + defer c.sdConn.Close() + c.errorPaths = make(map[snet.PathFingerprint]struct{}) + return integration.AttemptRepeatedly("End2End", c.attemptRequest) +} + +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() + // Convert path to Hummingbird path + if path != nil { + + // This works: + // Directly query the scion daemon. + reservations, err := c.sdConn.GetReservations(ctx, integration.Local.IA, remote.IA, 1, true) + if err != nil { + logger.Error("getting reservations from daemon", "err", err) + return false + } + reservation := reservations[0] + + // // This works: + // // Build with no flyovers. + // reservation, err := hummingbird.NewReservation( + // hummingbird.WithScionPath(path, nil)) + // if err != nil { + // logger.Error("Error converting path to Hummingbird", "err", err) + // return false + // } + + // // This works: + // // Get flyovers and build path. + // flyovers, err := c.sdConn.ListFlyovers(ctx) + // if err != nil { + // logger.Error("listing flyovers", "err", err) + // return false + // } + // reservation, err := hummingbird.NewReservation( + // hummingbird.WithScionPath(path, hummingbird.FlyoversToMap(flyovers))) + // if err != nil { + // logger.Error("Error converting path to Hummingbird", "err", err) + // return false + // } + + decoded := reservation.DeriveDataPlanePath(113, time.Now()) + raw := snetpath.Hummingbird{ + Raw: make([]byte, decoded.Len()), + } + err = decoded.SerializeTo(raw.Raw) + if err != nil { + logger.Error("Error assembling hummingbird path", "err", err) + } + remote.Path = raw + } + + // Send ping + if err := c.ping(ctx, n, path); err != nil { + tracing.Error(span, err) + logger.Error("Could not send packet", "err", err) + return false + } + // Receive pong + if err := c.pong(ctx); err != nil { + tracing.Error(span, err) + logger.Error("Error receiving pong", "err", err) + if path != nil { + c.errorPaths[snet.Fingerprint(path)] = struct{}{} + } + return false + } + return true +} + +func (c *client) ping(ctx context.Context, n int, path snet.Path) error { + rawPing, err := json.Marshal(Ping{ + Server: remote.IA, + Message: ping, + Trace: tracing.IDFromCtx(ctx), + }) + if err != nil { + return serrors.WrapStr("packing ping", err) + } + if err := c.conn.SetWriteDeadline(getDeadline(ctx)); err != nil { + return serrors.WrapStr("setting write deadline", err) + } + if remote.NextHop == nil { + remote.NextHop = &net.UDPAddr{ + IP: remote.Host.IP, + Port: topology.EndhostPort, + } + } + + remoteHostIP, ok := netip.AddrFromSlice(remote.Host.IP) + if !ok { + return serrors.New("invalid remote host IP", "ip", remote.Host.IP) + } + localHostIP, ok := netip.AddrFromSlice(integration.Local.Host.IP) + if !ok { + return serrors.New("invalid local host IP", "ip", integration.Local.Host.IP) + } + pkt := &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Destination: snet.SCIONAddress{ + IA: remote.IA, + Host: addr.HostIP(remoteHostIP), + }, + Source: snet.SCIONAddress{ + IA: integration.Local.IA, + Host: addr.HostIP(localHostIP), + }, + Path: remote.Path, + Payload: snet.UDPPayload{ + SrcPort: c.port, + DstPort: uint16(remote.Host.Port), + Payload: rawPing, + }, + }, + } + log.Info("sending ping", "attempt", n, "path", path) + if err := c.conn.WriteTo(pkt, remote.NextHop); err != nil { + return err + } + return nil +} + +func (c *client) getRemote(ctx context.Context, n int) (snet.Path, error) { + // TODO: this function should return all possible paths. + if remote.IA.Equal(integration.Local.IA) { + remote.Path = snetpath.Empty{} + return nil, nil + } + span, ctx := tracing.StartSpanFromCtx(ctx, "attempt.get_remote") + defer span.Finish() + withTag := func(err error) error { + tracing.Error(span, err) + return err + } + + paths, err := c.sdConn.Paths(ctx, remote.IA, integration.Local.IA, + daemon.PathReqFlags{Refresh: n != 0}) + if err != nil { + return nil, withTag(serrors.WrapStr("requesting paths", err)) + } + // If all paths had an error, let's try them again. + if len(paths) <= len(c.errorPaths) { + c.errorPaths = make(map[snet.PathFingerprint]struct{}) + } + // Select first path that didn't error before. + var path snet.Path + for _, p := range paths { + if _, ok := c.errorPaths[snet.Fingerprint(p)]; ok { + continue + } + path = p + break + } + if path == nil { + return nil, withTag(serrors.New("no path found", + "candidates", len(paths), + "errors", len(c.errorPaths), + )) + } + + // Extract forwarding path from the SCION Daemon response. + 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) + } + var p snet.Packet + var ov net.UDPAddr + if err := readFrom(c.conn, &p, &ov); err != nil { + return serrors.WrapStr("reading packet", err) + } + + udp, ok := p.Payload.(snet.UDPPayload) + if !ok { + return serrors.New("unexpected payload received", "type", common.TypeOf(p.Payload)) + } + + var pld Pong + if err := json.Unmarshal(udp.Payload, &pld); err != nil { + return serrors.WrapStr("unpacking pong", err, "data", string(udp.Payload)) + } + + 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", p.Source) + 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.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/tools/end2end_hbird_integration/BUILD.bazel b/tools/end2end_hbird_integration/BUILD.bazel new file mode 100644 index 0000000000..c8152a2e3b --- /dev/null +++ b/tools/end2end_hbird_integration/BUILD.bazel @@ -0,0 +1,31 @@ +load("//tools/lint:go.bzl", "go_library") +load("//:scion.bzl", "scion_go_binary") + +go_library( + name = "go_default_library", + srcs = ["main.go"], + importpath = "github.com/scionproto/scion/tools/end2end_hbird_integration", + visibility = ["//visibility:private"], + deps = [ + "//pkg/addr:go_default_library", + "//pkg/daemon:go_default_library", + "//pkg/hummingbird:go_default_library", + "//pkg/log:go_default_library", + "//pkg/private/common:go_default_library", + "//pkg/private/serrors:go_default_library", + "//pkg/private/util:go_default_library", + "//pkg/slayers/path/hummingbird:go_default_library", + "//pkg/snet:go_default_library", + "//private/app/feature:go_default_library", + "//private/keyconf:go_default_library", + "//private/topology:go_default_library", + "//router/control:go_default_library", + "//tools/integration:go_default_library", + ], +) + +scion_go_binary( + name = "end2end_hbird_integration", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/tools/end2end_hbird_integration/main.go b/tools/end2end_hbird_integration/main.go new file mode 100644 index 0000000000..3581986ec4 --- /dev/null +++ b/tools/end2end_hbird_integration/main.go @@ -0,0 +1,556 @@ +// Copyright 2018 ETH Zurich, Anapaya Systems +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "crypto/aes" + "flag" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/daemon" + "github.com/scionproto/scion/pkg/hummingbird" + "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/private/common" + "github.com/scionproto/scion/pkg/private/serrors" + "github.com/scionproto/scion/pkg/private/util" + hbirddp "github.com/scionproto/scion/pkg/slayers/path/hummingbird" + "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/private/app/feature" + "github.com/scionproto/scion/private/keyconf" + "github.com/scionproto/scion/private/topology" + "github.com/scionproto/scion/router/control" + "github.com/scionproto/scion/tools/integration" +) + +var ( + subset string + attempts int + timeout = &util.DurWrap{Duration: 10 * time.Second} + parallelism int + name string + cmd string + features string +) + +func getCmd() (string, bool) { + return cmd, strings.Contains(cmd, "end2end_hbird") +} + +func main() { + os.Exit(realMain()) +} + +func realMain() int { + addFlags() + if err := integration.Init(); err != nil { + fmt.Fprintf(os.Stderr, "Failed to init: %s\n", err) + return 1 + } + defer log.HandlePanic() + defer log.Flush() + if len(features) != 0 { + if _, err := feature.ParseDefault(strings.Split(features, ",")); err != nil { + fmt.Fprintf(os.Stderr, "Error parsing features: %s\n", err) + return 1 + } + } + + clientArgs := []string{ + "-log.console", "debug", + "-attempts", strconv.Itoa(attempts), + "-timeout", timeout.String(), + "-local", integration.SrcAddrPattern + ":0", + "-remote", integration.DstAddrPattern + ":" + integration.ServerPortReplace, + } + serverArgs := []string{ + "-mode", "server", + "-local", integration.DstAddrPattern + ":0", + } + if len(features) != 0 { + clientArgs = append(clientArgs, "--features", features) + serverArgs = append(serverArgs, "--features", features) + } + if !*integration.Docker { + clientArgs = append(clientArgs, "-sciond", integration.Daemon) + serverArgs = append(serverArgs, "-sciond", integration.Daemon) + } + + in := integration.NewBinaryIntegration(name, cmd, clientArgs, serverArgs) + pairs, err := getPairs() + if err != nil { + log.Error("Error selecting tests", "err", err) + return 1 + } + err = addMockFlyovers(time.Now(), pairs) + if err != nil { + log.Error("Error adding mock flyovers", "err", err) + return 1 + } + if err := runTests(in, pairs); err != nil { + log.Error("Error during tests", "err", err) + return 1 + } + return 0 +} + +// addFlags adds the necessary flags. +func addFlags() { + flag.IntVar(&attempts, "attempts", 1, "Number of attempts per client before giving up.") + flag.StringVar(&cmd, "cmd", "./bin/end2end_hbird", + "The end2end binary to run (default: ./bin/end2end_hbird)") + flag.StringVar(&name, "name", "end2end_hbird_integration", + "The name of the test that is running (default: end2end_hbird_integration)") + flag.Var(timeout, "timeout", "The timeout for each attempt") + flag.StringVar(&subset, "subset", "all", "Subset of pairs to run (all|core#core|"+ + "noncore#localcore|noncore#core|noncore#noncore)") + flag.IntVar(¶llelism, "parallelism", 1, "How many end2end tests run in parallel.") + flag.StringVar(&features, "features", "", + fmt.Sprintf("enable development features (%v)", feature.String(&feature.Default{}, "|"))) +} + +// runTests runs the end2end hbird tests for all pairs. In case of an error the +// function is terminated immediately. +func runTests(in integration.Integration, pairs []integration.IAPair) error { + return integration.ExecuteTimed(in.Name(), func() error { + // Make sure that all executed commands can write to the RPC server + // after shutdown. + defer time.Sleep(time.Second) + + // Estimating the timeout we should have is hard. CI will abort after 10 + // minutes anyway. Thus this value. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + + // First run all servers + type srvResult struct { + cleaner func() + err error + } + // Start servers in parallel. + srvResults := make(chan srvResult) + for _, dst := range integration.ExtractUniqueDsts(pairs) { + go func(dst *snet.UDPAddr) { + defer log.HandlePanic() + + srvCtx, cancel := context.WithCancel(ctx) + waiter, err := in.StartServer(srvCtx, dst) + if err != nil { + log.Error(fmt.Sprintf("Error in server: %s", dst.String()), "err", err) + } + cleaner := func() { + cancel() + if waiter != nil { + _ = waiter.Wait() + } + } + srvResults <- srvResult{cleaner: cleaner, err: err} + }(dst) + } + // Wait for all servers being started. + var errs serrors.List + for range integration.ExtractUniqueDsts(pairs) { + res := <-srvResults + // We need to register a cleanup for all servers. + // Do not short-cut exit here. + if res.err != nil { + errs = append(errs, res.err) + } + defer res.cleaner() + } + if err := errs.ToError(); err != nil { + return err + } + + // Start a done signal listener. This is how the end2end binary + // communicates with this integration test. This is solely used to print + // the progress of the test. + var ctrMtx sync.Mutex + var ctr int + doneDir, err := filepath.Abs(filepath.Join(integration.LogDir(), "socks")) + if err != nil { + return serrors.WrapStr("determining abs path", err) + } + if err := os.MkdirAll(doneDir, os.ModePerm); err != nil { + return serrors.WrapStr("creating socks directory", err) + } + // this is a bit of a hack, socket file names have a max length of 108 + // and inside bazel tests we easily have longer paths, therefore we + // create a temporary symlink to the directory where we put the socket + // file. + tmpDir, err := os.MkdirTemp("", "e2e_hbird_integration") + if err != nil { + return serrors.WrapStr("creating temp dir", err) + } + if err := os.Remove(tmpDir); err != nil { + return serrors.WrapStr("deleting temp dir", err) + } + if err := os.Symlink(doneDir, tmpDir); err != nil { + return serrors.WrapStr("symlinking socks dir", err) + } + doneDir = tmpDir + defer os.Remove(doneDir) + socket, clean, err := integration.ListenDone(doneDir, func(src, dst addr.IA) { + ctrMtx.Lock() + defer ctrMtx.Unlock() + ctr++ + testInfo := fmt.Sprintf("%v -> %v (%v/%v)", src, dst, ctr, len(pairs)) + log.Info(fmt.Sprintf("Test %v: %s", in.Name(), testInfo)) + }) + if err != nil { + return serrors.WrapStr("creating done listener", err) + } + defer clean() + + if *integration.Docker { + socket = strings.Replace(socket, doneDir, "/share/logs/socks", -1) + } + + // CI collapses if parallelism is too high. + semaphore := make(chan struct{}, parallelism) + + // Docker exec comes with a 1 second overhead. We group all the pairs by + // the clients. And run all pairs for a given client in one execution. + // Thus, reducing the overhead dramatically. + groups := integration.GroupBySource(pairs) + clientResults := make(chan error, len(groups)) + for src, dsts := range groups { + go func(src *snet.UDPAddr, dsts []*snet.UDPAddr) { + defer log.HandlePanic() + + semaphore <- struct{}{} + defer func() { <-semaphore }() + // Aggregate all the commands that need to be run. + cmds := make([]integration.Cmd, 0, len(dsts)) + for _, dst := range dsts { + cmd, err := clientTemplate(socket).Template(src, dst) + if err != nil { + clientResults <- err + return + } + cmds = append(cmds, cmd) + } + var tester string + if *integration.Docker { + tester = integration.TesterID(src) + } + logFile := fmt.Sprintf("%s/client_%s.log", + logDir(), + addr.FormatIA(src.IA, addr.WithFileSeparator()), + ) + err := integration.Run(ctx, integration.RunConfig{ + Commands: cmds, + LogFile: logFile, + Tester: tester, + }) + if err != nil { + err = serrors.WithCtx(err, "file", relFile(logFile)) + } + clientResults <- err + }(src, dsts) + } + errs = nil + for range groups { + err := <-clientResults + if err != nil { + errs = append(errs, err) + } + } + return errs.ToError() + }) +} + +func clientTemplate(progressSock string) integration.Cmd { + bin, progress := getCmd() + cmd := integration.Cmd{ + Binary: bin, + Args: []string{ + "-log.console", "debug", + "-attempts", strconv.Itoa(attempts), + "-timeout", timeout.String(), + "-local", integration.SrcAddrPattern + ":0", + "-remote", integration.DstAddrPattern + ":" + integration.ServerPortReplace, + }, + } + if len(features) != 0 { + cmd.Args = append(cmd.Args, "--features", features) + } + if progress { + cmd.Args = append(cmd.Args, "-progress", progressSock) + } + if !*integration.Docker { + cmd.Args = append(cmd.Args, "-sciond", integration.Daemon) + } + return cmd +} + +// getPairs returns the pairs to test according to the specified subset. +func getPairs() ([]integration.IAPair, error) { + pairs := integration.IAPairs(integration.DispAddr) + if subset == "all" { + return pairs, nil + } + parts := strings.Split(subset, "#") + if len(parts) != 2 { + return nil, serrors.New("Invalid subset", "subset", subset) + } + return filter(parts[0], parts[1], pairs, integration.LoadedASList), nil +} + +// filter returns the list of ASes that are part of the desired subset. +func filter( + src, dst string, + pairs []integration.IAPair, + ases *integration.ASList, +) []integration.IAPair { + + var res []integration.IAPair + s, err1 := addr.ParseIA(src) + d, err2 := addr.ParseIA(dst) + if err1 == nil && err2 == nil { + for _, pair := range pairs { + if pair.Src.IA.Equal(s) && pair.Dst.IA.Equal(d) { + res = append(res, pair) + return res + } + } + } + for _, pair := range pairs { + filter := !contains(ases, src != "noncore", pair.Src.IA) + filter = filter || !contains(ases, dst != "noncore", pair.Dst.IA) + if dst == "localcore" { + filter = filter || pair.Src.IA.ISD() != pair.Dst.IA.ISD() + } + if !filter { + res = append(res, pair) + } + } + return res +} + +func contains(ases *integration.ASList, core bool, ia addr.IA) bool { + l := ases.Core + if !core { + l = ases.NonCore + } + for _, as := range l { + if ia.Equal(as) { + return true + } + } + return false +} + +// addMockFlyovers creates and stores the necessary flyovers for the given pairs. +// It uses the scion daemon to add the flyovers to the DB. +func addMockFlyovers(now time.Time, pairs []integration.IAPair) error { + perAS, err := getTopoPerAS(pairs) + if err != nil { + return nil + } + + flyovers, err := createMockFlyovers(perAS, now) + if err != nil { + return err + } + + // Insert each flyover into the DB of each AS. Allow timeout.Duration per AS to do so. + wg := sync.WaitGroup{} + wg.Add(len(perAS)) + errCh := make(chan error) + for ia, c := range perAS { + ia, c := ia, c + go func() { + defer log.HandlePanic() + defer wg.Done() + ctx, cancelF := context.WithTimeout(context.Background(), timeout.Duration) + defer cancelF() + errCh <- insertFlyoversInAS(ctx, ia, c, flyovers) + }() + } + // Collect any possible error and bail on the first non nil one. + go func() { + defer log.HandlePanic() + for errPerAS := range errCh { + if err != nil { + err = errPerAS + } + } + }() + wg.Wait() + close(errCh) + + if err != nil { + return serrors.WrapStr("at least one AS returned an error while inserting flyovers", err) + } + return nil +} + +type topoPerAS struct { + ASDirName string + Interfaces []common.IFIDType +} + +func getTopoPerAS(pairs []integration.IAPair) (map[addr.IA]topoPerAS, error) { + m := make(map[addr.IA]topoPerAS) + for _, pair := range pairs { + ia := pair.Src.IA + if _, ok := m[ia]; ok { + continue + } + + // Load their topology. + path := integration.GenFile( + filepath.Join( + addr.FormatAS(ia.AS(), addr.WithDefaultPrefix(), addr.WithFileSeparator()), + "topology.json", + ), + ) + topo, err := topology.FromJSONFile(path) + if err != nil { + return nil, serrors.WrapStr("loading topology", err, "ia", ia) + } + + // Set the values for this AS. + m[ia] = topoPerAS{ + ASDirName: addr.FormatAS(ia.AS(), + addr.WithDefaultPrefix(), addr.WithFileSeparator()), + Interfaces: topo.InterfaceIDs(), + } + } + + return m, nil +} + +func createMockFlyovers( + perAS map[addr.IA]topoPerAS, + now time.Time, +) ([]*hummingbird.Flyover, error) { + + // Per IA, insert a flyover with BW units of bandwidth for each interface pair. + // Note that BW has to be enough for one AS to send a ping to another. + const BW = uint16(10) + flyovers := make([]*hummingbird.Flyover, 0) + for ia, c := range perAS { + var resIDPerIA uint32 // reservation ID unique per IA + // Load master key for this ia. It is used to create the mock flyover, by deriving here + // the correct Ak that the border routers will check. + masterFile := integration.GenFile(filepath.Join(c.ASDirName, "keys")) + master0, err := keyconf.LoadMaster(masterFile) + if err != nil { + return nil, serrors.WrapStr("could not load master secret for IA", err, "ia", ia) + } + + // Add the "itself" interface ID to the slice. + ifaces := append(c.Interfaces, 0) + // Create a flyover for each possible ingress->egress s.t. ingress <> egress + inToEgressesMap := ifIDSequenceToMap(ifaces) + for in, egressInterfaces := range inToEgressesMap { + for _, eg := range egressInterfaces { + f := hummingbird.Flyover{ + BaseHop: hummingbird.BaseHop{ + IA: ia, + Ingress: uint16(in), + Egress: uint16(eg), + }, + Bw: BW, + StartTime: util.TimeToSecs(now), + // Duration: 60, // 1 Minute + // deleteme: change to 1 minute again + Duration: 300, // 1 Hour + ResID: resIDPerIA, // unique per ia + } + + key0 := control.DeriveHbirdSecretValue(master0.Key0) + prf, _ := aes.NewCipher(key0) + buffer := make([]byte, 16) + ak := hbirddp.DeriveAuthKey(prf, f.ResID, f.Bw, f.Ingress, f.Egress, + f.StartTime, f.Duration, buffer) + copy(f.Ak[:], ak[0:16]) + + // Increment the reservation ID per AS to make it unique (per AS). + resIDPerIA++ + + flyovers = append(flyovers, &f) + } + } + } + return flyovers, nil +} + +func insertFlyoversInAS( + ctx context.Context, + ia addr.IA, + config topoPerAS, + flyovers []*hummingbird.Flyover, +) error { + + daemonAddr, err := integration.GetSCIONDAddress( + integration.GenFile(integration.DaemonAddressesFile), ia) + if err != nil { + return serrors.WrapStr("getting the sciond address", err, "ia", ia) + } + conn, err := daemon.NewService(daemonAddr).Connect(ctx) + if err != nil { + return serrors.WrapStr("opening daemon connection", err, "ia", ia) + } + + err = conn.StoreFlyovers(ctx, flyovers) + if err != nil { + return serrors.WrapStr("storing flyovers using daemon", err, "ia", ia) + } + + err = conn.Close() + if err != nil { + return serrors.WrapStr("closing daemon connection", err, "ia", ia) + } + + return nil +} + +// ifIDSequenceToMap takes a slice of interfaces and returns a map where each ingress has +// a list to egress interfaces from the slice. +func ifIDSequenceToMap(ifSeq []common.IFIDType) map[common.IFIDType][]common.IFIDType { + + m := make(map[common.IFIDType][]common.IFIDType, len(ifSeq)) + for _, src := range ifSeq { + for _, dst := range ifSeq { + if src == dst { + continue + } + m[src] = append(m[src], dst) + } + } + return m +} + +func logDir() string { + return filepath.Join(integration.LogDir(), name) +} + +func relFile(file string) string { + rel, err := filepath.Rel(filepath.Dir(integration.LogDir()), file) + if err != nil { + return file + } + return rel +} diff --git a/tools/topology/go.py b/tools/topology/go.py index d8f06f6058..c6199ee09c 100644 --- a/tools/topology/go.py +++ b/tools/topology/go.py @@ -158,6 +158,10 @@ def _build_sciond_conf(self, topo_id, ia, base): 'path_db': { 'connection': os.path.join(self.db_dir, '%s.path.db' % name), }, + + 'hbird_db': { + 'connection': os.path.join(self.db_dir, '%s.hbird.db' % name), + }, 'sd': { 'address': socket_address_str(ip, SD_API_PORT), },