mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 17:36:52 +01:00
Clock cached msg2 resends per path in the fragment table, so a copy of msg1 arriving by a relay or a second address is answered even when the direct path only works one way
This commit is contained in:
1 parent
1d0281cba5
commit
bd82089a3f
8 files changed
+207
-57
No files matched your search
@@ -597,3 +597,84 @@ func TestTableAnswered(t *testing.T) {
|
||||
assert.Empty(t, tb.answered)
|
||||
tb.mu.Unlock()
|
||||
}
|
||||
|
||||
// TestTableRepeatInterval checks that an answered message is reported as a
|
||||
// repeat at most once per RepeatInterval on one key, while another key, a
|
||||
// second path the same message arrives by, keeps its own clock.
|
||||
func TestTableRepeatInterval(t *testing.T) {
|
||||
r := rand.New(rand.NewPCG(4, 4))
|
||||
cfg := testConfig()
|
||||
cfg.RepeatInterval = 100 * time.Millisecond
|
||||
tb := NewTable[testKey, string](cfg)
|
||||
now := time.Unix(1000, 0)
|
||||
msg := randMsg(r, 8009) // K=7, 3 repair
|
||||
|
||||
attempt := func(s *Sender) [][]byte {
|
||||
data, repair, err := s.Attempt(DefaultRepair(s.Layout().K))
|
||||
require.NoError(t, err)
|
||||
return append(data, repair...)
|
||||
}
|
||||
repeats := func(key testKey, pkts [][]byte) int {
|
||||
n := 0
|
||||
for _, p := range pkts {
|
||||
_, h, sym := parse(t, p)
|
||||
res, err := tb.Add(key, key.src, now, h, sym)
|
||||
require.NoError(t, err)
|
||||
if res != nil {
|
||||
require.True(t, res.Repeat)
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
answer := func(key testKey, s *Sender) {
|
||||
var res *Result
|
||||
for _, p := range attempt(s) {
|
||||
_, h, sym := parse(t, p)
|
||||
got, err := tb.Add(key, key.src, now, h, sym)
|
||||
require.NoError(t, err)
|
||||
if got != nil {
|
||||
res = got
|
||||
}
|
||||
}
|
||||
require.NotNil(t, res)
|
||||
tb.Answered(key, res, now)
|
||||
}
|
||||
|
||||
// Two paths carry the same message. Each has its own sender here because
|
||||
// the initiator offers the same symbols on every path.
|
||||
direct := testKey{"direct", 1}
|
||||
relayed := testKey{"relay", 1}
|
||||
sd, err := NewSender(msg, testSubtype, 0, 1, 1)
|
||||
require.NoError(t, err)
|
||||
sr, err := NewSender(msg, testSubtype, 0, 1, 1)
|
||||
require.NoError(t, err)
|
||||
answer(direct, sd)
|
||||
answer(relayed, sr)
|
||||
|
||||
// A retry right after the answer carries K fresh symbols, but the clock on
|
||||
// that path has not run out.
|
||||
now = now.Add(10 * time.Millisecond)
|
||||
assert.Zero(t, repeats(direct, attempt(sd)), "a repeat inside the interval is not reported")
|
||||
|
||||
// Once it has, the first fresh symbol of the next retry is enough: the
|
||||
// fresh symbols kept counting.
|
||||
now = now.Add(100 * time.Millisecond)
|
||||
retry := attempt(sd)
|
||||
assert.Equal(t, 1, repeats(direct, retry[:1]), "the held repeat is reported at once")
|
||||
assert.Zero(t, repeats(direct, retry[1:]), "and only once per retry")
|
||||
|
||||
// The other path has its own clock, which only its own answer started.
|
||||
assert.Equal(t, 1, repeats(relayed, attempt(sr)), "a repeat on another path is independent")
|
||||
assert.Zero(t, repeats(relayed, attempt(sr)), "and is itself clocked")
|
||||
|
||||
// Without an interval every K fresh symbols is a repeat, which is what the
|
||||
// other tests rely on.
|
||||
tb = NewTable[testKey, string](testConfig())
|
||||
s2, err := NewSender(msg, testSubtype, 0, 1, 2)
|
||||
require.NoError(t, err)
|
||||
k2 := testKey{"a", 2}
|
||||
answer(k2, s2)
|
||||
assert.Equal(t, 1, repeats(k2, attempt(s2)))
|
||||
assert.Equal(t, 1, repeats(k2, attempt(s2)))
|
||||
}
|
||||
+24
-12
@@ -24,6 +24,10 @@ type TableConfig struct {
|
||||
MaxPerSource int // messages in progress per source
|
||||
MaxMsgLen int // largest message accepted
|
||||
TTL time.Duration // an entry idle this long is dropped
|
||||
|
||||
// RepeatInterval is how often, at most, an answered message is reported as a
|
||||
// repeat on one key. Zero reports every K fresh symbols.
|
||||
RepeatInterval time.Duration
|
||||
}
|
||||
|
||||
// Table reassembles messages from senders that have not authenticated yet, the
|
||||
@@ -38,7 +42,10 @@ type TableConfig struct {
|
||||
// still arriving for it do not start it over. Without that, the repair
|
||||
// fragments trailing a message whose data fragments already completed it would
|
||||
// open a new entry that, for K above the repair count, never completes: it holds
|
||||
// symbols and a source's quota until it expires.
|
||||
// symbols and a source's quota until it expires. A retry of an answered message
|
||||
// is reported as a repeat, at most once per RepeatInterval on that key. Keys
|
||||
// include the path a message arrives by, so the same message arriving by two
|
||||
// paths is two keys with two clocks.
|
||||
//
|
||||
// A Table is safe for concurrent use.
|
||||
type Table[K, S comparable] struct {
|
||||
@@ -55,13 +62,14 @@ type Table[K, S comparable] struct {
|
||||
// indexes have been seen, so a retry's fresh symbols can be told apart from
|
||||
// stragglers of an attempt already decoded.
|
||||
type answeredEntry struct {
|
||||
hash [sha256.Size]byte
|
||||
msgLen uint16
|
||||
k int
|
||||
seen [fec.MaxSymbols]bool
|
||||
fresh int
|
||||
received int
|
||||
lastSeen time.Time
|
||||
hash [sha256.Size]byte
|
||||
msgLen uint16
|
||||
k int
|
||||
seen [fec.MaxSymbols]bool
|
||||
fresh int
|
||||
received int
|
||||
lastSeen time.Time
|
||||
lastRepeat time.Time // when it was answered, or last reported as a repeat
|
||||
}
|
||||
|
||||
type tableEntry[K, S comparable] struct {
|
||||
@@ -107,7 +115,7 @@ func (t *Table[K, S]) Add(key K, source S, now time.Time, h Header, sym []byte)
|
||||
t.mu.Lock()
|
||||
if a, ok := t.answered[key]; ok {
|
||||
if now.Sub(a.lastSeen) <= t.cfg.TTL {
|
||||
res := a.add(now, h, sym)
|
||||
res := a.add(now, h, sym, t.cfg.RepeatInterval)
|
||||
t.mu.Unlock()
|
||||
return res, nil
|
||||
}
|
||||
@@ -165,7 +173,7 @@ func (t *Table[K, S]) Add(key K, source S, now time.Time, h Header, sym []byte)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *answeredEntry) add(now time.Time, h Header, sym []byte) *Result {
|
||||
func (a *answeredEntry) add(now time.Time, h Header, sym []byte, interval time.Duration) *Result {
|
||||
if h.MsgLen != a.msgLen {
|
||||
return nil
|
||||
}
|
||||
@@ -176,12 +184,16 @@ func (a *answeredEntry) add(now time.Time, h Header, sym []byte) *Result {
|
||||
}
|
||||
a.seen[h.Index] = true
|
||||
a.fresh++
|
||||
if a.fresh < a.k {
|
||||
if a.fresh < a.k || now.Sub(a.lastRepeat) < interval {
|
||||
// Not enough fresh symbols yet, or this key was answered within the
|
||||
// interval. The fresh symbols keep counting, so the first one to arrive
|
||||
// once the interval is up reports the repeat.
|
||||
return nil
|
||||
}
|
||||
r := &Result{Hash: a.hash, Received: a.received, Repeat: true}
|
||||
a.fresh = 0
|
||||
a.received = 0
|
||||
a.lastRepeat = now
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -205,7 +217,7 @@ func (t *Table[K, S]) Answered(key K, res *Result, now time.Time) {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.answered[key] = &answeredEntry{hash: res.Hash, msgLen: res.msgLen, k: res.k, seen: res.seen, lastSeen: now}
|
||||
t.answered[key] = &answeredEntry{hash: res.Hash, msgLen: res.msgLen, k: res.k, seen: res.seen, lastSeen: now, lastRepeat: now}
|
||||
}
|
||||
|
||||
// Forget drops what Answered recorded for key, so its fragments are reassembled
|
||||
|
||||
Reference in new issue
Block a user