mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-12 05:37:32 +00:00
all: implement pcap streaming for datapath debugging
Updates: tailscale/corp#8470 Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
@@ -76,6 +76,7 @@ import (
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
"tailscale.com/wgengine"
|
||||
"tailscale.com/wgengine/capture"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/magicsock"
|
||||
"tailscale.com/wgengine/router"
|
||||
@@ -147,6 +148,7 @@ type LocalBackend struct {
|
||||
em *expiryManager // non-nil
|
||||
sshAtomicBool atomic.Bool
|
||||
shutdownCalled bool // if Shutdown has been called
|
||||
debugSink *capture.Sink
|
||||
|
||||
// lastProfileID tracks the last profile we've seen from the ProfileManager.
|
||||
// It's used to detect when the user has changed their profile.
|
||||
@@ -516,6 +518,11 @@ func (b *LocalBackend) Shutdown() {
|
||||
b.sshServer = nil
|
||||
}
|
||||
b.closePeerAPIListenersLocked()
|
||||
if b.debugSink != nil {
|
||||
b.e.InstallCaptureHook(nil)
|
||||
b.debugSink.Close()
|
||||
b.debugSink = nil
|
||||
}
|
||||
b.mu.Unlock()
|
||||
|
||||
b.unregisterLinkMon()
|
||||
@@ -4837,3 +4844,45 @@ func (b *LocalBackend) ResetAuth() error {
|
||||
}
|
||||
return b.resetForProfileChangeLockedOnEntry()
|
||||
}
|
||||
|
||||
// StreamDebugCapture writes a pcap stream of packets traversing
|
||||
// tailscaled to the provided response writer.
|
||||
func (b *LocalBackend) StreamDebugCapture(ctx context.Context, w io.Writer) error {
|
||||
var s *capture.Sink
|
||||
|
||||
b.mu.Lock()
|
||||
if b.debugSink == nil {
|
||||
s = capture.New()
|
||||
b.debugSink = s
|
||||
b.e.InstallCaptureHook(s.LogPacket)
|
||||
} else {
|
||||
s = b.debugSink
|
||||
}
|
||||
b.mu.Unlock()
|
||||
|
||||
unregister := s.RegisterOutput(w)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-s.WaitCh():
|
||||
}
|
||||
unregister()
|
||||
|
||||
// Shut down & uninstall the sink if there are no longer
|
||||
// any outputs on it.
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
if b.debugSink != nil && b.debugSink.NumOutputs() == 0 {
|
||||
s := b.debugSink
|
||||
b.e.InstallCaptureHook(nil)
|
||||
b.debugSink = nil
|
||||
return s.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -68,6 +68,7 @@ var handler = map[string]localAPIHandler{
|
||||
"debug-derp-region": (*Handler).serveDebugDERPRegion,
|
||||
"debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches,
|
||||
"debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules,
|
||||
"debug-capture": (*Handler).serveDebugCapture,
|
||||
"derpmap": (*Handler).serveDERPMap,
|
||||
"dev-set-state-store": (*Handler).serveDevSetStateStore,
|
||||
"set-push-device-token": (*Handler).serveSetPushDeviceToken,
|
||||
@@ -1556,6 +1557,21 @@ func defBool(a string, def bool) bool {
|
||||
return v
|
||||
}
|
||||
|
||||
func (h *Handler) serveDebugCapture(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "POST required", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(200)
|
||||
w.(http.Flusher).Flush()
|
||||
h.b.StreamDebugCapture(r.Context(), w)
|
||||
}
|
||||
|
||||
var (
|
||||
metricInvalidRequests = clientmetric.NewCounter("localapi_invalid_requests")
|
||||
|
||||
|
Reference in New Issue
Block a user