capture.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package capture formats packet logging into a debug pcap stream.
  4. package capture
  5. import (
  6. "bytes"
  7. "context"
  8. "encoding/binary"
  9. "io"
  10. "net/http"
  11. "sync"
  12. "time"
  13. _ "embed"
  14. "tailscale.com/util/set"
  15. )
  16. //go:embed ts-dissector.lua
  17. var DissectorLua string
  18. // Callback describes a function which is called to
  19. // record packets when debugging packet-capture.
  20. // Such callbacks must not take ownership of the
  21. // provided data slice: it may only copy out of it
  22. // within the lifetime of the function.
  23. type Callback func(Path, time.Time, []byte)
  24. var bufferPool = sync.Pool{
  25. New: func() any {
  26. return new(bytes.Buffer)
  27. },
  28. }
  29. const flushPeriod = 100 * time.Millisecond
  30. func writePcapHeader(w io.Writer) {
  31. binary.Write(w, binary.LittleEndian, uint32(0xA1B2C3D4)) // pcap magic number
  32. binary.Write(w, binary.LittleEndian, uint16(2)) // version major
  33. binary.Write(w, binary.LittleEndian, uint16(4)) // version minor
  34. binary.Write(w, binary.LittleEndian, uint32(0)) // this zone
  35. binary.Write(w, binary.LittleEndian, uint32(0)) // zone significant figures
  36. binary.Write(w, binary.LittleEndian, uint32(65535)) // max packet len
  37. binary.Write(w, binary.LittleEndian, uint32(147)) // link-layer ID - USER0
  38. }
  39. func writePktHeader(w *bytes.Buffer, when time.Time, length int) {
  40. s := when.Unix()
  41. us := when.UnixMicro() - (s * 1000000)
  42. binary.Write(w, binary.LittleEndian, uint32(s)) // timestamp in seconds
  43. binary.Write(w, binary.LittleEndian, uint32(us)) // timestamp microseconds
  44. binary.Write(w, binary.LittleEndian, uint32(length)) // length present
  45. binary.Write(w, binary.LittleEndian, uint32(length)) // total length
  46. }
  47. // Path describes where in the data path the packet was captured.
  48. type Path uint8
  49. // Valid Path values.
  50. const (
  51. // FromLocal indicates the packet was logged as it traversed the FromLocal path:
  52. // i.e.: A packet from the local system into the TUN.
  53. FromLocal Path = 0
  54. // FromPeer indicates the packet was logged upon reception from a remote peer.
  55. FromPeer Path = 1
  56. // SynthesizedToLocal indicates the packet was generated from within tailscaled,
  57. // and is being routed to the local machine's network stack.
  58. SynthesizedToLocal Path = 2
  59. // SynthesizedToPeer indicates the packet was generated from within tailscaled,
  60. // and is being routed to a remote Wireguard peer.
  61. SynthesizedToPeer Path = 3
  62. // PathDisco indicates the packet is information about a disco frame.
  63. PathDisco Path = 254
  64. )
  65. // New creates a new capture sink.
  66. func New() *Sink {
  67. ctx, c := context.WithCancel(context.Background())
  68. return &Sink{
  69. ctx: ctx,
  70. ctxCancel: c,
  71. }
  72. }
  73. // Type Sink handles callbacks with packets to be logged,
  74. // formatting them into a pcap stream which is mirrored to
  75. // all registered outputs.
  76. type Sink struct {
  77. ctx context.Context
  78. ctxCancel context.CancelFunc
  79. mu sync.Mutex
  80. outputs set.HandleSet[io.Writer]
  81. flushTimer *time.Timer // or nil if none running
  82. }
  83. // RegisterOutput connects an output to this sink, which
  84. // will be written to with a pcap stream as packets are logged.
  85. // A function is returned which unregisters the output when
  86. // called.
  87. //
  88. // If w implements io.Closer, it will be closed upon error
  89. // or when the sink is closed. If w implements http.Flusher,
  90. // it will be flushed periodically.
  91. func (s *Sink) RegisterOutput(w io.Writer) (unregister func()) {
  92. select {
  93. case <-s.ctx.Done():
  94. return func() {}
  95. default:
  96. }
  97. writePcapHeader(w)
  98. s.mu.Lock()
  99. hnd := s.outputs.Add(w)
  100. s.mu.Unlock()
  101. return func() {
  102. s.mu.Lock()
  103. defer s.mu.Unlock()
  104. delete(s.outputs, hnd)
  105. }
  106. }
  107. // NumOutputs returns the number of outputs registered with the sink.
  108. func (s *Sink) NumOutputs() int {
  109. s.mu.Lock()
  110. defer s.mu.Unlock()
  111. return len(s.outputs)
  112. }
  113. // Close shuts down the sink. Future calls to LogPacket
  114. // are ignored, and any registered output that implements
  115. // io.Closer is closed.
  116. func (s *Sink) Close() error {
  117. s.ctxCancel()
  118. s.mu.Lock()
  119. defer s.mu.Unlock()
  120. if s.flushTimer != nil {
  121. s.flushTimer.Stop()
  122. s.flushTimer = nil
  123. }
  124. for _, o := range s.outputs {
  125. if o, ok := o.(io.Closer); ok {
  126. o.Close()
  127. }
  128. }
  129. s.outputs = nil
  130. return nil
  131. }
  132. // WaitCh returns a channel which blocks untill
  133. // the sink is closed.
  134. func (s *Sink) WaitCh() <-chan struct{} {
  135. return s.ctx.Done()
  136. }
  137. // LogPacket is called to insert a packet into the capture.
  138. //
  139. // This function does not take ownership of the provided data slice.
  140. func (s *Sink) LogPacket(path Path, when time.Time, data []byte) {
  141. select {
  142. case <-s.ctx.Done():
  143. return
  144. default:
  145. }
  146. b := bufferPool.Get().(*bytes.Buffer)
  147. b.Reset()
  148. b.Grow(16 + 2 + len(data)) // 16b pcap header + 2b custom data + len
  149. defer bufferPool.Put(b)
  150. writePktHeader(b, when, len(data)+2)
  151. // Custom tailscale debugging data
  152. binary.Write(b, binary.LittleEndian, uint16(path))
  153. b.Write(data)
  154. s.mu.Lock()
  155. defer s.mu.Unlock()
  156. var hadError []set.Handle
  157. for hnd, o := range s.outputs {
  158. if _, err := o.Write(b.Bytes()); err != nil {
  159. hadError = append(hadError, hnd)
  160. continue
  161. }
  162. }
  163. for _, hnd := range hadError {
  164. if o, ok := s.outputs[hnd].(io.Closer); ok {
  165. o.Close()
  166. }
  167. delete(s.outputs, hnd)
  168. }
  169. if s.flushTimer == nil {
  170. s.flushTimer = time.AfterFunc(flushPeriod, func() {
  171. s.mu.Lock()
  172. defer s.mu.Unlock()
  173. for _, o := range s.outputs {
  174. if f, ok := o.(http.Flusher); ok {
  175. f.Flush()
  176. }
  177. }
  178. s.flushTimer = nil
  179. })
  180. }
  181. }