table.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. package fragment
  2. import (
  3. "crypto/sha256"
  4. "errors"
  5. "math/rand/v2"
  6. "sync"
  7. "time"
  8. "github.com/slackhq/nebula/handshake/fec"
  9. "github.com/slackhq/nebula/header"
  10. )
  11. var (
  12. ErrTooLarge = errors.New("fragmented message exceeds the size limit")
  13. ErrSourceQuota = errors.New("source has too many messages in progress")
  14. ErrNoRoom = errors.New("no room for fragment")
  15. )
  16. // TableConfig bounds a Table.
  17. type TableConfig struct {
  18. MaxBytes int // symbol bytes held across all entries
  19. MaxEntries int // messages in progress across all sources
  20. MaxPerSource int // messages in progress per source
  21. MaxMsgLen int // largest message accepted
  22. TTL time.Duration // an entry idle this long is dropped
  23. // RepeatInterval is how often, at most, an answered message is reported as a
  24. // repeat on one key. Zero reports every K fresh symbols.
  25. RepeatInterval time.Duration
  26. }
  27. // Table reassembles messages from senders that have not authenticated yet, the
  28. // responder's view of msg1. Every resource is capped. When a cap is hit a
  29. // random entry is evicted; random eviction is harder to aim than LRU, which a
  30. // flood can use to flush specific entries.
  31. //
  32. // K identifies one message, typically the source plus FragID. S identifies the
  33. // source for quotas, typically the address masked to /32 or /64.
  34. //
  35. // A message the caller answered is remembered, see Answered, so the fragments
  36. // still arriving for it do not start it over. Without that, the repair
  37. // fragments trailing a message whose data fragments already completed it would
  38. // open a new entry that, for K above the repair count, never completes: it holds
  39. // symbols and a source's quota until it expires. A retry of an answered message
  40. // is reported as a repeat, at most once per RepeatInterval on that key. Keys
  41. // include the path a message arrives by, so the same message arriving by two
  42. // paths is two keys with two clocks.
  43. //
  44. // A Table is safe for concurrent use.
  45. type Table[K, S comparable] struct {
  46. mu sync.Mutex
  47. cfg TableConfig
  48. entries map[K]*tableEntry[K, S]
  49. order []K
  50. perSource map[S]int
  51. bytes int
  52. answered map[K]*answeredEntry
  53. }
  54. // answeredEntry is what is kept of an answered message: no symbols, only which
  55. // indexes have been seen, so a retry's fresh symbols can be told apart from
  56. // stragglers of an attempt already decoded.
  57. type answeredEntry struct {
  58. hash [sha256.Size]byte
  59. msgLen uint16
  60. k int
  61. seen [fec.MaxSymbols]bool
  62. fresh int
  63. received int
  64. lastSeen time.Time
  65. lastRepeat time.Time // when it was answered, or last reported as a repeat
  66. }
  67. type tableEntry[K, S comparable] struct {
  68. a *Assembly
  69. source S
  70. lastSeen time.Time
  71. pos int
  72. }
  73. func NewTable[K, S comparable](cfg TableConfig) *Table[K, S] {
  74. return &Table[K, S]{
  75. cfg: cfg,
  76. entries: make(map[K]*tableEntry[K, S]),
  77. perSource: make(map[S]int),
  78. answered: make(map[K]*answeredEntry),
  79. }
  80. }
  81. // Result describes a completed message.
  82. type Result struct {
  83. Msg []byte // the decoded message, nil for a repeat
  84. Packet []byte // header.Len bytes for the caller's Nebula header, then Msg
  85. Hash [sha256.Size]byte // sha256 of the message
  86. Received int // packet bytes received for it, for the amplification budget
  87. // Repeat means a message marked Answered was sent again: as many fresh
  88. // symbols as it has data symbols arrived, which a retry offers and stragglers
  89. // of an attempt already decoded do not.
  90. Repeat bool
  91. msgLen uint16
  92. k int
  93. seen [fec.MaxSymbols]bool
  94. }
  95. // Add records a fragment parsed by Parse. When the message is complete its entry
  96. // is removed and the result returned; otherwise the result is nil. A decode
  97. // error means the message is lost and must be retransmitted.
  98. func (t *Table[K, S]) Add(key K, source S, now time.Time, h Header, sym []byte) (*Result, error) {
  99. if int(h.MsgLen) > t.cfg.MaxMsgLen {
  100. return nil, ErrTooLarge
  101. }
  102. t.mu.Lock()
  103. if a, ok := t.answered[key]; ok {
  104. if now.Sub(a.lastSeen) <= t.cfg.TTL {
  105. res := a.add(now, h, sym, t.cfg.RepeatInterval)
  106. t.mu.Unlock()
  107. return res, nil
  108. }
  109. delete(t.answered, key)
  110. }
  111. e, ok := t.entries[key]
  112. if ok && now.Sub(e.lastSeen) > t.cfg.TTL {
  113. t.remove(key, e)
  114. ok = false
  115. }
  116. if !ok {
  117. var err error
  118. e, err = t.insert(key, source, h)
  119. if err != nil {
  120. t.mu.Unlock()
  121. return nil, err
  122. }
  123. }
  124. e.lastSeen = now
  125. if !t.makeRoom(key, e.a.wouldStore(h, sym)) {
  126. if e.a.Stored() == 0 {
  127. t.remove(key, e)
  128. }
  129. t.mu.Unlock()
  130. return nil, ErrNoRoom
  131. }
  132. before := e.a.Stored()
  133. ready, err := e.a.Add(h, sym)
  134. t.bytes += e.a.Stored() - before
  135. if err != nil || !ready {
  136. if errors.Is(err, ErrMismatch) || (err != nil && e.a.Stored() == 0) {
  137. t.remove(key, e)
  138. }
  139. t.mu.Unlock()
  140. return nil, err
  141. }
  142. t.remove(key, e)
  143. t.mu.Unlock()
  144. pkt, err := e.a.Decode(make([]byte, header.Len))
  145. if err != nil {
  146. return nil, err
  147. }
  148. msg := pkt[header.Len:]
  149. return &Result{
  150. Msg: msg,
  151. Packet: pkt,
  152. Hash: sha256.Sum256(msg),
  153. Received: e.a.Received(),
  154. msgLen: e.a.msgLen,
  155. k: e.a.dec.Layout().K,
  156. seen: e.a.dec.Seen(),
  157. }, nil
  158. }
  159. func (a *answeredEntry) add(now time.Time, h Header, sym []byte, interval time.Duration) *Result {
  160. if h.MsgLen != a.msgLen {
  161. return nil
  162. }
  163. a.lastSeen = now
  164. a.received += header.Len + HeaderLen + len(sym)
  165. if a.seen[h.Index] {
  166. return nil
  167. }
  168. a.seen[h.Index] = true
  169. a.fresh++
  170. if a.fresh < a.k || now.Sub(a.lastRepeat) < interval {
  171. // Not enough fresh symbols yet, or this key was answered within the
  172. // interval. The fresh symbols keep counting, so the first one to arrive
  173. // once the interval is up reports the repeat.
  174. return nil
  175. }
  176. r := &Result{Hash: a.hash, Received: a.received, Repeat: true}
  177. a.fresh = 0
  178. a.received = 0
  179. a.lastRepeat = now
  180. return r
  181. }
  182. // Answered records that the caller answered res, the result of Add for key, so
  183. // later fragments of it are counted rather than reassembled. A message is kept
  184. // for the TTL after its last fragment, outside the entry, byte and source caps;
  185. // at most MaxEntries are kept.
  186. func (t *Table[K, S]) Answered(key K, res *Result, now time.Time) {
  187. t.mu.Lock()
  188. defer t.mu.Unlock()
  189. // A straggler may have opened an entry while the caller worked.
  190. if e, ok := t.entries[key]; ok {
  191. t.remove(key, e)
  192. }
  193. if res.Repeat {
  194. return
  195. }
  196. if _, ok := t.answered[key]; !ok && len(t.answered) >= t.cfg.MaxEntries {
  197. for k := range t.answered {
  198. delete(t.answered, k)
  199. break
  200. }
  201. }
  202. t.answered[key] = &answeredEntry{hash: res.Hash, msgLen: res.msgLen, k: res.k, seen: res.seen, lastSeen: now, lastRepeat: now}
  203. }
  204. // Forget drops what Answered recorded for key, so its fragments are reassembled
  205. // again.
  206. func (t *Table[K, S]) Forget(key K) {
  207. t.mu.Lock()
  208. defer t.mu.Unlock()
  209. delete(t.answered, key)
  210. }
  211. // Expire drops every entry idle for longer than the TTL.
  212. func (t *Table[K, S]) Expire(now time.Time) {
  213. t.mu.Lock()
  214. defer t.mu.Unlock()
  215. for i := 0; i < len(t.order); {
  216. key := t.order[i]
  217. e := t.entries[key]
  218. if now.Sub(e.lastSeen) > t.cfg.TTL {
  219. // remove swaps the last entry into position i, so look at i again.
  220. t.remove(key, e)
  221. continue
  222. }
  223. i++
  224. }
  225. for key, a := range t.answered {
  226. if now.Sub(a.lastSeen) > t.cfg.TTL {
  227. delete(t.answered, key)
  228. }
  229. }
  230. }
  231. // Len returns the number of messages in progress.
  232. func (t *Table[K, S]) Len() int {
  233. t.mu.Lock()
  234. defer t.mu.Unlock()
  235. return len(t.entries)
  236. }
  237. // Load returns how full the table is, from 0 to 1, by whichever of its byte and
  238. // entry limits is closer.
  239. func (t *Table[K, S]) Load() float64 {
  240. t.mu.Lock()
  241. defer t.mu.Unlock()
  242. return max(float64(t.bytes)/float64(t.cfg.MaxBytes), float64(len(t.entries))/float64(t.cfg.MaxEntries))
  243. }
  244. // Bytes returns the number of symbol bytes held.
  245. func (t *Table[K, S]) Bytes() int {
  246. t.mu.Lock()
  247. defer t.mu.Unlock()
  248. return t.bytes
  249. }
  250. func (t *Table[K, S]) insert(key K, source S, h Header) (*tableEntry[K, S], error) {
  251. if t.perSource[source] >= t.cfg.MaxPerSource {
  252. return nil, ErrSourceQuota
  253. }
  254. a, err := NewAssembly(h)
  255. if err != nil {
  256. return nil, err
  257. }
  258. for len(t.entries) >= t.cfg.MaxEntries {
  259. if !t.evictRandom(nil) {
  260. return nil, ErrNoRoom
  261. }
  262. }
  263. e := &tableEntry[K, S]{a: a, source: source, pos: len(t.order)}
  264. t.entries[key] = e
  265. t.order = append(t.order, key)
  266. t.perSource[source]++
  267. return e, nil
  268. }
  269. // makeRoom evicts random entries other than keep until n more bytes fit.
  270. func (t *Table[K, S]) makeRoom(keep K, n int) bool {
  271. for t.bytes+n > t.cfg.MaxBytes {
  272. if !t.evictRandom(&keep) {
  273. return false
  274. }
  275. }
  276. return true
  277. }
  278. func (t *Table[K, S]) evictRandom(keep *K) bool {
  279. candidates := len(t.order)
  280. if keep != nil {
  281. if _, ok := t.entries[*keep]; ok {
  282. candidates--
  283. }
  284. }
  285. if candidates <= 0 {
  286. return false
  287. }
  288. for {
  289. key := t.order[rand.IntN(len(t.order))]
  290. if keep != nil && key == *keep {
  291. continue
  292. }
  293. t.remove(key, t.entries[key])
  294. return true
  295. }
  296. }
  297. func (t *Table[K, S]) remove(key K, e *tableEntry[K, S]) {
  298. last := len(t.order) - 1
  299. moved := t.order[last]
  300. t.order[e.pos] = moved
  301. t.entries[moved].pos = e.pos
  302. t.order = t.order[:last]
  303. delete(t.entries, key)
  304. t.bytes -= e.a.Stored()
  305. if t.perSource[e.source] <= 1 {
  306. delete(t.perSource, e.source)
  307. } else {
  308. t.perSource[e.source]--
  309. }
  310. }