mirror of https://github.com/tailscale/tailscale/
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>pull/14778/head
parent
2c98c44d9a
commit
68a66ee81b
@ -0,0 +1,80 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios && !ts_omit_capture
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
"tailscale.com/feature/capture/dissector"
|
||||
)
|
||||
|
||||
func init() {
|
||||
debugCaptureCmd = mkDebugCaptureCmd
|
||||
}
|
||||
|
||||
func mkDebugCaptureCmd() *ffcli.Command {
|
||||
return &ffcli.Command{
|
||||
Name: "capture",
|
||||
ShortUsage: "tailscale debug capture",
|
||||
Exec: runCapture,
|
||||
ShortHelp: "Stream pcaps for debugging",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("capture")
|
||||
fs.StringVar(&captureArgs.outFile, "o", "", "path to stream the pcap (or - for stdout), leave empty to start wireshark")
|
||||
return fs
|
||||
})(),
|
||||
}
|
||||
}
|
||||
|
||||
var captureArgs struct {
|
||||
outFile string
|
||||
}
|
||||
|
||||
func runCapture(ctx context.Context, args []string) error {
|
||||
stream, err := localClient.StreamDebugCapture(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
switch captureArgs.outFile {
|
||||
case "-":
|
||||
fmt.Fprintln(Stderr, "Press Ctrl-C to stop the capture.")
|
||||
_, err = io.Copy(os.Stdout, stream)
|
||||
return err
|
||||
case "":
|
||||
lua, err := os.CreateTemp("", "ts-dissector")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(lua.Name())
|
||||
io.WriteString(lua, dissector.Lua)
|
||||
if err := lua.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wireshark := exec.CommandContext(ctx, "wireshark", "-X", "lua_script:"+lua.Name(), "-k", "-i", "-")
|
||||
wireshark.Stdin = stream
|
||||
wireshark.Stdout = os.Stdout
|
||||
wireshark.Stderr = os.Stderr
|
||||
return wireshark.Run()
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(captureArgs.outFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
fmt.Fprintln(Stderr, "Press Ctrl-C to stop the capture.")
|
||||
_, err = io.Copy(f, stream)
|
||||
return err
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package dissector contains the Lua dissector for Tailscale packets.
|
||||
package dissector
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed ts-dissector.lua
|
||||
var Lua string
|
||||
@ -0,0 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios && !ts_omit_capture
|
||||
|
||||
package condregister
|
||||
|
||||
import _ "tailscale.com/feature/capture"
|
||||
@ -0,0 +1,75 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package packet
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/netip"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Callback describes a function which is called to
|
||||
// record packets when debugging packet-capture.
|
||||
// Such callbacks must not take ownership of the
|
||||
// provided data slice: it may only copy out of it
|
||||
// within the lifetime of the function.
|
||||
type CaptureCallback func(CapturePath, time.Time, []byte, CaptureMeta)
|
||||
|
||||
// CaptureSink is the minimal interface from [tailscale.com/feature/capture]'s
|
||||
// Sink type that is needed by the core (magicsock/LocalBackend/wgengine/etc).
|
||||
// This lets the relativel heavy feature/capture package be optionally linked.
|
||||
type CaptureSink interface {
|
||||
// Close closes
|
||||
Close() error
|
||||
|
||||
// NumOutputs returns the number of outputs registered with the sink.
|
||||
NumOutputs() int
|
||||
|
||||
// CaptureCallback returns a callback which can be used to
|
||||
// write packets to the sink.
|
||||
CaptureCallback() CaptureCallback
|
||||
|
||||
// WaitCh returns a channel which blocks until
|
||||
// the sink is closed.
|
||||
WaitCh() <-chan struct{}
|
||||
|
||||
// RegisterOutput connects an output to this sink, which
|
||||
// will be written to with a pcap stream as packets are logged.
|
||||
// A function is returned which unregisters the output when
|
||||
// called.
|
||||
//
|
||||
// If w implements io.Closer, it will be closed upon error
|
||||
// or when the sink is closed. If w implements http.Flusher,
|
||||
// it will be flushed periodically.
|
||||
RegisterOutput(w io.Writer) (unregister func())
|
||||
}
|
||||
|
||||
// CaptureMeta contains metadata that is used when debugging.
|
||||
type CaptureMeta struct {
|
||||
DidSNAT bool // SNAT was performed & the address was updated.
|
||||
OriginalSrc netip.AddrPort // The source address before SNAT was performed.
|
||||
DidDNAT bool // DNAT was performed & the address was updated.
|
||||
OriginalDst netip.AddrPort // The destination address before DNAT was performed.
|
||||
}
|
||||
|
||||
// CapturePath describes where in the data path the packet was captured.
|
||||
type CapturePath uint8
|
||||
|
||||
// CapturePath values
|
||||
const (
|
||||
// FromLocal indicates the packet was logged as it traversed the FromLocal path:
|
||||
// i.e.: A packet from the local system into the TUN.
|
||||
FromLocal CapturePath = 0
|
||||
// FromPeer indicates the packet was logged upon reception from a remote peer.
|
||||
FromPeer CapturePath = 1
|
||||
// SynthesizedToLocal indicates the packet was generated from within tailscaled,
|
||||
// and is being routed to the local machine's network stack.
|
||||
SynthesizedToLocal CapturePath = 2
|
||||
// SynthesizedToPeer indicates the packet was generated from within tailscaled,
|
||||
// and is being routed to a remote Wireguard peer.
|
||||
SynthesizedToPeer CapturePath = 3
|
||||
|
||||
// PathDisco indicates the packet is information about a disco frame.
|
||||
PathDisco CapturePath = 254
|
||||
)
|
||||
Loading…
Reference in New Issue