diff --git a/control/beaconing/BUILD.bazel b/control/beaconing/BUILD.bazel index d3123fdd6a..9d32339391 100644 --- a/control/beaconing/BUILD.bazel +++ b/control/beaconing/BUILD.bazel @@ -37,6 +37,7 @@ go_library( "//private/segment/verifier:go_default_library", "//private/topology:go_default_library", "//private/tracing:go_default_library", + "//private/trust:go_default_library", "@com_github_opentracing_opentracing_go//:go_default_library", ], ) @@ -70,6 +71,7 @@ go_test( "//pkg/scrypto/signed:go_default_library", "//pkg/segment:go_default_library", "//pkg/segment/extensions/staticinfo:go_default_library", + "//pkg/slayers/path:go_default_library", "//pkg/slayers/path/scion:go_default_library", "//pkg/snet:go_default_library", "//pkg/snet/addrutil:go_default_library", diff --git a/control/beaconing/extender.go b/control/beaconing/extender.go index 7e5fe00a49..cf440eaa16 100644 --- a/control/beaconing/extender.go +++ b/control/beaconing/extender.go @@ -23,14 +23,22 @@ import ( "github.com/scionproto/scion/control/ifstate" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/log" + "github.com/scionproto/scion/pkg/metrics" "github.com/scionproto/scion/pkg/private/serrors" "github.com/scionproto/scion/pkg/private/util" seg "github.com/scionproto/scion/pkg/segment" "github.com/scionproto/scion/pkg/segment/extensions/digest" "github.com/scionproto/scion/pkg/segment/extensions/epic" "github.com/scionproto/scion/pkg/slayers/path" + "github.com/scionproto/scion/private/trust" ) +// SignerGen generates signers and returns their expiration time. +type SignerGen interface { + // Generate generates a signer it. + Generate(ctx context.Context) (trust.Signer, error) +} + // Extender extends path segments. type Extender interface { // Extend extends the path segment. The zero value for ingress indicates @@ -44,8 +52,8 @@ type Extender interface { type DefaultExtender struct { // IA is the local IA IA addr.IA - // Signer is used to sign path segments. - Signer seg.Signer + // SignerGen is used to sign path segments. + SignerGen SignerGen // MAC is used to calculate the hop field MAC. MAC func() hash.Hash // Intfs holds all interfaces in the AS. @@ -60,6 +68,11 @@ type DefaultExtender struct { StaticInfo func() *StaticInfoCfg // EPIC defines whether the EPIC authenticators should be added when the segment is extended. EPIC bool + + // SegmentExpirationDeficient is a gauge that is set to 1 if the expiration time of the segment + // is below the maximum expiration time. This happens when the signer expiration time is lower + // than the maximum segment expiration time. + SegmentExpirationDeficient metrics.Gauge } // Extend extends the beacon with hop fields. @@ -85,8 +98,27 @@ func (s *DefaultExtender) Extend( } ts := pseg.Info.Timestamp + signer, err := s.SignerGen.Generate(ctx) + if err != nil { + return serrors.WrapStr("getting signer", err) + } + // Make sure the hop expiration time is not longer than the signer expiration time. + expTime := s.MaxExpTime() + if ts.Add(path.ExpTimeToDuration(expTime)).After(signer.Expiration) { + metrics.GaugeSet(s.SegmentExpirationDeficient, 1) + var err error + expTime, err = path.ExpTimeFromDuration(signer.Expiration.Sub(ts)) + if err != nil { + return serrors.WrapStr( + "calculating expiry time from signer expiration time", err, + "signer_expiration", signer.Expiration, + ) + } + } else { + metrics.GaugeSet(s.SegmentExpirationDeficient, 0) + } hopBeta := extractBeta(pseg) - hopEntry, epicHopMac, err := s.createHopEntry(ingress, egress, ts, hopBeta) + hopEntry, epicHopMac, err := s.createHopEntry(ingress, egress, expTime, ts, hopBeta) if err != nil { return serrors.WrapStr("creating hop entry", err) } @@ -104,7 +136,7 @@ func (s *DefaultExtender) Extend( // is traversed. peerBeta := hopBeta ^ binary.BigEndian.Uint16(hopEntry.HopField.MAC[:2]) - peerEntries, epicPeerMacs, err := s.createPeerEntries(egress, peers, ts, peerBeta) + peerEntries, epicPeerMacs, err := s.createPeerEntries(egress, peers, expTime, ts, peerBeta) if err != nil { return err } @@ -143,7 +175,7 @@ func (s *DefaultExtender) Extend( } } - if err := pseg.AddASEntry(ctx, asEntry, s.Signer); err != nil { + if err := pseg.AddASEntry(ctx, asEntry, signer); err != nil { return err } if egress == 0 { @@ -153,12 +185,12 @@ func (s *DefaultExtender) Extend( } func (s *DefaultExtender) createPeerEntries(egress uint16, peers []uint16, - ts time.Time, beta uint16) ([]seg.PeerEntry, [][]byte, error) { + expTime uint8, ts time.Time, beta uint16) ([]seg.PeerEntry, [][]byte, error) { peerEntries := make([]seg.PeerEntry, 0, len(peers)) peerEpicMacs := make([][]byte, 0, len(peers)) for _, peer := range peers { - peerEntry, epicMac, err := s.createPeerEntry(peer, egress, ts, beta) + peerEntry, epicMac, err := s.createPeerEntry(peer, egress, expTime, ts, beta) if err != nil { log.Debug("Ignoring peer link upon error", "task", s.Task, "peer_interface", peer, "err", err) @@ -170,15 +202,20 @@ func (s *DefaultExtender) createPeerEntries(egress uint16, peers []uint16, return peerEntries, peerEpicMacs, nil } -func (s *DefaultExtender) createHopEntry(ingress, egress uint16, ts time.Time, - beta uint16) (seg.HopEntry, []byte, error) { +func (s *DefaultExtender) createHopEntry( + ingress, + egress uint16, + expTime uint8, + ts time.Time, + beta uint16, +) (seg.HopEntry, []byte, error) { remoteInMTU, err := s.remoteMTU(ingress) if err != nil { return seg.HopEntry{}, nil, serrors.WrapStr("checking remote ingress interface (mtu)", err, "interfaces", ingress) } - hopF, epicMac := s.createHopF(ingress, egress, ts, beta) + hopF, epicMac := s.createHopF(ingress, egress, expTime, ts, beta) return seg.HopEntry{ IngressMTU: int(remoteInMTU), HopField: seg.HopField{ @@ -190,7 +227,7 @@ func (s *DefaultExtender) createHopEntry(ingress, egress uint16, ts time.Time, }, epicMac, nil } -func (s *DefaultExtender) createPeerEntry(ingress, egress uint16, ts time.Time, +func (s *DefaultExtender) createPeerEntry(ingress, egress uint16, expTime uint8, ts time.Time, beta uint16) (seg.PeerEntry, []byte, error) { remoteInIA, remoteInIfID, remoteInMTU, err := s.remoteInfo(ingress) @@ -198,7 +235,7 @@ func (s *DefaultExtender) createPeerEntry(ingress, egress uint16, ts time.Time, return seg.PeerEntry{}, nil, serrors.WrapStr("checking remote ingress interface", err, "ingress_interface", ingress) } - hopF, epicMac := s.createHopF(ingress, egress, ts, beta) + hopF, epicMac := s.createHopF(ingress, egress, expTime, ts, beta) return seg.PeerEntry{ PeerMTU: int(remoteInMTU), Peer: remoteInIA, @@ -259,10 +296,9 @@ func (s *DefaultExtender) remoteInfo(ifid uint16) ( return topoInfo.IA, topoInfo.RemoteID, topoInfo.MTU, nil } -func (s *DefaultExtender) createHopF(ingress, egress uint16, ts time.Time, +func (s *DefaultExtender) createHopF(ingress, egress uint16, expTime uint8, ts time.Time, beta uint16) (path.HopField, []byte) { - expTime := s.MaxExpTime() input := make([]byte, path.MACBufferSize) path.MACInput(beta, util.TimeToSecs(ts), expTime, ingress, egress, input) diff --git a/control/beaconing/extender_test.go b/control/beaconing/extender_test.go index 99163a683f..cfd0a72a3d 100644 --- a/control/beaconing/extender_test.go +++ b/control/beaconing/extender_test.go @@ -36,7 +36,9 @@ import ( cryptopb "github.com/scionproto/scion/pkg/proto/crypto" "github.com/scionproto/scion/pkg/scrypto" seg "github.com/scionproto/scion/pkg/segment" + "github.com/scionproto/scion/pkg/slayers/path" "github.com/scionproto/scion/private/topology" + "github.com/scionproto/scion/private/trust" ) func TestDefaultExtenderExtend(t *testing.T) { @@ -98,8 +100,8 @@ func TestDefaultExtenderExtend(t *testing.T) { intfs.Get(peer).Activate(peerRemoteIfs[peer]) } ext := &beaconing.DefaultExtender{ - IA: topo.IA(), - Signer: testSigner(t, priv, topo.IA()), + IA: topo.IA(), + SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())}, MAC: func() hash.Hash { mac, err := scrypto.InitMac(make([]byte, 16)) require.NoError(t, err) @@ -171,8 +173,8 @@ func TestDefaultExtenderExtend(t *testing.T) { intfs := ifstate.NewInterfaces(interfaceInfos(topo), ifstate.Config{}) require.NoError(t, err) ext := &beaconing.DefaultExtender{ - IA: topo.IA(), - Signer: testSigner(t, priv, topo.IA()), + IA: topo.IA(), + SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())}, MAC: func() hash.Hash { mac, err := scrypto.InitMac(make([]byte, 16)) require.NoError(t, err) @@ -189,8 +191,106 @@ func TestDefaultExtenderExtend(t *testing.T) { err = ext.Extend(context.Background(), pseg, 0, graph.If_111_A_112_X, []uint16{}) require.NoError(t, err) assert.Equal(t, uint8(1), pseg.ASEntries[0].HopEntry.HopField.ExpTime) - }) + t.Run("segment and signer expiration interaction", func(t *testing.T) { + ts := time.Now() + testCases := map[string]struct { + SignerGen beaconing.SignerGen + MaxExpTime func() uint8 + ExpTime uint8 + ErrAssertion assert.ErrorAssertionFunc + }{ + "signer expires before max expiration time": { + SignerGen: testSignerGen{ + Signer: func() trust.Signer { + s := testSigner(t, priv, topo.IA()) + s.Expiration = ts.Add(path.MaxTTL / 2) + return s + }(), + }, + ExpTime: 127, + MaxExpTime: func() uint8 { return 255 }, + ErrAssertion: assert.NoError, + }, + "signer expires after max expiration time": { + SignerGen: testSignerGen{ + Signer: func() trust.Signer { + s := testSigner(t, priv, topo.IA()) + s.Expiration = ts.Add(path.MaxTTL) + return s + }(), + }, + ExpTime: 254, + MaxExpTime: func() uint8 { return 254 }, + ErrAssertion: assert.NoError, + }, + "minimum signer expiration time": { + SignerGen: testSignerGen{ + Signer: func() trust.Signer { + s := testSigner(t, priv, topo.IA()) + s.Expiration = ts.Add(path.MaxTTL / 256) + return s + }(), + }, + ExpTime: 0, + MaxExpTime: func() uint8 { return 10 }, + ErrAssertion: assert.NoError, + }, + "signer expiration time too small": { + SignerGen: testSignerGen{ + Signer: func() trust.Signer { + s := testSigner(t, priv, topo.IA()) + s.Expiration = ts.Add(path.MaxTTL / 257) + return s + }(), + }, + MaxExpTime: func() uint8 { return 10 }, + ErrAssertion: assert.Error, + }, + "signer expiration time too large uses MaxExpTime": { + SignerGen: testSignerGen{ + Signer: func() trust.Signer { + s := testSigner(t, priv, topo.IA()) + s.Expiration = ts.Add(2 * path.MaxTTL) + return s + }(), + }, + ExpTime: 157, + MaxExpTime: func() uint8 { return 157 }, + ErrAssertion: assert.NoError, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + mctrl := gomock.NewController(t) + defer mctrl.Finish() + intfs := ifstate.NewInterfaces(interfaceInfos(topo), ifstate.Config{}) + require.NoError(t, err) + ext := &beaconing.DefaultExtender{ + IA: topo.IA(), + SignerGen: tc.SignerGen, + MAC: func() hash.Hash { + mac, err := scrypto.InitMac(make([]byte, 16)) + require.NoError(t, err) + return mac + }, + Intfs: intfs, + MTU: 1337, + MaxExpTime: tc.MaxExpTime, + StaticInfo: func() *beaconing.StaticInfoCfg { return nil }, + } + pseg, err := seg.CreateSegment(ts, uint16(mrand.Int())) + require.NoError(t, err) + err = ext.Extend(context.Background(), pseg, 0, graph.If_111_A_112_X, []uint16{}) + tc.ErrAssertion(t, err) + if err != nil { + return + } + assert.Equal(t, tc.ExpTime, pseg.ASEntries[0].HopEntry.HopField.ExpTime) + }) + } + }) + t.Run("segment is not extended on error", func(t *testing.T) { defaultSigner := func(t *testing.T) seg.Signer { return testSigner(t, priv, topo.IA()) @@ -238,8 +338,8 @@ func TestDefaultExtenderExtend(t *testing.T) { defer mctrl.Finish() intfs := ifstate.NewInterfaces(interfaceInfos(topo), ifstate.Config{}) ext := &beaconing.DefaultExtender{ - IA: topo.IA(), - Signer: testSigner(t, priv, topo.IA()), + IA: topo.IA(), + SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())}, MAC: func() hash.Hash { mac, err := scrypto.InitMac(make([]byte, 16)) require.NoError(t, err) diff --git a/control/beaconing/originator_test.go b/control/beaconing/originator_test.go index 3c9d78d1f8..8c15c57a19 100644 --- a/control/beaconing/originator_test.go +++ b/control/beaconing/originator_test.go @@ -69,7 +69,7 @@ func TestOriginatorRun(t *testing.T) { Extender: &beaconing.DefaultExtender{ IA: topo.IA(), MTU: topo.MTU(), - Signer: signer, + SignerGen: testSignerGen{Signer: signer}, Intfs: intfs, MAC: macFactory, MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime }, @@ -130,7 +130,7 @@ func TestOriginatorRun(t *testing.T) { Extender: &beaconing.DefaultExtender{ IA: topo.IA(), MTU: topo.MTU(), - Signer: signer, + SignerGen: testSignerGen{Signer: signer}, Intfs: intfs, MAC: macFactory, MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime }, diff --git a/control/beaconing/propagator_test.go b/control/beaconing/propagator_test.go index 1bd1ea99e8..90bac1be66 100644 --- a/control/beaconing/propagator_test.go +++ b/control/beaconing/propagator_test.go @@ -64,7 +64,7 @@ func TestPropagatorRunNonCore(t *testing.T) { Extender: &beaconing.DefaultExtender{ IA: topo.IA(), MTU: topo.MTU(), - Signer: testSigner(t, priv, topo.IA()), + SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())}, Intfs: intfs, MAC: macFactory, MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime }, @@ -138,7 +138,7 @@ func TestPropagatorRunCore(t *testing.T) { Extender: &beaconing.DefaultExtender{ IA: topo.IA(), MTU: topo.MTU(), - Signer: testSigner(t, priv, topo.IA()), + SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())}, Intfs: intfs, MAC: macFactory, MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime }, @@ -226,7 +226,7 @@ func TestPropagatorFastRecovery(t *testing.T) { Extender: &beaconing.DefaultExtender{ IA: topo.IA(), MTU: topo.MTU(), - Signer: testSigner(t, priv, topo.IA()), + SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())}, Intfs: intfs, MAC: macFactory, MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime }, diff --git a/control/beaconing/writer_test.go b/control/beaconing/writer_test.go index 4613aa1e99..e253f6b2d3 100644 --- a/control/beaconing/writer_test.go +++ b/control/beaconing/writer_test.go @@ -99,7 +99,7 @@ func TestRegistrarRun(t *testing.T) { Extender: &beaconing.DefaultExtender{ IA: topo.IA(), MTU: topo.MTU(), - Signer: testSigner(t, priv, topo.IA()), + SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())}, Intfs: intfs, MAC: macFactory, MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime }, @@ -185,7 +185,7 @@ func TestRegistrarRun(t *testing.T) { Extender: &beaconing.DefaultExtender{ IA: topo.IA(), MTU: topo.MTU(), - Signer: testSigner(t, priv, topo.IA()), + SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())}, Intfs: intfs, MAC: macFactory, MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime }, @@ -284,7 +284,7 @@ func TestRegistrarRun(t *testing.T) { Extender: &beaconing.DefaultExtender{ IA: topo.IA(), MTU: topo.MTU(), - Signer: testSigner(t, priv, topo.IA()), + SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())}, Intfs: intfs, MAC: macFactory, MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime }, @@ -330,7 +330,7 @@ func testBeacon(g *graph.Graph, desc []uint16) beacon.Beacon { } } -func testSigner(t *testing.T, priv crypto.Signer, ia addr.IA) seg.Signer { +func testSigner(t *testing.T, priv crypto.Signer, ia addr.IA) trust.Signer { return trust.Signer{ PrivateKey: priv, Algorithm: signed.ECDSAWithSHA256, @@ -345,6 +345,14 @@ func testSigner(t *testing.T, priv crypto.Signer, ia addr.IA) seg.Signer { } } +type testSignerGen struct { + Signer trust.Signer +} + +func (s testSignerGen) Generate(ctx context.Context) (trust.Signer, error) { + return s.Signer, nil +} + var macFactory = func() hash.Hash { mac, err := scrypto.InitMac(make([]byte, 16)) // This can only happen if the library is messed up badly. diff --git a/control/cmd/control/main.go b/control/cmd/control/main.go index 1b192cf139..8c01a43a3d 100644 --- a/control/cmd/control/main.go +++ b/control/cmd/control/main.go @@ -776,7 +776,7 @@ func realMain(ctx context.Context) error { }, SegmentRegister: beaconinggrpc.Registrar{Dialer: dialer}, BeaconStore: beaconStore, - Signer: signer, + SignerGen: signer.SignerGen, Inspector: inspector, Metrics: metrics, DRKeyEngine: drkeyEngine, diff --git a/control/mgmtapi/testdata/TestAPI_beacon b/control/mgmtapi/testdata/TestAPI_beacon index 8f4c65c161..f47062d66f 100644 --- a/control/mgmtapi/testdata/TestAPI_beacon +++ b/control/mgmtapi/testdata/TestAPI_beacon @@ -1,6 +1,6 @@ { "beacon": { - "expiration": "2021-01-01T08:05:37Z", + "expiration": "2021-01-01T08:05:37.5Z", "hops": [ { "interface": 1, diff --git a/control/mgmtapi/testdata/TestAPI_beacon_id_prefix b/control/mgmtapi/testdata/TestAPI_beacon_id_prefix index 8f4c65c161..f47062d66f 100644 --- a/control/mgmtapi/testdata/TestAPI_beacon_id_prefix +++ b/control/mgmtapi/testdata/TestAPI_beacon_id_prefix @@ -1,6 +1,6 @@ { "beacon": { - "expiration": "2021-01-01T08:05:37Z", + "expiration": "2021-01-01T08:05:37.5Z", "hops": [ { "interface": 1, diff --git a/control/mgmtapi/testdata/TestAPI_beacons b/control/mgmtapi/testdata/TestAPI_beacons index ef36d2859c..cc1ac3bf20 100644 --- a/control/mgmtapi/testdata/TestAPI_beacons +++ b/control/mgmtapi/testdata/TestAPI_beacons @@ -1,7 +1,7 @@ { "beacons": [ { - "expiration": "2021-01-01T08:05:37Z", + "expiration": "2021-01-01T08:05:37.5Z", "hops": [ { "interface": 1, @@ -26,7 +26,7 @@ ] }, { - "expiration": "2021-02-01T08:05:37Z", + "expiration": "2021-02-01T08:05:37.5Z", "hops": [ { "interface": 5, diff --git a/control/mgmtapi/testdata/TestAPI_beacons_descending_order b/control/mgmtapi/testdata/TestAPI_beacons_descending_order index d09cdf60ff..71b3bfeec0 100644 --- a/control/mgmtapi/testdata/TestAPI_beacons_descending_order +++ b/control/mgmtapi/testdata/TestAPI_beacons_descending_order @@ -1,7 +1,7 @@ { "beacons": [ { - "expiration": "2021-02-01T08:05:37Z", + "expiration": "2021-02-01T08:05:37.5Z", "hops": [ { "interface": 5, @@ -25,7 +25,7 @@ ] }, { - "expiration": "2021-01-01T08:05:37Z", + "expiration": "2021-01-01T08:05:37.5Z", "hops": [ { "interface": 1, diff --git a/control/mgmtapi/testdata/TestAPI_beacons_existing_usages b/control/mgmtapi/testdata/TestAPI_beacons_existing_usages index f32ef063b2..081823fc24 100644 --- a/control/mgmtapi/testdata/TestAPI_beacons_existing_usages +++ b/control/mgmtapi/testdata/TestAPI_beacons_existing_usages @@ -1,7 +1,7 @@ { "beacons": [ { - "expiration": "2021-01-01T08:05:37Z", + "expiration": "2021-01-01T08:05:37.5Z", "hops": [ { "interface": 1, diff --git a/control/mgmtapi/testdata/TestAPI_beacons_sort_by_ingress_interface b/control/mgmtapi/testdata/TestAPI_beacons_sort_by_ingress_interface index d09cdf60ff..71b3bfeec0 100644 --- a/control/mgmtapi/testdata/TestAPI_beacons_sort_by_ingress_interface +++ b/control/mgmtapi/testdata/TestAPI_beacons_sort_by_ingress_interface @@ -1,7 +1,7 @@ { "beacons": [ { - "expiration": "2021-02-01T08:05:37Z", + "expiration": "2021-02-01T08:05:37.5Z", "hops": [ { "interface": 5, @@ -25,7 +25,7 @@ ] }, { - "expiration": "2021-01-01T08:05:37Z", + "expiration": "2021-01-01T08:05:37.5Z", "hops": [ { "interface": 1, diff --git a/control/observability.go b/control/observability.go index e8ef240953..371f3a0564 100644 --- a/control/observability.go +++ b/control/observability.go @@ -73,6 +73,7 @@ type Metrics struct { SegmentLookupRequestsTotal *prometheus.CounterVec SegmentLookupSegmentsSentTotal *prometheus.CounterVec SegmentRegistrationsTotal *prometheus.CounterVec + SegmentExpirationDeficient *prometheus.GaugeVec TrustDBQueriesTotal *prometheus.CounterVec TrustLatestTRCNotBefore prometheus.Gauge TrustLatestTRCNotAfter prometheus.Gauge @@ -242,6 +243,15 @@ func NewMetrics() *Metrics { }, []string{"src", "seg_type", prom.LabelResult}, ), + SegmentExpirationDeficient: promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "control_segment_expiration_deficient", + Help: "Indicates whether the expiration time of the segment is below the " + + "configured maximum. This happens when the signer expiration time is lower " + + "than the maximum segment expiration time.", + }, + []string{}, + ), TrustDBQueriesTotal: promauto.NewCounterVec( prometheus.CounterOpts{ Name: "trustengine_db_queries_total", diff --git a/control/tasks.go b/control/tasks.go index fedae5e905..4de2588cc6 100644 --- a/control/tasks.go +++ b/control/tasks.go @@ -56,7 +56,7 @@ type TasksConfig struct { BeaconSenderFactory beaconing.SenderFactory SegmentRegister beaconing.RPC BeaconStore Store - Signer seg.Signer + SignerGen beaconing.SignerGen Inspector trust.Inspector Metrics *Metrics DRKeyEngine *drkey.ServiceEngine @@ -92,7 +92,6 @@ func (t *TasksConfig) Originator() *periodic.Runner { IA: t.IA, AllInterfaces: t.AllInterfaces, OriginationInterfaces: t.OriginationInterfaces, - Signer: t.Signer, Tick: beaconing.NewTick(t.OriginationInterval), } if t.Metrics != nil { @@ -110,7 +109,6 @@ func (t *TasksConfig) Propagator() *periodic.Runner { SenderFactory: t.BeaconSenderFactory, Provider: t.BeaconStore, IA: t.IA, - Signer: t.Signer, AllInterfaces: t.AllInterfaces, PropagationInterfaces: t.PropagationInterfaces, AllowIsdLoop: t.AllowIsdLoop, @@ -204,12 +202,16 @@ func (t *TasksConfig) segmentWriter(segType seg.Type, return periodic.Start(r, 500*time.Millisecond, t.RegistrationInterval) } -func (t *TasksConfig) extender(task string, ia addr.IA, mtu uint16, - maxExp func() uint8) beaconing.Extender { +func (t *TasksConfig) extender( + task string, + ia addr.IA, + mtu uint16, + maxExp func() uint8, +) beaconing.Extender { return &beaconing.DefaultExtender{ IA: ia, - Signer: t.Signer, + SignerGen: t.SignerGen, MAC: t.MACGen, Intfs: t.AllInterfaces, MTU: mtu, @@ -217,6 +219,12 @@ func (t *TasksConfig) extender(task string, ia addr.IA, mtu uint16, StaticInfo: t.StaticInfo, Task: task, EPIC: t.EPIC, + SegmentExpirationDeficient: func() metrics.Gauge { + if t.Metrics == nil { + return nil + } + return metrics.NewPromGauge(t.Metrics.SegmentExpirationDeficient) + }(), } } diff --git a/pkg/experimental/hiddenpath/beaconwriter_test.go b/pkg/experimental/hiddenpath/beaconwriter_test.go index 3cd383fa2c..b9b0cabcce 100644 --- a/pkg/experimental/hiddenpath/beaconwriter_test.go +++ b/pkg/experimental/hiddenpath/beaconwriter_test.go @@ -196,7 +196,7 @@ func TestRemoteBeaconWriterWrite(t *testing.T) { Extender: &beaconing.DefaultExtender{ IA: topo.IA(), MTU: topo.MTU(), - Signer: testSigner(t, priv, topo.IA()), + SignerGen: testSignerGen{Signer: testSigner(t, priv, topo.IA())}, Intfs: intfs, MAC: macFactory, MaxExpTime: func() uint8 { return beacon.DefaultMaxExpTime }, @@ -236,7 +236,7 @@ func testBeacon(g *graph.Graph, desc []uint16) beacon.Beacon { } } -func testSigner(t *testing.T, priv crypto.Signer, ia addr.IA) seg.Signer { +func testSigner(t *testing.T, priv crypto.Signer, ia addr.IA) trust.Signer { return trust.Signer{ PrivateKey: priv, Algorithm: signed.ECDSAWithSHA256, @@ -352,6 +352,14 @@ func (w topoWrap) UnderlayNextHop(id uint16) *net.UDPAddr { return a } +type testSignerGen struct { + Signer trust.Signer +} + +func (s testSignerGen) Generate(ctx context.Context) (trust.Signer, error) { + return s.Signer, nil +} + func interfaceInfos(topo topology.Topology) map[uint16]ifstate.InterfaceInfo { in := topo.IFInfoMap() result := make(map[uint16]ifstate.InterfaceInfo, len(in)) diff --git a/pkg/segment/seg.go b/pkg/segment/seg.go index ecc0467bff..a873b55b6d 100644 --- a/pkg/segment/seg.go +++ b/pkg/segment/seg.go @@ -220,7 +220,7 @@ func (ps *PathSegment) MaxExpiry() time.Time { // MinExpiry returns the minimum expiry of all hop fields. // Assumes segment is validated. func (ps *PathSegment) MinExpiry() time.Time { - return ps.expiry(path.MaxTTL*time.Second, func(hfTtl time.Duration, ttl time.Duration) bool { + return ps.expiry(path.MaxTTL, func(hfTtl time.Duration, ttl time.Duration) bool { return hfTtl < ttl }) } diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index 0cb6b48709..c4415ce671 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -30,10 +30,10 @@ const ( MacLen = 6 ) -// MaxTTL is the maximum age of a HopField in seconds. -const MaxTTL = 24 * 60 * 60 // One day in seconds +// MaxTTL is the maximum age of a HopField. +const MaxTTL = 24 * time.Hour -const expTimeUnit = MaxTTL / 256 // ~5m38s +const expTimeUnit = MaxTTL / 256 // ~5m38.5s // HopField is the HopField used in the SCION and OneHop path types. // @@ -142,5 +142,20 @@ func (h *HopField) SerializeTo(b []byte) (err error) { // Calls to ExpTimeToDuration are guaranteed to always terminate. // @ decreases func ExpTimeToDuration(expTime uint8) time.Duration { - return (time.Duration(expTime) + 1) * time.Duration(expTimeUnit) * time.Second + return (time.Duration(expTime) + 1) * expTimeUnit +} + +// ExpTimeFromDuration calculates the largest relative expiration time that +// represents a duration <= the provided duration, that is: +// d <= ExpTimeToDuration(ExpTimeFromDuration(d)). +// The returned value is the ExpTime that can be used in a HopField. +// For durations that are out of range, an error is returned. +func ExpTimeFromDuration(d time.Duration) (uint8, error) { + if d < expTimeUnit { + return 0, serrors.New("duration too small", "seconds", d) + } + if d > MaxTTL { + return 0, serrors.New("duration too large", "seconds", d) + } + return uint8((d*256)/MaxTTL - 1), nil } diff --git a/pkg/slayers/path/hopfield_test.go b/pkg/slayers/path/hopfield_test.go index 4a7cfd8ad2..c8f9ec803a 100644 --- a/pkg/slayers/path/hopfield_test.go +++ b/pkg/slayers/path/hopfield_test.go @@ -16,6 +16,7 @@ package path_test import ( "testing" + "time" "github.com/stretchr/testify/assert" @@ -38,3 +39,49 @@ func TestHopSerializeDecode(t *testing.T) { assert.NoError(t, got.DecodeFromBytes(b)) assert.Equal(t, want, got) } + +func TestExpTimeFromDuration(t *testing.T) { + tests := map[string]struct { + d time.Duration + ExpTime uint8 + ErrorF assert.ErrorAssertionFunc + }{ + "Zero": { + d: 0, + ExpTime: 0, + ErrorF: assert.Error, + }, + "Max": { + d: path.MaxTTL, + ExpTime: 255, + ErrorF: assert.NoError, + }, + "Overflow": { + d: (path.MaxTTL + 1), + ExpTime: 0, + ErrorF: assert.Error, + }, + "Underflow": { + d: -1, + ExpTime: 0, + ErrorF: assert.Error, + }, + "Max-1": { + d: (path.MaxTTL - 1), + ExpTime: 254, + ErrorF: assert.NoError, + }, + "Half": { + d: (path.MaxTTL / 2), + ExpTime: 127, + ErrorF: assert.NoError, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + expTime, err := path.ExpTimeFromDuration(test.d) + test.ErrorF(t, err) + assert.Equal(t, test.ExpTime, expTime) + }) + } +} diff --git a/private/app/appnet/intra_as.go b/private/app/appnet/intra_as.go index 6735dce534..743ac9631a 100644 --- a/private/app/appnet/intra_as.go +++ b/private/app/appnet/intra_as.go @@ -42,7 +42,7 @@ func (q IntraASPathQuerier) Query(_ context.Context, _ addr.IA) ([]snet.Path, er DataplanePath: path.Empty{}, Meta: snet.PathMetadata{ MTU: q.MTU, - Expiry: time.Now().Add(rawpath.MaxTTL * time.Second), + Expiry: time.Now().Add(rawpath.MaxTTL), }, }}, nil } diff --git a/private/mgmtapi/segments/api/testdata/segments-by-id.json b/private/mgmtapi/segments/api/testdata/segments-by-id.json index 1efc22952e..5ecdc2864b 100644 --- a/private/mgmtapi/segments/api/testdata/segments-by-id.json +++ b/private/mgmtapi/segments/api/testdata/segments-by-id.json @@ -1,5 +1,5 @@ { - "expiration": "2021-01-19T10:17:38Z", + "expiration": "2021-01-19T10:17:38.5Z", "hops": [ { "interface": 1, diff --git a/private/path/combinator/expiry_test.go b/private/path/combinator/expiry_test.go index e87b71c9dd..6d8e86bbf4 100644 --- a/private/path/combinator/expiry_test.go +++ b/private/path/combinator/expiry_test.go @@ -39,7 +39,7 @@ func TestComputeSegmentExpTime(t *testing.T) { { Name: "non-zero hop ttl field value", Segment: buildTestSegment(0, 1), - ExpectedExpiration: 674, + ExpectedExpiration: 675, }, { Name: "two hop fields, min should be taken", @@ -49,29 +49,29 @@ func TestComputeSegmentExpTime(t *testing.T) { { Name: "maximum ttl selected", Segment: buildTestSegment(0, 255), - ExpectedExpiration: 24*60*60 - 128, // rounding error drift + ExpectedExpiration: 24 * 60 * 60, }, { Name: "ttl relative to info field timestamp", Segment: buildTestSegment(100, 1), - ExpectedExpiration: 774, + ExpectedExpiration: 775, }, { Name: "ttl relative to maximum info field timestamp", Segment: buildTestSegment(4294967295, 1), - ExpectedExpiration: 4294967969, + ExpectedExpiration: 4294967970, }, { Name: "maximum possible value", Segment: buildTestSegment(4294967295, 255), - ExpectedExpiration: 4295053695 - 128, // rounding error drift + ExpectedExpiration: 4295053695, }, } t.Log("Expiration values should be correct") for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { computedExpiration := tc.Segment.ComputeExpTime() - assert.Equal(t, computedExpiration.Unix(), tc.ExpectedExpiration) + assert.Equal(t, tc.ExpectedExpiration, computedExpiration.Unix()) }) } diff --git a/private/path/combinator/graph.go b/private/path/combinator/graph.go index eb092ca709..cfb1e71603 100644 --- a/private/path/combinator/graph.go +++ b/private/path/combinator/graph.go @@ -627,7 +627,7 @@ func (segment *segment) ComputeExpTime() time.Time { } func (segment *segment) computeHopFieldsTTL() time.Duration { - minTTL := time.Duration(path.MaxTTL) * time.Second + minTTL := path.MaxTTL for _, hf := range segment.HopFields { offset := path.ExpTimeToDuration(hf.ExpTime) if minTTL > offset { diff --git a/private/segment/segfetcher/pather.go b/private/segment/segfetcher/pather.go index 68b39962df..f168cc6ac0 100644 --- a/private/segment/segfetcher/pather.go +++ b/private/segment/segfetcher/pather.go @@ -70,7 +70,7 @@ func (p *Pather) GetPaths(ctx context.Context, dst addr.IA, Dst: dst, Meta: snet.PathMetadata{ MTU: p.MTU, - Expiry: time.Now().Add(rawpath.MaxTTL * time.Second), + Expiry: time.Now().Add(rawpath.MaxTTL), }, }}, nil }