tun_linux_offload_test.go 24 KB

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