udp_linux_writebatch.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. //go:build linux && !android && !e2e_testing
  2. package udp
  3. import (
  4. "context"
  5. "encoding/binary"
  6. "errors"
  7. "fmt"
  8. "log/slog"
  9. "net"
  10. "net/netip"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "unsafe"
  15. "golang.org/x/sys/unix"
  16. )
  17. // batchWriter owns the sendmmsg(2)/UDP-GSO transmit path for a StdConn: the
  18. // scratch WriteBatch packs mmsghdr entries into, plus the GSO capability
  19. // state probed at socket creation. Each queue has its own StdConn and
  20. // batchWriter; mu serializes batches from the TUN reader and relay forwarding.
  21. //
  22. // Terminology, smallest to largest:
  23. //
  24. // packet one element of bufs: a single UDP datagram. The unit of the
  25. // returned written count.
  26. // run consecutive packets planRun groups into one entry: same
  27. // destination, equal sizes (a shorter packet only last), within
  28. // maxGSOBytes and maxGSOSegments. Without GSO a run is always one
  29. // packet. Runs are atomic: packed whole into one entry, or
  30. // skipped whole if the socket cannot address their destination,
  31. // leaving a hole (bufs indices covered by no entry).
  32. // entry one mmsghdr slot of the sendmmsg array; the kernel's unit of
  33. // success and failure. A multi-packet entry carries a UDP_SEGMENT
  34. // cmsg and is sent as one superpacket the kernel segments into
  35. // gso_size-byte datagrams. Entries never split.
  36. // chunk the entries packed for one sendmmsg call, at most MaxWriteBatch.
  37. // batch the caller's whole bufs/addrs pair, processed as one or more chunks.
  38. type batchWriter struct {
  39. mu sync.Mutex
  40. fd int
  41. isV4 bool
  42. // UDP GSO (sendmsg with UDP_SEGMENT cmsg) support, probed once at
  43. // socket creation and cleared by WriteBatch if the kernel later rejects
  44. // a GSO send (the setsockopt probe cannot see per-route limitations).
  45. // When true, WriteBatch coalesces runs into UDP_SEGMENT entries;
  46. // otherwise each packet is its own entry.
  47. gsoSupported bool
  48. maxGSOSegments int
  49. // sendmmsg scratch, sized to MaxWriteBatch at construction; WriteBatch
  50. // chunks larger inputs.
  51. msgs []rawMessage
  52. iovs []iovec
  53. names [][]byte
  54. l *slog.Logger
  55. // sendFn sends n prepared entries beginning at w.msgs[start]
  56. // This is a function pointer to facilitate testing.
  57. sendFn func(start, n int) (int, error)
  58. // Per-entry cmsg scratch: one contiguous slab of
  59. // MaxWriteBatch * cmsgSpace bytes holding one UDP_SEGMENT cmsg per entry.
  60. cmsg []byte
  61. cmsgSpace int
  62. // entryEnd[e] is the bufs index after the last packet packed into entry e.
  63. // entryEnd[e]-entryPkts[e] recovers the bufs index the entry's run started at,
  64. // used to rewind i for the GSO-disable replay.
  65. entryEnd []int
  66. // entryPkts[e] is the number of packets packed into entry e.
  67. entryPkts []int
  68. }
  69. func newBatchWriter(fd int, isV4 bool, l *slog.Logger, offloadsEnabled bool) *batchWriter {
  70. w := &batchWriter{fd: fd, isV4: isV4, l: l}
  71. w.sendFn = w.sendmmsg
  72. if offloadsEnabled {
  73. w.prepareGSO()
  74. }
  75. w.prepareWriteMessages(MaxWriteBatch, offloadsEnabled)
  76. return w
  77. }
  78. // prepareWriteMessages allocates the per-entry mmsghdr/iovec/sockaddr/cmsg
  79. // scratch. Hdr.Iov/Iovlen/Control/Controllen are wired per call, since an
  80. // entry spans a variable number of iovecs and may or may not carry a cmsg.
  81. //
  82. // Each entry's cmsg slot holds one UDP_SEGMENT (gso_size, uint16) header,
  83. // pre-filled here; only its payload is rewritten per call.
  84. // Hdr.Control/Controllen select whether it applies (none / segment).
  85. func (w *batchWriter) prepareWriteMessages(n int, offloadsEnabled bool) {
  86. w.msgs = make([]rawMessage, n)
  87. w.iovs = make([]iovec, n)
  88. w.names = make([][]byte, n)
  89. w.entryEnd = make([]int, n)
  90. w.entryPkts = make([]int, n)
  91. w.cmsgSpace = unix.CmsgSpace(2)
  92. for i := range w.msgs {
  93. w.names[i] = make([]byte, unix.SizeofSockaddrInet6)
  94. w.msgs[i].Hdr.Name = &w.names[i][0]
  95. }
  96. if !offloadsEnabled || !w.gsoSupported {
  97. return //avoid allocating cmsg space if we will never use it
  98. }
  99. w.cmsg = make([]byte, n*w.cmsgSpace)
  100. for k := range n {
  101. base := k * w.cmsgSpace
  102. seg := (*unix.Cmsghdr)(unsafe.Pointer(&w.cmsg[base]))
  103. seg.Level = unix.SOL_UDP
  104. seg.Type = unix.UDP_SEGMENT
  105. setCmsgLen(seg, unix.CmsgLen(2))
  106. }
  107. }
  108. // maxGSOBytes bounds the total payload of one UDP_SEGMENT send. The kernel
  109. // builds a single skb, which must fit the 16-bit UDP length field and
  110. // sk_gso_max_size (65536 on most devices); 65000 leaves headroom for headers.
  111. const maxGSOBytes = 65000
  112. // prepareGSO probes UDP_SEGMENT support and sets w.gsoSupported on success.
  113. // Best-effort; failure leaves it false.
  114. func (w *batchWriter) prepareGSO() {
  115. w.maxGSOSegments = 63 // pre-6.9 cap; see gsoMaxSegments
  116. if err := unix.SetsockoptInt(w.fd, unix.IPPROTO_UDP, unix.UDP_SEGMENT, 0); err != nil {
  117. w.l.Info("udp: GSO disabled", "reason", "rawconn control failed", "error", err)
  118. recordCapability("udp.gso.enabled", false)
  119. return
  120. }
  121. var un unix.Utsname
  122. if err := unix.Uname(&un); err != nil {
  123. w.l.Warn("udp: kernel version probe failed, capping GSO at 63 segments", "error", err)
  124. } else {
  125. w.maxGSOSegments = gsoMaxSegments(string(un.Release[:]))
  126. }
  127. w.gsoSupported = true
  128. w.l.Info("udp: GSO enabled", "maxGSOSegments", w.maxGSOSegments)
  129. recordCapability("udp.gso.enabled", true)
  130. }
  131. // gsoMaxSegments returns the most segments one UDP_SEGMENT send may carry:
  132. // the kernel cap (UDP_MAX_SEGMENTS: 64 before 6.9, 128 after) minus one,
  133. // because the kernel counts the 8-byte UDP header against the gso_size * UDP_MAX_SEGMENTS budget.
  134. func gsoMaxSegments(release string) int {
  135. major, minor := parseRelease(release)
  136. if major > 6 || (major == 6 && minor >= 9) {
  137. return 127
  138. }
  139. return 63
  140. }
  141. func parseRelease(r string) (major, minor int) {
  142. // strip anything after the second dot or any non-digit
  143. parts := strings.SplitN(r, ".", 3)
  144. if len(parts) < 2 {
  145. return 0, 0
  146. }
  147. major, _ = strconv.Atoi(parts[0])
  148. // minor may have trailing junk like "15-generic"
  149. mp := parts[1]
  150. for i, c := range mp {
  151. if c < '0' || c > '9' {
  152. mp = mp[:i]
  153. break
  154. }
  155. }
  156. minor, _ = strconv.Atoi(mp)
  157. return
  158. }
  159. // WriteBatch sends bufs via sendmmsg(2), coalescing runs into UDP_SEGMENT
  160. // entries, so one syscall can mix GSO superpackets and plain datagrams.
  161. // Without GSO support every packet is its own entry.
  162. // Callers shall deliver same-destination packets contiguously and in counter order
  163. //
  164. // Batches larger than the scratch take one sendmmsg per chunk.
  165. // A partial success resumes the same prepared entries at the first unsent entry.
  166. // A zero-sent error means the kernel rejected the first remaining entry:
  167. // its packets are dropped and the rest of the chunk resumes in place.
  168. //
  169. // Returns the number of packets sent. An error means the call itself failed.
  170. // A short count means some destinations were undeliverable.
  171. func (w *batchWriter) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) (int, error) {
  172. w.mu.Lock()
  173. defer w.mu.Unlock()
  174. if len(bufs) != len(addrs) {
  175. return 0, fmt.Errorf("WriteBatch: len(bufs)=%d != len(addrs)=%d", len(bufs), len(addrs))
  176. }
  177. // A destination the kernel rejects results in us dropping that entry (one packet, or one same-destination GSO run).
  178. // We count what actually made it out rather than returning an error.
  179. written := 0
  180. i := 0
  181. for i < len(bufs) {
  182. entry := 0
  183. iovIdx := 0
  184. for entry < len(w.msgs) && i < len(bufs) {
  185. iovBudget := len(w.iovs) - iovIdx
  186. if iovBudget < 1 {
  187. break
  188. }
  189. runLen, segSize := w.planRun(bufs, addrs, i, iovBudget)
  190. if runLen == 0 {
  191. break
  192. }
  193. for k := range runLen {
  194. b := bufs[i+k]
  195. if len(b) == 0 {
  196. w.iovs[iovIdx+k].Base = nil
  197. setIovLen(&w.iovs[iovIdx+k], 0)
  198. } else {
  199. w.iovs[iovIdx+k].Base = &b[0]
  200. setIovLen(&w.iovs[iovIdx+k], len(b))
  201. }
  202. }
  203. nlen, err := writeSockaddr(w.names[entry], addrs[i], w.isV4)
  204. if err != nil {
  205. // The destination's address family does not match the socket
  206. // (e.g. an IPv6 remote on a v4-bound socket). The packets are
  207. // undeliverable and no entry is committed yet: skip the run.
  208. if w.l.Enabled(context.Background(), slog.LevelDebug) {
  209. w.l.Debug("skipping unroutable batch destination", "udpAddr", addrs[i], "packets", runLen, "error", err)
  210. }
  211. i += runLen
  212. continue
  213. }
  214. hdr := &w.msgs[entry].Hdr
  215. hdr.Iov = &w.iovs[iovIdx]
  216. setMsgIovlen(hdr, runLen)
  217. hdr.Namelen = uint32(nlen)
  218. w.writeEntryCmsg(entry, runLen, segSize)
  219. i += runLen
  220. iovIdx += runLen
  221. w.entryEnd[entry] = i
  222. w.entryPkts[entry] = runLen
  223. entry++
  224. }
  225. if entry == 0 {
  226. // Every remaining packet was skipped; i reached len(bufs).
  227. break
  228. }
  229. // Drain the packed entries without repacking: everything the packing
  230. // loop wired (iovecs, names, cmsgs) stays intact until the next chunk
  231. // overwrites it, so a partial success resumes the same sendmmsg array
  232. // at the first unsent entry, and a rejected entry is skipped in place.
  233. // Only the GSO-disable path replans, since its entries change shape.
  234. done := 0
  235. for done < entry {
  236. sent, serr := w.sendFn(done, entry-done)
  237. if sent > 0 {
  238. // Count packets per entry; the bufs index span would
  239. // overcount across holes left by skipped runs.
  240. for e := done; e < done+sent; e++ {
  241. written += w.entryPkts[e]
  242. }
  243. done += sent
  244. continue
  245. }
  246. if serr == nil {
  247. return written, fmt.Errorf("sendmmsg made no progress")
  248. }
  249. // sent<=0 means the first remaining entry itself failed.
  250. // EIO on a superpacket means the route cannot carry a GSO send even though the setsockopt probe passed:
  251. // udp_send_skb() returns EIO when:
  252. // * the egress device lacks TX checksum offload (kernels through 6.10)
  253. // * or when an xfrm policy covers the route.
  254. // Persistent, so disable GSO and replay from the failed run as one-packet entries.
  255. if w.gsoSupported && w.entryPkts[done] >= 2 && errors.Is(serr, unix.EIO) {
  256. w.gsoSupported = false
  257. w.l.Warn("udp: kernel rejected GSO send, disabling GSO", "error", serr)
  258. recordCapability("udp.gso.enabled", false)
  259. i = w.entryEnd[done] - w.entryPkts[done]
  260. break
  261. }
  262. // Any other zero-sent error is a per-entry failure.
  263. // Transient errnos (EINTR, ENOBUFS) were already retried inside sendFn.
  264. // These packets are doomed. Log them and move on.
  265. if w.l.Enabled(context.Background(), slog.LevelDebug) {
  266. w.l.Debug("sendmmsg rejected entry",
  267. "error", serr,
  268. "udpAddr", addrs[w.entryEnd[done]-w.entryPkts[done]],
  269. "packets", w.entryPkts[done],
  270. "gso", w.gsoSupported,
  271. )
  272. }
  273. done++
  274. }
  275. }
  276. return written, nil
  277. }
  278. // planRun returns the length of the run starting at start and its segment
  279. // size (len(bufs[start])). A run of length 1 carries no UDP_SEGMENT cmsg
  280. // and is sent as a plain datagram; without GSO support planRun always returns 1.
  281. func (w *batchWriter) planRun(bufs [][]byte, addrs []netip.AddrPort, start, iovBudget int) (int, int) {
  282. if start >= len(bufs) || iovBudget < 1 {
  283. return 0, 0
  284. }
  285. segSize := len(bufs[start])
  286. if !w.gsoSupported || segSize == 0 || segSize > maxGSOBytes {
  287. return 1, segSize
  288. }
  289. dst := addrs[start]
  290. maxLen := min(iovBudget, w.maxGSOSegments)
  291. runLen := 1
  292. total := segSize
  293. for runLen < maxLen && start+runLen < len(bufs) {
  294. nextLen := len(bufs[start+runLen])
  295. if nextLen == 0 || nextLen > segSize {
  296. break
  297. }
  298. if addrs[start+runLen] != dst {
  299. break
  300. }
  301. if total+nextLen > maxGSOBytes {
  302. break
  303. }
  304. total += nextLen
  305. runLen++
  306. if nextLen < segSize {
  307. // A short packet must be the last in the run.
  308. break
  309. }
  310. }
  311. return runLen, segSize
  312. }
  313. // writeEntryCmsg writes one entry's UDP_SEGMENT payload when runLen >= 2 and
  314. // points Hdr.Control at it; a single-packet entry carries no cmsg.
  315. func (w *batchWriter) writeEntryCmsg(entry, runLen, segSize int) {
  316. hdr := &w.msgs[entry].Hdr
  317. base := entry * w.cmsgSpace
  318. if runLen >= 2 {
  319. dataOff := base + unix.CmsgLen(0)
  320. binary.NativeEndian.PutUint16(w.cmsg[dataOff:dataOff+2], uint16(segSize))
  321. hdr.Control = &w.cmsg[base]
  322. setMsgControllen(hdr, w.cmsgSpace)
  323. } else {
  324. hdr.Control = nil
  325. setMsgControllen(hdr, 0)
  326. }
  327. }
  328. // sendmmsg issues sendmmsg(2) against n entries of w.msgs starting at start.
  329. //
  330. // EINTR is automatically retried and will never be returned.
  331. // ENOBUFS is retried enobufsRetries times, and should be treated like any other error
  332. func (w *batchWriter) sendmmsg(start, n int) (int, error) {
  333. const enobufsRetries = 3
  334. for enobufs := 0; ; {
  335. r1, _, errno := unix.Syscall6(unix.SYS_SENDMMSG, uintptr(w.fd),
  336. uintptr(unsafe.Pointer(&w.msgs[start])), uintptr(n),
  337. 0, 0, 0,
  338. )
  339. switch {
  340. case errno == unix.EINTR: //similar to stdlib's ignoringEINTRIO
  341. continue
  342. case errno == unix.ENOBUFS && enobufs < enobufsRetries:
  343. enobufs++ //worth a retry or three
  344. continue
  345. case errno != 0:
  346. return int(r1), &net.OpError{Op: "sendmmsg", Err: errno}
  347. }
  348. return int(r1), nil
  349. }
  350. }