1
0

tun_linux_offload_test.go 36 KB

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