tun_linux_offload_test.go 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122
  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. // TestProtoFromGSOTypeMasksECN guards the CWR-superpacket drop bug: the
  17. // kernel qualifies a TSO superpacket whose TCP header carries CWR with
  18. // VIRTIO_NET_HDR_GSO_ECN (we negotiate TUN_F_TSO_ECN, so it WILL send
  19. // them once ECN feedback flows), and the decoder must mask that bit
  20. // rather than reject the packet as an unknown type.
  21. func TestProtoFromGSOTypeMasksECN(t *testing.T) {
  22. cases := []struct {
  23. typ uint8
  24. want GSOProto
  25. }{
  26. {unix.VIRTIO_NET_HDR_GSO_TCPV4, GSOProtoTCP},
  27. {unix.VIRTIO_NET_HDR_GSO_TCPV4 | unix.VIRTIO_NET_HDR_GSO_ECN, GSOProtoTCP},
  28. {unix.VIRTIO_NET_HDR_GSO_TCPV6, GSOProtoTCP},
  29. {unix.VIRTIO_NET_HDR_GSO_TCPV6 | unix.VIRTIO_NET_HDR_GSO_ECN, GSOProtoTCP},
  30. {unix.VIRTIO_NET_HDR_GSO_UDP_L4, GSOProtoUDP},
  31. }
  32. for _, c := range cases {
  33. got, err := protoFromGSOType(c.typ)
  34. if err != nil || got != c.want {
  35. t.Errorf("protoFromGSOType(%#x) = (%v, %v), want (%v, nil)", c.typ, got, err, c.want)
  36. }
  37. }
  38. if _, err := protoFromGSOType(unix.VIRTIO_NET_HDR_GSO_NONE); err == nil {
  39. t.Error("GSO_NONE must still be rejected")
  40. }
  41. if _, err := protoFromGSOType(unix.VIRTIO_NET_HDR_GSO_ECN); err == nil {
  42. t.Error("a bare ECN bit with no base type must still be rejected")
  43. }
  44. }
  45. // verifyChecksum confirms that the one's-complement sum across `b`, seeded
  46. // with a folded pseudo-header sum, equals all-ones (valid).
  47. func verifyChecksum(b []byte, pseudo uint16) bool {
  48. return checksum.Checksum(b, pseudo) == 0xffff
  49. }
  50. // segmentForTest is the test-only counterpart to the production
  51. // SegmentSuperpacket path. It handles GSO_NONE (with optional
  52. // finishChecksum) inline and dispatches GSO superpackets through
  53. // SegmentSuperpacket, draining each yielded segment into a
  54. // freshly-copied [][]byte slot so callers can iterate after the call
  55. // returns. Tests pre-set hdr.HdrLen correctly, so correctHdrLen is not
  56. // invoked here.
  57. func segmentForTest(pkt []byte, hdr virtio.Hdr, out *[][]byte, scratch []byte) error {
  58. if hdr.GSOType() == unix.VIRTIO_NET_HDR_GSO_NONE {
  59. cp := append([]byte(nil), pkt...)
  60. if hdr.Flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 {
  61. if err := virtio.FinishChecksum(cp, hdr); err != nil {
  62. return err
  63. }
  64. }
  65. *out = append(*out, cp)
  66. return nil
  67. }
  68. proto, err := protoFromGSOType(hdr.GSOType())
  69. if err != nil {
  70. return err
  71. }
  72. gso := GSOInfo{
  73. Size: hdr.GSOSize,
  74. HdrLen: hdr.HdrLen,
  75. CsumStart: hdr.CsumStart,
  76. Proto: proto,
  77. }
  78. return SegmentSuperpacket(Packet{Bytes: pkt, GSO: gso}, func(seg []byte) error {
  79. *out = append(*out, append([]byte(nil), seg...))
  80. return nil
  81. })
  82. }
  83. // pseudoHeaderIPv4 returns the folded pseudo-header sum used to verify a
  84. // TCP/UDP segment's checksum in tests. src/dst are 4 bytes each.
  85. func pseudoHeaderIPv4(src, dst []byte, proto byte, l4Len int) uint16 {
  86. s := uint32(checksum.Checksum(src, 0)) + uint32(checksum.Checksum(dst, 0))
  87. s += uint32(proto) + uint32(l4Len)
  88. s = (s & 0xffff) + (s >> 16)
  89. s = (s & 0xffff) + (s >> 16)
  90. return uint16(s)
  91. }
  92. // pseudoHeaderIPv6 returns the folded pseudo-header sum used to verify a
  93. // TCP/UDP segment's checksum in tests. src/dst are 16 bytes each.
  94. func pseudoHeaderIPv6(src, dst []byte, proto byte, l4Len int) uint16 {
  95. s := uint32(checksum.Checksum(src, 0)) + uint32(checksum.Checksum(dst, 0))
  96. s += uint32(l4Len>>16) + uint32(l4Len&0xffff) + uint32(proto)
  97. s = (s & 0xffff) + (s >> 16)
  98. s = (s & 0xffff) + (s >> 16)
  99. return uint16(s)
  100. }
  101. // buildTSOv4 builds a synthetic IPv4/TCP TSO superpacket with a payload of
  102. // `payLen` bytes split at `mss`.
  103. func buildTSOv4(t *testing.T, payLen, mss int) ([]byte, virtio.Hdr) {
  104. t.Helper()
  105. const ipLen = 20
  106. const tcpLen = 20
  107. pkt := make([]byte, ipLen+tcpLen+payLen)
  108. // IPv4 header
  109. pkt[0] = 0x45 // version 4, IHL 5
  110. // total length is meaningless for TSO but set it anyway
  111. binary.BigEndian.PutUint16(pkt[2:4], uint16(ipLen+tcpLen+payLen))
  112. binary.BigEndian.PutUint16(pkt[4:6], 0x4242) // original ID
  113. pkt[8] = 64 // TTL
  114. pkt[9] = unix.IPPROTO_TCP
  115. copy(pkt[12:16], []byte{10, 0, 0, 1}) // src
  116. copy(pkt[16:20], []byte{10, 0, 0, 2}) // dst
  117. // TCP header
  118. binary.BigEndian.PutUint16(pkt[20:22], 12345) // sport
  119. binary.BigEndian.PutUint16(pkt[22:24], 80) // dport
  120. binary.BigEndian.PutUint32(pkt[24:28], 10000) // seq
  121. binary.BigEndian.PutUint32(pkt[28:32], 20000) // ack
  122. pkt[32] = 0x50 // data offset 5 words
  123. pkt[33] = 0x18 // ACK | PSH
  124. binary.BigEndian.PutUint16(pkt[34:36], 65535) // window
  125. // payload
  126. for i := 0; i < payLen; i++ {
  127. pkt[ipLen+tcpLen+i] = byte(i & 0xff)
  128. }
  129. return pkt, virtio.NewHeader(
  130. unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
  131. unix.VIRTIO_NET_HDR_GSO_TCPV4, /*gsoType*/
  132. uint16(ipLen+tcpLen), /*hdrLen*/
  133. uint16(mss), /*gsoSize*/
  134. uint16(ipLen), /*csumStart*/
  135. 16, /*csumOffset*/
  136. )
  137. }
  138. func TestSegmentTCPv4(t *testing.T) {
  139. const mss = 100
  140. const numSeg = 3
  141. pkt, hdr := buildTSOv4(t, mss*numSeg, mss)
  142. scratch := make([]byte, testSegScratchSize)
  143. var out [][]byte
  144. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  145. t.Fatalf("segmentForTest: %v", err)
  146. }
  147. if len(out) != numSeg {
  148. t.Fatalf("expected %d segments, got %d", numSeg, len(out))
  149. }
  150. for i, seg := range out {
  151. if len(seg) != 40+mss {
  152. t.Errorf("seg %d: unexpected len %d", i, len(seg))
  153. }
  154. totalLen := binary.BigEndian.Uint16(seg[2:4])
  155. if totalLen != uint16(40+mss) {
  156. t.Errorf("seg %d: total_len=%d want %d", i, totalLen, 40+mss)
  157. }
  158. id := binary.BigEndian.Uint16(seg[4:6])
  159. if id != 0x4242+uint16(i) {
  160. t.Errorf("seg %d: ip id=%#x want %#x", i, id, 0x4242+uint16(i))
  161. }
  162. seq := binary.BigEndian.Uint32(seg[24:28])
  163. wantSeq := uint32(10000 + i*mss)
  164. if seq != wantSeq {
  165. t.Errorf("seg %d: seq=%d want %d", i, seq, wantSeq)
  166. }
  167. flags := seg[33]
  168. wantFlags := byte(0x10) // ACK only, PSH cleared
  169. if i == numSeg-1 {
  170. wantFlags = 0x18 // ACK | PSH preserved on last
  171. }
  172. if flags != wantFlags {
  173. t.Errorf("seg %d: flags=%#x want %#x", i, flags, wantFlags)
  174. }
  175. // IPv4 header checksum must verify against itself.
  176. if !verifyChecksum(seg[:20], 0) {
  177. t.Errorf("seg %d: bad IPv4 header checksum", i)
  178. }
  179. // TCP checksum must verify against the pseudo-header.
  180. psum := pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_TCP, 20+mss)
  181. if !verifyChecksum(seg[20:], psum) {
  182. t.Errorf("seg %d: bad TCP checksum", i)
  183. }
  184. }
  185. }
  186. func TestSegmentTCPv4OddTail(t *testing.T) {
  187. // Payload of 250 bytes with MSS 100 → segments of 100, 100, 50.
  188. pkt, hdr := buildTSOv4(t, 250, 100)
  189. scratch := make([]byte, testSegScratchSize)
  190. var out [][]byte
  191. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  192. t.Fatalf("segmentForTest: %v", err)
  193. }
  194. if len(out) != 3 {
  195. t.Fatalf("want 3 segments, got %d", len(out))
  196. }
  197. wantPayLens := []int{100, 100, 50}
  198. for i, seg := range out {
  199. if len(seg)-40 != wantPayLens[i] {
  200. t.Errorf("seg %d: pay len %d want %d", i, len(seg)-40, wantPayLens[i])
  201. }
  202. if !verifyChecksum(seg[:20], 0) {
  203. t.Errorf("seg %d: bad IPv4 header checksum", i)
  204. }
  205. psum := pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_TCP, 20+wantPayLens[i])
  206. if !verifyChecksum(seg[20:], psum) {
  207. t.Errorf("seg %d: bad TCP checksum", i)
  208. }
  209. }
  210. }
  211. func TestSegmentTCPv6(t *testing.T) {
  212. const ipLen = 40
  213. const tcpLen = 20
  214. const mss = 120
  215. const numSeg = 2
  216. payLen := mss * numSeg
  217. pkt := make([]byte, ipLen+tcpLen+payLen)
  218. // IPv6 header
  219. pkt[0] = 0x60 // version 6
  220. binary.BigEndian.PutUint16(pkt[4:6], uint16(tcpLen+payLen))
  221. pkt[6] = unix.IPPROTO_TCP
  222. pkt[7] = 64
  223. // src/dst fe80::1 / fe80::2
  224. pkt[8] = 0xfe
  225. pkt[9] = 0x80
  226. pkt[23] = 1
  227. pkt[24] = 0xfe
  228. pkt[25] = 0x80
  229. pkt[39] = 2
  230. // TCP header
  231. binary.BigEndian.PutUint16(pkt[40:42], 12345)
  232. binary.BigEndian.PutUint16(pkt[42:44], 80)
  233. binary.BigEndian.PutUint32(pkt[44:48], 7)
  234. binary.BigEndian.PutUint32(pkt[48:52], 99)
  235. pkt[52] = 0x50
  236. pkt[53] = 0x19 // FIN | ACK | PSH — exercise FIN clearing too
  237. binary.BigEndian.PutUint16(pkt[54:56], 65535)
  238. for i := 0; i < payLen; i++ {
  239. pkt[ipLen+tcpLen+i] = byte(i)
  240. }
  241. hdr := virtio.NewHeader(
  242. unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
  243. unix.VIRTIO_NET_HDR_GSO_TCPV6, /*gsoType*/
  244. uint16(ipLen+tcpLen), /*hdrLen*/
  245. uint16(mss), /*gsoSize*/
  246. uint16(ipLen), /*csumStart*/
  247. 16, /*csumOffset*/
  248. )
  249. scratch := make([]byte, testSegScratchSize)
  250. var out [][]byte
  251. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  252. t.Fatalf("segmentForTest: %v", err)
  253. }
  254. if len(out) != numSeg {
  255. t.Fatalf("want %d segments, got %d", numSeg, len(out))
  256. }
  257. for i, seg := range out {
  258. if len(seg) != ipLen+tcpLen+mss {
  259. t.Errorf("seg %d: len %d want %d", i, len(seg), ipLen+tcpLen+mss)
  260. }
  261. pl := binary.BigEndian.Uint16(seg[4:6])
  262. if pl != uint16(tcpLen+mss) {
  263. t.Errorf("seg %d: payload_length=%d want %d", i, pl, tcpLen+mss)
  264. }
  265. seq := binary.BigEndian.Uint32(seg[44:48])
  266. if seq != uint32(7+i*mss) {
  267. t.Errorf("seg %d: seq=%d want %d", i, seq, 7+i*mss)
  268. }
  269. flags := seg[53]
  270. // Original flags = 0x19 (FIN|ACK|PSH). FIN(0x01)+PSH(0x08) should be
  271. // cleared on all but the last; ACK(0x10) always preserved.
  272. wantFlags := byte(0x10)
  273. if i == numSeg-1 {
  274. wantFlags = 0x19
  275. }
  276. if flags != wantFlags {
  277. t.Errorf("seg %d: flags=%#x want %#x", i, flags, wantFlags)
  278. }
  279. psum := pseudoHeaderIPv6(seg[8:24], seg[24:40], unix.IPPROTO_TCP, tcpLen+mss)
  280. if !verifyChecksum(seg[ipLen:], psum) {
  281. t.Errorf("seg %d: bad TCP checksum", i)
  282. }
  283. }
  284. }
  285. func TestSegmentGSONonePassesThrough(t *testing.T) {
  286. pkt, hdr := buildTSOv4(t, 100, 100)
  287. hdr.SetGSOType(unix.VIRTIO_NET_HDR_GSO_NONE)
  288. hdr.Flags = 0 // no NEEDS_CSUM, leave packet untouched
  289. scratch := make([]byte, testSegScratchSize)
  290. var out [][]byte
  291. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  292. t.Fatalf("segmentForTest: %v", err)
  293. }
  294. if len(out) != 1 {
  295. t.Fatalf("want 1 segment, got %d", len(out))
  296. }
  297. if len(out[0]) != len(pkt) {
  298. t.Fatalf("unexpected length: %d vs %d", len(out[0]), len(pkt))
  299. }
  300. }
  301. // TestSegmentRejectsLegacyUDPGSO ensures the legacy GSO_UDP (UFO) marker is
  302. // still rejected; only modern GSO_UDP_L4 (USO) is supported.
  303. func TestSegmentRejectsLegacyUDPGSO(t *testing.T) {
  304. hdr := virtio.NewHeader(0, unix.VIRTIO_NET_HDR_GSO_UDP, 0, 0, 0, 0)
  305. var out [][]byte
  306. if err := segmentForTest(nil, hdr, &out, nil); err == nil {
  307. t.Fatalf("expected rejection for legacy UDP GSO")
  308. }
  309. }
  310. // buildUSOv4 builds a synthetic IPv4/UDP USO superpacket with payload of
  311. // payLen bytes, segmented at gsoSize.
  312. func buildUSOv4(t *testing.T, payLen, gsoSize int) ([]byte, virtio.Hdr) {
  313. t.Helper()
  314. const ipLen = 20
  315. const udpLen = 8
  316. pkt := make([]byte, ipLen+udpLen+payLen)
  317. // IPv4 header
  318. pkt[0] = 0x45 // version 4, IHL 5
  319. binary.BigEndian.PutUint16(pkt[2:4], uint16(ipLen+udpLen+payLen))
  320. binary.BigEndian.PutUint16(pkt[4:6], 0x4242)
  321. pkt[8] = 64
  322. pkt[9] = unix.IPPROTO_UDP
  323. copy(pkt[12:16], []byte{10, 0, 0, 1})
  324. copy(pkt[16:20], []byte{10, 0, 0, 2})
  325. // UDP header. The kernel hands us a USO superpacket whose length field
  326. // covers the WHOLE superpacket; the segmenter overwrites it per segment.
  327. // Populating it here matters: leaving it zero makes the base-checksum path
  328. // that must exclude it untestable, since excluding zero is a no-op.
  329. binary.BigEndian.PutUint16(pkt[20:22], 12345) // sport
  330. binary.BigEndian.PutUint16(pkt[22:24], 53) // dport
  331. binary.BigEndian.PutUint16(pkt[24:26], uint16(udpLen+payLen)) // superpacket length
  332. for i := 0; i < payLen; i++ {
  333. pkt[ipLen+udpLen+i] = byte(i & 0xff)
  334. }
  335. return pkt, virtio.NewHeader(
  336. unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
  337. unix.VIRTIO_NET_HDR_GSO_UDP_L4, /*gsoType*/
  338. uint16(ipLen+udpLen), /*hdrLen*/
  339. uint16(gsoSize), /*gsoSize*/
  340. uint16(ipLen), /*csumStart*/
  341. 6, /*csumOffset*/
  342. )
  343. }
  344. func TestSegmentUDPv4(t *testing.T) {
  345. const gso = 100
  346. const numSeg = 3
  347. pkt, hdr := buildUSOv4(t, gso*numSeg, gso)
  348. scratch := make([]byte, testSegScratchSize)
  349. var out [][]byte
  350. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  351. t.Fatalf("segmentForTest: %v", err)
  352. }
  353. if len(out) != numSeg {
  354. t.Fatalf("expected %d segments, got %d", numSeg, len(out))
  355. }
  356. for i, seg := range out {
  357. if len(seg) != 28+gso {
  358. t.Errorf("seg %d: len %d want %d", i, len(seg), 28+gso)
  359. }
  360. totalLen := binary.BigEndian.Uint16(seg[2:4])
  361. if totalLen != uint16(28+gso) {
  362. t.Errorf("seg %d: total_len=%d want %d", i, totalLen, 28+gso)
  363. }
  364. // Software UDP GSO bumps the IPv4 ID per segment exactly like TSO
  365. // (inet_gso_segment's fixed-ID case is TCP-only); wireguard-go's
  366. // gsoSplit increments unconditionally too.
  367. id := binary.BigEndian.Uint16(seg[4:6])
  368. if id != 0x4242+uint16(i) {
  369. t.Errorf("seg %d: ip id=%#x want %#x", i, id, 0x4242+uint16(i))
  370. }
  371. udpLen := binary.BigEndian.Uint16(seg[24:26])
  372. if udpLen != uint16(8+gso) {
  373. t.Errorf("seg %d: udp len=%d want %d", i, udpLen, 8+gso)
  374. }
  375. if !verifyChecksum(seg[:20], 0) {
  376. t.Errorf("seg %d: bad IPv4 header checksum", i)
  377. }
  378. psum := pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_UDP, 8+gso)
  379. if !verifyChecksum(seg[20:], psum) {
  380. t.Errorf("seg %d: bad UDP checksum", i)
  381. }
  382. }
  383. }
  384. func TestSegmentUDPv4OddTail(t *testing.T) {
  385. // 250 bytes payload, gsoSize=100 → segments of 100, 100, 50.
  386. pkt, hdr := buildUSOv4(t, 250, 100)
  387. scratch := make([]byte, testSegScratchSize)
  388. var out [][]byte
  389. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  390. t.Fatalf("segmentForTest: %v", err)
  391. }
  392. if len(out) != 3 {
  393. t.Fatalf("want 3 segments, got %d", len(out))
  394. }
  395. wantPay := []int{100, 100, 50}
  396. for i, seg := range out {
  397. if len(seg)-28 != wantPay[i] {
  398. t.Errorf("seg %d: pay len %d want %d", i, len(seg)-28, wantPay[i])
  399. }
  400. udpLen := binary.BigEndian.Uint16(seg[24:26])
  401. if udpLen != uint16(8+wantPay[i]) {
  402. t.Errorf("seg %d: udp len=%d want %d", i, udpLen, 8+wantPay[i])
  403. }
  404. if !verifyChecksum(seg[:20], 0) {
  405. t.Errorf("seg %d: bad IPv4 header checksum", i)
  406. }
  407. psum := pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_UDP, 8+wantPay[i])
  408. if !verifyChecksum(seg[20:], psum) {
  409. t.Errorf("seg %d: bad UDP checksum", i)
  410. }
  411. }
  412. }
  413. func TestSegmentUDPv6(t *testing.T) {
  414. const ipLen = 40
  415. const udpLen = 8
  416. const gso = 120
  417. const numSeg = 2
  418. payLen := gso * numSeg
  419. pkt := make([]byte, ipLen+udpLen+payLen)
  420. // IPv6 header
  421. pkt[0] = 0x60
  422. binary.BigEndian.PutUint16(pkt[4:6], uint16(udpLen+payLen))
  423. pkt[6] = unix.IPPROTO_UDP
  424. pkt[7] = 64
  425. pkt[8] = 0xfe
  426. pkt[9] = 0x80
  427. pkt[23] = 1
  428. pkt[24] = 0xfe
  429. pkt[25] = 0x80
  430. pkt[39] = 2
  431. binary.BigEndian.PutUint16(pkt[40:42], 12345)
  432. binary.BigEndian.PutUint16(pkt[42:44], 53)
  433. // Superpacket-wide length, as the kernel supplies it; see buildUSOv4.
  434. binary.BigEndian.PutUint16(pkt[44:46], uint16(udpLen+payLen))
  435. for i := 0; i < payLen; i++ {
  436. pkt[ipLen+udpLen+i] = byte(i)
  437. }
  438. hdr := virtio.NewHeader(
  439. unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
  440. unix.VIRTIO_NET_HDR_GSO_UDP_L4, /*gsoType*/
  441. uint16(ipLen+udpLen), /*hdrLen*/
  442. uint16(gso), /*gsoSize*/
  443. uint16(ipLen), /*csumStart*/
  444. 6, /*csumOffset*/
  445. )
  446. scratch := make([]byte, testSegScratchSize)
  447. var out [][]byte
  448. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  449. t.Fatalf("segmentForTest: %v", err)
  450. }
  451. if len(out) != numSeg {
  452. t.Fatalf("want %d segments, got %d", numSeg, len(out))
  453. }
  454. for i, seg := range out {
  455. if len(seg) != ipLen+udpLen+gso {
  456. t.Errorf("seg %d: len %d want %d", i, len(seg), ipLen+udpLen+gso)
  457. }
  458. pl := binary.BigEndian.Uint16(seg[4:6])
  459. if pl != uint16(udpLen+gso) {
  460. t.Errorf("seg %d: payload_length=%d want %d", i, pl, udpLen+gso)
  461. }
  462. ul := binary.BigEndian.Uint16(seg[ipLen+4 : ipLen+6])
  463. if ul != uint16(udpLen+gso) {
  464. t.Errorf("seg %d: udp len=%d want %d", i, ul, udpLen+gso)
  465. }
  466. psum := pseudoHeaderIPv6(seg[8:24], seg[24:40], unix.IPPROTO_UDP, udpLen+gso)
  467. if !verifyChecksum(seg[ipLen:], psum) {
  468. t.Errorf("seg %d: bad UDP checksum", i)
  469. }
  470. }
  471. }
  472. // TestSegmentUDPCEPropagates confirms IP-level CE marks on the seed appear on
  473. // every segment. UDP has no transport-level CWR/ECE: the IP TOS/TC byte is
  474. // copied verbatim into every segment by the segment-prefix copy.
  475. func TestSegmentUDPCEPropagates(t *testing.T) {
  476. pkt, hdr := buildUSOv4(t, 200, 100)
  477. pkt[1] = 0x03 // CE codepoint in IP-ECN
  478. scratch := make([]byte, testSegScratchSize)
  479. var out [][]byte
  480. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  481. t.Fatalf("segmentForTest: %v", err)
  482. }
  483. if len(out) != 2 {
  484. t.Fatalf("want 2 segments, got %d", len(out))
  485. }
  486. for i, seg := range out {
  487. if seg[1]&0x03 != 0x03 {
  488. t.Errorf("seg %d: CE missing (tos=%#x)", i, seg[1])
  489. }
  490. if !verifyChecksum(seg[:20], 0) {
  491. t.Errorf("seg %d: bad IPv4 header checksum", i)
  492. }
  493. }
  494. }
  495. // TestSegmentTCPCwrFirstSegmentOnly confirms RFC 3168 §6.1.2: when a TSO
  496. // burst's seed has CWR set, only the first emitted segment carries CWR.
  497. // ECE is preserved on every segment (different signal, persistent state).
  498. func TestSegmentTCPCwrFirstSegmentOnly(t *testing.T) {
  499. const mss = 100
  500. const numSeg = 3
  501. pkt, hdr := buildTSOv4(t, mss*numSeg, mss)
  502. // Seed flags: CWR | ECE | ACK | PSH.
  503. pkt[33] = 0x80 | 0x40 | 0x10 | 0x08
  504. scratch := make([]byte, testSegScratchSize)
  505. var out [][]byte
  506. if err := segmentForTest(pkt, hdr, &out, scratch); err != nil {
  507. t.Fatalf("segmentForTest: %v", err)
  508. }
  509. if len(out) != numSeg {
  510. t.Fatalf("expected %d segments, got %d", numSeg, len(out))
  511. }
  512. for i, seg := range out {
  513. flags := seg[33]
  514. hasCwr := flags&0x80 != 0
  515. hasEce := flags&0x40 != 0
  516. hasPsh := flags&0x08 != 0
  517. wantCwr := i == 0
  518. wantPsh := i == numSeg-1
  519. if hasCwr != wantCwr {
  520. t.Errorf("seg %d: CWR=%v want %v (flags=%#x)", i, hasCwr, wantCwr, flags)
  521. }
  522. if !hasEce {
  523. t.Errorf("seg %d: ECE missing (flags=%#x)", i, flags)
  524. }
  525. if hasPsh != wantPsh {
  526. t.Errorf("seg %d: PSH=%v want %v (flags=%#x)", i, hasPsh, wantPsh, flags)
  527. }
  528. // IP and TCP checksums must still verify after the flag rewrite.
  529. if !verifyChecksum(seg[:20], 0) {
  530. t.Errorf("seg %d: bad IPv4 header checksum", i)
  531. }
  532. psum := pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_TCP, 20+mss)
  533. if !verifyChecksum(seg[20:], psum) {
  534. t.Errorf("seg %d: bad TCP checksum", i)
  535. }
  536. }
  537. }
  538. func BenchmarkSegmentTCPv4(b *testing.B) {
  539. sizes := []struct {
  540. name string
  541. payLen int
  542. mss int
  543. }{
  544. {"64KiB_MSS1460", 65000, 1460},
  545. {"16KiB_MSS1460", 16384, 1460},
  546. {"4KiB_MSS1460", 4096, 1460},
  547. }
  548. for _, sz := range sizes {
  549. b.Run(sz.name, func(b *testing.B) {
  550. const ipLen = 20
  551. const tcpLen = 20
  552. pkt := make([]byte, ipLen+tcpLen+sz.payLen)
  553. pkt[0] = 0x45
  554. binary.BigEndian.PutUint16(pkt[2:4], uint16(ipLen+tcpLen+sz.payLen))
  555. binary.BigEndian.PutUint16(pkt[4:6], 0x4242)
  556. pkt[8] = 64
  557. pkt[9] = unix.IPPROTO_TCP
  558. copy(pkt[12:16], []byte{10, 0, 0, 1})
  559. copy(pkt[16:20], []byte{10, 0, 0, 2})
  560. binary.BigEndian.PutUint16(pkt[20:22], 12345)
  561. binary.BigEndian.PutUint16(pkt[22:24], 80)
  562. binary.BigEndian.PutUint32(pkt[24:28], 10000)
  563. binary.BigEndian.PutUint32(pkt[28:32], 20000)
  564. pkt[32] = 0x50
  565. pkt[33] = 0x18
  566. binary.BigEndian.PutUint16(pkt[34:36], 65535)
  567. for i := 0; i < sz.payLen; i++ {
  568. pkt[ipLen+tcpLen+i] = byte(i)
  569. }
  570. hdr := virtio.NewHeader(
  571. unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
  572. unix.VIRTIO_NET_HDR_GSO_TCPV4, /*gsoType*/
  573. uint16(ipLen+tcpLen), /*hdrLen*/
  574. uint16(sz.mss), /*gsoSize*/
  575. uint16(ipLen), /*csumStart*/
  576. 16, /*csumOffset*/
  577. )
  578. scratch := make([]byte, testSegScratchSize)
  579. out := make([][]byte, 0, 64)
  580. // SegmentSuperpacket consumes its input destructively; restore
  581. // pkt from a master copy each iteration. The restore mirrors the
  582. // kernel→userspace copy that hands a fresh GSO blob to the
  583. // segmenter in production, so it's representative cost rather
  584. // than bench overhead.
  585. master := append([]byte(nil), pkt...)
  586. work := make([]byte, len(pkt))
  587. b.SetBytes(int64(len(pkt)))
  588. b.ResetTimer()
  589. for i := 0; i < b.N; i++ {
  590. copy(work, master)
  591. out = out[:0]
  592. if err := segmentForTest(work, hdr, &out, scratch); err != nil {
  593. b.Fatal(err)
  594. }
  595. }
  596. })
  597. }
  598. }
  599. // TestTunFileWriteVnetHdrNoAlloc verifies the IFF_VNET_HDR fast-path write is
  600. // allocation-free. We write to /dev/null so every call succeeds synchronously.
  601. func TestTunFileWriteVnetHdrNoAlloc(t *testing.T) {
  602. fd, err := unix.Open("/dev/null", os.O_WRONLY, 0)
  603. if err != nil {
  604. t.Fatalf("open /dev/null: %v", err)
  605. }
  606. t.Cleanup(func() { _ = unix.Close(fd) })
  607. tf := &Offload{fd: fd}
  608. payload := make([]byte, 1400)
  609. // Warm up (first call may trigger one-time internal allocations elsewhere).
  610. if _, err := tf.Write(payload); err != nil {
  611. t.Fatalf("Write: %v", err)
  612. }
  613. allocs := testing.AllocsPerRun(1000, func() {
  614. if _, err := tf.Write(payload); err != nil {
  615. t.Fatalf("Write: %v", err)
  616. }
  617. })
  618. if allocs != 0 {
  619. t.Fatalf("Write allocated %.1f times per call, want 0", allocs)
  620. }
  621. }
  622. // TestSegmentSuperpacketNoAlloc pins the segmenters' zero-allocation
  623. // contract. Both SegmentTCP and SegmentUDP derive their per-superpacket
  624. // constants into fixed-size arrays (tmp/ipTmp/savedHdr) that must stay on
  625. // the stack, and both take a yield closure that must not escape. Any of
  626. // those escaping turns one allocation into one-per-superpacket on the
  627. // hottest path in the reader, which BenchmarkSegmentSuperpacketAllocsTSO
  628. // reports but nothing fails on. This does.
  629. //
  630. // The yield closure here only touches captured scalars: appending segments
  631. // to a slice would allocate in the test itself and mask the measurement.
  632. func TestSegmentSuperpacketNoAlloc(t *testing.T) {
  633. const mss = 1400
  634. const numSeg = 8
  635. cases := []struct {
  636. name string
  637. build func() ([]byte, virtio.Hdr)
  638. }{
  639. {"tso-v4", func() ([]byte, virtio.Hdr) { return buildTSOv4(t, mss*numSeg, mss) }},
  640. {"uso-v4", func() ([]byte, virtio.Hdr) { return buildUSOv4(t, mss*numSeg, mss) }},
  641. }
  642. for _, tc := range cases {
  643. t.Run(tc.name, func(t *testing.T) {
  644. master, hdr := tc.build()
  645. proto, err := protoFromGSOType(hdr.GSOType())
  646. if err != nil {
  647. t.Fatalf("protoFromGSOType: %v", err)
  648. }
  649. work := make([]byte, len(master))
  650. p := Packet{Bytes: work, GSO: GSOInfo{
  651. Size: hdr.GSOSize,
  652. HdrLen: hdr.HdrLen,
  653. CsumStart: hdr.CsumStart,
  654. Proto: proto,
  655. }}
  656. // Segmentation consumes its input destructively, so restore from
  657. // the master copy each run; copy(2) into an existing slice does
  658. // not allocate. seen/bytes keep the closure from being optimized
  659. // away and double as a sanity check that work actually happened.
  660. var seen, bytes int
  661. run := func() {
  662. copy(work, master)
  663. seen, bytes = 0, 0
  664. if err := SegmentSuperpacket(p, func(seg []byte) error {
  665. seen++
  666. bytes += len(seg)
  667. return nil
  668. }); err != nil {
  669. t.Fatalf("SegmentSuperpacket: %v", err)
  670. }
  671. }
  672. run() // warm up: absorb any one-time allocation elsewhere
  673. if seen != numSeg {
  674. t.Fatalf("yielded %d segments, want %d", seen, numSeg)
  675. }
  676. if allocs := testing.AllocsPerRun(200, run); allocs != 0 {
  677. t.Fatalf("SegmentSuperpacket allocated %.1f times per call, want 0", allocs)
  678. }
  679. if seen != numSeg || bytes == 0 {
  680. t.Fatalf("post-measure sanity: seen=%d bytes=%d", seen, bytes)
  681. }
  682. })
  683. }
  684. }
  685. // buildTSOv6 builds a synthetic IPv6/TCP TSO superpacket with payLen bytes
  686. // of payload, segmented at gso. Returns the packet bytes only; the
  687. // virtio_net_hdr is the caller's responsibility.
  688. func buildTSOv6(payLen, gso int) []byte {
  689. const ipLen = 40
  690. const tcpLen = 20
  691. pkt := make([]byte, ipLen+tcpLen+payLen)
  692. pkt[0] = 0x60 // version 6
  693. binary.BigEndian.PutUint16(pkt[4:6], uint16(tcpLen+payLen))
  694. pkt[6] = unix.IPPROTO_TCP
  695. pkt[7] = 64
  696. pkt[8] = 0xfe
  697. pkt[9] = 0x80
  698. pkt[23] = 1
  699. pkt[24] = 0xfe
  700. pkt[25] = 0x80
  701. pkt[39] = 2
  702. binary.BigEndian.PutUint16(pkt[40:42], 12345)
  703. binary.BigEndian.PutUint16(pkt[42:44], 80)
  704. binary.BigEndian.PutUint32(pkt[44:48], 7)
  705. binary.BigEndian.PutUint32(pkt[48:52], 99)
  706. pkt[52] = 0x50
  707. pkt[53] = 0x10 // ACK only
  708. binary.BigEndian.PutUint16(pkt[54:56], 65535)
  709. for i := 0; i < payLen; i++ {
  710. pkt[ipLen+tcpLen+i] = byte(i)
  711. }
  712. return pkt
  713. }
  714. // TestDecodeReadFitsMaxTSOAtDrainThreshold proves the rxBuf sizing is
  715. // correct: when rxOff is at the maximum value the drain headroom check
  716. // allows, decodeRead must still be able to absorb a worst-case 64KiB
  717. // TSO superpacket without dropping the burst. With segmentation deferred
  718. // to encrypt time, decodeRead writes only the kernel-supplied bytes into
  719. // rxBuf, so the size requirement is just "fit one worst-case input."
  720. //
  721. // Regression history: in a prior layout the rx buffer doubled as the
  722. // segmentation output, a near-threshold drain read returned "scratch too
  723. // small", the whole 45-segment TSO burst was dropped, and the remote's TCP
  724. // fast-retransmit collapsed cwnd. Keeping this test in the new layout
  725. // guards against re-introducing a drain headroom shortfall.
  726. func TestDecodeReadFitsMaxTSOAtDrainThreshold(t *testing.T) {
  727. const ipv6HdrLen = 40
  728. const tcpHdrLen = 20
  729. const headerLen = ipv6HdrLen + tcpHdrLen
  730. // Maximum TUN read body at the drain threshold. readv bounds the body
  731. // iovec by the space actually left in rxBuf, and the drain gate keeps that
  732. // at >= tunRxBufSize, so that is the largest superpacket the kernel can
  733. // hand back on the last permitted drain read.
  734. pktLen := tunRxBufSize
  735. payLen := pktLen - headerLen
  736. const targetSegs = 64
  737. gsoSize := (payLen + targetSegs - 1) / targetSegs
  738. pkt := buildTSOv6(payLen, gsoSize)
  739. if len(pkt) != pktLen {
  740. t.Fatalf("buildTSOv6 produced %d bytes, want %d", len(pkt), pktLen)
  741. }
  742. o := &Offload{
  743. rxBuf: make([]byte, tunRxBufCap),
  744. }
  745. // rxOff at the maximum value the drain headroom check permits before
  746. // it would refuse another read. Any drain-time read up to this
  747. // threshold MUST still process correctly.
  748. o.rxOff = tunRxBufCap - tunRxBufSize
  749. // Stage the body in rxBuf as if readv(2) just placed it there.
  750. copy(o.rxBuf[o.rxOff:], pkt)
  751. // Encode the matching virtio_net_hdr.
  752. hdr := virtio.NewHeader(
  753. unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
  754. unix.VIRTIO_NET_HDR_GSO_TCPV6, /*gsoType*/
  755. uint16(headerLen), /*hdrLen*/
  756. uint16(gsoSize), /*gsoSize*/
  757. uint16(ipv6HdrLen), /*csumStart*/
  758. 16, /*csumOffset*/
  759. )
  760. hdr.Encode(o.readVnetScratch[:])
  761. startRxOff := o.rxOff
  762. if err := o.decodeRead(pktLen); err != nil {
  763. t.Fatalf("decodeRead at drain threshold returned %v — rxBuf sizing regression: "+
  764. "tunRxBufSize=%d must hold one worst-case input (%d)",
  765. err, tunRxBufSize, pktLen)
  766. }
  767. if len(o.pending) != 1 {
  768. t.Fatalf("got %d packets, want 1 superpacket entry", len(o.pending))
  769. }
  770. got := o.pending[0]
  771. if !got.GSO.IsSuperpacket() {
  772. t.Fatalf("expected superpacket GSO metadata, got %+v", got.GSO)
  773. }
  774. if got.GSO.Proto != GSOProtoTCP {
  775. t.Errorf("GSO.Proto=%d want TCP", got.GSO.Proto)
  776. }
  777. if got.GSO.Size != uint16(gsoSize) {
  778. t.Errorf("GSO.Size=%d want %d", got.GSO.Size, gsoSize)
  779. }
  780. if got.GSO.HdrLen != uint16(headerLen) {
  781. t.Errorf("GSO.HdrLen=%d want %d", got.GSO.HdrLen, headerLen)
  782. }
  783. if got.GSO.CsumStart != uint16(ipv6HdrLen) {
  784. t.Errorf("GSO.CsumStart=%d want %d", got.GSO.CsumStart, ipv6HdrLen)
  785. }
  786. if len(got.Bytes) != pktLen {
  787. t.Errorf("len(Bytes)=%d want %d", len(got.Bytes), pktLen)
  788. }
  789. // rxOff advances exactly by the kernel-supplied body length — no
  790. // segmentation output to account for any more.
  791. if o.rxOff != startRxOff+pktLen {
  792. t.Errorf("rxOff=%d want %d", o.rxOff, startRxOff+pktLen)
  793. }
  794. if o.rxOff > tunRxBufCap {
  795. t.Fatalf("rxOff=%d overran rxBuf (cap=%d)", o.rxOff, tunRxBufCap)
  796. }
  797. // Validate that segmenting the returned superpacket reproduces the
  798. // expected per-segment IPv6 payload length and TCP checksum.
  799. wantSegs := (payLen + gsoSize - 1) / gsoSize
  800. gotSegs := 0
  801. if err := SegmentSuperpacket(got, func(seg []byte) error {
  802. defer func() { gotSegs++ }()
  803. if len(seg) < headerLen+1 {
  804. t.Errorf("seg %d too short: %d", gotSegs, len(seg))
  805. return nil
  806. }
  807. if seg[0]>>4 != 6 {
  808. t.Errorf("seg %d: bad IP version %#x", gotSegs, seg[0])
  809. }
  810. segPay := len(seg) - headerLen
  811. gotPL := binary.BigEndian.Uint16(seg[4:6])
  812. if gotPL != uint16(tcpHdrLen+segPay) {
  813. t.Errorf("seg %d: payload_len=%d want %d", gotSegs, gotPL, tcpHdrLen+segPay)
  814. }
  815. psum := pseudoHeaderIPv6(seg[8:24], seg[24:40], unix.IPPROTO_TCP, tcpHdrLen+segPay)
  816. if !verifyChecksum(seg[ipv6HdrLen:], psum) {
  817. t.Errorf("seg %d: bad TCP checksum", gotSegs)
  818. }
  819. return nil
  820. }); err != nil {
  821. t.Fatalf("SegmentSuperpacket: %v", err)
  822. }
  823. if gotSegs != wantSegs {
  824. t.Fatalf("got %d segments, want %d", gotSegs, wantSegs)
  825. }
  826. }
  827. // TestOffloadWriteZeroLength: a zero-length Write must be a no-op, not a
  828. // panic. The guard used to live below the &buf[0] that tripped on it.
  829. func TestOffloadWriteZeroLength(t *testing.T) {
  830. tf := &Offload{fd: -1} // any write reaching the fd would fail loudly
  831. for _, buf := range [][]byte{nil, {}} {
  832. n, err := tf.Write(buf)
  833. if n != 0 || err != nil {
  834. t.Errorf("Write(len=0) = (%d, %v), want (0, nil)", n, err)
  835. }
  836. }
  837. }
  838. // TestWriteGSOSuperpacketGeometry decodes the vnet header the kernel would see for a multi-segment write:
  839. // the GSO type must match the proto and IP version
  840. // gso_size must be the per-segment size (the kernel rejects a superpacket with gso_size == 0),
  841. // and the csum fields must point at the transport header's checksum slot.
  842. // Write through a pipe so the bytes can be read back and decoded.
  843. func TestWriteGSOSuperpacketGeometry(t *testing.T) {
  844. var pfds [2]int
  845. if err := unix.Pipe(pfds[:]); err != nil {
  846. t.Fatalf("pipe: %v", err)
  847. }
  848. t.Cleanup(func() { unix.Close(pfds[0]); unix.Close(pfds[1]) })
  849. o := &Offload{fd: pfds[1], gsoIovs: make([]unix.Iovec, 2, gsoMaxIovs)}
  850. o.gsoIovs[0].Base = &o.gsoHdrBuf[0]
  851. o.gsoIovs[0].SetLen(virtio.Size)
  852. ipHdr := make([]byte, 20)
  853. ipHdr[0] = 0x45
  854. udpHdr := make([]byte, 8)
  855. seg := make([]byte, 1200)
  856. if err := o.WriteGSO(ipHdr, udpHdr, [][]byte{seg, seg}, GSOProtoUDP); err != nil {
  857. t.Fatalf("WriteGSO: %v", err)
  858. }
  859. buf := make([]byte, virtio.Size+len(ipHdr)+len(udpHdr)+2*len(seg)+64)
  860. n, err := unix.Read(pfds[0], buf)
  861. if err != nil {
  862. t.Fatalf("read pipe: %v", err)
  863. }
  864. var vhdr virtio.Hdr
  865. vhdr.Decode(buf[:virtio.Size])
  866. if vhdr.GSOType() != unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
  867. t.Errorf("gsoType=%d want UDP_L4", vhdr.GSOType())
  868. }
  869. if vhdr.GSOSize != 1200 {
  870. t.Errorf("GSOSize=%d want 1200 (per-segment size from pays[0])", vhdr.GSOSize)
  871. }
  872. if vhdr.HdrLen != uint16(len(ipHdr)+len(udpHdr)) {
  873. t.Errorf("HdrLen=%d want %d", vhdr.HdrLen, len(ipHdr)+len(udpHdr))
  874. }
  875. if vhdr.CsumStart != uint16(len(ipHdr)) || vhdr.CsumOffset != 6 {
  876. t.Errorf("csum start/offset = %d/%d want %d/6", vhdr.CsumStart, vhdr.CsumOffset, len(ipHdr))
  877. }
  878. if want := virtio.Size + len(ipHdr) + len(udpHdr) + 2*len(seg); n != want {
  879. t.Errorf("wrote %d bytes want %d", n, want)
  880. }
  881. }
  882. // TestWriteGSORejectsBadGeometry pins the length-check contracts
  883. func TestWriteGSORejectsBadGeometry(t *testing.T) {
  884. fd, err := unix.Open("/dev/null", os.O_WRONLY, 0)
  885. if err != nil {
  886. t.Fatalf("open /dev/null: %v", err)
  887. }
  888. t.Cleanup(func() { _ = unix.Close(fd) })
  889. o := &Offload{fd: fd, gsoIovs: make([]unix.Iovec, 2, gsoMaxIovs)}
  890. o.gsoIovs[0].Base = &o.gsoHdrBuf[0]
  891. o.gsoIovs[0].SetLen(virtio.Size)
  892. ipHdr := make([]byte, 20)
  893. ipHdr[0] = 0x45
  894. udpHdr := make([]byte, 8)
  895. tcpHdr := make([]byte, 20)
  896. seg := make([]byte, 1200)
  897. cases := []struct {
  898. name string
  899. hdr, thdr []byte
  900. pays [][]byte
  901. proto GSOProto
  902. wantErr bool
  903. }{
  904. {"empty-ip-hdr-with-payload", nil, udpHdr, [][]byte{seg}, GSOProtoUDP, true},
  905. {"udp-transport-too-short-for-csum", ipHdr, udpHdr[:6], [][]byte{seg}, GSOProtoUDP, true},
  906. {"tcp-transport-too-short-for-csum", ipHdr, tcpHdr[:16], [][]byte{seg}, GSOProtoTCP, true},
  907. {"superpacket-over-65535", ipHdr, tcpHdr, [][]byte{make([]byte, 40000), make([]byte, 40000)}, GSOProtoTCP, true},
  908. {"sole-payload-empty", ipHdr, udpHdr, [][]byte{{}}, GSOProtoUDP, true},
  909. {"leading-empty-fragment", ipHdr, udpHdr, [][]byte{{}, seg, seg}, GSOProtoUDP, true},
  910. {"trailing-empty-fragment", ipHdr, tcpHdr, [][]byte{seg, {}}, GSOProtoTCP, true},
  911. {"oversize-middle-fragment", ipHdr, udpHdr, [][]byte{seg, make([]byte, 1201), seg}, GSOProtoUDP, true},
  912. {"undersize-middle-fragment", ipHdr, udpHdr, [][]byte{seg, make([]byte, 100), seg}, GSOProtoUDP, true},
  913. {"oversize-last-fragment", ipHdr, tcpHdr, [][]byte{seg, make([]byte, 1201)}, GSOProtoTCP, true},
  914. {"short-last-fragment-ok", ipHdr, udpHdr, [][]byte{seg, seg, make([]byte, 100)}, GSOProtoUDP, false},
  915. {"multi-segment-bad-ip-version", []byte{0x05}, udpHdr, [][]byte{seg, seg}, GSOProtoUDP, true},
  916. {"single-segment-bad-ip-version-ok", []byte{0x05}, udpHdr, [][]byte{seg}, GSOProtoUDP, false},
  917. {"no-pays-noop", ipHdr, udpHdr, nil, GSOProtoUDP, false},
  918. {"valid-udp", ipHdr, udpHdr, [][]byte{seg, seg}, GSOProtoUDP, false},
  919. {"valid-tcp", ipHdr, tcpHdr, [][]byte{seg, seg}, GSOProtoTCP, false},
  920. }
  921. for _, tc := range cases {
  922. t.Run(tc.name, func(t *testing.T) {
  923. err := o.WriteGSO(tc.hdr, tc.thdr, tc.pays, tc.proto)
  924. if tc.wantErr && err == nil {
  925. t.Errorf("WriteGSO = nil, want error")
  926. }
  927. if !tc.wantErr && err != nil {
  928. t.Errorf("WriteGSO = %v, want nil", err)
  929. }
  930. })
  931. }
  932. }
  933. // BenchmarkSegmentUDPv4 is the USO counterpart to BenchmarkSegmentTCPv4. The
  934. // yield is a no-op so the measurement is segmentation plus checksum work only.
  935. func BenchmarkSegmentUDPv4(b *testing.B) {
  936. sizes := []struct {
  937. name string
  938. payLen int
  939. gsoSize int
  940. }{
  941. {"64KiB_GSO1400", 64000, 1400},
  942. {"16KiB_GSO1400", 16384, 1400},
  943. {"4KiB_GSO1400", 4096, 1400},
  944. }
  945. for _, sz := range sizes {
  946. b.Run(sz.name, func(b *testing.B) {
  947. const ipLen = 20
  948. const udpLen = 8
  949. pkt := make([]byte, ipLen+udpLen+sz.payLen)
  950. pkt[0] = 0x45
  951. binary.BigEndian.PutUint16(pkt[2:4], uint16(ipLen+udpLen+sz.payLen))
  952. binary.BigEndian.PutUint16(pkt[4:6], 0x4242)
  953. pkt[8] = 64
  954. pkt[9] = unix.IPPROTO_UDP
  955. copy(pkt[12:16], []byte{10, 0, 0, 1})
  956. copy(pkt[16:20], []byte{10, 0, 0, 2})
  957. binary.BigEndian.PutUint16(pkt[20:22], 12345)
  958. binary.BigEndian.PutUint16(pkt[22:24], 53)
  959. binary.BigEndian.PutUint16(pkt[24:26], uint16(udpLen+sz.payLen))
  960. for i := 0; i < sz.payLen; i++ {
  961. pkt[ipLen+udpLen+i] = byte(i)
  962. }
  963. master := append([]byte(nil), pkt...)
  964. work := make([]byte, len(pkt))
  965. p := Packet{Bytes: work, GSO: GSOInfo{
  966. Size: uint16(sz.gsoSize),
  967. HdrLen: ipLen + udpLen,
  968. CsumStart: ipLen,
  969. Proto: GSOProtoUDP,
  970. }}
  971. b.SetBytes(int64(len(pkt)))
  972. b.ReportAllocs()
  973. b.ResetTimer()
  974. for i := 0; i < b.N; i++ {
  975. copy(work, master)
  976. if err := SegmentSuperpacket(p, func(seg []byte) error { return nil }); err != nil {
  977. b.Fatal(err)
  978. }
  979. }
  980. })
  981. }
  982. }
  983. // BenchmarkSegmentUDPv6 mirrors BenchmarkSegmentUDPv4 for IPv6, where the
  984. // pseudo-header address sum is 32 bytes rather than 8.
  985. func BenchmarkSegmentUDPv6(b *testing.B) {
  986. sizes := []struct {
  987. name string
  988. payLen int
  989. gsoSize int
  990. }{
  991. {"64KiB_GSO1400", 64000, 1400},
  992. {"16KiB_GSO1400", 16384, 1400},
  993. {"4KiB_GSO1400", 4096, 1400},
  994. }
  995. for _, sz := range sizes {
  996. b.Run(sz.name, func(b *testing.B) {
  997. const ipLen = 40
  998. const udpLen = 8
  999. pkt := make([]byte, ipLen+udpLen+sz.payLen)
  1000. pkt[0] = 0x60
  1001. binary.BigEndian.PutUint16(pkt[4:6], uint16(udpLen+sz.payLen))
  1002. pkt[6] = unix.IPPROTO_UDP
  1003. pkt[7] = 64
  1004. pkt[8], pkt[9], pkt[23] = 0xfe, 0x80, 1
  1005. pkt[24], pkt[25], pkt[39] = 0xfe, 0x80, 2
  1006. binary.BigEndian.PutUint16(pkt[40:42], 12345)
  1007. binary.BigEndian.PutUint16(pkt[42:44], 53)
  1008. binary.BigEndian.PutUint16(pkt[44:46], uint16(udpLen+sz.payLen))
  1009. for i := 0; i < sz.payLen; i++ {
  1010. pkt[ipLen+udpLen+i] = byte(i)
  1011. }
  1012. master := append([]byte(nil), pkt...)
  1013. work := make([]byte, len(pkt))
  1014. p := Packet{Bytes: work, GSO: GSOInfo{
  1015. Size: uint16(sz.gsoSize),
  1016. HdrLen: ipLen + udpLen,
  1017. CsumStart: ipLen,
  1018. Proto: GSOProtoUDP,
  1019. }}
  1020. b.SetBytes(int64(len(pkt)))
  1021. b.ReportAllocs()
  1022. b.ResetTimer()
  1023. for i := 0; i < b.N; i++ {
  1024. copy(work, master)
  1025. if err := SegmentSuperpacket(p, func(seg []byte) error { return nil }); err != nil {
  1026. b.Fatal(err)
  1027. }
  1028. }
  1029. })
  1030. }
  1031. }