tun_linux_offload.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. //go:build !android && !e2e_testing
  2. // +build !android,!e2e_testing
  3. package overlay
  4. import (
  5. "encoding/binary"
  6. "fmt"
  7. "golang.org/x/sys/unix"
  8. )
  9. // Size of the legacy struct virtio_net_hdr that the kernel prepends/expects on
  10. // a TUN opened with IFF_VNET_HDR (TUNSETVNETHDRSZ not set).
  11. const virtioNetHdrLen = 10
  12. // Maximum size we accept for a single read from a TUN with IFF_VNET_HDR. A
  13. // TSO superpacket can be up to 64KiB of payload plus a single L2/L3/L4 header
  14. // prefix plus the virtio header.
  15. const tunReadBufSize = 65535
  16. // Space for segmented output. Worst case is many small segments, each paying
  17. // an IP+TCP header. 128KiB comfortably covers the 64KiB payload ceiling.
  18. const tunSegBufSize = 131072
  19. type virtioNetHdr struct {
  20. Flags uint8
  21. GSOType uint8
  22. HdrLen uint16
  23. GSOSize uint16
  24. CsumStart uint16
  25. CsumOffset uint16
  26. }
  27. // decode reads a virtio_net_hdr in host byte order (TUN default; we never
  28. // call TUNSETVNETLE so the kernel matches our endianness).
  29. func (h *virtioNetHdr) decode(b []byte) {
  30. h.Flags = b[0]
  31. h.GSOType = b[1]
  32. h.HdrLen = binary.NativeEndian.Uint16(b[2:4])
  33. h.GSOSize = binary.NativeEndian.Uint16(b[4:6])
  34. h.CsumStart = binary.NativeEndian.Uint16(b[6:8])
  35. h.CsumOffset = binary.NativeEndian.Uint16(b[8:10])
  36. }
  37. // segmentInto splits a TUN-side packet described by hdr into one or more
  38. // IP packets, each appended to *out as a slice of scratch. scratch must be
  39. // sized to hold every segment (including replicated headers).
  40. func segmentInto(pkt []byte, hdr virtioNetHdr, out *[][]byte, scratch []byte) error {
  41. switch hdr.GSOType {
  42. case unix.VIRTIO_NET_HDR_GSO_NONE:
  43. if len(pkt) > len(scratch) {
  44. return fmt.Errorf("packet larger than segment buffer: %d > %d", len(pkt), len(scratch))
  45. }
  46. copy(scratch, pkt)
  47. seg := scratch[:len(pkt)]
  48. if hdr.Flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 {
  49. if err := finishChecksum(seg, hdr); err != nil {
  50. return err
  51. }
  52. }
  53. *out = append(*out, seg)
  54. return nil
  55. case unix.VIRTIO_NET_HDR_GSO_TCPV4, unix.VIRTIO_NET_HDR_GSO_TCPV6:
  56. return segmentTCP(pkt, hdr, out, scratch)
  57. default:
  58. return fmt.Errorf("unsupported virtio gso type: %d", hdr.GSOType)
  59. }
  60. }
  61. // finishChecksum computes the L4 checksum for a non-GSO packet that the kernel
  62. // handed us with NEEDS_CSUM set. csum_start / csum_offset point at the 16-bit
  63. // checksum field; we zero it, fold a full sum (the field was pre-loaded with
  64. // the pseudo-header partial sum by the kernel), and store the result.
  65. func finishChecksum(seg []byte, hdr virtioNetHdr) error {
  66. cs := int(hdr.CsumStart)
  67. co := int(hdr.CsumOffset)
  68. if cs+co+2 > len(seg) {
  69. return fmt.Errorf("csum offsets out of range: start=%d offset=%d len=%d", cs, co, len(seg))
  70. }
  71. // The kernel stores a partial pseudo-header sum at [cs+co:]; sum over the
  72. // L4 region starting at cs, folding the prior partial in as the seed.
  73. partial := uint32(binary.BigEndian.Uint16(seg[cs+co : cs+co+2]))
  74. seg[cs+co] = 0
  75. seg[cs+co+1] = 0
  76. sum := checksumBytes(seg[cs:], partial)
  77. binary.BigEndian.PutUint16(seg[cs+co:cs+co+2], checksumFold(sum))
  78. return nil
  79. }
  80. // segmentTCP software-segments a TSO superpacket into one IP packet per MSS
  81. // chunk. The caller guarantees hdr.GSOType is TCPV4 or TCPV6.
  82. func segmentTCP(pkt []byte, hdr virtioNetHdr, out *[][]byte, scratch []byte) error {
  83. if hdr.GSOSize == 0 {
  84. return fmt.Errorf("gso_size is zero")
  85. }
  86. if int(hdr.HdrLen) > len(pkt) || hdr.HdrLen == 0 {
  87. return fmt.Errorf("hdr_len %d out of range (pkt %d)", hdr.HdrLen, len(pkt))
  88. }
  89. if hdr.CsumStart == 0 || hdr.CsumStart >= hdr.HdrLen {
  90. return fmt.Errorf("csum_start %d out of range (hdr_len %d)", hdr.CsumStart, hdr.HdrLen)
  91. }
  92. isV4 := hdr.GSOType == unix.VIRTIO_NET_HDR_GSO_TCPV4
  93. headerLen := int(hdr.HdrLen)
  94. csumStart := int(hdr.CsumStart)
  95. if isV4 && csumStart < 20 {
  96. return fmt.Errorf("csum_start %d too small for IPv4", csumStart)
  97. }
  98. if !isV4 && csumStart < 40 {
  99. return fmt.Errorf("csum_start %d too small for IPv6", csumStart)
  100. }
  101. if headerLen-csumStart < 20 {
  102. return fmt.Errorf("tcp header region too small: %d", headerLen-csumStart)
  103. }
  104. payload := pkt[headerLen:]
  105. payLen := len(payload)
  106. gso := int(hdr.GSOSize)
  107. numSeg := (payLen + gso - 1) / gso
  108. if numSeg == 0 {
  109. numSeg = 1
  110. }
  111. need := numSeg*headerLen + payLen
  112. if need > len(scratch) {
  113. return fmt.Errorf("scratch too small for %d segments: need %d have %d", numSeg, need, len(scratch))
  114. }
  115. origSeq := binary.BigEndian.Uint32(pkt[csumStart+4 : csumStart+8])
  116. origFlags := pkt[csumStart+13]
  117. const tcpFinPsh = 0x09 // FIN(0x01) | PSH(0x08)
  118. var origIPID uint16
  119. if isV4 {
  120. origIPID = binary.BigEndian.Uint16(pkt[4:6])
  121. }
  122. off := 0
  123. for i := 0; i < numSeg; i++ {
  124. segStart := i * gso
  125. segEnd := segStart + gso
  126. if segEnd > payLen {
  127. segEnd = payLen
  128. }
  129. segPayLen := segEnd - segStart
  130. // Materialise IP+TCP header and this segment's payload chunk.
  131. copy(scratch[off:], pkt[:headerLen])
  132. copy(scratch[off+headerLen:], payload[segStart:segEnd])
  133. seg := scratch[off : off+headerLen+segPayLen]
  134. off += headerLen + segPayLen
  135. // Fix IP header: total/payload length, v4 ID, v4 header csum.
  136. if isV4 {
  137. ihl := int(seg[0]&0x0f) * 4
  138. if ihl < 20 || ihl > csumStart {
  139. return fmt.Errorf("bad IPv4 IHL: %d", ihl)
  140. }
  141. binary.BigEndian.PutUint16(seg[2:4], uint16(headerLen+segPayLen))
  142. binary.BigEndian.PutUint16(seg[4:6], origIPID+uint16(i))
  143. seg[10] = 0
  144. seg[11] = 0
  145. binary.BigEndian.PutUint16(seg[10:12], checksumFold(checksumBytes(seg[:ihl], 0)))
  146. } else {
  147. // IPv6 payload length excludes the 40-byte fixed header but
  148. // includes any extension headers that sit between [40:csumStart].
  149. binary.BigEndian.PutUint16(seg[4:6], uint16(headerLen-40+segPayLen))
  150. }
  151. // Fix TCP header: seq, flags, checksum.
  152. segSeq := origSeq + uint32(segStart)
  153. binary.BigEndian.PutUint32(seg[csumStart+4:csumStart+8], segSeq)
  154. if i != numSeg-1 {
  155. seg[csumStart+13] = origFlags &^ tcpFinPsh
  156. } else {
  157. seg[csumStart+13] = origFlags
  158. }
  159. seg[csumStart+16] = 0
  160. seg[csumStart+17] = 0
  161. tcpLen := headerLen - csumStart + segPayLen
  162. var psum uint32
  163. if isV4 {
  164. psum = pseudoHeaderIPv4(seg[12:16], seg[16:20], unix.IPPROTO_TCP, tcpLen)
  165. } else {
  166. psum = pseudoHeaderIPv6(seg[8:24], seg[24:40], unix.IPPROTO_TCP, tcpLen)
  167. }
  168. binary.BigEndian.PutUint16(seg[csumStart+16:csumStart+18], checksumFold(checksumBytes(seg[csumStart:csumStart+tcpLen], psum)))
  169. *out = append(*out, seg)
  170. }
  171. return nil
  172. }
  173. func checksumBytes(b []byte, initial uint32) uint32 {
  174. sum := initial
  175. for len(b) >= 2 {
  176. sum += uint32(binary.BigEndian.Uint16(b[:2]))
  177. b = b[2:]
  178. }
  179. if len(b) == 1 {
  180. sum += uint32(b[0]) << 8
  181. }
  182. return sum
  183. }
  184. func checksumFold(sum uint32) uint16 {
  185. for sum>>16 != 0 {
  186. sum = (sum & 0xffff) + (sum >> 16)
  187. }
  188. return ^uint16(sum)
  189. }
  190. func pseudoHeaderIPv4(src, dst []byte, proto byte, tcpLen int) uint32 {
  191. sum := checksumBytes(src, 0)
  192. sum = checksumBytes(dst, sum)
  193. sum += uint32(proto)
  194. sum += uint32(tcpLen)
  195. return sum
  196. }
  197. func pseudoHeaderIPv6(src, dst []byte, proto byte, tcpLen int) uint32 {
  198. sum := checksumBytes(src, 0)
  199. sum = checksumBytes(dst, sum)
  200. sum += uint32(tcpLen >> 16)
  201. sum += uint32(tcpLen & 0xffff)
  202. sum += uint32(proto)
  203. return sum
  204. }