tun_linux_offload_test.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. //go:build linux && !android && !e2e_testing
  2. // +build linux,!android,!e2e_testing
  3. package tio
  4. import (
  5. "encoding/binary"
  6. "os"
  7. "testing"
  8. "golang.org/x/sys/unix"
  9. "gvisor.dev/gvisor/pkg/tcpip/checksum"
  10. "github.com/slackhq/nebula/overlay/tio/virtio"
  11. )
  12. // testSegScratchSize is a generous segmentation scratch sized to fit any
  13. // of the synthetic TSO/USO superpackets these tests generate (one
  14. // worst-case 64 KiB superpacket plus replicated per-segment headers).
  15. const testSegScratchSize = 192 * 1024
  16. // verifyChecksum confirms that the one's-complement sum across `b`, seeded
  17. // with a folded pseudo-header sum, equals all-ones (valid).
  18. func verifyChecksum(b []byte, pseudo uint16) bool {
  19. return checksum.Checksum(b, pseudo) == 0xffff
  20. }
  21. // segmentForTest is the test-only counterpart to the production
  22. // SegmentSuperpacket path. It handles GSO_NONE (with optional
  23. // finishChecksum) inline and dispatches GSO superpackets through
  24. // SegmentSuperpacket, draining each yielded segment into a
  25. // freshly-copied [][]byte slot so callers can iterate after the call
  26. // returns. Tests pre-set hdr.HdrLen correctly, so correctHdrLen is not
  27. // invoked here.
  28. func segmentForTest(pkt []byte, hdr virtio.Hdr, out *[][]byte, scratch []byte) error {
  29. if hdr.GSOType == unix.VIRTIO_NET_HDR_GSO_NONE {
  30. cp := append([]byte(nil), pkt...)
  31. if hdr.Flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 {
  32. if err := virtio.FinishChecksum(cp, hdr); err != nil {
  33. return err
  34. }
  35. }
  36. *out = append(*out, cp)
  37. return nil
  38. }
  39. proto, err := protoFromGSOType(hdr.GSOType)
  40. if err != nil {
  41. return err
  42. }
  43. gso := GSOInfo{
  44. Size: hdr.GSOSize,
  45. HdrLen: hdr.HdrLen,
  46. CsumStart: hdr.CsumStart,
  47. Proto: proto,
  48. }
  49. return SegmentSuperpacket(Packet{Bytes: pkt, GSO: gso}, func(seg []byte) error {
  50. *out = append(*out, append([]byte(nil), seg...))
  51. return nil
  52. })
  53. }
  54. // pseudoHeaderIPv4 returns the folded pseudo-header sum used to verify a
  55. // TCP/UDP segment's checksum in tests. src/dst are 4 bytes each.
  56. func pseudoHeaderIPv4(src, dst []byte, proto byte, l4Len int) uint16 {
  57. s := uint32(checksum.Checksum(src, 0)) + uint32(checksum.Checksum(dst, 0))
  58. s += uint32(proto) + uint32(l4Len)
  59. s = (s & 0xffff) + (s >> 16)
  60. s = (s & 0xffff) + (s >> 16)
  61. return uint16(s)
  62. }
  63. // pseudoHeaderIPv6 returns the folded pseudo-header sum used to verify a
  64. // TCP/UDP segment's checksum in tests. src/dst are 16 bytes each.
  65. func pseudoHeaderIPv6(src, dst []byte, proto byte, l4Len int) uint16 {
  66. s := uint32(checksum.Checksum(src, 0)) + uint32(checksum.Checksum(dst, 0))
  67. s += uint32(l4Len>>16) + uint32(l4Len&0xffff) + uint32(proto)
  68. s = (s & 0xffff) + (s >> 16)
  69. s = (s & 0xffff) + (s >> 16)
  70. return uint16(s)
  71. }
  72. // buildTSOv4 builds a synthetic IPv4/TCP TSO superpacket with a payload of
  73. // `payLen` bytes split at `mss`.
  74. func buildTSOv4(t *testing.T, payLen, mss int) ([]byte, virtio.Hdr) {
  75. t.Helper()
  76. const ipLen = 20
  77. const tcpLen = 20
  78. pkt := make([]byte, ipLen+tcpLen+payLen)
  79. // IPv4 header
  80. pkt[0] = 0x45 // version 4, IHL 5
  81. // total length is meaningless for TSO but set it anyway
  82. binary.BigEndian.PutUint16(pkt[2:4], uint16(ipLen+tcpLen+payLen))
  83. binary.BigEndian.PutUint16(pkt[4:6], 0x4242) // original ID
  84. pkt[8] = 64 // TTL
  85. pkt[9] = unix.IPPROTO_TCP
  86. copy(pkt[12:16], []byte{10, 0, 0, 1}) // src
  87. copy(pkt[16:20], []byte{10, 0, 0, 2}) // dst
  88. // TCP header
  89. binary.BigEndian.PutUint16(pkt[20:22], 12345) // sport
  90. binary.BigEndian.PutUint16(pkt[22:24], 80) // dport
  91. binary.BigEndian.PutUint32(pkt[24:28], 10000) // seq
  92. binary.BigEndian.PutUint32(pkt[28:32], 20000) // ack
  93. pkt[32] = 0x50 // data offset 5 words
  94. pkt[33] = 0x18 // ACK | PSH
  95. binary.BigEndian.PutUint16(pkt[34:36], 65535) // window
  96. // payload
  97. for i := 0; i < payLen; i++ {
  98. pkt[ipLen+tcpLen+i] = byte(i & 0xff)
  99. }
  100. return pkt, virtio.Hdr{
  101. Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
  102. GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV4,
  103. HdrLen: uint16(ipLen + tcpLen),
  104. GSOSize: uint16(mss),
  105. CsumStart: uint16(ipLen),
  106. CsumOffset: 16,
  107. }
  108. }
  109. func TestSegmentTCPv4(t *testing.T) {
  110. const mss = 100
  111. const numSeg = 3
  112. pkt, hdr := buildTSOv4(t, mss*numSeg, mss)
  113. scratch := make([]byte, testSegScratchSize)
  114. var out [][]byte
  115. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  116. t.Fatalf("segmentForTest: %v", err)
  117. }
  118. if len(out) != numSeg {
  119. t.Fatalf("expected %d segments, got %d", numSeg, len(out))
  120. }
  121. for i, seg := range out {
  122. if len(seg) != 40+mss {
  123. t.Errorf("seg %d: unexpected len %d", i, len(seg))
  124. }
  125. totalLen := binary.BigEndian.Uint16(seg[2:4])
  126. if totalLen != uint16(40+mss) {
  127. t.Errorf("seg %d: total_len=%d want %d", i, totalLen, 40+mss)
  128. }
  129. id := binary.BigEndian.Uint16(seg[4:6])
  130. if id != 0x4242+uint16(i) {
  131. t.Errorf("seg %d: ip id=%#x want %#x", i, id, 0x4242+uint16(i))
  132. }
  133. seq := binary.BigEndian.Uint32(seg[24:28])
  134. wantSeq := uint32(10000 + i*mss)
  135. if seq != wantSeq {
  136. t.Errorf("seg %d: seq=%d want %d", i, seq, wantSeq)
  137. }
  138. flags := seg[33]
  139. wantFlags := byte(0x10) // ACK only, PSH cleared
  140. if i == numSeg-1 {
  141. wantFlags = 0x18 // ACK | PSH preserved on last
  142. }
  143. if flags != wantFlags {
  144. t.Errorf("seg %d: flags=%#x want %#x", i, flags, wantFlags)
  145. }
  146. // IPv4 header checksum must verify against itself.
  147. if !verifyChecksum(seg[:20], 0) {
  148. t.Errorf("seg %d: bad IPv4 header checksum", i)
  149. }
  150. // TCP checksum must verify against the pseudo-header.
  151. psum := pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_TCP, 20+mss)
  152. if !verifyChecksum(seg[20:], psum) {
  153. t.Errorf("seg %d: bad TCP checksum", i)
  154. }
  155. }
  156. }
  157. func TestSegmentTCPv4OddTail(t *testing.T) {
  158. // Payload of 250 bytes with MSS 100 → segments of 100, 100, 50.
  159. pkt, hdr := buildTSOv4(t, 250, 100)
  160. scratch := make([]byte, testSegScratchSize)
  161. var out [][]byte
  162. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  163. t.Fatalf("segmentForTest: %v", err)
  164. }
  165. if len(out) != 3 {
  166. t.Fatalf("want 3 segments, got %d", len(out))
  167. }
  168. wantPayLens := []int{100, 100, 50}
  169. for i, seg := range out {
  170. if len(seg)-40 != wantPayLens[i] {
  171. t.Errorf("seg %d: pay len %d want %d", i, len(seg)-40, wantPayLens[i])
  172. }
  173. if !verifyChecksum(seg[:20], 0) {
  174. t.Errorf("seg %d: bad IPv4 header checksum", i)
  175. }
  176. psum := pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_TCP, 20+wantPayLens[i])
  177. if !verifyChecksum(seg[20:], psum) {
  178. t.Errorf("seg %d: bad TCP checksum", i)
  179. }
  180. }
  181. }
  182. func TestSegmentTCPv6(t *testing.T) {
  183. const ipLen = 40
  184. const tcpLen = 20
  185. const mss = 120
  186. const numSeg = 2
  187. payLen := mss * numSeg
  188. pkt := make([]byte, ipLen+tcpLen+payLen)
  189. // IPv6 header
  190. pkt[0] = 0x60 // version 6
  191. binary.BigEndian.PutUint16(pkt[4:6], uint16(tcpLen+payLen))
  192. pkt[6] = unix.IPPROTO_TCP
  193. pkt[7] = 64
  194. // src/dst fe80::1 / fe80::2
  195. pkt[8] = 0xfe
  196. pkt[9] = 0x80
  197. pkt[23] = 1
  198. pkt[24] = 0xfe
  199. pkt[25] = 0x80
  200. pkt[39] = 2
  201. // TCP header
  202. binary.BigEndian.PutUint16(pkt[40:42], 12345)
  203. binary.BigEndian.PutUint16(pkt[42:44], 80)
  204. binary.BigEndian.PutUint32(pkt[44:48], 7)
  205. binary.BigEndian.PutUint32(pkt[48:52], 99)
  206. pkt[52] = 0x50
  207. pkt[53] = 0x19 // FIN | ACK | PSH — exercise FIN clearing too
  208. binary.BigEndian.PutUint16(pkt[54:56], 65535)
  209. for i := 0; i < payLen; i++ {
  210. pkt[ipLen+tcpLen+i] = byte(i)
  211. }
  212. hdr := virtio.Hdr{
  213. Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
  214. GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV6,
  215. HdrLen: uint16(ipLen + tcpLen),
  216. GSOSize: uint16(mss),
  217. CsumStart: uint16(ipLen),
  218. CsumOffset: 16,
  219. }
  220. scratch := make([]byte, testSegScratchSize)
  221. var out [][]byte
  222. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  223. t.Fatalf("segmentForTest: %v", err)
  224. }
  225. if len(out) != numSeg {
  226. t.Fatalf("want %d segments, got %d", numSeg, len(out))
  227. }
  228. for i, seg := range out {
  229. if len(seg) != ipLen+tcpLen+mss {
  230. t.Errorf("seg %d: len %d want %d", i, len(seg), ipLen+tcpLen+mss)
  231. }
  232. pl := binary.BigEndian.Uint16(seg[4:6])
  233. if pl != uint16(tcpLen+mss) {
  234. t.Errorf("seg %d: payload_length=%d want %d", i, pl, tcpLen+mss)
  235. }
  236. seq := binary.BigEndian.Uint32(seg[44:48])
  237. if seq != uint32(7+i*mss) {
  238. t.Errorf("seg %d: seq=%d want %d", i, seq, 7+i*mss)
  239. }
  240. flags := seg[53]
  241. // Original flags = 0x19 (FIN|ACK|PSH). FIN(0x01)+PSH(0x08) should be
  242. // cleared on all but the last; ACK(0x10) always preserved.
  243. wantFlags := byte(0x10)
  244. if i == numSeg-1 {
  245. wantFlags = 0x19
  246. }
  247. if flags != wantFlags {
  248. t.Errorf("seg %d: flags=%#x want %#x", i, flags, wantFlags)
  249. }
  250. psum := pseudoHeaderIPv6(seg[8:24], seg[24:40], unix.IPPROTO_TCP, tcpLen+mss)
  251. if !verifyChecksum(seg[ipLen:], psum) {
  252. t.Errorf("seg %d: bad TCP checksum", i)
  253. }
  254. }
  255. }
  256. func TestSegmentGSONonePassesThrough(t *testing.T) {
  257. pkt, hdr := buildTSOv4(t, 100, 100)
  258. hdr.GSOType = unix.VIRTIO_NET_HDR_GSO_NONE
  259. hdr.Flags = 0 // no NEEDS_CSUM, leave packet untouched
  260. scratch := make([]byte, testSegScratchSize)
  261. var out [][]byte
  262. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  263. t.Fatalf("segmentForTest: %v", err)
  264. }
  265. if len(out) != 1 {
  266. t.Fatalf("want 1 segment, got %d", len(out))
  267. }
  268. if len(out[0]) != len(pkt) {
  269. t.Fatalf("unexpected length: %d vs %d", len(out[0]), len(pkt))
  270. }
  271. }
  272. // TestSegmentRejectsLegacyUDPGSO ensures the legacy GSO_UDP (UFO) marker is
  273. // still rejected; only modern GSO_UDP_L4 (USO) is supported.
  274. func TestSegmentRejectsLegacyUDPGSO(t *testing.T) {
  275. hdr := virtio.Hdr{GSOType: unix.VIRTIO_NET_HDR_GSO_UDP}
  276. var out [][]byte
  277. if err := segmentForTest(nil, hdr, &out, nil); err == nil {
  278. t.Fatalf("expected rejection for legacy UDP GSO")
  279. }
  280. }
  281. // buildUSOv4 builds a synthetic IPv4/UDP USO superpacket with payload of
  282. // payLen bytes, segmented at gsoSize.
  283. func buildUSOv4(t *testing.T, payLen, gsoSize int) ([]byte, virtio.Hdr) {
  284. t.Helper()
  285. const ipLen = 20
  286. const udpLen = 8
  287. pkt := make([]byte, ipLen+udpLen+payLen)
  288. // IPv4 header
  289. pkt[0] = 0x45 // version 4, IHL 5
  290. binary.BigEndian.PutUint16(pkt[2:4], uint16(ipLen+udpLen+payLen))
  291. binary.BigEndian.PutUint16(pkt[4:6], 0x4242)
  292. pkt[8] = 64
  293. pkt[9] = unix.IPPROTO_UDP
  294. copy(pkt[12:16], []byte{10, 0, 0, 1})
  295. copy(pkt[16:20], []byte{10, 0, 0, 2})
  296. // UDP header (length + checksum filled in per segment by segmentUDPYield)
  297. binary.BigEndian.PutUint16(pkt[20:22], 12345) // sport
  298. binary.BigEndian.PutUint16(pkt[22:24], 53) // dport
  299. for i := 0; i < payLen; i++ {
  300. pkt[ipLen+udpLen+i] = byte(i & 0xff)
  301. }
  302. return pkt, virtio.Hdr{
  303. Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
  304. GSOType: unix.VIRTIO_NET_HDR_GSO_UDP_L4,
  305. HdrLen: uint16(ipLen + udpLen),
  306. GSOSize: uint16(gsoSize),
  307. CsumStart: uint16(ipLen),
  308. CsumOffset: 6,
  309. }
  310. }
  311. func TestSegmentUDPv4(t *testing.T) {
  312. const gso = 100
  313. const numSeg = 3
  314. pkt, hdr := buildUSOv4(t, gso*numSeg, gso)
  315. scratch := make([]byte, testSegScratchSize)
  316. var out [][]byte
  317. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  318. t.Fatalf("segmentForTest: %v", err)
  319. }
  320. if len(out) != numSeg {
  321. t.Fatalf("expected %d segments, got %d", numSeg, len(out))
  322. }
  323. for i, seg := range out {
  324. if len(seg) != 28+gso {
  325. t.Errorf("seg %d: len %d want %d", i, len(seg), 28+gso)
  326. }
  327. totalLen := binary.BigEndian.Uint16(seg[2:4])
  328. if totalLen != uint16(28+gso) {
  329. t.Errorf("seg %d: total_len=%d want %d", i, totalLen, 28+gso)
  330. }
  331. // kernel UDP-GSO does NOT bump the IPv4 ID across segments; every
  332. // segment carries the same ID as the seed.
  333. id := binary.BigEndian.Uint16(seg[4:6])
  334. if id != 0x4242 {
  335. t.Errorf("seg %d: ip id=%#x want %#x", i, id, 0x4242)
  336. }
  337. udpLen := binary.BigEndian.Uint16(seg[24:26])
  338. if udpLen != uint16(8+gso) {
  339. t.Errorf("seg %d: udp len=%d want %d", i, udpLen, 8+gso)
  340. }
  341. if !verifyChecksum(seg[:20], 0) {
  342. t.Errorf("seg %d: bad IPv4 header checksum", i)
  343. }
  344. psum := pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_UDP, 8+gso)
  345. if !verifyChecksum(seg[20:], psum) {
  346. t.Errorf("seg %d: bad UDP checksum", i)
  347. }
  348. }
  349. }
  350. func TestSegmentUDPv4OddTail(t *testing.T) {
  351. // 250 bytes payload, gsoSize=100 → segments of 100, 100, 50.
  352. pkt, hdr := buildUSOv4(t, 250, 100)
  353. scratch := make([]byte, testSegScratchSize)
  354. var out [][]byte
  355. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  356. t.Fatalf("segmentForTest: %v", err)
  357. }
  358. if len(out) != 3 {
  359. t.Fatalf("want 3 segments, got %d", len(out))
  360. }
  361. wantPay := []int{100, 100, 50}
  362. for i, seg := range out {
  363. if len(seg)-28 != wantPay[i] {
  364. t.Errorf("seg %d: pay len %d want %d", i, len(seg)-28, wantPay[i])
  365. }
  366. udpLen := binary.BigEndian.Uint16(seg[24:26])
  367. if udpLen != uint16(8+wantPay[i]) {
  368. t.Errorf("seg %d: udp len=%d want %d", i, udpLen, 8+wantPay[i])
  369. }
  370. if !verifyChecksum(seg[:20], 0) {
  371. t.Errorf("seg %d: bad IPv4 header checksum", i)
  372. }
  373. psum := pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_UDP, 8+wantPay[i])
  374. if !verifyChecksum(seg[20:], psum) {
  375. t.Errorf("seg %d: bad UDP checksum", i)
  376. }
  377. }
  378. }
  379. func TestSegmentUDPv6(t *testing.T) {
  380. const ipLen = 40
  381. const udpLen = 8
  382. const gso = 120
  383. const numSeg = 2
  384. payLen := gso * numSeg
  385. pkt := make([]byte, ipLen+udpLen+payLen)
  386. // IPv6 header
  387. pkt[0] = 0x60
  388. binary.BigEndian.PutUint16(pkt[4:6], uint16(udpLen+payLen))
  389. pkt[6] = unix.IPPROTO_UDP
  390. pkt[7] = 64
  391. pkt[8] = 0xfe
  392. pkt[9] = 0x80
  393. pkt[23] = 1
  394. pkt[24] = 0xfe
  395. pkt[25] = 0x80
  396. pkt[39] = 2
  397. binary.BigEndian.PutUint16(pkt[40:42], 12345)
  398. binary.BigEndian.PutUint16(pkt[42:44], 53)
  399. for i := 0; i < payLen; i++ {
  400. pkt[ipLen+udpLen+i] = byte(i)
  401. }
  402. hdr := virtio.Hdr{
  403. Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
  404. GSOType: unix.VIRTIO_NET_HDR_GSO_UDP_L4,
  405. HdrLen: uint16(ipLen + udpLen),
  406. GSOSize: uint16(gso),
  407. CsumStart: uint16(ipLen),
  408. CsumOffset: 6,
  409. }
  410. scratch := make([]byte, testSegScratchSize)
  411. var out [][]byte
  412. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  413. t.Fatalf("segmentForTest: %v", err)
  414. }
  415. if len(out) != numSeg {
  416. t.Fatalf("want %d segments, got %d", numSeg, len(out))
  417. }
  418. for i, seg := range out {
  419. if len(seg) != ipLen+udpLen+gso {
  420. t.Errorf("seg %d: len %d want %d", i, len(seg), ipLen+udpLen+gso)
  421. }
  422. pl := binary.BigEndian.Uint16(seg[4:6])
  423. if pl != uint16(udpLen+gso) {
  424. t.Errorf("seg %d: payload_length=%d want %d", i, pl, udpLen+gso)
  425. }
  426. ul := binary.BigEndian.Uint16(seg[ipLen+4 : ipLen+6])
  427. if ul != uint16(udpLen+gso) {
  428. t.Errorf("seg %d: udp len=%d want %d", i, ul, udpLen+gso)
  429. }
  430. psum := pseudoHeaderIPv6(seg[8:24], seg[24:40], unix.IPPROTO_UDP, udpLen+gso)
  431. if !verifyChecksum(seg[ipLen:], psum) {
  432. t.Errorf("seg %d: bad UDP checksum", i)
  433. }
  434. }
  435. }
  436. // TestSegmentUDPCEPropagates confirms IP-level CE marks on the seed appear on
  437. // every segment. UDP has no transport-level CWR/ECE: the IP TOS/TC byte is
  438. // copied verbatim into every segment by the segment-prefix copy.
  439. func TestSegmentUDPCEPropagates(t *testing.T) {
  440. pkt, hdr := buildUSOv4(t, 200, 100)
  441. pkt[1] = 0x03 // CE codepoint in IP-ECN
  442. scratch := make([]byte, testSegScratchSize)
  443. var out [][]byte
  444. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  445. t.Fatalf("segmentForTest: %v", err)
  446. }
  447. if len(out) != 2 {
  448. t.Fatalf("want 2 segments, got %d", len(out))
  449. }
  450. for i, seg := range out {
  451. if seg[1]&0x03 != 0x03 {
  452. t.Errorf("seg %d: CE missing (tos=%#x)", i, seg[1])
  453. }
  454. if !verifyChecksum(seg[:20], 0) {
  455. t.Errorf("seg %d: bad IPv4 header checksum", i)
  456. }
  457. }
  458. }
  459. // TestSegmentTCPCwrFirstSegmentOnly confirms RFC 3168 §6.1.2: when a TSO
  460. // burst's seed has CWR set, only the first emitted segment carries CWR.
  461. // ECE is preserved on every segment (different signal, persistent state).
  462. func TestSegmentTCPCwrFirstSegmentOnly(t *testing.T) {
  463. const mss = 100
  464. const numSeg = 3
  465. pkt, hdr := buildTSOv4(t, mss*numSeg, mss)
  466. // Seed flags: CWR | ECE | ACK | PSH.
  467. pkt[33] = 0x80 | 0x40 | 0x10 | 0x08
  468. scratch := make([]byte, testSegScratchSize)
  469. var out [][]byte
  470. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  471. t.Fatalf("segmentForTest: %v", err)
  472. }
  473. if len(out) != numSeg {
  474. t.Fatalf("expected %d segments, got %d", numSeg, len(out))
  475. }
  476. for i, seg := range out {
  477. flags := seg[33]
  478. hasCwr := flags&0x80 != 0
  479. hasEce := flags&0x40 != 0
  480. hasPsh := flags&0x08 != 0
  481. wantCwr := i == 0
  482. wantPsh := i == numSeg-1
  483. if hasCwr != wantCwr {
  484. t.Errorf("seg %d: CWR=%v want %v (flags=%#x)", i, hasCwr, wantCwr, flags)
  485. }
  486. if !hasEce {
  487. t.Errorf("seg %d: ECE missing (flags=%#x)", i, flags)
  488. }
  489. if hasPsh != wantPsh {
  490. t.Errorf("seg %d: PSH=%v want %v (flags=%#x)", i, hasPsh, wantPsh, flags)
  491. }
  492. // IP and TCP checksums must still verify after the flag rewrite.
  493. if !verifyChecksum(seg[:20], 0) {
  494. t.Errorf("seg %d: bad IPv4 header checksum", i)
  495. }
  496. psum := pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_TCP, 20+mss)
  497. if !verifyChecksum(seg[20:], psum) {
  498. t.Errorf("seg %d: bad TCP checksum", i)
  499. }
  500. }
  501. }
  502. func BenchmarkSegmentTCPv4(b *testing.B) {
  503. sizes := []struct {
  504. name string
  505. payLen int
  506. mss int
  507. }{
  508. {"64KiB_MSS1460", 65000, 1460},
  509. {"16KiB_MSS1460", 16384, 1460},
  510. {"4KiB_MSS1460", 4096, 1460},
  511. }
  512. for _, sz := range sizes {
  513. b.Run(sz.name, func(b *testing.B) {
  514. const ipLen = 20
  515. const tcpLen = 20
  516. pkt := make([]byte, ipLen+tcpLen+sz.payLen)
  517. pkt[0] = 0x45
  518. binary.BigEndian.PutUint16(pkt[2:4], uint16(ipLen+tcpLen+sz.payLen))
  519. binary.BigEndian.PutUint16(pkt[4:6], 0x4242)
  520. pkt[8] = 64
  521. pkt[9] = unix.IPPROTO_TCP
  522. copy(pkt[12:16], []byte{10, 0, 0, 1})
  523. copy(pkt[16:20], []byte{10, 0, 0, 2})
  524. binary.BigEndian.PutUint16(pkt[20:22], 12345)
  525. binary.BigEndian.PutUint16(pkt[22:24], 80)
  526. binary.BigEndian.PutUint32(pkt[24:28], 10000)
  527. binary.BigEndian.PutUint32(pkt[28:32], 20000)
  528. pkt[32] = 0x50
  529. pkt[33] = 0x18
  530. binary.BigEndian.PutUint16(pkt[34:36], 65535)
  531. for i := 0; i < sz.payLen; i++ {
  532. pkt[ipLen+tcpLen+i] = byte(i)
  533. }
  534. hdr := virtio.Hdr{
  535. Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
  536. GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV4,
  537. HdrLen: uint16(ipLen + tcpLen),
  538. GSOSize: uint16(sz.mss),
  539. CsumStart: uint16(ipLen),
  540. CsumOffset: 16,
  541. }
  542. scratch := make([]byte, testSegScratchSize)
  543. out := make([][]byte, 0, 64)
  544. // SegmentSuperpacket consumes its input destructively; restore
  545. // pkt from a master copy each iteration. The restore mirrors the
  546. // kernel→userspace copy that hands a fresh GSO blob to the
  547. // segmenter in production, so it's representative cost rather
  548. // than bench overhead.
  549. master := append([]byte(nil), pkt...)
  550. work := make([]byte, len(pkt))
  551. b.SetBytes(int64(len(pkt)))
  552. b.ResetTimer()
  553. for i := 0; i < b.N; i++ {
  554. copy(work, master)
  555. out = out[:0]
  556. if err := segmentForTest(work, hdr, &out, scratch); err != nil {
  557. b.Fatal(err)
  558. }
  559. }
  560. })
  561. }
  562. }
  563. // TestTunFileWriteVnetHdrNoAlloc verifies the IFF_VNET_HDR fast-path write is
  564. // allocation-free. We write to /dev/null so every call succeeds synchronously.
  565. func TestTunFileWriteVnetHdrNoAlloc(t *testing.T) {
  566. fd, err := unix.Open("/dev/null", os.O_WRONLY, 0)
  567. if err != nil {
  568. t.Fatalf("open /dev/null: %v", err)
  569. }
  570. t.Cleanup(func() { _ = unix.Close(fd) })
  571. tf := &Offload{fd: fd}
  572. payload := make([]byte, 1400)
  573. // Warm up (first call may trigger one-time internal allocations elsewhere).
  574. if _, err := tf.Write(payload); err != nil {
  575. t.Fatalf("Write: %v", err)
  576. }
  577. allocs := testing.AllocsPerRun(1000, func() {
  578. if _, err := tf.Write(payload); err != nil {
  579. t.Fatalf("Write: %v", err)
  580. }
  581. })
  582. if allocs != 0 {
  583. t.Fatalf("Write allocated %.1f times per call, want 0", allocs)
  584. }
  585. }
  586. // buildTSOv6 builds a synthetic IPv6/TCP TSO superpacket with payLen bytes
  587. // of payload, segmented at gso. Returns the packet bytes only; the
  588. // virtio_net_hdr is the caller's responsibility.
  589. func buildTSOv6(payLen, gso int) []byte {
  590. const ipLen = 40
  591. const tcpLen = 20
  592. pkt := make([]byte, ipLen+tcpLen+payLen)
  593. pkt[0] = 0x60 // version 6
  594. binary.BigEndian.PutUint16(pkt[4:6], uint16(tcpLen+payLen))
  595. pkt[6] = unix.IPPROTO_TCP
  596. pkt[7] = 64
  597. pkt[8] = 0xfe
  598. pkt[9] = 0x80
  599. pkt[23] = 1
  600. pkt[24] = 0xfe
  601. pkt[25] = 0x80
  602. pkt[39] = 2
  603. binary.BigEndian.PutUint16(pkt[40:42], 12345)
  604. binary.BigEndian.PutUint16(pkt[42:44], 80)
  605. binary.BigEndian.PutUint32(pkt[44:48], 7)
  606. binary.BigEndian.PutUint32(pkt[48:52], 99)
  607. pkt[52] = 0x50
  608. pkt[53] = 0x10 // ACK only
  609. binary.BigEndian.PutUint16(pkt[54:56], 65535)
  610. for i := 0; i < payLen; i++ {
  611. pkt[ipLen+tcpLen+i] = byte(i)
  612. }
  613. return pkt
  614. }
  615. // TestDecodeReadFitsMaxTSOAtDrainThreshold proves the rxBuf sizing is
  616. // correct: when rxOff is at the maximum value the drain headroom check
  617. // allows, decodeRead must still be able to absorb a worst-case 64KiB
  618. // TSO superpacket without dropping the burst. With segmentation deferred
  619. // to encrypt time, decodeRead writes only the kernel-supplied bytes into
  620. // rxBuf, so the size requirement is just "fit one worst-case input."
  621. //
  622. // Regression history: in a prior layout the rx buffer doubled as the
  623. // segmentation output, a near-threshold drain read returned "scratch too
  624. // small", the whole 45-segment TSO burst was dropped, and the remote's TCP
  625. // fast-retransmit collapsed cwnd. Keeping this test in the new layout
  626. // guards against re-introducing a drain headroom shortfall.
  627. func TestDecodeReadFitsMaxTSOAtDrainThreshold(t *testing.T) {
  628. const ipv6HdrLen = 40
  629. const tcpHdrLen = 20
  630. const headerLen = ipv6HdrLen + tcpHdrLen
  631. // Maximum TUN read body. The tunReadBufSize cap on readv's body iovec
  632. // is what bounds the kernel's superpacket length.
  633. pktLen := tunReadBufSize
  634. payLen := pktLen - headerLen
  635. const targetSegs = 64
  636. gsoSize := (payLen + targetSegs - 1) / targetSegs
  637. pkt := buildTSOv6(payLen, gsoSize)
  638. if len(pkt) != pktLen {
  639. t.Fatalf("buildTSOv6 produced %d bytes, want %d", len(pkt), pktLen)
  640. }
  641. o := &Offload{
  642. rxBuf: make([]byte, tunRxBufCap),
  643. }
  644. // rxOff at the maximum value the drain headroom check permits before
  645. // it would refuse another read. Any drain-time read up to this
  646. // threshold MUST still process correctly.
  647. o.rxOff = tunRxBufCap - tunRxBufSize
  648. // Stage the body in rxBuf as if readv(2) just placed it there.
  649. copy(o.rxBuf[o.rxOff:], pkt)
  650. // Encode the matching virtio_net_hdr.
  651. hdr := virtio.Hdr{
  652. Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
  653. GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV6,
  654. HdrLen: uint16(headerLen),
  655. GSOSize: uint16(gsoSize),
  656. CsumStart: uint16(ipv6HdrLen),
  657. CsumOffset: 16,
  658. }
  659. hdr.Encode(o.readVnetScratch[:])
  660. startRxOff := o.rxOff
  661. if err := o.decodeRead(pktLen); err != nil {
  662. t.Fatalf("decodeRead at drain threshold returned %v — rxBuf sizing regression: "+
  663. "tunRxBufSize=%d must hold one worst-case input (%d)",
  664. err, tunRxBufSize, pktLen)
  665. }
  666. if len(o.pending) != 1 {
  667. t.Fatalf("got %d packets, want 1 superpacket entry", len(o.pending))
  668. }
  669. got := o.pending[0]
  670. if !got.GSO.IsSuperpacket() {
  671. t.Fatalf("expected superpacket GSO metadata, got %+v", got.GSO)
  672. }
  673. if got.GSO.Proto != GSOProtoTCP {
  674. t.Errorf("GSO.Proto=%d want TCP", got.GSO.Proto)
  675. }
  676. if got.GSO.Size != uint16(gsoSize) {
  677. t.Errorf("GSO.Size=%d want %d", got.GSO.Size, gsoSize)
  678. }
  679. if got.GSO.HdrLen != uint16(headerLen) {
  680. t.Errorf("GSO.HdrLen=%d want %d", got.GSO.HdrLen, headerLen)
  681. }
  682. if got.GSO.CsumStart != uint16(ipv6HdrLen) {
  683. t.Errorf("GSO.CsumStart=%d want %d", got.GSO.CsumStart, ipv6HdrLen)
  684. }
  685. if len(got.Bytes) != pktLen {
  686. t.Errorf("len(Bytes)=%d want %d", len(got.Bytes), pktLen)
  687. }
  688. // rxOff advances exactly by the kernel-supplied body length — no
  689. // segmentation output to account for any more.
  690. if o.rxOff != startRxOff+pktLen {
  691. t.Errorf("rxOff=%d want %d", o.rxOff, startRxOff+pktLen)
  692. }
  693. if o.rxOff > tunRxBufCap {
  694. t.Fatalf("rxOff=%d overran rxBuf (cap=%d)", o.rxOff, tunRxBufCap)
  695. }
  696. // Validate that segmenting the returned superpacket reproduces the
  697. // expected per-segment IPv6 payload length and TCP checksum.
  698. wantSegs := (payLen + gsoSize - 1) / gsoSize
  699. gotSegs := 0
  700. if err := SegmentSuperpacket(got, func(seg []byte) error {
  701. defer func() { gotSegs++ }()
  702. if len(seg) < headerLen+1 {
  703. t.Errorf("seg %d too short: %d", gotSegs, len(seg))
  704. return nil
  705. }
  706. if seg[0]>>4 != 6 {
  707. t.Errorf("seg %d: bad IP version %#x", gotSegs, seg[0])
  708. }
  709. segPay := len(seg) - headerLen
  710. gotPL := binary.BigEndian.Uint16(seg[4:6])
  711. if gotPL != uint16(tcpHdrLen+segPay) {
  712. t.Errorf("seg %d: payload_len=%d want %d", gotSegs, gotPL, tcpHdrLen+segPay)
  713. }
  714. psum := pseudoHeaderIPv6(seg[8:24], seg[24:40], unix.IPPROTO_TCP, tcpHdrLen+segPay)
  715. if !verifyChecksum(seg[ipv6HdrLen:], psum) {
  716. t.Errorf("seg %d: bad TCP checksum", gotSegs)
  717. }
  718. return nil
  719. }); err != nil {
  720. t.Fatalf("SegmentSuperpacket: %v", err)
  721. }
  722. if gotSegs != wantSegs {
  723. t.Fatalf("got %d segments, want %d", gotSegs, wantSegs)
  724. }
  725. }