tio.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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 successfully negotiated.
  13. // Callers consult this to decide which coalescers to wire onto the write path.
  14. type Capabilities struct {
  15. // TSO means the FD was opened with IFF_VNET_HDR and the kernel agreed to TUN_F_TSO4|TSO6,
  16. // and WriteGSO with GSOProtoTCP is safe.
  17. TSO bool
  18. // USO means the kernel additionally agreed to TUN_F_USO4|USO6,
  19. // so WriteGSO with GSOProtoUDP is safe. Linux ≥ 6.2.
  20. USO bool
  21. }
  22. // Queue is a readable/writable Poll queue.
  23. // Concurrency contract: a single read goroutine drives Read; plain Write is safe for concurrent callers;
  24. // WriteGSO (on Queues that implement GSOWriter) is single-writer per queue.
  25. //
  26. // Close on an individual Queue does NOT unblock a Read parked in poll — closing an fd
  27. // never wakes its pollers. Orderly teardown goes through the owning QueueSet's Close,
  28. // which first signals a shared shutdown eventfd every reader polls alongside its own fd.
  29. // That eventfd is a set-wide kill switch: once signaled, every Queue in the set returns
  30. // os.ErrClosed from Read, so it cannot be used to stop a single Queue.
  31. type Queue interface {
  32. io.Closer
  33. // Read returns one or more packets.
  34. // The returned Packet.Bytes slices are borrowed from the Queue's internal buffer and are only valid
  35. // until the next Read or Close on this Queue.
  36. // A Packet may carry a GSO/USO superpacket (see GSOInfo)
  37. // Single-reader only: not safe for concurrent Reads (it reuses per-queue rx scratch each call).
  38. Read() ([]Packet, error)
  39. // Write emits a single packet on the plaintext (outside→inside) delivery path.
  40. // Safe for concurrent use.
  41. Write(p []byte) (int, error)
  42. }
  43. // Packet is the unit Queue.Read returns.
  44. // Bytes points into the queue's internal buffer and is only valid until the next Read or Close on the queue that produced it.
  45. // GSO is the zero value for an already-segmented IP datagram;
  46. // when non-zero it describes a kernel-supplied TSO/USO superpacket the caller must segment before consuming.
  47. type Packet struct {
  48. Bytes []byte
  49. GSO GSOInfo
  50. }
  51. // GSOInfo describes a kernel-supplied superpacket sitting in Packet.Bytes.
  52. // The zero value means Bytes is one regular IP datagram and no segmentation is required.
  53. type GSOInfo struct {
  54. // Size is the GSO segment size: max payload bytes per segment
  55. // (== TCP MSS for TSO, == UDP payload chunk for USO). Zero means not a superpacket.
  56. Size uint16
  57. // HdrLen is the total L3+L4 header length within Bytes (already corrected via correctHdrLen, so safe to slice on).
  58. HdrLen uint16
  59. // CsumStart is the L4 header offset inside Bytes (== L3 header length).
  60. CsumStart uint16
  61. // Proto picks the L4 protocol (TCP or UDP) so the segmenter knows which checksum/header layout to apply.
  62. Proto GSOProto
  63. }
  64. // IsSuperpacket reports whether g describes a multi-segment GSO/USO
  65. // superpacket that needs segmentation before its bytes can be encrypted and sent on the wire.
  66. func (g GSOInfo) IsSuperpacket() bool { return g.Size > 0 }
  67. // SegmentCount reports how many segments SegmentSuperpacket will cut p into, without touching its bytes.
  68. func (p Packet) SegmentCount() int {
  69. if !p.GSO.IsSuperpacket() {
  70. return 1
  71. }
  72. n := (len(p.Bytes) - int(p.GSO.HdrLen) + int(p.GSO.Size) - 1) / int(p.GSO.Size)
  73. return max(n, 1)
  74. }
  75. // Clone returns a Packet whose Bytes is a freshly allocated copy of p.Bytes,
  76. // safe to retain past the next Read or Close on the originating Queue.
  77. // GSO metadata is copied verbatim.
  78. // Use this only when a caller needs the data to outlive the borrowed-slice contract.
  79. func (p Packet) Clone() Packet {
  80. if p.Bytes == nil {
  81. return p
  82. }
  83. cp := make([]byte, len(p.Bytes))
  84. copy(cp, p.Bytes)
  85. return Packet{Bytes: cp, GSO: p.GSO}
  86. }
  87. // CapsProvider is an optional interface implemented by Queues that negotiate kernel offload features at open time.
  88. // Callers pick a write-path coalescer based on the result.
  89. // Queues that don't implement it are treated as having no offload capability.
  90. type CapsProvider interface {
  91. Capabilities() Capabilities
  92. }
  93. // GSOProto selects the L4 protocol for a GSO superpacket.
  94. // Determines which VIRTIO_NET_HDR_GSO_* type the writer stamps and which checksum offset
  95. // inside the transport header virtio NEEDS_CSUM expects.
  96. type GSOProto uint8
  97. const (
  98. GSOProtoUnknown GSOProto = iota
  99. GSOProtoTCP
  100. GSOProtoUDP
  101. )
  102. // GSOWriter is implemented by Queues that can emit a TCP or UDP superpacket
  103. // assembled from a header prefix plus one or more borrowed payload fragments,
  104. // in a single vectored write (writev with a leading virtio_net_hdr).
  105. // This lets the coalescer avoid copying payload bytes between the caller's decrypt buffer and the TUN.
  106. // Backends without GSO support do not implement this interface and coalescing is skipped.
  107. //
  108. // hdr contains the IPv4/IPv6 header prefix (mutable: callers will have filled in total length and IP csum).
  109. // transportHdr is the TCP or UDP header
  110. // (mutable: the L4 checksum field must hold the pseudo-header partial, single-fold not inverted, per virtio NEEDS_CSUM semantics).
  111. // pays are non-overlapping payload fragments whose concatenation is the full superpacket payload.
  112. // They are read-only from the writer's perspective and must remain valid until the call returns.
  113. // Every segment in pays except possibly the last must be exactly the same size.
  114. // proto picks the L4 protocol so the writer knows which gsoType / CsumOffset to set.
  115. //
  116. // Callers should also consult CapsProvider (via SupportsGSO) for the per-protocol negotiated capability:
  117. // USO may not have been negotiated even when TSO was.
  118. type GSOWriter interface {
  119. io.Writer
  120. CapsProvider
  121. WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte, proto GSOProto) error
  122. }
  123. // SupportsGSO reports whether w implements GSOWriter and the underlying
  124. // queue advertises the negotiated capability for `want`.
  125. func SupportsGSO(w io.Writer, want GSOProto) (GSOWriter, bool) {
  126. gw, ok := w.(GSOWriter)
  127. if !ok {
  128. return nil, false
  129. }
  130. caps := gw.Capabilities()
  131. switch want {
  132. case GSOProtoTCP:
  133. return gw, caps.TSO
  134. case GSOProtoUDP:
  135. return gw, caps.USO
  136. default:
  137. return gw, false
  138. }
  139. }