tio.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package tio
  2. import (
  3. "io"
  4. )
  5. // QueueSet holds one or many Queue objects and helps close them in an orderly way.
  6. type QueueSet interface {
  7. io.Closer
  8. Queues() []Queue
  9. // Add takes a tun fd, adds it to the set, and prepares it for use as a Queue.
  10. Add(fd int) error
  11. }
  12. // Capabilities advertises which kernel offload features a Queue
  13. // successfully negotiated. Callers consult this to decide which coalescers
  14. // to wire onto the write path — a Queue without TSO can't usefully accept a
  15. // TCPCoalescer, and a Queue without USO can't accept a UDPCoalescer.
  16. type Capabilities struct {
  17. // TSO means the FD was opened with IFF_VNET_HDR and the kernel agreed
  18. // to TUN_F_TSO4|TSO6 — i.e. WriteGSO with GSOProtoTCP is safe.
  19. TSO bool
  20. // USO means the kernel additionally agreed to TUN_F_USO4|USO6, so
  21. // WriteGSO with GSOProtoUDP is safe. Linux ≥ 6.2.
  22. USO bool
  23. }
  24. // Queue is a readable/writable Poll queue. One Queue is driven by a single
  25. // read goroutine plus a single writer (see Write below).
  26. type Queue interface {
  27. io.Closer
  28. // Read returns one or more packets. The returned Packet.Bytes slices
  29. // are borrowed from the Queue's internal buffer and are only valid
  30. // until the next Read or Close on this Queue - callers must encrypt
  31. // or copy each slice before the next call. A Packet may carry a
  32. // GSO/USO superpacket (see GSOInfo); when GSO.IsSuperpacket() is
  33. // true the caller must segment Bytes before treating it as a single
  34. // IP datagram. Not safe for concurrent Reads.
  35. Read() ([]Packet, error)
  36. // Write emits a single packet on the plaintext (outside→inside)
  37. // delivery path. Not safe for concurrent Writes.
  38. Write(p []byte) (int, error)
  39. }
  40. // Packet is the unit Queue.Read returns. Bytes points into the queue's
  41. // internal buffer and is only valid until the next Read or Close on the
  42. // queue that produced it. GSO is the zero value for an already-segmented
  43. // IP datagram; when non-zero it describes a kernel-supplied TSO/USO
  44. // superpacket the caller must segment before consuming.
  45. type Packet struct {
  46. Bytes []byte
  47. GSO GSOInfo
  48. }
  49. // GSOInfo describes a kernel-supplied superpacket sitting in Packet.Bytes.
  50. // The zero value means "not a superpacket" — Bytes is one regular IP
  51. // datagram and no segmentation is required.
  52. type GSOInfo struct {
  53. // Size is the GSO segment size: max payload bytes per segment
  54. // (== TCP MSS for TSO, == UDP payload chunk for USO). Zero means
  55. // not a superpacket.
  56. Size uint16
  57. // HdrLen is the total L3+L4 header length within Bytes (already
  58. // corrected via correctHdrLen, so safe to slice on).
  59. HdrLen uint16
  60. // CsumStart is the L4 header offset inside Bytes (== L3 header
  61. // length).
  62. CsumStart uint16
  63. // Proto picks the L4 protocol (TCP or UDP) so the segmenter knows
  64. // which checksum/header layout to apply.
  65. Proto GSOProto
  66. }
  67. // IsSuperpacket reports whether g describes a multi-segment GSO/USO
  68. // superpacket that needs segmentation before its bytes can be encrypted
  69. // and sent on the wire.
  70. func (g GSOInfo) IsSuperpacket() bool { return g.Size > 0 }
  71. // Clone returns a Packet whose Bytes is a freshly allocated copy of p.Bytes,
  72. // safe to retain past the next Read or Close on the originating Queue.
  73. // GSO metadata is copied verbatim. Use this only when a caller genuinely
  74. // needs to outlive the borrowed-slice contract — the hot path reads should
  75. // continue to consume the borrow synchronously to avoid the allocation.
  76. func (p Packet) Clone() Packet {
  77. if p.Bytes == nil {
  78. return p
  79. }
  80. cp := make([]byte, len(p.Bytes))
  81. copy(cp, p.Bytes)
  82. return Packet{Bytes: cp, GSO: p.GSO}
  83. }
  84. // CapsProvider is an optional interface implemented by Queues that
  85. // successfully negotiated kernel offload features at open time. Callers
  86. // pick a write-path coalescer based on the result. Queues that don't
  87. // implement it are treated as having no offload capability — callers must
  88. // fall back to plain per-packet writes.
  89. type CapsProvider interface {
  90. Capabilities() Capabilities
  91. }
  92. // QueueCapabilities returns q's negotiated offload capabilities, or the
  93. // zero value when q does not advertise any.
  94. func QueueCapabilities(q Queue) Capabilities {
  95. if cp, ok := q.(CapsProvider); ok {
  96. return cp.Capabilities()
  97. }
  98. return Capabilities{}
  99. }
  100. // GSOProto selects the L4 protocol for a GSO superpacket. Determines which
  101. // VIRTIO_NET_HDR_GSO_* type the writer stamps and which checksum offset
  102. // inside the transport header virtio NEEDS_CSUM expects.
  103. type GSOProto uint8
  104. const (
  105. GSOProtoTCP GSOProto = iota
  106. GSOProtoUDP
  107. )
  108. // GSOWriter is implemented by Queues that can emit a TCP or UDP superpacket
  109. // assembled from a header prefix plus one or more borrowed payload
  110. // fragments, in a single vectored write (writev with a leading
  111. // virtio_net_hdr). This lets the coalescer avoid copying payload bytes
  112. // between the caller's decrypt buffer and the TUN. Backends without GSO
  113. // support do not implement this interface and coalescing is skipped.
  114. //
  115. // hdr contains the IPv4/IPv6 header prefix (mutable - callers will have
  116. // filled in total length and IP csum). transportHdr is the TCP or UDP
  117. // header (mutable - the L4 checksum field must hold the pseudo-header
  118. // partial, single-fold not inverted, per virtio NEEDS_CSUM semantics).
  119. // pays are non-overlapping payload fragments whose concatenation is the
  120. // full superpacket payload; they are read-only from the writer's
  121. // perspective and must remain valid until the call returns. Every segment
  122. // in pays except possibly the last is exactly the same size. proto picks
  123. // the L4 protocol so the writer knows which GSOType / CsumOffset to set.
  124. //
  125. // Callers should also consult CapsProvider (via SupportsGSO or
  126. // QueueCapabilities) for the per-protocol negotiated capability; an
  127. // implementation of GSOWriter is necessary but not sufficient since USO
  128. // may not have been negotiated even when TSO was.
  129. type GSOWriter interface {
  130. WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte, proto GSOProto) error
  131. }
  132. // SupportsGSO reports whether w implements GSOWriter and the underlying
  133. // queue advertises the negotiated capability for `want`. A writer that
  134. // implements GSOWriter but not CapsProvider is treated as permissive
  135. // (used by tests and fakes that don't negotiate).
  136. func SupportsGSO(w any, want GSOProto) (GSOWriter, bool) {
  137. gw, ok := w.(GSOWriter)
  138. if !ok {
  139. return nil, false
  140. }
  141. cp, ok := w.(CapsProvider)
  142. if !ok {
  143. return gw, true
  144. }
  145. caps := cp.Capabilities()
  146. switch want {
  147. case GSOProtoTCP:
  148. return gw, caps.TSO
  149. case GSOProtoUDP:
  150. return gw, caps.USO
  151. }
  152. return gw, false
  153. }