1
0

wire_buffer.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. package nebula
  2. import (
  3. "io"
  4. "sync"
  5. "github.com/slackhq/nebula/firewall"
  6. "github.com/slackhq/nebula/header"
  7. "github.com/slackhq/nebula/noiseutil"
  8. "github.com/slackhq/nebula/overlay"
  9. )
  10. // WireBuffer is the per-goroutine working set for processing one IP packet
  11. // through the data plane. It owns:
  12. //
  13. // - The IP-payload byte buffer used to hold the current inbound or
  14. // outbound packet, with prefixLen bytes of slack at the front for
  15. // the BSD AF_INET protocol-family marker.
  16. // - The fwPacket scratch parsed by newPacket().
  17. // - The 12-byte AEAD nonce scratch.
  18. // - The header.H parse target used by the receive path.
  19. // - An mtu-sized wire-output scratch for sendNoMetrics and for building
  20. // reject packets.
  21. //
  22. // One WireBuffer is allocated per data-plane goroutine (listenIn for the
  23. // TUN-side, listenOut for the UDP-side) and reused for every packet. No
  24. // per-packet allocation. Future GRO/GSO/TSO and reliable-transport work
  25. // will likely extend this to carry batch state and fragment metadata.
  26. //
  27. // The TUN protocol-family prefix is handled here, not in the overlay
  28. // package. On BSDs the kernel writes the 4-byte marker into the slack on
  29. // read, and we stamp it into the slack before write. On linux/windows
  30. // /userspace devices prefixLen is 0 and the slack is empty.
  31. type WireBuffer struct {
  32. // FwPacket is the parsed IP packet metadata (5-tuple, fragment flags,
  33. // etc.) populated by newPacket().
  34. FwPacket *firewall.Packet
  35. // NB is a 12-byte scratch the AEAD uses for the nonce; reused so we
  36. // don't allocate one per encrypt/decrypt.
  37. NB []byte
  38. // H is the parse target for inbound nebula headers. Receive path only.
  39. H *header.H
  40. // Out is an mtu-sized wire-output scratch passed to sendNoMetrics and
  41. // rejectInside / rejectOutside. Sized to fit any single wire packet.
  42. Out []byte
  43. // ip is the IP-payload region: a slice of len 0, cap linkMTU sliced
  44. // from raw at offset prefixLen. The current packet (if any) is
  45. // ip[:bodyN]. The TUN prefix slack lives at raw[0:prefixLen] just
  46. // before ip.
  47. ip []byte
  48. // raw is the backing slab. Layout:
  49. // [prefixLen bytes prefix slack | linkMTU bytes IP region | outSize bytes Out scratch]
  50. // Holding it lets ReadIPFromTUN / WriteIPToTUN address the slack
  51. // region directly.
  52. raw []byte
  53. prefixLen int
  54. bodyN int
  55. }
  56. // NewWireBuffer returns a buffer sized to hold any single IP packet up to
  57. // linkMTU, plus a disjoint wire-output scratch sliced from the same backing
  58. // slab (the AEAD's Seal contract requires plaintext and dst not to partially
  59. // overlap, and keeping them in one slab gives a single allocation per
  60. // goroutine). Out is sized for the relay worst case
  61. // (linkMTU + 2*header.Len + 2*AEADOverhead).
  62. //
  63. // prefixLen is the number of bytes the destination tun device prepends/
  64. // expects on each IP packet (overlay.Device.TunPrefixLen). On BSDs this
  65. // is 4 (AF_INET marker); on linux/windows/userspace devices it is 0.
  66. func NewWireBuffer(linkMTU, prefixLen int) *WireBuffer {
  67. outSize := linkMTU + 2*header.Len + 2*AEADOverhead
  68. raw := make([]byte, prefixLen+linkMTU+outSize)
  69. outStart := prefixLen + linkMTU
  70. return &WireBuffer{
  71. FwPacket: &firewall.Packet{},
  72. NB: make([]byte, NonceSize),
  73. H: &header.H{},
  74. Out: raw[outStart : outStart : outStart+outSize],
  75. ip: raw[prefixLen:prefixLen:outStart],
  76. raw: raw,
  77. prefixLen: prefixLen,
  78. }
  79. }
  80. // Reset clears the body-length record so the buffer is ready for another
  81. // recv (e.g. relay-receive recursion before a nested decrypt).
  82. func (b *WireBuffer) Reset() { b.bodyN = 0 }
  83. // IPPacket returns the IP packet currently held in the payload region (after
  84. // a successful ReadIPFromTUN or DecryptDatagram). The slice aliases the
  85. // buffer; do not retain past the next operation.
  86. func (b *WireBuffer) IPPacket() []byte {
  87. return b.ip[:b.bodyN]
  88. }
  89. // Seal stamps a nebula header at the front of buf.Out and AEAD-seals p as the
  90. // payload, treating the header as additional authenticated data. The lock
  91. // scope around counter increment + encrypt matches what goboring AESGCMTLS
  92. // requires; non-boring builds skip the lock.
  93. //
  94. // Returns the wire bytes (header || ciphertext || tag), aliased to buf.Out.
  95. // The slice is invalidated by the next Seal* call on this buffer.
  96. func (b *WireBuffer) Seal(ci *ConnectionState, t header.MessageType, st header.MessageSubType, remoteIndex uint32, p []byte) ([]byte, error) {
  97. return b.sealInto(b.Out[:cap(b.Out)], ci, t, st, remoteIndex, p)
  98. }
  99. // SealForRelay is like Seal but reserves header.Len bytes of slack at the front
  100. // of buf.Out for an outer relay header. The inner header + ciphertext lands at
  101. // offset header.Len so a follow-up SealRelayInPlace can stamp the outer header
  102. // without copying. Use this when the caller may need to wrap the result in a
  103. // relay envelope after the fact.
  104. func (b *WireBuffer) SealForRelay(ci *ConnectionState, t header.MessageType, st header.MessageSubType, remoteIndex uint32, p []byte) ([]byte, error) {
  105. return b.sealInto(b.Out[header.Len:cap(b.Out)], ci, t, st, remoteIndex, p)
  106. }
  107. func (b *WireBuffer) sealInto(out []byte, ci *ConnectionState, t header.MessageType, st header.MessageSubType, remoteIndex uint32, p []byte) ([]byte, error) {
  108. if noiseutil.EncryptLockNeeded {
  109. ci.writeLock.Lock()
  110. }
  111. c := ci.messageCounter.Add(1)
  112. out = header.Encode(out, header.Version, t, st, remoteIndex, c)
  113. out, err := ci.eKey.EncryptDanger(out, out, p, c, b.NB)
  114. if noiseutil.EncryptLockNeeded {
  115. ci.writeLock.Unlock()
  116. }
  117. return out, err
  118. }
  119. // SealRelayInPlace wraps an inner message that is already staged at
  120. // buf.Out[header.Len:header.Len+innerLen] (either from a SealForRelay encrypt
  121. // or from a copy via the SendVia entry point). It stamps the outer relay
  122. // header into buf.Out[:header.Len] and AAD-only seals over the entire region,
  123. // producing the wire bytes for the relay tunnel.
  124. //
  125. // Returns the wire bytes aliased to buf.Out; invalidated by the next Seal*
  126. // call on this buffer.
  127. func (b *WireBuffer) SealRelayInPlace(ci *ConnectionState, remoteIndex uint32, innerLen int) ([]byte, error) {
  128. if noiseutil.EncryptLockNeeded {
  129. ci.writeLock.Lock()
  130. }
  131. c := ci.messageCounter.Add(1)
  132. out := b.Out[:cap(b.Out)]
  133. out = header.Encode(out, header.Version, header.Message, header.MessageRelay, remoteIndex, c)
  134. out = out[:header.Len+innerLen]
  135. out, err := ci.eKey.EncryptDanger(out, out, nil, c, b.NB)
  136. if noiseutil.EncryptLockNeeded {
  137. ci.writeLock.Unlock()
  138. }
  139. return out, err
  140. }
  141. // StageRelayInner copies ad into the inner-payload slot at buf.Out[header.Len:]
  142. // so SealRelayInPlace can wrap it on the next call. Used by SendVia when ad
  143. // did not come from a prior SealForRelay (e.g. a handshake message being
  144. // forwarded through a relay tunnel without our own encryption).
  145. func (b *WireBuffer) StageRelayInner(ad []byte) int {
  146. return copy(b.Out[header.Len:cap(b.Out)], ad)
  147. }
  148. // ReadIPFromTUN reads one IP packet from r into the payload region and
  149. // updates bodyN. On BSDs the kernel writes its 4-byte protocol-family
  150. // marker into the slack at raw[0:prefixLen] and the IP packet at
  151. // raw[prefixLen:prefixLen+n]; we hand it the slack-prefixed slice so
  152. // the kernel can do this in one syscall with no copy. On linux/windows/
  153. // userspace devices prefixLen is 0 and the slack is empty.
  154. func (b *WireBuffer) ReadIPFromTUN(r io.Reader) (int, error) {
  155. n, err := r.Read(b.raw[:b.prefixLen+cap(b.ip)])
  156. if err != nil {
  157. b.bodyN = 0
  158. return 0, err
  159. }
  160. if n < b.prefixLen {
  161. b.bodyN = 0
  162. return 0, nil
  163. }
  164. b.bodyN = n - b.prefixLen
  165. return b.bodyN, nil
  166. }
  167. // WriteIPToTUN writes the IP packet currently in the payload region to w.
  168. // On BSDs we stamp the protocol-family marker into the slack at
  169. // raw[0:prefixLen] in place and write the entire slack+IP region in a
  170. // single syscall, so the kernel sees [marker][ip] back to back without a
  171. // userspace copy. On linux/windows/userspace devices the slack is empty
  172. // and we just write the IP region.
  173. func (b *WireBuffer) WriteIPToTUN(w io.Writer) (int, error) {
  174. out := b.raw[:b.prefixLen+b.bodyN]
  175. if b.prefixLen > 0 {
  176. if err := overlay.StampTunPrefix(out); err != nil {
  177. return 0, err
  178. }
  179. }
  180. return w.Write(out)
  181. }
  182. // DecryptDatagram decrypts an inbound UDP packet into the payload region.
  183. func (b *WireBuffer) DecryptDatagram(ci *ConnectionState, packet []byte, mc uint64) error {
  184. dst, err := ci.dKey.DecryptDanger(b.ip[:0], packet[:header.Len], packet[header.Len:], mc, b.NB)
  185. if err != nil {
  186. b.bodyN = 0
  187. return err
  188. }
  189. b.bodyN = len(dst)
  190. return nil
  191. }
  192. // DecryptForHandler decrypts an inbound UDP packet (lighthouse, test,
  193. // control, close-tunnel) into the payload region and returns the plaintext
  194. // slice for the in-process handler. Returned slice aliases the buffer.
  195. func (b *WireBuffer) DecryptForHandler(ci *ConnectionState, packet []byte, mc uint64) ([]byte, error) {
  196. dst, err := ci.dKey.DecryptDanger(b.ip[:0], packet[:header.Len], packet[header.Len:], mc, b.NB)
  197. if err != nil {
  198. b.bodyN = 0
  199. return nil, err
  200. }
  201. b.bodyN = len(dst)
  202. return dst, nil
  203. }
  204. // WireBufferAllocator hands out reusable WireBuffers for cold callers that
  205. // don't own a long-lived per-goroutine buffer (control plane, relay manager,
  206. // connection manager teardown, etc.). Hot-path goroutines hold their own
  207. // buffer for the life of the goroutine and don't need to acquire one.
  208. type WireBufferAllocator interface {
  209. Acquire() *WireBuffer
  210. Release(*WireBuffer)
  211. }
  212. // wireBufferPool is a sync.Pool-backed WireBufferAllocator. The pool is
  213. // keyed off a single linkMTU and prefixLen; cold callers send across the
  214. // data-plane mtu and target the same Device, so we size the pool's
  215. // buffers the same way.
  216. type wireBufferPool struct {
  217. pool sync.Pool
  218. }
  219. func NewWireBufferPool(linkMTU, prefixLen int) *wireBufferPool {
  220. return &wireBufferPool{
  221. pool: sync.Pool{
  222. New: func() any {
  223. return NewWireBuffer(linkMTU, prefixLen)
  224. },
  225. },
  226. }
  227. }
  228. func (p *wireBufferPool) Acquire() *WireBuffer {
  229. return p.pool.Get().(*WireBuffer)
  230. }
  231. func (p *wireBufferPool) Release(b *WireBuffer) {
  232. b.Reset()
  233. p.pool.Put(b)
  234. }