mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-11 13:18:53 +00:00
feature/capture: move packet capture to feature/*, out of iOS + CLI
We had the debug packet capture code + Lua dissector in the CLI + the iOS app. Now we don't, with tests to lock it in. As a bonus, tailscale.com/net/packet and tailscale.com/net/flowtrack no longer appear in the CLI's binary either. A new build tag ts_omit_capture disables the packet capture code and was added to build_dist.sh's --extra-small mode. Updates #12614 Change-Id: I79b0628c0d59911bd4d510c732284d97b0160f10 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:

committed by
Brad Fitzpatrick

parent
2c98c44d9a
commit
68a66ee81b
@@ -73,6 +73,7 @@ import (
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/paths"
|
||||
@@ -115,7 +116,6 @@ 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"
|
||||
@@ -209,7 +209,7 @@ type LocalBackend struct {
|
||||
// Tailscale on port 5252.
|
||||
exposeRemoteWebClientAtomicBool atomic.Bool
|
||||
shutdownCalled bool // if Shutdown has been called
|
||||
debugSink *capture.Sink
|
||||
debugSink packet.CaptureSink
|
||||
sockstatLogger *sockstatlog.Logger
|
||||
|
||||
// getTCPHandlerForFunnelFlow returns a handler for an incoming TCP flow for
|
||||
@@ -948,6 +948,40 @@ func (b *LocalBackend) onHealthChange(w *health.Warnable, us *health.UnhealthySt
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetCaptureSink returns the current packet capture sink, creating it
|
||||
// with the provided newSink function if it does not already exist.
|
||||
func (b *LocalBackend) GetOrSetCaptureSink(newSink func() packet.CaptureSink) packet.CaptureSink {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if b.debugSink != nil {
|
||||
return b.debugSink
|
||||
}
|
||||
s := newSink()
|
||||
b.debugSink = s
|
||||
b.e.InstallCaptureHook(s.CaptureCallback())
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *LocalBackend) ClearCaptureSink() {
|
||||
// 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
|
||||
default:
|
||||
}
|
||||
if b.debugSink != nil && b.debugSink.NumOutputs() == 0 {
|
||||
s := b.debugSink
|
||||
b.e.InstallCaptureHook(nil)
|
||||
b.debugSink = nil
|
||||
s.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown halts the backend and all its sub-components. The backend
|
||||
// can no longer be used after Shutdown returns.
|
||||
func (b *LocalBackend) Shutdown() {
|
||||
@@ -7154,48 +7188,6 @@ func (b *LocalBackend) ResetAuth() error {
|
||||
return b.resetForProfileChangeLockedOnEntry(unlock)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (b *LocalBackend) GetPeerEndpointChanges(ctx context.Context, ip netip.Addr) ([]magicsock.EndpointChange, error) {
|
||||
pip, ok := b.e.PeerForIP(ip)
|
||||
if !ok {
|
||||
|
@@ -68,12 +68,12 @@ import (
|
||||
"tailscale.com/wgengine/magicsock"
|
||||
)
|
||||
|
||||
type localAPIHandler func(*Handler, http.ResponseWriter, *http.Request)
|
||||
type LocalAPIHandler func(*Handler, http.ResponseWriter, *http.Request)
|
||||
|
||||
// handler is the set of LocalAPI handlers, keyed by the part of the
|
||||
// Request.URL.Path after "/localapi/v0/". If the key ends with a trailing slash
|
||||
// then it's a prefix match.
|
||||
var handler = map[string]localAPIHandler{
|
||||
var handler = map[string]LocalAPIHandler{
|
||||
// The prefix match handlers end with a slash:
|
||||
"cert/": (*Handler).serveCert,
|
||||
"file-put/": (*Handler).serveFilePut,
|
||||
@@ -90,7 +90,6 @@ var handler = map[string]localAPIHandler{
|
||||
"check-udp-gro-forwarding": (*Handler).serveCheckUDPGROForwarding,
|
||||
"component-debug-logging": (*Handler).serveComponentDebugLogging,
|
||||
"debug": (*Handler).serveDebug,
|
||||
"debug-capture": (*Handler).serveDebugCapture,
|
||||
"debug-derp-region": (*Handler).serveDebugDERPRegion,
|
||||
"debug-dial-types": (*Handler).serveDebugDialTypes,
|
||||
"debug-log": (*Handler).serveDebugLog,
|
||||
@@ -152,6 +151,14 @@ var handler = map[string]localAPIHandler{
|
||||
"whois": (*Handler).serveWhoIs,
|
||||
}
|
||||
|
||||
// Register registers a new LocalAPI handler for the given name.
|
||||
func Register(name string, fn LocalAPIHandler) {
|
||||
if _, ok := handler[name]; ok {
|
||||
panic("duplicate LocalAPI handler registration: " + name)
|
||||
}
|
||||
handler[name] = fn
|
||||
}
|
||||
|
||||
var (
|
||||
// The clientmetrics package is stateful, but we want to expose a simple
|
||||
// imperative API to local clients, so we need to keep track of
|
||||
@@ -196,6 +203,10 @@ type Handler struct {
|
||||
clock tstime.Clock
|
||||
}
|
||||
|
||||
func (h *Handler) LocalBackend() *ipnlocal.LocalBackend {
|
||||
return h.b
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if h.b == nil {
|
||||
http.Error(w, "server has no local backend", http.StatusInternalServerError)
|
||||
@@ -260,7 +271,7 @@ func (h *Handler) validHost(hostname string) bool {
|
||||
|
||||
// handlerForPath returns the LocalAPI handler for the provided Request.URI.Path.
|
||||
// (the path doesn't include any query parameters)
|
||||
func handlerForPath(urlPath string) (h localAPIHandler, ok bool) {
|
||||
func handlerForPath(urlPath string) (h LocalAPIHandler, ok bool) {
|
||||
if urlPath == "/" {
|
||||
return (*Handler).serveLocalAPIRoot, true
|
||||
}
|
||||
@@ -2689,21 +2700,6 @@ 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(http.StatusOK)
|
||||
w.(http.Flusher).Flush()
|
||||
h.b.StreamDebugCapture(r.Context(), w)
|
||||
}
|
||||
|
||||
func (h *Handler) serveDebugLog(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitRead {
|
||||
http.Error(w, "debug-log access denied", http.StatusForbidden)
|
||||
|
Reference in New Issue
Block a user