rx_reorder_linux.go 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. //go:build !android && !e2e_testing
  2. // +build !android,!e2e_testing
  3. package udp
  4. import (
  5. "cmp"
  6. "net/netip"
  7. "slices"
  8. )
  9. // rxSegment is one nebula packet pulled out of a recvmmsg entry — either a
  10. // lone datagram or one segment of a GRO superpacket. cnt is the big-endian
  11. // uint64 message counter at bytes [8:16] of the nebula header; 0 if the
  12. // segment is too short to contain a header. ecn is the 2-bit IP-level ECN
  13. // codepoint stamped on the carrier (one value per slot, since GRO requires
  14. // equal ECN across coalesced datagrams).
  15. type rxSegment struct {
  16. src netip.AddrPort
  17. cnt uint64
  18. buf []byte
  19. ecn byte
  20. }
  21. // rxReorderBuffer accumulates one recvmmsg batch worth of segments,
  22. // splits any GRO superpackets at gso_size boundaries, stable-sorts by
  23. // (src, port, counter), then delivers in order. The reorder distance is
  24. // bounded by len(buf), which the caller sizes to stay well within the
  25. // receiver's ReplayWindow so older arrivals are not rejected as replays.
  26. type rxReorderBuffer struct {
  27. buf []rxSegment
  28. }
  29. func newRxReorderBuffer(initialCap int) *rxReorderBuffer {
  30. return &rxReorderBuffer{buf: make([]rxSegment, 0, initialCap)}
  31. }
  32. // reset prepares the buffer for the next recvmmsg batch.
  33. func (r *rxReorderBuffer) reset() { r.buf = r.buf[:0] }
  34. // addEntry expands one recvmmsg slot into rxSegments. When segSize <= 0 or
  35. // segSize >= len(payload) the payload is appended as a single segment;
  36. // otherwise the kernel-coalesced GRO superpacket is split at segSize
  37. // boundaries (the kernel guarantees every segment is exactly segSize bytes
  38. // except for the final one, which may be short). ecn applies uniformly to
  39. // every produced segment because GRO requires equal ECN across coalesced
  40. // datagrams.
  41. func (r *rxReorderBuffer) addEntry(from netip.AddrPort, payload []byte, segSize int, ecn byte) {
  42. if segSize <= 0 || segSize >= len(payload) {
  43. r.buf = append(r.buf, rxSegment{from, headerCounter(payload), payload, ecn})
  44. return
  45. }
  46. for off := 0; off < len(payload); off += segSize {
  47. end := off + segSize
  48. if end > len(payload) {
  49. end = len(payload)
  50. }
  51. seg := payload[off:end]
  52. r.buf = append(r.buf, rxSegment{from, headerCounter(seg), seg, ecn})
  53. }
  54. }
  55. // sortStable orders the accumulated segments by (src addr, src port,
  56. // counter). Same-source segments are reordered into counter order;
  57. // cross-source relative order is determined by a stable address compare so
  58. // the sort is total and predictable.
  59. func (r *rxReorderBuffer) sortStable() {
  60. slices.SortStableFunc(r.buf, func(a, b rxSegment) int {
  61. if c := a.src.Addr().Compare(b.src.Addr()); c != 0 {
  62. return c
  63. }
  64. if c := cmp.Compare(a.src.Port(), b.src.Port()); c != 0 {
  65. return c
  66. }
  67. return cmp.Compare(a.cnt, b.cnt)
  68. })
  69. }
  70. // deliver invokes fn once per segment in sorted order, then nils the
  71. // per-entry buf reference so the next batch's append doesn't alias it.
  72. func (r *rxReorderBuffer) deliver(fn EncReader) {
  73. for k := range r.buf {
  74. fn(r.buf[k].src, r.buf[k].buf, RxMeta{OuterECN: r.buf[k].ecn})
  75. r.buf[k].buf = nil
  76. }
  77. }