tio_gso_linux.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. //go:build linux && !android
  2. package tio
  3. import (
  4. "context"
  5. "fmt"
  6. "io"
  7. "log/slog"
  8. "os"
  9. "sync/atomic"
  10. "syscall"
  11. "unsafe"
  12. "golang.org/x/sys/unix"
  13. "github.com/slackhq/nebula/overlay/tio/virtio"
  14. )
  15. const maxSuperpacketLen = 65535
  16. // tunRxBufSize is the per-Read worst-case footprint inside rxBuf: one kernel-supplied packet body, which is at most ~64 KiB.
  17. // Segmentation happens at encrypt time on a per-routine MTU-sized scratch
  18. // (see SegmentSuperpacket), so rxBuf only holds raw kernel-supplied bytes.
  19. // We round up to give margin for the drain headroom check below.
  20. const tunRxBufSize = 64 * 1024
  21. // tunRxBufCap is the total size we allocate for the per-reader rx buffer.
  22. // Each drain iteration consumes up to tunRxBufSize of headroom for the kernel-supplied bytes.
  23. // Sized to eight such iterations so a single poll wake can drain several TSO/USO superpackets under bulk load,
  24. // amortizing the wake and giving the sendmmsg planner longer same-destination runs.
  25. // Hold latency stays bounded because listenIn flushes its send batch incrementally rather than only at end-of-drain.
  26. const tunRxBufCap = tunRxBufSize * 8
  27. // tunDrainCap caps how many packets a single Read will accumulate via the post-wake drain loop.
  28. // Sized to soak up a burst of small ACKs while bounding how much work a single caller holds before handing off.
  29. const tunDrainCap = 64
  30. // gsoMaxIovs caps the iovec budget WriteGSO assembles per call:
  31. // 3 fixed entries (virtio_net_hdr, IP hdr, transport hdr), plus up to gsoMaxIovs-3 payload fragments.
  32. // Sized comfortably above the typical kernel GSO segment cap (Linux UDP_GRO is 64)
  33. // so realistic coalesced bursts never touch the limit.
  34. // iovecs are tiny (16 bytes), so the entire scratch is 4 KiB.
  35. // WriteGSO returns an error rather than reallocating when a caller exceeds this budget.
  36. const gsoMaxIovs = 256
  37. // validVnetHdr is the 10-byte virtio_net_hdr we prepend to every non-GSO TUN write.
  38. // Only flag set is VIRTIO_NET_HDR_F_DATA_VALID. Note the tun write path
  39. // (__virtio_net_hdr_to_skb) ignores this bit — only the virtio-net driver's RX
  40. // helper honors it — so packets land CHECKSUM_NONE and the stack verifies the
  41. // L4 checksum anyway. What matters here is what the header does NOT say:
  42. // no NEEDS_CSUM, so the kernel is never asked to finish a checksum.
  43. // All packets that reach the plain Write paths already carry a valid L4 checksum.
  44. var validVnetHdr = [virtio.Size]byte{unix.VIRTIO_NET_HDR_F_DATA_VALID}
  45. // Offload wraps a TUN file descriptor with poll-based reads. The FD provided will be changed to non-blocking.
  46. // A shared eventfd allows Close to wake all readers blocked in poll.
  47. //
  48. // Field order is deliberate: the read-mostly fds and the writer-owned GSO scratch fill
  49. // the first cache line, and the state the reader mutates per packet (rxOff, pending,
  50. // readIovs) all sits after it, so per-packet reader stores never invalidate the line
  51. // concurrent Write callers load fd from.
  52. type Offload struct {
  53. fd int
  54. shutdownFd int
  55. // usoEnabled records whether the kernel agreed to TUN_F_USO* on this FD,
  56. // so writers can decide whether emitting GSO_UDP_L4 superpackets is safe.
  57. usoEnabled bool
  58. // shutdown is set before shutdownFd is signaled, a busy reader never polls to see that
  59. shutdown atomic.Bool
  60. closed atomic.Bool
  61. // gsoHdrBuf is a per-queue 10-byte scratch for the virtio_net_hdr emitted
  62. // by WriteGSO. Kept separate from the read-only package-level validVnetHdr
  63. // so non-GSO Writes can ship that constant directly while WriteGSO
  64. // rewrites this scratch on every call.
  65. gsoHdrBuf [virtio.Size]byte
  66. // gsoIovs is the writev iovec scratch for WriteGSO. Pre-sized to
  67. // gsoMaxIovs at construction; never grown. WriteGSO returns an error
  68. // (and drops the call) if a caller hands it more fragments than fit.
  69. gsoIovs []unix.Iovec
  70. rxBuf []byte // backing store for kernel-handed packets read this drain
  71. rxOff int // cursor into rxBuf for the current Read drain
  72. pending []Packet // packets returned from the most recent Read
  73. // readVnetScratch holds the 10-byte virtio_net_hdr split off the front of
  74. // every TUN read via readv(2). Decoupling the header from the packet body
  75. // lets us read the body directly into rxBuf at the current rxOff with
  76. // no userspace copy on the GSO_NONE fast path.
  77. readVnetScratch [virtio.Size]byte
  78. // readIovs is the readv(2) iovec scratch wired once at construction,
  79. // iovec[0] points at readVnetScratch
  80. // iovec[1].Base/Len is updated per read to address the current rxBuf slot.
  81. readIovs [2]unix.Iovec
  82. // l is only consulted on the rare bad-vnet-header drop path; it lives
  83. // after the hot state on purpose. May be nil (tests); drops go unlogged then.
  84. l *slog.Logger
  85. }
  86. func newOffload(fd int, shutdownFd int, usoEnabled bool, l *slog.Logger) (*Offload, error) {
  87. if err := unix.SetNonblock(fd, true); err != nil {
  88. return nil, fmt.Errorf("failed to set tun fd non-blocking: %w", err)
  89. }
  90. out := &Offload{
  91. fd: fd,
  92. shutdownFd: shutdownFd,
  93. usoEnabled: usoEnabled,
  94. closed: atomic.Bool{},
  95. l: l,
  96. rxBuf: make([]byte, tunRxBufCap),
  97. gsoIovs: make([]unix.Iovec, 2, gsoMaxIovs),
  98. }
  99. out.gsoIovs[0].Base = &out.gsoHdrBuf[0]
  100. out.gsoIovs[0].SetLen(virtio.Size)
  101. // readIovs[0] is wired once to the virtio_net_hdr scratch; per-read we
  102. // only repoint readIovs[1] at the next rxBuf slot (see readPacket).
  103. out.readIovs[0].Base = &out.readVnetScratch[0]
  104. out.readIovs[0].SetLen(virtio.Size)
  105. return out, nil
  106. }
  107. func (r *Offload) blockOnRead() error {
  108. return blockOn(int32(r.fd), int32(r.shutdownFd), unix.POLLIN)
  109. }
  110. func (r *Offload) blockOnWrite() error {
  111. return blockOn(int32(r.fd), int32(r.shutdownFd), unix.POLLOUT)
  112. }
  113. // readPacket issues a single readv(2), splitting the virtio_net_hdr off into readVnetScratch
  114. // and reading the packet body directly into rxBuf at the current rxOff.
  115. // Returns the body length (zero virtio header bytes, just the IP packet/superpacket).
  116. // block controls whether EAGAIN is retried via poll: the initial read of a drain blocks; subsequent drain reads do not.
  117. func (r *Offload) readPacket(block bool) (int, error) {
  118. for {
  119. r.readIovs[1].Base = &r.rxBuf[r.rxOff]
  120. r.readIovs[1].SetLen(len(r.rxBuf) - r.rxOff)
  121. n, _, errno := syscall.Syscall(unix.SYS_READV, uintptr(r.fd), uintptr(unsafe.Pointer(&r.readIovs[0])), uintptr(len(r.readIovs)))
  122. if errno == 0 {
  123. if int(n) < virtio.Size {
  124. return 0, fmt.Errorf("tun read shorter than virtio_net_hdr: %d bytes", n)
  125. }
  126. return int(n) - virtio.Size, nil
  127. }
  128. if errno == unix.EAGAIN {
  129. if !block {
  130. return 0, errno
  131. }
  132. if err := r.blockOnRead(); err != nil {
  133. return 0, err
  134. }
  135. continue
  136. }
  137. if errno == unix.EINTR {
  138. continue
  139. }
  140. if errno == unix.EBADF {
  141. return 0, os.ErrClosed
  142. }
  143. return 0, errno
  144. }
  145. }
  146. // Read returns one or more packets from the tun.
  147. // Each Packet either carries a single ready-to-use IP datagram (GSO zero) or a TSO/USO superpacket plus the GSOInfo a caller needs to segment it (see SegmentSuperpacket).
  148. // The first read blocks via poll; once the fd is known readable we drain additional packets non-blocking until:
  149. // - the kernel queue is empty (EAGAIN)
  150. // - we've collected tunDrainCap packets,
  151. // - or we're out of rxBuf headroom.
  152. //
  153. // This amortizes the poll wake over bursts of small packets (e.g. TCP ACKs).
  154. // Packet.Bytes slices point into the Offload's internal buffer and are only valid until the next Read or Close on this Queue.
  155. func (r *Offload) Read() ([]Packet, error) {
  156. if r.shutdown.Load() {
  157. return nil, os.ErrClosed
  158. }
  159. r.pending = r.pending[:0]
  160. r.rxOff = 0
  161. // Initial (blocking) read.
  162. // Retry on decode errors so a single bad packet does not stall the reader.
  163. for {
  164. n, err := r.readPacket(true)
  165. if err != nil {
  166. return nil, err
  167. }
  168. if err := r.decodeRead(n); err != nil {
  169. // Drop and read again. A bad packet should not kill the reader,
  170. // but a systematic decode failure must not be invisible either.
  171. r.logDroppedRead(err)
  172. continue
  173. }
  174. break
  175. }
  176. // Drain: non-blocking reads until the kernel queue is empty, the drain
  177. // cap is reached, or rxBuf no longer has room for another worst-case
  178. // kernel-supplied packet (tunRxBufSize).
  179. for len(r.pending) < tunDrainCap && tunRxBufCap-r.rxOff >= tunRxBufSize {
  180. n, err := r.readPacket(false)
  181. if err != nil {
  182. // EAGAIN / EINTR / anything else: stop draining. We already
  183. // have a valid batch from the first read.
  184. break
  185. }
  186. if n <= 0 {
  187. break
  188. }
  189. if err := r.decodeRead(n); err != nil {
  190. // Drop this packet and stop the drain; we'd rather hand off
  191. // what we have than keep spinning here.
  192. r.logDroppedRead(err)
  193. break
  194. }
  195. }
  196. return r.pending, nil
  197. }
  198. // logDroppedRead reports a tun packet dropped for a bad/unsupported virtio
  199. // header. Debug-gated so the happy path never pays for attribute assembly.
  200. func (r *Offload) logDroppedRead(err error) {
  201. if r.l != nil && r.l.Enabled(context.Background(), slog.LevelDebug) {
  202. r.l.Debug("dropping tun packet with bad virtio header", "error", err)
  203. }
  204. }
  205. // decodeRead processes the packet sitting in rxBuf at rxOff (length pktLen).
  206. // The bytes stay in rxBuf:
  207. // - for GSO_NONE we slice them as a regular IP datagram (running finishChecksum if NEEDS_CSUM is set);
  208. // - for TSO/USO superpackets we attach the corrected GSO metadata, so the caller can segment lazily at encrypt time.
  209. //
  210. // rxOff advances by pktLen on success
  211. func (r *Offload) decodeRead(pktLen int) error {
  212. if pktLen <= 0 {
  213. return fmt.Errorf("short tun read: %d", pktLen)
  214. }
  215. var hdr virtio.Hdr
  216. hdr.Decode(r.readVnetScratch[:])
  217. body := r.rxBuf[r.rxOff : r.rxOff+pktLen]
  218. if hdr.GSOType() == unix.VIRTIO_NET_HDR_GSO_NONE {
  219. if hdr.Flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 {
  220. if err := virtio.FinishChecksum(body, hdr); err != nil {
  221. return err
  222. }
  223. }
  224. r.pending = append(r.pending, Packet{Bytes: body})
  225. r.rxOff += pktLen
  226. return nil
  227. }
  228. if err := virtio.CheckValid(body, hdr); err != nil {
  229. return err
  230. }
  231. if err := virtio.CorrectHdrLen(body, &hdr); err != nil {
  232. return err
  233. }
  234. proto, err := protoFromGSOType(hdr.GSOType())
  235. if err != nil {
  236. return err
  237. }
  238. r.pending = append(r.pending, Packet{
  239. Bytes: body,
  240. GSO: GSOInfo{
  241. Size: hdr.GSOSize,
  242. HdrLen: hdr.HdrLen,
  243. CsumStart: hdr.CsumStart,
  244. Proto: proto,
  245. },
  246. })
  247. r.rxOff += pktLen
  248. return nil
  249. }
  250. func (r *Offload) Write(buf []byte) (int, error) {
  251. if len(buf) == 0 {
  252. return 0, nil
  253. }
  254. iovs := [2]unix.Iovec{
  255. {Base: &validVnetHdr[0]},
  256. {Base: &buf[0]},
  257. }
  258. iovs[0].SetLen(virtio.Size)
  259. iovs[1].SetLen(len(buf))
  260. return r.rawWrite(unsafe.Slice(&iovs[0], 2))
  261. }
  262. func (r *Offload) rawWrite(iovs []unix.Iovec) (int, error) {
  263. for {
  264. n, _, errno := syscall.Syscall(unix.SYS_WRITEV, uintptr(r.fd), uintptr(unsafe.Pointer(&iovs[0])), uintptr(len(iovs)))
  265. if errno == 0 {
  266. if int(n) < virtio.Size {
  267. return 0, io.ErrShortWrite
  268. }
  269. return int(n) - virtio.Size, nil
  270. }
  271. if errno == unix.EAGAIN {
  272. if err := r.blockOnWrite(); err != nil {
  273. return 0, err
  274. }
  275. continue
  276. }
  277. if errno == unix.EINTR {
  278. continue
  279. }
  280. if errno == unix.EBADF {
  281. return 0, os.ErrClosed
  282. }
  283. return 0, errno
  284. }
  285. }
  286. // Capabilities reports the offload features negotiated for this Queue. TSO
  287. // is always true for Offload (we only construct it on IFF_VNET_HDR FDs);
  288. // USO is true only when the kernel agreed to TUN_F_USO4|6 at open time (Linux ≥ 6.2).
  289. func (r *Offload) Capabilities() Capabilities {
  290. return Capabilities{TSO: true, USO: r.usoEnabled}
  291. }
  292. func (r *Offload) WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte, proto GSOProto) error {
  293. if len(pays) == 0 {
  294. // There are no payload fragments. There is nothing to send.
  295. return nil
  296. }
  297. var csumOff uint16 // csumOff is the offset of the L4 checksum field in transportHdr
  298. switch proto {
  299. case GSOProtoUDP:
  300. csumOff = 6
  301. case GSOProtoTCP:
  302. csumOff = 16
  303. default:
  304. return fmt.Errorf("unknown GSO proto: %d", proto)
  305. }
  306. // Incorrect geometry must cause an error, not a silent drop.
  307. // No sane packet should ever make it inside this branch.
  308. if len(hdr) == 0 || len(transportHdr) < int(csumOff)+2 {
  309. return fmt.Errorf("tio: WriteGSO header too short: ip=%d transport=%d (csum field at %d)", len(hdr), len(transportHdr), csumOff)
  310. }
  311. // Make the iovec array: [virtio_hdr, hdr, transportHdr, pays...].
  312. // The constructor attaches r.gsoIovs[0] to gsoHdrBuf. That entry does not change.
  313. need := 3 + len(pays)
  314. if need > cap(r.gsoIovs) {
  315. return fmt.Errorf("tio: WriteGSO needs %d iovecs but cap is %d", need, cap(r.gsoIovs))
  316. }
  317. r.gsoIovs = r.gsoIovs[:need]
  318. r.gsoIovs[1].Base = &hdr[0]
  319. r.gsoIovs[1].SetLen(len(hdr))
  320. r.gsoIovs[2].Base = &transportHdr[0]
  321. r.gsoIovs[2].SetLen(len(transportHdr))
  322. segSize := len(pays[0])
  323. total := len(hdr) + len(transportHdr)
  324. for i, p := range pays {
  325. if len(p) == 0 {
  326. // The coalescers route zero-payload packets down the non-GSO path,
  327. // so an empty fragment means the caller's accounting is broken.
  328. return fmt.Errorf("tio: WriteGSO empty payload fragment %d of %d", i, len(pays))
  329. } else if len(p) > segSize || (len(p) < segSize && i != len(pays)-1) {
  330. // all segments must be the same size, except for the last one
  331. return fmt.Errorf("tio: WriteGSO fragment %d is %dB, want %dB segments (only the last may be shorter)", i, len(p), segSize)
  332. }
  333. total += len(p)
  334. r.gsoIovs[3+i].Base = &p[0]
  335. r.gsoIovs[3+i].SetLen(len(p))
  336. }
  337. // This check keeps `total` in the uint16 range. Anything larger would wrap around and cause trouble.
  338. if total > maxSuperpacketLen {
  339. return fmt.Errorf("tio: WriteGSO superpacket %dB exceeds %d", total, maxSuperpacketLen)
  340. }
  341. // A single segment ships as a plain checksummed packet (GSO_NONE, size 0).
  342. // Multiple segments carry the real GSO type and segSize, which the loop
  343. // above verified is the size of every fragment except possibly the last.
  344. gsoType := uint8(unix.VIRTIO_NET_HDR_GSO_NONE)
  345. if len(pays) > 1 {
  346. gsoType = gsoTypeFromProto(proto, hdr[0]>>4)
  347. if gsoType == unix.VIRTIO_NET_HDR_GSO_NONE {
  348. // gsoTypeFromProto only yields GSO_NONE for a bogus IP version nibble.
  349. // A multi-segment superpacket must carry a real GSO type, or the kernel would deliver it as a single jumbo packet.
  350. return fmt.Errorf("tio: WriteGSO IP version %d is not GSO-capable", hdr[0]>>4)
  351. }
  352. }
  353. var gsoSize uint16
  354. if gsoType != unix.VIRTIO_NET_HDR_GSO_NONE {
  355. gsoSize = uint16(segSize)
  356. }
  357. virtio.EncodeHeader(
  358. r.gsoHdrBuf[:],
  359. unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
  360. gsoType, /*gsoType*/
  361. uint16(len(hdr)+len(transportHdr)), /*hdrLen*/
  362. gsoSize, /*gsoSize*/
  363. uint16(len(hdr)), /*csumStart*/
  364. csumOff, /*csumOffset*/
  365. )
  366. _, err := r.rawWrite(r.gsoIovs)
  367. return err
  368. }
  369. func (r *Offload) Close() error {
  370. if r.closed.Swap(true) {
  371. return nil
  372. }
  373. // shutdownFd is owned by the container, so we should not close it
  374. // Close the underlying fd but do NOT null r.fd: a reader may still be loading it in readPacket, and mutating the field would race that load.
  375. // That reader gets EBADF -> os.ErrClosed on its next syscall. A reader already parked in
  376. // poll is NOT woken by this close; only the QueueSet's shutdown eventfd wake does that (see Queue.Close docs).
  377. // closed.Swap already guarantees we only close once.
  378. return unix.Close(r.fd)
  379. }