1
0

tcp_coalesce_test.go 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287
  1. package batch
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "io"
  6. "testing"
  7. "github.com/slackhq/nebula/overlay/tio"
  8. "github.com/slackhq/nebula/test"
  9. )
  10. // fakeTunWriter records plain Writes and WriteGSO calls without touching a
  11. // real TUN fd. WriteGSO records the IP header, transport header, and
  12. // borrowed payload fragments separately so tests can inspect each.
  13. // noTSO / noUSO withhold one offload from an otherwise GSO-capable writer, so
  14. // tests can build the half-capable queues real kernels hand us (USO needs a
  15. // newer kernel than TSO).
  16. type fakeTunWriter struct {
  17. gsoEnabled bool
  18. noTSO bool
  19. noUSO bool
  20. writes [][]byte
  21. gsoWrites []fakeGSOWrite
  22. // order records the interleaving of Write ("write") and WriteGSO ("gso")
  23. // calls for tests that assert cross-call emission order.
  24. order []string
  25. }
  26. // fakeGSOWrite captures one WriteGSO call. hdr is the concatenation of the
  27. // IP and transport headers (in that order), gsoSize / isV6 / csumStart are
  28. // derived from the call so existing assertions keep working unchanged.
  29. type fakeGSOWrite struct {
  30. hdr []byte
  31. pays [][]byte
  32. gsoSize uint16
  33. isV6 bool
  34. csumStart uint16
  35. }
  36. // total returns hdrLen + sum of pay lens.
  37. func (g fakeGSOWrite) total() int {
  38. n := len(g.hdr)
  39. for _, p := range g.pays {
  40. n += len(p)
  41. }
  42. return n
  43. }
  44. // payLen sums the pays.
  45. func (g fakeGSOWrite) payLen() int {
  46. var n int
  47. for _, p := range g.pays {
  48. n += len(p)
  49. }
  50. return n
  51. }
  52. func (w *fakeTunWriter) Write(p []byte) (int, error) {
  53. buf := make([]byte, len(p))
  54. copy(buf, p)
  55. w.writes = append(w.writes, buf)
  56. w.order = append(w.order, "write")
  57. return len(p), nil
  58. }
  59. func (w *fakeTunWriter) WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte, _ tio.GSOProto) error {
  60. hcopy := make([]byte, len(hdr)+len(transportHdr))
  61. copy(hcopy, hdr)
  62. copy(hcopy[len(hdr):], transportHdr)
  63. paysCopy := make([][]byte, len(pays))
  64. for i, p := range pays {
  65. pc := make([]byte, len(p))
  66. copy(pc, p)
  67. paysCopy[i] = pc
  68. }
  69. var gsoSize uint16
  70. if len(pays) > 1 {
  71. gsoSize = uint16(len(pays[0]))
  72. }
  73. isV6 := len(hdr) > 0 && hdr[0]>>4 == 6
  74. w.gsoWrites = append(w.gsoWrites, fakeGSOWrite{
  75. hdr: hcopy,
  76. pays: paysCopy,
  77. gsoSize: gsoSize,
  78. isV6: isV6,
  79. csumStart: uint16(len(hdr)),
  80. })
  81. w.order = append(w.order, "gso")
  82. return nil
  83. }
  84. func (w *fakeTunWriter) Capabilities() tio.Capabilities {
  85. return tio.Capabilities{TSO: w.gsoEnabled && !w.noTSO, USO: w.gsoEnabled && !w.noUSO}
  86. }
  87. // buildTCPv4 constructs a minimal IPv4+TCP packet with the given payload,
  88. // seq, and flags. Assumes no IP options and a 20-byte TCP header.
  89. func buildTCPv4(seq uint32, flags byte, payload []byte) []byte {
  90. return buildTCPv4Ports(1000, 2000, seq, flags, payload)
  91. }
  92. // buildTCPv4Ports is buildTCPv4 with caller-specified ports so tests can
  93. // build distinct flows.
  94. func buildTCPv4Ports(sport, dport uint16, seq uint32, flags byte, payload []byte) []byte {
  95. const ipHdrLen = 20
  96. const tcpHdrLen = 20
  97. total := ipHdrLen + tcpHdrLen + len(payload)
  98. pkt := make([]byte, total)
  99. pkt[0] = 0x45
  100. pkt[1] = 0x00
  101. binary.BigEndian.PutUint16(pkt[2:4], uint16(total))
  102. binary.BigEndian.PutUint16(pkt[4:6], 0)
  103. binary.BigEndian.PutUint16(pkt[6:8], 0x4000)
  104. pkt[8] = 64
  105. pkt[9] = ipProtoTCP
  106. copy(pkt[12:16], []byte{10, 0, 0, 1})
  107. copy(pkt[16:20], []byte{10, 0, 0, 2})
  108. binary.BigEndian.PutUint16(pkt[20:22], sport)
  109. binary.BigEndian.PutUint16(pkt[22:24], dport)
  110. binary.BigEndian.PutUint32(pkt[24:28], seq)
  111. binary.BigEndian.PutUint32(pkt[28:32], 12345)
  112. pkt[32] = 0x50
  113. pkt[33] = flags
  114. binary.BigEndian.PutUint16(pkt[34:36], 0xffff)
  115. copy(pkt[40:], payload)
  116. return pkt
  117. }
  118. const (
  119. tcpAck = 0x10
  120. tcpPsh = 0x08
  121. tcpSyn = 0x02
  122. tcpFin = 0x01
  123. tcpAckPsh = tcpAck | tcpPsh
  124. )
  125. // setIPv4ID stamps an IPv4 ID and DF state onto a builder packet. The
  126. // builders default to DF=1/ID=0 (an atomic datagram); the ID-admission
  127. // tests use this to fabricate non-atomic (DF=0) senders.
  128. func setIPv4ID(pkt []byte, id uint16, df bool) {
  129. binary.BigEndian.PutUint16(pkt[4:6], id)
  130. var flags uint16
  131. if df {
  132. flags = 0x4000
  133. }
  134. binary.BigEndian.PutUint16(pkt[6:8], flags)
  135. }
  136. // newTestTCPCoalescer builds a coalescer over w and fails the test if w can't
  137. // do TSO. Every test but TestNewTCPCoalescerRefusesWhenGSOUnavailable wants the
  138. // GSO path, and the constructor now hands back a nil coalescer otherwise.
  139. func newTestTCPCoalescer(tb testing.TB, w io.Writer) *TCPCoalescer {
  140. tb.Helper()
  141. c := NewTCPCoalescer(w, test.NewLogger())
  142. if c == nil {
  143. tb.Fatal("NewTCPCoalescer: writer does not support TSO")
  144. }
  145. return c
  146. }
  147. // TestNewTCPCoalescerRefusesWhenGSOUnavailable pins the constructor
  148. // precondition: no TSO, no coalescer. There's no degraded mode — the caller
  149. // (MultiCoalescer) sends TCP down the verbatim lane instead.
  150. func TestNewTCPCoalescerRefusesWhenGSOUnavailable(t *testing.T) {
  151. if c := NewTCPCoalescer(&fakeTunWriter{gsoEnabled: false}, test.NewLogger()); c != nil {
  152. t.Fatalf("want nil for a non-TSO writer, got %v", c)
  153. }
  154. // A writer that isn't a GSOWriter at all is refused the same way.
  155. if c := NewTCPCoalescer(&plainOnlyWriter{}, test.NewLogger()); c != nil {
  156. t.Fatalf("want nil for a plain writer, got %v", c)
  157. }
  158. }
  159. // plainOnlyWriter is an io.Writer with no GSO support at all — the
  160. // single-packet Queue shape.
  161. type plainOnlyWriter struct{ writes int }
  162. func (w *plainOnlyWriter) Write(p []byte) (int, error) {
  163. w.writes++
  164. return len(p), nil
  165. }
  166. func TestCoalescerNonTCPPassthrough(t *testing.T) {
  167. w := &fakeTunWriter{gsoEnabled: true}
  168. c := newTestTCPCoalescer(t, w)
  169. pkt := make([]byte, 28)
  170. pkt[0] = 0x45
  171. binary.BigEndian.PutUint16(pkt[2:4], 28)
  172. pkt[9] = 1
  173. copy(pkt[12:16], []byte{10, 0, 0, 1})
  174. copy(pkt[16:20], []byte{10, 0, 0, 2})
  175. if err := c.Commit(pkt); err != nil {
  176. t.Fatal(err)
  177. }
  178. if err := c.Flush(); err != nil {
  179. t.Fatal(err)
  180. }
  181. if len(w.writes) != 1 || len(w.gsoWrites) != 0 {
  182. t.Fatalf("ICMP should pass through unchanged")
  183. }
  184. }
  185. func TestCoalescerSeedThenFlushAlone(t *testing.T) {
  186. w := &fakeTunWriter{gsoEnabled: true}
  187. c := newTestTCPCoalescer(t, w)
  188. pkt := buildTCPv4(1000, tcpAck, make([]byte, 1000))
  189. if err := c.Commit(pkt); err != nil {
  190. t.Fatal(err)
  191. }
  192. if len(w.writes) != 0 || len(w.gsoWrites) != 0 {
  193. t.Fatalf("unexpected output before flush")
  194. }
  195. if err := c.Flush(); err != nil {
  196. t.Fatal(err)
  197. }
  198. // A slot that never grew past one segment flushes as a plain Write of
  199. // the original packet bytes: the original (already valid) checksum
  200. // ships via the DATA_VALID path, so the kernel does no csum work.
  201. // WriteGSO is reserved for slots that actually coalesced (>=2 segs).
  202. if len(w.writes) != 1 || len(w.gsoWrites) != 0 {
  203. t.Fatalf("single-seg flush: writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  204. }
  205. if !bytes.Equal(w.writes[0], pkt) {
  206. t.Errorf("plain write not byte-identical to committed packet: got %d bytes want %d", len(w.writes[0]), len(pkt))
  207. }
  208. }
  209. // TestCoalescerPureAckDoesNotSealRun pins the pure-ACK fast path: a bare
  210. // acknowledgment (zero payload, nothing beyond ACK|PSH|ECE) rides its lane
  211. // as a verbatim WITHOUT sealing the flow's open slot, so an inbound data
  212. // run on a bidirectional connection keeps coalescing across the peer ACKs
  213. // interleaved into it. The ACK is emitted after the superpacket (stale ACKs
  214. // are ignored by receivers, so the reorder is harmless by design).
  215. func TestCoalescerPureAckDoesNotSealRun(t *testing.T) {
  216. w := &fakeTunWriter{gsoEnabled: true}
  217. c := newTestTCPCoalescer(t, w)
  218. pay := make([]byte, 1200)
  219. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  220. t.Fatal(err)
  221. }
  222. ack := buildTCPv4(2200, tcpAck, nil)
  223. if err := c.Commit(ack); err != nil {
  224. t.Fatal(err)
  225. }
  226. if err := c.Commit(buildTCPv4(2200, tcpAck, pay)); err != nil {
  227. t.Fatal(err)
  228. }
  229. if err := c.Flush(); err != nil {
  230. t.Fatal(err)
  231. }
  232. if len(w.gsoWrites) != 1 || len(w.writes) != 1 {
  233. t.Fatalf("ACK sealed the run: writes=%d gso=%d, want 1 gso (2 pays) + 1 plain", len(w.writes), len(w.gsoWrites))
  234. }
  235. if got := len(w.gsoWrites[0].pays); got != 2 {
  236. t.Errorf("pay count=%d want 2 (data kept coalescing across the ACK)", got)
  237. }
  238. if !bytes.Equal(w.writes[0], ack) {
  239. t.Errorf("plain write is not the ACK packet: got %d bytes want %d", len(w.writes[0]), len(ack))
  240. }
  241. if got, want := w.order, []string{"gso", "write"}; !stringSliceEq(got, want) {
  242. t.Errorf("flush order=%v want %v (slot order: data run seeded first)", got, want)
  243. }
  244. }
  245. // TestCoalescerFinStillSealsRun is the guard rail for the pure-ACK fast
  246. // path: control flags (here FIN|ACK, zero payload) must keep sealing the
  247. // open slot so data never reorders across a flow-state transition.
  248. func TestCoalescerFinStillSealsRun(t *testing.T) {
  249. w := &fakeTunWriter{gsoEnabled: true}
  250. c := newTestTCPCoalescer(t, w)
  251. pay := make([]byte, 1200)
  252. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  253. t.Fatal(err)
  254. }
  255. if err := c.Commit(buildTCPv4(2200, tcpFin|tcpAck, nil)); err != nil {
  256. t.Fatal(err)
  257. }
  258. if err := c.Commit(buildTCPv4(2200, tcpAck, pay)); err != nil {
  259. t.Fatal(err)
  260. }
  261. if err := c.Flush(); err != nil {
  262. t.Fatal(err)
  263. }
  264. // FIN evicts the open slot; the third packet seeds a fresh one. All
  265. // three stay single-segment, so all three emit as plain writes in
  266. // arrival order — any gso write would mean data coalesced across FIN.
  267. if len(w.writes) != 3 || len(w.gsoWrites) != 0 {
  268. t.Fatalf("FIN must seal the run: writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  269. }
  270. }
  271. func TestCoalescerCoalescesAdjacentACKs(t *testing.T) {
  272. w := &fakeTunWriter{gsoEnabled: true}
  273. c := newTestTCPCoalescer(t, w)
  274. pay := make([]byte, 1200)
  275. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  276. t.Fatal(err)
  277. }
  278. if err := c.Commit(buildTCPv4(2200, tcpAck, pay)); err != nil {
  279. t.Fatal(err)
  280. }
  281. if err := c.Commit(buildTCPv4(3400, tcpAck, pay)); err != nil {
  282. t.Fatal(err)
  283. }
  284. if err := c.Flush(); err != nil {
  285. t.Fatal(err)
  286. }
  287. if len(w.gsoWrites) != 1 {
  288. t.Fatalf("want 1 gso write, got %d (plain=%d)", len(w.gsoWrites), len(w.writes))
  289. }
  290. g := w.gsoWrites[0]
  291. if g.gsoSize != 1200 {
  292. t.Errorf("gsoSize=%d want 1200", g.gsoSize)
  293. }
  294. if len(g.hdr) != 40 {
  295. t.Errorf("hdrLen=%d want 40", len(g.hdr))
  296. }
  297. if g.csumStart != 20 {
  298. t.Errorf("csumStart=%d want 20", g.csumStart)
  299. }
  300. if len(g.pays) != 3 {
  301. t.Errorf("pay count=%d want 3", len(g.pays))
  302. }
  303. if g.total() != 40+3*1200 {
  304. t.Errorf("superpacket len=%d want %d", g.total(), 40+3*1200)
  305. }
  306. if tot := binary.BigEndian.Uint16(g.hdr[2:4]); int(tot) != g.total() {
  307. t.Errorf("ip total_length=%d want %d", tot, g.total())
  308. }
  309. }
  310. func TestCoalescerRejectsSeqGap(t *testing.T) {
  311. w := &fakeTunWriter{gsoEnabled: true}
  312. c := newTestTCPCoalescer(t, w)
  313. pay := make([]byte, 1200)
  314. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  315. t.Fatal(err)
  316. }
  317. if err := c.Commit(buildTCPv4(3000, tcpAck, pay)); err != nil {
  318. t.Fatal(err)
  319. }
  320. if err := c.Flush(); err != nil {
  321. t.Fatal(err)
  322. }
  323. // Each packet stays a single-segment slot and flushes as its own plain
  324. // write of the original bytes.
  325. if len(w.writes) != 2 || len(w.gsoWrites) != 0 {
  326. t.Fatalf("seq gap: want 2 plain writes got writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  327. }
  328. }
  329. func TestCoalescerRejectsFlagMismatch(t *testing.T) {
  330. w := &fakeTunWriter{gsoEnabled: true}
  331. c := newTestTCPCoalescer(t, w)
  332. pay := make([]byte, 1200)
  333. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  334. t.Fatal(err)
  335. }
  336. // SYN|ACK is non-admissible. Must flush the matching flow's slot —
  337. // single-segment, so a plain write of the original bytes — and then
  338. // plain-write the SYN packet itself.
  339. syn := buildTCPv4(2200, tcpSyn|tcpAck, pay)
  340. if err := c.Commit(syn); err != nil {
  341. t.Fatal(err)
  342. }
  343. if err := c.Flush(); err != nil {
  344. t.Fatal(err)
  345. }
  346. if len(w.writes) != 2 || len(w.gsoWrites) != 0 {
  347. t.Fatalf("flag mismatch: want 2 plain writes (flushed seed + SYN), got writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  348. }
  349. if !bytes.Equal(w.writes[1], syn) {
  350. t.Errorf("second plain write should be the SYN packet, got %d bytes", len(w.writes[1]))
  351. }
  352. }
  353. func TestCoalescerRejectsFIN(t *testing.T) {
  354. w := &fakeTunWriter{gsoEnabled: true}
  355. c := newTestTCPCoalescer(t, w)
  356. fin := buildTCPv4(1000, tcpAck|tcpFin, []byte("x"))
  357. if err := c.Commit(fin); err != nil {
  358. t.Fatal(err)
  359. }
  360. if err := c.Flush(); err != nil {
  361. t.Fatal(err)
  362. }
  363. // FIN isn't admissible — verbatim as plain, no slot, no gso.
  364. if len(w.writes) != 1 || len(w.gsoWrites) != 0 {
  365. t.Fatalf("FIN should be verbatim, got writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  366. }
  367. }
  368. func TestCoalescerShortLastSegmentClosesChain(t *testing.T) {
  369. w := &fakeTunWriter{gsoEnabled: true}
  370. c := newTestTCPCoalescer(t, w)
  371. full := make([]byte, 1200)
  372. half := make([]byte, 500)
  373. if err := c.Commit(buildTCPv4(1000, tcpAck, full)); err != nil {
  374. t.Fatal(err)
  375. }
  376. if err := c.Commit(buildTCPv4(2200, tcpAck, half)); err != nil {
  377. t.Fatal(err)
  378. }
  379. // Chain now closed; next packet seeds a new slot on the same flow
  380. // after flushing the old one.
  381. if err := c.Commit(buildTCPv4(2700, tcpAck, full)); err != nil {
  382. t.Fatal(err)
  383. }
  384. if err := c.Flush(); err != nil {
  385. t.Fatal(err)
  386. }
  387. // Expect one gso write for the first two packets coalesced, then the
  388. // third — still single-segment — flushed as a plain write of the
  389. // original packet.
  390. if len(w.gsoWrites) != 1 {
  391. t.Fatalf("want 1 gso write got %d", len(w.gsoWrites))
  392. }
  393. if len(w.writes) != 1 {
  394. t.Fatalf("want 1 plain write got %d", len(w.writes))
  395. }
  396. if w.gsoWrites[0].gsoSize != 1200 {
  397. t.Errorf("gsoSize=%d want 1200", w.gsoWrites[0].gsoSize)
  398. }
  399. if got, want := w.gsoWrites[0].total(), 40+1200+500; got != want {
  400. t.Errorf("super len=%d want %d", got, want)
  401. }
  402. if got, want := len(w.writes[0]), 40+1200; got != want {
  403. t.Errorf("plain write len=%d want %d", got, want)
  404. }
  405. }
  406. func TestCoalescerPSHFinalizesChain(t *testing.T) {
  407. w := &fakeTunWriter{gsoEnabled: true}
  408. c := newTestTCPCoalescer(t, w)
  409. pay := make([]byte, 1200)
  410. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  411. t.Fatal(err)
  412. }
  413. if err := c.Commit(buildTCPv4(2200, tcpAckPsh, pay)); err != nil {
  414. t.Fatal(err)
  415. }
  416. if err := c.Commit(buildTCPv4(3400, tcpAck, pay)); err != nil {
  417. t.Fatal(err)
  418. }
  419. if err := c.Flush(); err != nil {
  420. t.Fatal(err)
  421. }
  422. // First two coalesce into one gso write; the third seeds a fresh slot
  423. // that stays single-segment and flushes as a plain write.
  424. if len(w.gsoWrites) != 1 {
  425. t.Fatalf("want 1 gso write got %d", len(w.gsoWrites))
  426. }
  427. if len(w.writes) != 1 {
  428. t.Fatalf("want 1 plain write got %d", len(w.writes))
  429. }
  430. }
  431. // TestCoalescerPropagatesPSHFromAppended ensures that when an appended
  432. // segment carries PSH (or is short, sealing the chain), the PSH bit ends
  433. // up in the emitted superpacket's TCP flags. The kernel TSO path keeps
  434. // PSH only on the last segment iff the input header has it set; if the
  435. // coalescer drops it the sender's push signal never reaches the receiver.
  436. func TestCoalescerPropagatesPSHFromAppended(t *testing.T) {
  437. w := &fakeTunWriter{gsoEnabled: true}
  438. c := newTestTCPCoalescer(t, w)
  439. pay := make([]byte, 1200)
  440. // Seed has no PSH; second segment carries PSH and seals the chain.
  441. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  442. t.Fatal(err)
  443. }
  444. if err := c.Commit(buildTCPv4(2200, tcpAckPsh, pay)); err != nil {
  445. t.Fatal(err)
  446. }
  447. if err := c.Flush(); err != nil {
  448. t.Fatal(err)
  449. }
  450. if len(w.gsoWrites) != 1 {
  451. t.Fatalf("want 1 gso write got %d", len(w.gsoWrites))
  452. }
  453. g := w.gsoWrites[0]
  454. const ipHdrLen = 20
  455. flags := g.hdr[ipHdrLen+13]
  456. if flags&tcpPsh == 0 {
  457. t.Fatalf("PSH lost from coalesced superpacket: flags=0x%02x", flags)
  458. }
  459. if flags&tcpAck == 0 {
  460. t.Fatalf("ACK missing from coalesced superpacket: flags=0x%02x", flags)
  461. }
  462. }
  463. func TestCoalescerRejectsDifferentFlow(t *testing.T) {
  464. w := &fakeTunWriter{gsoEnabled: true}
  465. c := newTestTCPCoalescer(t, w)
  466. pay := make([]byte, 1200)
  467. p1 := buildTCPv4(1000, tcpAck, pay)
  468. p2 := buildTCPv4(2200, tcpAck, pay)
  469. binary.BigEndian.PutUint16(p2[20:22], 9999)
  470. if err := c.Commit(p1); err != nil {
  471. t.Fatal(err)
  472. }
  473. if err := c.Commit(p2); err != nil {
  474. t.Fatal(err)
  475. }
  476. if err := c.Flush(); err != nil {
  477. t.Fatal(err)
  478. }
  479. // Two independent flows, each stays single-segment and flushes as its
  480. // own plain write of the original bytes.
  481. if len(w.writes) != 2 || len(w.gsoWrites) != 0 {
  482. t.Fatalf("diff flow: want 2 plain writes got writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  483. }
  484. }
  485. func TestCoalescerRejectsIPOptions(t *testing.T) {
  486. w := &fakeTunWriter{gsoEnabled: true}
  487. c := newTestTCPCoalescer(t, w)
  488. pay := make([]byte, 500)
  489. pkt := buildTCPv4(1000, tcpAck, pay)
  490. // Bump IHL to 6 to simulate 4 bytes of IP options. Don't actually add
  491. // bytes — parser should bail before it matters.
  492. pkt[0] = 0x46
  493. if err := c.Commit(pkt); err != nil {
  494. t.Fatal(err)
  495. }
  496. if err := c.Flush(); err != nil {
  497. t.Fatal(err)
  498. }
  499. // Non-admissible parse → verbatim as plain.
  500. if len(w.writes) != 1 || len(w.gsoWrites) != 0 {
  501. t.Fatalf("IP options should verbatim, got writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  502. }
  503. }
  504. func TestCoalescerCapBySegments(t *testing.T) {
  505. w := &fakeTunWriter{gsoEnabled: true}
  506. c := newTestTCPCoalescer(t, w)
  507. pay := make([]byte, 512)
  508. seq := uint32(1000)
  509. for range tcpCoalesceMaxSegs + 5 {
  510. if err := c.Commit(buildTCPv4(seq, tcpAck, pay)); err != nil {
  511. t.Fatal(err)
  512. }
  513. seq += uint32(len(pay))
  514. }
  515. if err := c.Flush(); err != nil {
  516. t.Fatal(err)
  517. }
  518. for _, g := range w.gsoWrites {
  519. segs := len(g.pays)
  520. if segs > tcpCoalesceMaxSegs {
  521. t.Fatalf("super exceeded seg cap: %d > %d", segs, tcpCoalesceMaxSegs)
  522. }
  523. }
  524. }
  525. // TestCoalescerMultipleFlowsInSameBatch proves two interleaved bulk TCP
  526. // flows coalesce independently in a single Flush.
  527. func TestCoalescerMultipleFlowsInSameBatch(t *testing.T) {
  528. w := &fakeTunWriter{gsoEnabled: true}
  529. c := newTestTCPCoalescer(t, w)
  530. pay := make([]byte, 1200)
  531. // Flow A: sport 1000. Flow B: sport 3000.
  532. if err := c.Commit(buildTCPv4Ports(1000, 2000, 100, tcpAck, pay)); err != nil {
  533. t.Fatal(err)
  534. }
  535. if err := c.Commit(buildTCPv4Ports(3000, 2000, 500, tcpAck, pay)); err != nil {
  536. t.Fatal(err)
  537. }
  538. if err := c.Commit(buildTCPv4Ports(1000, 2000, 1300, tcpAck, pay)); err != nil {
  539. t.Fatal(err)
  540. }
  541. if err := c.Commit(buildTCPv4Ports(3000, 2000, 1700, tcpAck, pay)); err != nil {
  542. t.Fatal(err)
  543. }
  544. if err := c.Commit(buildTCPv4Ports(1000, 2000, 2500, tcpAck, pay)); err != nil {
  545. t.Fatal(err)
  546. }
  547. if err := c.Commit(buildTCPv4Ports(3000, 2000, 2900, tcpAck, pay)); err != nil {
  548. t.Fatal(err)
  549. }
  550. if err := c.Flush(); err != nil {
  551. t.Fatal(err)
  552. }
  553. if len(w.gsoWrites) != 2 {
  554. t.Fatalf("want 2 gso writes (one per flow), got %d", len(w.gsoWrites))
  555. }
  556. if len(w.writes) != 0 {
  557. t.Fatalf("want no plain writes, got %d", len(w.writes))
  558. }
  559. // Each superpacket should carry 3 segments.
  560. for i, g := range w.gsoWrites {
  561. if len(g.pays) != 3 {
  562. t.Errorf("gso[%d]: segs=%d want 3", i, len(g.pays))
  563. }
  564. if g.gsoSize != 1200 {
  565. t.Errorf("gso[%d]: gsoSize=%d want 1200", i, g.gsoSize)
  566. }
  567. }
  568. // Verify each superpacket carries the source port it was seeded with.
  569. seenSports := map[uint16]bool{}
  570. for _, g := range w.gsoWrites {
  571. sp := binary.BigEndian.Uint16(g.hdr[20:22])
  572. seenSports[sp] = true
  573. }
  574. if !seenSports[1000] || !seenSports[3000] {
  575. t.Errorf("expected superpackets for sports 1000 and 3000, got %v", seenSports)
  576. }
  577. }
  578. // TestCoalescerPreservesArrivalOrder confirms that with verbatim and
  579. // coalesced events both queued, Flush emits them in Add order rather than
  580. // writing verbatim packets synchronously.
  581. func TestCoalescerPreservesArrivalOrder(t *testing.T) {
  582. w := &fakeTunWriter{gsoEnabled: true}
  583. c := newTestTCPCoalescer(t, w)
  584. // Sequence: coalesceable TCP, ICMP (verbatim), coalesceable TCP on
  585. // a different flow. Both TCP slots stay single-segment, so all three
  586. // emit as plain writes; the packet order (X, ICMP, Y) is asserted by
  587. // byte content since the kinds no longer distinguish them.
  588. pay := make([]byte, 1200)
  589. tcpX := buildTCPv4Ports(1000, 2000, 100, tcpAck, pay)
  590. if err := c.Commit(tcpX); err != nil {
  591. t.Fatal(err)
  592. }
  593. icmp := make([]byte, 28)
  594. icmp[0] = 0x45
  595. binary.BigEndian.PutUint16(icmp[2:4], 28)
  596. icmp[9] = 1
  597. copy(icmp[12:16], []byte{10, 0, 0, 1})
  598. copy(icmp[16:20], []byte{10, 0, 0, 3})
  599. if err := c.Commit(icmp); err != nil {
  600. t.Fatal(err)
  601. }
  602. tcpY := buildTCPv4Ports(3000, 2000, 500, tcpAck, pay)
  603. if err := c.Commit(tcpY); err != nil {
  604. t.Fatal(err)
  605. }
  606. // Nothing should have hit the writer synchronously.
  607. if len(w.order) != 0 {
  608. t.Fatalf("Add emitted events synchronously: %v", w.order)
  609. }
  610. if err := c.Flush(); err != nil {
  611. t.Fatal(err)
  612. }
  613. if got, want := w.order, []string{"write", "write", "write"}; !stringSliceEq(got, want) {
  614. t.Fatalf("flush order=%v want %v", got, want)
  615. }
  616. for i, want := range [][]byte{tcpX, icmp, tcpY} {
  617. if !bytes.Equal(w.writes[i], want) {
  618. t.Fatalf("write %d out of arrival order: got %d bytes, want %d bytes", i, len(w.writes[i]), len(want))
  619. }
  620. }
  621. }
  622. func stringSliceEq(a, b []string) bool {
  623. if len(a) != len(b) {
  624. return false
  625. }
  626. for i := range a {
  627. if a[i] != b[i] {
  628. return false
  629. }
  630. }
  631. return true
  632. }
  633. // TestCoalescerInterleavedFlowsPreserveOrdering checks that a non-admissible
  634. // packet (SYN) mid-flow only flushes its own flow, not others.
  635. func TestCoalescerInterleavedFlowsPreserveOrdering(t *testing.T) {
  636. w := &fakeTunWriter{gsoEnabled: true}
  637. c := newTestTCPCoalescer(t, w)
  638. pay := make([]byte, 1200)
  639. // Flow A two segments.
  640. if err := c.Commit(buildTCPv4Ports(1000, 2000, 100, tcpAck, pay)); err != nil {
  641. t.Fatal(err)
  642. }
  643. if err := c.Commit(buildTCPv4Ports(1000, 2000, 1300, tcpAck, pay)); err != nil {
  644. t.Fatal(err)
  645. }
  646. // Flow B two segments.
  647. if err := c.Commit(buildTCPv4Ports(3000, 2000, 500, tcpAck, pay)); err != nil {
  648. t.Fatal(err)
  649. }
  650. if err := c.Commit(buildTCPv4Ports(3000, 2000, 1700, tcpAck, pay)); err != nil {
  651. t.Fatal(err)
  652. }
  653. // Flow A SYN (non-admissible) — must flush only flow A's slot.
  654. syn := buildTCPv4Ports(1000, 2000, 9999, tcpSyn|tcpAck, pay)
  655. if err := c.Commit(syn); err != nil {
  656. t.Fatal(err)
  657. }
  658. // Flow B continues — should still be coalesced with its seed.
  659. if err := c.Commit(buildTCPv4Ports(3000, 2000, 2900, tcpAck, pay)); err != nil {
  660. t.Fatal(err)
  661. }
  662. if err := c.Flush(); err != nil {
  663. t.Fatal(err)
  664. }
  665. // Expected:
  666. // - 1 gso for flow A (first 2 segments)
  667. // - 1 plain for flow A SYN
  668. // - 1 gso for flow B (3 segments)
  669. if len(w.gsoWrites) != 2 {
  670. t.Fatalf("want 2 gso writes, got %d", len(w.gsoWrites))
  671. }
  672. if len(w.writes) != 1 {
  673. t.Fatalf("want 1 plain write (SYN), got %d", len(w.writes))
  674. }
  675. // Find the 3-segment gso (flow B) and the 2-segment gso (flow A).
  676. var segCounts []int
  677. for _, g := range w.gsoWrites {
  678. segCounts = append(segCounts, len(g.pays))
  679. }
  680. if !(segCounts[0] == 2 && segCounts[1] == 3) && !(segCounts[0] == 3 && segCounts[1] == 2) {
  681. t.Errorf("unexpected segment counts: %v (want 2 and 3)", segCounts)
  682. }
  683. }
  684. // ECN test helpers and constants.
  685. const (
  686. tcpEce = 0x40
  687. tcpCwr = 0x80
  688. // 2-bit IP-level ECN codepoints (lower 2 bits of IPv4 ToS / IPv6 TC).
  689. ecnNotECT = 0x00
  690. ecnECT1 = 0x01
  691. ecnECT0 = 0x02
  692. ecnCE = 0x03
  693. )
  694. // buildTCPv4WithToS is buildTCPv4 with caller-specified IPv4 ToS so tests can
  695. // drive DSCP and ECN bits.
  696. func buildTCPv4WithToS(tos byte, seq uint32, flags byte, payload []byte) []byte {
  697. pkt := buildTCPv4(seq, flags, payload)
  698. pkt[1] = tos
  699. return pkt
  700. }
  701. // buildTCPv6 mirrors buildTCPv4 for IPv6. tcLow is the low 4 bits of Traffic
  702. // Class, which carries the ECN codepoint (mask 0x03) and the bottom 2 DSCP
  703. // bits — enough to drive the ECN paths under test.
  704. func buildTCPv6(tcLow byte, seq uint32, flags byte, payload []byte) []byte {
  705. const ipHdrLen = 40
  706. const tcpHdrLen = 20
  707. pkt := make([]byte, ipHdrLen+tcpHdrLen+len(payload))
  708. pkt[0] = 0x60 // version=6, TC[7:4]=0
  709. pkt[1] = (tcLow & 0x0f) << 4 // TC[3:0] in high nibble; flow=0
  710. binary.BigEndian.PutUint16(pkt[4:6], uint16(tcpHdrLen+len(payload)))
  711. pkt[6] = ipProtoTCP
  712. pkt[7] = 64
  713. copy(pkt[8:24], []byte{0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
  714. copy(pkt[24:40], []byte{0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2})
  715. binary.BigEndian.PutUint16(pkt[40:42], 1000)
  716. binary.BigEndian.PutUint16(pkt[42:44], 2000)
  717. binary.BigEndian.PutUint32(pkt[44:48], seq)
  718. binary.BigEndian.PutUint32(pkt[48:52], 12345)
  719. pkt[52] = 0x50
  720. pkt[53] = flags
  721. binary.BigEndian.PutUint16(pkt[54:56], 0xffff)
  722. copy(pkt[60:], payload)
  723. return pkt
  724. }
  725. // TestCoalescerCoalescesEceFlow confirms that ECN-Echo-marked ACKs (an
  726. // ECN-aware flow under congestion) keep getting coalesced into a TSO
  727. // superpacket instead of falling out to verbatim, and that the seed
  728. // retains ECE on the wire.
  729. func TestCoalescerCoalescesEceFlow(t *testing.T) {
  730. w := &fakeTunWriter{gsoEnabled: true}
  731. c := newTestTCPCoalescer(t, w)
  732. pay := make([]byte, 1200)
  733. flags := byte(tcpAck | tcpEce)
  734. if err := c.Commit(buildTCPv4(1000, flags, pay)); err != nil {
  735. t.Fatal(err)
  736. }
  737. if err := c.Commit(buildTCPv4(2200, flags, pay)); err != nil {
  738. t.Fatal(err)
  739. }
  740. if err := c.Flush(); err != nil {
  741. t.Fatal(err)
  742. }
  743. if len(w.gsoWrites) != 1 {
  744. t.Fatalf("want 1 gso write, got %d (plain=%d)", len(w.gsoWrites), len(w.writes))
  745. }
  746. g := w.gsoWrites[0]
  747. if len(g.pays) != 2 {
  748. t.Errorf("pay count=%d want 2", len(g.pays))
  749. }
  750. if seedFlags := g.hdr[20+13]; seedFlags&tcpEce == 0 {
  751. t.Errorf("seed flags=0x%02x want ECE preserved", seedFlags)
  752. }
  753. }
  754. // TestCoalescerCwrSealsFlow confirms that a CWR-bearing segment in the
  755. // middle of a flow goes to verbatim and seals the open slot, so a later
  756. // in-flow segment seeds a new slot rather than extending the prior burst.
  757. func TestCoalescerCwrSealsFlow(t *testing.T) {
  758. w := &fakeTunWriter{gsoEnabled: true}
  759. c := newTestTCPCoalescer(t, w)
  760. pay := make([]byte, 1200)
  761. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  762. t.Fatal(err)
  763. }
  764. if err := c.Commit(buildTCPv4(2200, tcpAck|tcpCwr, pay)); err != nil {
  765. t.Fatal(err)
  766. }
  767. if err := c.Commit(buildTCPv4(3400, tcpAck, pay)); err != nil {
  768. t.Fatal(err)
  769. }
  770. if err := c.Flush(); err != nil {
  771. t.Fatal(err)
  772. }
  773. // All three emissions are plain writes: the seed before CWR and the
  774. // fresh seed after both stay single-segment, and the CWR packet itself
  775. // is verbatim. Order: seed, CWR, reseed.
  776. if len(w.writes) != 3 || len(w.gsoWrites) != 0 {
  777. t.Fatalf("want 3 plain writes (seed, CWR, reseed), got writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  778. }
  779. if flags := w.writes[1][20+13]; flags&tcpCwr == 0 {
  780. t.Errorf("middle write flags=0x%02x want CWR (verbatim in arrival order)", flags)
  781. }
  782. }
  783. // TestCoalescerEceMismatchReseeds confirms that toggling ECE mid-flow does
  784. // not silently merge — receivers expect ECE either set on every segment of
  785. // a CE-echoing window or none.
  786. func TestCoalescerEceMismatchReseeds(t *testing.T) {
  787. w := &fakeTunWriter{gsoEnabled: true}
  788. c := newTestTCPCoalescer(t, w)
  789. pay := make([]byte, 1200)
  790. if err := c.Commit(buildTCPv4(1000, tcpAck|tcpEce, pay)); err != nil {
  791. t.Fatal(err)
  792. }
  793. if err := c.Commit(buildTCPv4(2200, tcpAck, pay)); err != nil {
  794. t.Fatal(err)
  795. }
  796. if err := c.Flush(); err != nil {
  797. t.Fatal(err)
  798. }
  799. // Each seed stays single-segment and flushes as its own plain write.
  800. if len(w.writes) != 2 || len(w.gsoWrites) != 0 {
  801. t.Fatalf("want 2 separate plain writes, got writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  802. }
  803. if flags := w.writes[0][20+13]; flags&tcpEce == 0 {
  804. t.Errorf("first write lost ECE: flags=0x%02x", flags)
  805. }
  806. if flags := w.writes[1][20+13]; flags&tcpEce != 0 {
  807. t.Errorf("second write gained ECE: flags=0x%02x", flags)
  808. }
  809. }
  810. // TestCoalescerDifferingECNReseeds confirms that segments with differing IP
  811. // ECN codepoints do NOT coalesce: headersMatch compares the full ToS byte,
  812. // matching kernel GRO. Two ECT(0) segments merge into a superpacket; a CE
  813. // stamp mid-run seals the ECT(0) chain and reseeds, and the trailing ECT(0)
  814. // reseeds again — those reseeds stay single-segment and ship as plain
  815. // writes of the original packets, each keeping its own codepoint. ORing
  816. // the marks (the old buggy behavior) would have fabricated a false CE
  817. // across the whole burst.
  818. func TestCoalescerDifferingECNReseeds(t *testing.T) {
  819. w := &fakeTunWriter{gsoEnabled: true}
  820. c := newTestTCPCoalescer(t, w)
  821. pay := make([]byte, 1200)
  822. if err := c.Commit(buildTCPv4WithToS(ecnECT0, 1000, tcpAck, pay)); err != nil {
  823. t.Fatal(err)
  824. }
  825. if err := c.Commit(buildTCPv4WithToS(ecnECT0, 2200, tcpAck, pay)); err != nil {
  826. t.Fatal(err)
  827. }
  828. // Router along the path stamped CE on this one.
  829. if err := c.Commit(buildTCPv4WithToS(ecnCE, 3400, tcpAck, pay)); err != nil {
  830. t.Fatal(err)
  831. }
  832. if err := c.Commit(buildTCPv4WithToS(ecnECT0, 4600, tcpAck, pay)); err != nil {
  833. t.Fatal(err)
  834. }
  835. if err := c.Flush(); err != nil {
  836. t.Fatal(err)
  837. }
  838. // gso: the two ECT(0) segments merged; then plain CE alone; then plain
  839. // trailing ECT(0) alone. Emitted in seq order.
  840. if len(w.gsoWrites) != 1 || len(w.writes) != 2 {
  841. t.Fatalf("want 1 gso (ECT0 pair) + 2 plain (ECN split), got gso=%d plain=%d", len(w.gsoWrites), len(w.writes))
  842. }
  843. if got, want := w.order, []string{"gso", "write", "write"}; !stringSliceEq(got, want) {
  844. t.Fatalf("emission order=%v want %v", got, want)
  845. }
  846. g := w.gsoWrites[0]
  847. if len(g.pays) != 2 {
  848. t.Errorf("gso pay count=%d want 2", len(g.pays))
  849. }
  850. if got := g.hdr[1] & 0x03; got != ecnECT0 {
  851. t.Errorf("gso ECN=0x%02x want 0x%02x", got, ecnECT0)
  852. }
  853. wantECN := []byte{ecnCE, ecnECT0}
  854. for i, wnt := range wantECN {
  855. if got := w.writes[i][1] & 0x03; got != wnt {
  856. t.Errorf("plain %d ECN=0x%02x want 0x%02x", i, got, wnt)
  857. }
  858. }
  859. }
  860. // TestCoalescerECT0ThenECT1NoCE is the core regression for the ECN merge
  861. // bug: ORing ECT(0)=0b10 with ECT(1)=0b01 fabricates CE=0b11. The two
  862. // segments must land in separate emissions — both stay single-segment, so
  863. // each ships as a plain write of its original bytes, preserving its own
  864. // codepoint — and neither may end up CE-marked.
  865. func TestCoalescerECT0ThenECT1NoCE(t *testing.T) {
  866. w := &fakeTunWriter{gsoEnabled: true}
  867. c := newTestTCPCoalescer(t, w)
  868. pay := make([]byte, 1200)
  869. if err := c.Commit(buildTCPv4WithToS(ecnECT0, 1000, tcpAck, pay)); err != nil {
  870. t.Fatal(err)
  871. }
  872. if err := c.Commit(buildTCPv4WithToS(ecnECT1, 2200, tcpAck, pay)); err != nil {
  873. t.Fatal(err)
  874. }
  875. if err := c.Flush(); err != nil {
  876. t.Fatal(err)
  877. }
  878. if len(w.writes) != 2 || len(w.gsoWrites) != 0 {
  879. t.Fatalf("want 2 separate plain writes (ECT0 vs ECT1), got writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  880. }
  881. wantECN := []byte{ecnECT0, ecnECT1}
  882. for i, p := range w.writes {
  883. if got := p[1] & 0x03; got != wantECN[i] {
  884. t.Errorf("write %d ECN=0x%02x want 0x%02x", i, got, wantECN[i])
  885. }
  886. if got := p[1] & 0x03; got == ecnCE {
  887. t.Errorf("write %d fabricated CE from ECT merge", i)
  888. }
  889. }
  890. }
  891. // TestCoalescerDscpMismatchReseeds confirms that a DSCP difference (same
  892. // ECN) still splits — headersMatch compares the full ToS byte, so the upper
  893. // six DSCP bits must match too.
  894. func TestCoalescerDscpMismatchReseeds(t *testing.T) {
  895. w := &fakeTunWriter{gsoEnabled: true}
  896. c := newTestTCPCoalescer(t, w)
  897. pay := make([]byte, 1200)
  898. // Same ECN (Not-ECT), different DSCP (0x10 vs 0x20 in upper 6 bits).
  899. tosA := byte(0x10<<2) | ecnNotECT
  900. tosB := byte(0x20<<2) | ecnNotECT
  901. if err := c.Commit(buildTCPv4WithToS(tosA, 1000, tcpAck, pay)); err != nil {
  902. t.Fatal(err)
  903. }
  904. if err := c.Commit(buildTCPv4WithToS(tosB, 2200, tcpAck, pay)); err != nil {
  905. t.Fatal(err)
  906. }
  907. if err := c.Flush(); err != nil {
  908. t.Fatal(err)
  909. }
  910. // Both seeds stay single-segment → two plain writes, no gso.
  911. if len(w.writes) != 2 || len(w.gsoWrites) != 0 {
  912. t.Fatalf("want 2 separate plain writes (different DSCP), got writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  913. }
  914. }
  915. // TestCoalescerIPv6CoalescesEceFlow is the IPv6 analogue of
  916. // TestCoalescerCoalescesEceFlow.
  917. func TestCoalescerIPv6CoalescesEceFlow(t *testing.T) {
  918. w := &fakeTunWriter{gsoEnabled: true}
  919. c := newTestTCPCoalescer(t, w)
  920. pay := make([]byte, 1200)
  921. flags := byte(tcpAck | tcpEce)
  922. if err := c.Commit(buildTCPv6(0, 1000, flags, pay)); err != nil {
  923. t.Fatal(err)
  924. }
  925. if err := c.Commit(buildTCPv6(0, 2200, flags, pay)); err != nil {
  926. t.Fatal(err)
  927. }
  928. if err := c.Flush(); err != nil {
  929. t.Fatal(err)
  930. }
  931. if len(w.gsoWrites) != 1 {
  932. t.Fatalf("want 1 gso write, got %d", len(w.gsoWrites))
  933. }
  934. g := w.gsoWrites[0]
  935. if seedFlags := g.hdr[40+13]; seedFlags&tcpEce == 0 {
  936. t.Errorf("seed flags=0x%02x want ECE preserved", seedFlags)
  937. }
  938. }
  939. // TestCoalescerPSHKeepsChainBoundary verifies that a PSH-sealed chain is
  940. // not extended by a later seq-contiguous segment — PSH placement is part of
  941. // the wire signal and growing the superpacket past it would shift the
  942. // receiver's push boundary by an arbitrary number of segments.
  943. func TestCoalescerPSHKeepsChainBoundary(t *testing.T) {
  944. w := &fakeTunWriter{gsoEnabled: true}
  945. c := newTestTCPCoalescer(t, w)
  946. pay := make([]byte, 1200)
  947. // Seq 1000 (no PSH) + 2200 (PSH) → seal one slot with PSH set.
  948. // Seq 3400 is contiguous to the sealed chain's nextSeq; without the
  949. // seal check it would append in.
  950. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  951. t.Fatal(err)
  952. }
  953. if err := c.Commit(buildTCPv4(2200, tcpAckPsh, pay)); err != nil {
  954. t.Fatal(err)
  955. }
  956. if err := c.Commit(buildTCPv4(3400, tcpAck, pay)); err != nil {
  957. t.Fatal(err)
  958. }
  959. if err := c.Flush(); err != nil {
  960. t.Fatal(err)
  961. }
  962. // The PSH-sealed pair is a real superpacket; the fresh seed stays
  963. // single-segment and flushes as a plain write.
  964. if len(w.gsoWrites) != 1 || len(w.writes) != 1 {
  965. t.Fatalf("want 1 gso (PSH-sealed pair) + 1 plain (fresh seed), got gso=%d plain=%d", len(w.gsoWrites), len(w.writes))
  966. }
  967. }
  968. // TestCoalescerSynSealsFlowChain confirms a non-admissible in-flow packet
  969. // (SYN+ACK here) seals its flow's open chain and holds its emission
  970. // position: data committed after it seeds a fresh slot and emits after it,
  971. // never extending a chain created before it.
  972. func TestCoalescerSynSealsFlowChain(t *testing.T) {
  973. w := &fakeTunWriter{gsoEnabled: true}
  974. c := newTestTCPCoalescer(t, w)
  975. pay := make([]byte, 1200)
  976. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  977. t.Fatal(err)
  978. }
  979. // Discontiguous seq: evicts the 1000 slot and seeds its own.
  980. if err := c.Commit(buildTCPv4(3400, tcpAck, pay)); err != nil {
  981. t.Fatal(err)
  982. }
  983. // Non-coalesceable packet (SYN+ACK) seals the flow's open slot and
  984. // becomes a verbatim slot in c.slots.
  985. if err := c.Commit(buildTCPv4(9999, tcpSyn|tcpAck, pay)); err != nil {
  986. t.Fatal(err)
  987. }
  988. // Post-SYN data: must emit after the SYN, in its own slot.
  989. if err := c.Commit(buildTCPv4(2200, tcpAck, pay)); err != nil {
  990. t.Fatal(err)
  991. }
  992. if err := c.Flush(); err != nil {
  993. t.Fatal(err)
  994. }
  995. // All four packets emit as plain writes in creation order: 1000 and
  996. // 3400 are separate single-segment slots, the SYN is verbatim, and the
  997. // post-SYN 2200 is a fresh single-segment slot after it.
  998. if len(w.writes) != 4 || len(w.gsoWrites) != 0 {
  999. t.Fatalf("want 4 plain writes, got writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  1000. }
  1001. wantSeqs := []uint32{1000, 3400, 9999, 2200}
  1002. for i, want := range wantSeqs {
  1003. if seq := binary.BigEndian.Uint32(w.writes[i][24:28]); seq != want {
  1004. t.Errorf("write %d seq=%d want %d", i, seq, want)
  1005. }
  1006. }
  1007. }
  1008. // TestCoalescerIPv6DifferingECNReseeds is the IPv6 analogue of
  1009. // TestCoalescerDifferingECNReseeds. ECN bits live in TC[1:0] = byte 1 mask
  1010. // 0x30, so ipHeadersMatch (comparing byte 1 fully) still splits them.
  1011. func TestCoalescerIPv6DifferingECNReseeds(t *testing.T) {
  1012. w := &fakeTunWriter{gsoEnabled: true}
  1013. c := newTestTCPCoalescer(t, w)
  1014. pay := make([]byte, 1200)
  1015. // tcLow is the low 4 bits of TC; ECN occupies the bottom 2 of those.
  1016. if err := c.Commit(buildTCPv6(ecnECT0, 1000, tcpAck, pay)); err != nil {
  1017. t.Fatal(err)
  1018. }
  1019. if err := c.Commit(buildTCPv6(ecnECT0, 2200, tcpAck, pay)); err != nil {
  1020. t.Fatal(err)
  1021. }
  1022. if err := c.Commit(buildTCPv6(ecnCE, 3400, tcpAck, pay)); err != nil {
  1023. t.Fatal(err)
  1024. }
  1025. if err := c.Commit(buildTCPv6(ecnECT0, 4600, tcpAck, pay)); err != nil {
  1026. t.Fatal(err)
  1027. }
  1028. if err := c.Flush(); err != nil {
  1029. t.Fatal(err)
  1030. }
  1031. // Like the v4 test: the ECT(0) pair merges into one superpacket; the CE
  1032. // and trailing ECT(0) reseeds stay single-segment and ship as plain
  1033. // writes, in seq order.
  1034. if len(w.gsoWrites) != 1 || len(w.writes) != 2 {
  1035. t.Fatalf("want 1 gso (ECT0 pair) + 2 plain (ECN split), got gso=%d plain=%d", len(w.gsoWrites), len(w.writes))
  1036. }
  1037. // Byte 1 high nibble holds TC[3:0]; ECN is the low 2 bits of that nibble,
  1038. // which appears in byte 1 mask 0x30 (>>4 to read the codepoint value).
  1039. g := w.gsoWrites[0]
  1040. if len(g.pays) != 2 {
  1041. t.Errorf("gso pay count=%d want 2", len(g.pays))
  1042. }
  1043. if got := (g.hdr[1] >> 4) & 0x03; got != ecnECT0 {
  1044. t.Errorf("gso v6 ECN=0x%02x want 0x%02x", got, ecnECT0)
  1045. }
  1046. wantECN := []byte{ecnCE, ecnECT0}
  1047. for i, wnt := range wantECN {
  1048. if got := (w.writes[i][1] >> 4) & 0x03; got != wnt {
  1049. t.Errorf("plain %d v6 ECN=0x%02x want 0x%02x", i, got, wnt)
  1050. }
  1051. }
  1052. }
  1053. // TestCoalescerNonAtomicSequentialIDsCoalesce: with DF clear, coalescing
  1054. // is allowed when the IPv4 IDs already run seed+1 per segment — kernel
  1055. // TSO's re-stamp then reproduces the originals exactly (the kernel GRO
  1056. // admission rule).
  1057. func TestCoalescerNonAtomicSequentialIDsCoalesce(t *testing.T) {
  1058. w := &fakeTunWriter{gsoEnabled: true}
  1059. c := newTestTCPCoalescer(t, w)
  1060. pay := make([]byte, 1200)
  1061. seq := uint32(1000)
  1062. for i := range 3 {
  1063. pkt := buildTCPv4(seq, tcpAck, pay)
  1064. setIPv4ID(pkt, uint16(700+i), false)
  1065. if err := c.Commit(pkt); err != nil {
  1066. t.Fatal(err)
  1067. }
  1068. seq += uint32(len(pay))
  1069. }
  1070. if err := c.Flush(); err != nil {
  1071. t.Fatal(err)
  1072. }
  1073. if len(w.gsoWrites) != 1 || len(w.gsoWrites[0].pays) != 3 {
  1074. t.Fatalf("sequential-ID DF=0 chain must coalesce: gso=%d", len(w.gsoWrites))
  1075. }
  1076. if id := binary.BigEndian.Uint16(w.gsoWrites[0].hdr[4:6]); id != 700 {
  1077. t.Errorf("superpacket seed ID=%d want 700", id)
  1078. }
  1079. }
  1080. // TestCoalescerNonAtomicIDGapDoesNotCoalesce: with DF clear and an ID jump
  1081. // mid-flow, neither the append path nor the flush-time merge may combine
  1082. // the segments — TSO would re-stamp seed+n and rewrite the second
  1083. // packet's ID, which is meaningful on non-atomic datagrams. Each stays a
  1084. // single-segment slot and flushes as a plain write with its original ID.
  1085. func TestCoalescerNonAtomicIDGapDoesNotCoalesce(t *testing.T) {
  1086. w := &fakeTunWriter{gsoEnabled: true}
  1087. c := newTestTCPCoalescer(t, w)
  1088. pay := make([]byte, 1200)
  1089. p1 := buildTCPv4(1000, tcpAck, pay)
  1090. setIPv4ID(p1, 700, false)
  1091. p2 := buildTCPv4(1000+uint32(len(pay)), tcpAck, pay)
  1092. setIPv4ID(p2, 900, false)
  1093. if err := c.Commit(p1); err != nil {
  1094. t.Fatal(err)
  1095. }
  1096. if err := c.Commit(p2); err != nil {
  1097. t.Fatal(err)
  1098. }
  1099. if err := c.Flush(); err != nil {
  1100. t.Fatal(err)
  1101. }
  1102. if len(w.writes) != 2 || len(w.gsoWrites) != 0 {
  1103. t.Fatalf("ID gap on DF=0 must not coalesce (append or merge): writes=%d gso=%d", len(w.writes), len(w.gsoWrites))
  1104. }
  1105. for i, want := range []uint16{700, 900} {
  1106. if id := binary.BigEndian.Uint16(w.writes[i][4:6]); id != want {
  1107. t.Errorf("write %d: ID=%d want %d (must be preserved)", i, id, want)
  1108. }
  1109. }
  1110. }
  1111. // TestCoalescerAtomicRandomIDsCoalesce guards the other direction: DF set
  1112. // makes the datagram atomic (RFC 6864), so arbitrary IDs must not block
  1113. // coalescing.
  1114. func TestCoalescerAtomicRandomIDsCoalesce(t *testing.T) {
  1115. w := &fakeTunWriter{gsoEnabled: true}
  1116. c := newTestTCPCoalescer(t, w)
  1117. pay := make([]byte, 1200)
  1118. p1 := buildTCPv4(1000, tcpAck, pay)
  1119. setIPv4ID(p1, 0x1234, true)
  1120. p2 := buildTCPv4(1000+uint32(len(pay)), tcpAck, pay)
  1121. setIPv4ID(p2, 0x0007, true)
  1122. if err := c.Commit(p1); err != nil {
  1123. t.Fatal(err)
  1124. }
  1125. if err := c.Commit(p2); err != nil {
  1126. t.Fatal(err)
  1127. }
  1128. if err := c.Flush(); err != nil {
  1129. t.Fatal(err)
  1130. }
  1131. if len(w.gsoWrites) != 1 || len(w.gsoWrites[0].pays) != 2 {
  1132. t.Fatalf("DF=1 chain with arbitrary IDs must coalesce: gso=%d", len(w.gsoWrites))
  1133. }
  1134. }
  1135. // TestCoalescerSeqWrapAroundAppends pins the serial-number arithmetic on the
  1136. // append path: a chain crossing the 2^32 seq wrap must keep extending when
  1137. // contiguous.
  1138. func TestCoalescerSeqWrapAroundAppends(t *testing.T) {
  1139. w := &fakeTunWriter{gsoEnabled: true}
  1140. c := newTestTCPCoalescer(t, w)
  1141. payA := bytes.Repeat([]byte{'A'}, 32)
  1142. payB := bytes.Repeat([]byte{'B'}, 32)
  1143. seqA := uint32(0xffffffe0) // 32 before the wrap: nextSeq lands exactly on 0
  1144. if err := c.Commit(buildTCPv4(seqA, tcpAck, payA)); err != nil {
  1145. t.Fatal(err)
  1146. }
  1147. if err := c.Commit(buildTCPv4(0, tcpAck, payB)); err != nil {
  1148. t.Fatal(err)
  1149. }
  1150. if err := c.Flush(); err != nil {
  1151. t.Fatal(err)
  1152. }
  1153. if len(w.gsoWrites) != 1 {
  1154. t.Fatalf("want 1 gso write across the wrap, got %d (plain=%d)", len(w.gsoWrites), len(w.writes))
  1155. }
  1156. g := w.gsoWrites[0]
  1157. const ipHdrLen = 20
  1158. if seedSeq := binary.BigEndian.Uint32(g.hdr[ipHdrLen+4 : ipHdrLen+8]); seedSeq != seqA {
  1159. t.Errorf("seed seq=%#x want %#x", seedSeq, seqA)
  1160. }
  1161. if len(g.pays) != 2 {
  1162. t.Fatalf("segs=%d want 2", len(g.pays))
  1163. }
  1164. if !bytes.Equal(g.pays[0], payA) || !bytes.Equal(g.pays[1], payB) {
  1165. t.Errorf("payload order wrong across the wrap: got %q then %q", g.pays[0][:1], g.pays[1][:1])
  1166. }
  1167. }
  1168. // TestCoalescerUnparseableSealsAllChains: an unparseable packet's flow is
  1169. // unknowable, so it must close every open chain. Later data — even data
  1170. // seq-contiguous with a pre-existing chain — seeds a fresh slot and emits
  1171. // after the unparseable packet, exactly as transmitted.
  1172. func TestCoalescerUnparseableSealsAllChains(t *testing.T) {
  1173. w := &fakeTunWriter{gsoEnabled: true}
  1174. c := newTestTCPCoalescer(t, w)
  1175. pay := make([]byte, 1200)
  1176. if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
  1177. t.Fatal(err)
  1178. }
  1179. if err := c.Commit(buildTCPv4(2200, tcpAck, pay)); err != nil {
  1180. t.Fatal(err)
  1181. }
  1182. // IHL=6 fakes IP options: the parse bails, flow key unknown.
  1183. opts := buildTCPv4(5000, tcpAck, make([]byte, 500))
  1184. opts[0] = 0x46
  1185. if err := c.Commit(opts); err != nil {
  1186. t.Fatal(err)
  1187. }
  1188. // Contiguous with the first chain (nextSeq 3400), but that chain is
  1189. // sealed now: must not append, must not emit before the unparseable.
  1190. if err := c.Commit(buildTCPv4(3400, tcpAck, pay)); err != nil {
  1191. t.Fatal(err)
  1192. }
  1193. if err := c.Flush(); err != nil {
  1194. t.Fatal(err)
  1195. }
  1196. if len(w.gsoWrites) != 1 {
  1197. t.Fatalf("want 1 gso write (pre-fragment pair), got %d", len(w.gsoWrites))
  1198. }
  1199. if len(w.gsoWrites[0].pays) != 2 {
  1200. t.Fatalf("pre-fragment chain segs=%d want 2", len(w.gsoWrites[0].pays))
  1201. }
  1202. if len(w.writes) != 2 {
  1203. t.Fatalf("want 2 plain writes (unparseable + post-fragment seed), got %d", len(w.writes))
  1204. }
  1205. if w.writes[0][0] != 0x46 {
  1206. t.Errorf("first plain write must be the unparseable packet")
  1207. }
  1208. if seq := binary.BigEndian.Uint32(w.writes[1][24:28]); seq != 3400 {
  1209. t.Errorf("post-fragment data seq=%d want 3400", seq)
  1210. }
  1211. if len(w.order) != 3 || w.order[0] != "gso" || w.order[1] != "write" || w.order[2] != "write" {
  1212. t.Fatalf("emission order = %v, want [gso write write]", w.order)
  1213. }
  1214. }