mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-22 04:48:39 +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:
parent
2c98c44d9a
commit
68a66ee81b
@ -37,7 +37,7 @@ while [ "$#" -gt 1 ]; do
|
|||||||
--extra-small)
|
--extra-small)
|
||||||
shift
|
shift
|
||||||
ldflags="$ldflags -w -s"
|
ldflags="$ldflags -w -s"
|
||||||
tags="${tags:+$tags,}ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube,ts_omit_completion,ts_omit_ssh,ts_omit_wakeonlan"
|
tags="${tags:+$tags,}ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube,ts_omit_completion,ts_omit_ssh,ts_omit_wakeonlan,ts_omit_capture"
|
||||||
;;
|
;;
|
||||||
--box)
|
--box)
|
||||||
shift
|
shift
|
||||||
|
@ -802,6 +802,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
tailscale.com/envknob from tailscale.com/client/tailscale+
|
tailscale.com/envknob from tailscale.com/client/tailscale+
|
||||||
tailscale.com/envknob/featureknob from tailscale.com/client/web+
|
tailscale.com/envknob/featureknob from tailscale.com/client/web+
|
||||||
tailscale.com/feature from tailscale.com/feature/wakeonlan+
|
tailscale.com/feature from tailscale.com/feature/wakeonlan+
|
||||||
|
tailscale.com/feature/capture from tailscale.com/feature/condregister
|
||||||
tailscale.com/feature/condregister from tailscale.com/tsnet
|
tailscale.com/feature/condregister from tailscale.com/tsnet
|
||||||
L tailscale.com/feature/tap from tailscale.com/feature/condregister
|
L tailscale.com/feature/tap from tailscale.com/feature/condregister
|
||||||
tailscale.com/feature/wakeonlan from tailscale.com/feature/condregister
|
tailscale.com/feature/wakeonlan from tailscale.com/feature/condregister
|
||||||
@ -814,7 +815,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
💣 tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnlocal+
|
💣 tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnlocal+
|
||||||
tailscale.com/ipn/ipnlocal from tailscale.com/ipn/localapi+
|
tailscale.com/ipn/ipnlocal from tailscale.com/ipn/localapi+
|
||||||
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
|
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
|
||||||
tailscale.com/ipn/localapi from tailscale.com/tsnet
|
tailscale.com/ipn/localapi from tailscale.com/tsnet+
|
||||||
tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal
|
tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal
|
||||||
tailscale.com/ipn/store from tailscale.com/ipn/ipnlocal+
|
tailscale.com/ipn/store from tailscale.com/ipn/ipnlocal+
|
||||||
L tailscale.com/ipn/store/awsstore from tailscale.com/ipn/store
|
L tailscale.com/ipn/store/awsstore from tailscale.com/ipn/store
|
||||||
@ -969,7 +970,6 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
tailscale.com/version from tailscale.com/client/web+
|
tailscale.com/version from tailscale.com/client/web+
|
||||||
tailscale.com/version/distro from tailscale.com/client/web+
|
tailscale.com/version/distro from tailscale.com/client/web+
|
||||||
tailscale.com/wgengine from tailscale.com/ipn/ipnlocal+
|
tailscale.com/wgengine from tailscale.com/ipn/ipnlocal+
|
||||||
tailscale.com/wgengine/capture from tailscale.com/ipn/ipnlocal+
|
|
||||||
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
|
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
|
||||||
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
|
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
|
||||||
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
|
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
|
||||||
|
@ -212,7 +212,7 @@ change in the future.
|
|||||||
exitNodeCmd(),
|
exitNodeCmd(),
|
||||||
updateCmd,
|
updateCmd,
|
||||||
whoisCmd,
|
whoisCmd,
|
||||||
debugCmd,
|
debugCmd(),
|
||||||
driveCmd,
|
driveCmd,
|
||||||
idTokenCmd,
|
idTokenCmd,
|
||||||
advertiseCmd(),
|
advertiseCmd(),
|
||||||
|
@ -25,10 +25,12 @@ import (
|
|||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tka"
|
"tailscale.com/tka"
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
|
"tailscale.com/tstest/deptest"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/types/opt"
|
"tailscale.com/types/opt"
|
||||||
"tailscale.com/types/persist"
|
"tailscale.com/types/persist"
|
||||||
"tailscale.com/types/preftype"
|
"tailscale.com/types/preftype"
|
||||||
|
"tailscale.com/util/set"
|
||||||
"tailscale.com/version/distro"
|
"tailscale.com/version/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1568,3 +1570,31 @@ func TestDocs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
walk(t, root)
|
walk(t, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeps(t *testing.T) {
|
||||||
|
deptest.DepChecker{
|
||||||
|
GOOS: "linux",
|
||||||
|
GOARCH: "arm64",
|
||||||
|
WantDeps: set.Of(
|
||||||
|
"tailscale.com/feature/capture/dissector", // want the Lua by default
|
||||||
|
),
|
||||||
|
BadDeps: map[string]string{
|
||||||
|
"tailscale.com/feature/capture": "don't link capture code",
|
||||||
|
"tailscale.com/net/packet": "why we passing packets in the CLI?",
|
||||||
|
"tailscale.com/net/flowtrack": "why we tracking flows in the CLI?",
|
||||||
|
},
|
||||||
|
}.Check(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDepsNoCapture(t *testing.T) {
|
||||||
|
deptest.DepChecker{
|
||||||
|
GOOS: "linux",
|
||||||
|
GOARCH: "arm64",
|
||||||
|
Tags: "ts_omit_capture",
|
||||||
|
BadDeps: map[string]string{
|
||||||
|
"tailscale.com/feature/capture": "don't link capture code",
|
||||||
|
"tailscale.com/feature/capture/dissector": "don't like the Lua",
|
||||||
|
},
|
||||||
|
}.Check(t)
|
||||||
|
|
||||||
|
}
|
||||||
|
80
cmd/tailscale/cli/debug-capture.go
Normal file
80
cmd/tailscale/cli/debug-capture.go
Normal file
@ -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
|
||||||
|
}
|
@ -20,7 +20,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -45,307 +44,302 @@ import (
|
|||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/must"
|
"tailscale.com/util/must"
|
||||||
"tailscale.com/wgengine/capture"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var debugCmd = &ffcli.Command{
|
var (
|
||||||
Name: "debug",
|
debugCaptureCmd func() *ffcli.Command // or nil
|
||||||
Exec: runDebug,
|
)
|
||||||
ShortUsage: "tailscale debug <debug-flags | subcommand>",
|
|
||||||
ShortHelp: "Debug commands",
|
func debugCmd() *ffcli.Command {
|
||||||
LongHelp: hidden + `"tailscale debug" contains misc debug facilities; it is not a stable interface.`,
|
return &ffcli.Command{
|
||||||
FlagSet: (func() *flag.FlagSet {
|
Name: "debug",
|
||||||
fs := newFlagSet("debug")
|
Exec: runDebug,
|
||||||
fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME")
|
ShortUsage: "tailscale debug <debug-flags | subcommand>",
|
||||||
fs.StringVar(&debugArgs.cpuFile, "cpu-profile", "", "if non-empty, grab a CPU profile for --profile-seconds seconds and write it to this file; - for stdout")
|
ShortHelp: "Debug commands",
|
||||||
fs.StringVar(&debugArgs.memFile, "mem-profile", "", "if non-empty, grab a memory profile and write it to this file; - for stdout")
|
LongHelp: hidden + `"tailscale debug" contains misc debug facilities; it is not a stable interface.`,
|
||||||
fs.IntVar(&debugArgs.cpuSec, "profile-seconds", 15, "number of seconds to run a CPU profile for, when --cpu-profile is non-empty")
|
FlagSet: (func() *flag.FlagSet {
|
||||||
return fs
|
fs := newFlagSet("debug")
|
||||||
})(),
|
fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME")
|
||||||
Subcommands: []*ffcli.Command{
|
fs.StringVar(&debugArgs.cpuFile, "cpu-profile", "", "if non-empty, grab a CPU profile for --profile-seconds seconds and write it to this file; - for stdout")
|
||||||
{
|
fs.StringVar(&debugArgs.memFile, "mem-profile", "", "if non-empty, grab a memory profile and write it to this file; - for stdout")
|
||||||
Name: "derp-map",
|
fs.IntVar(&debugArgs.cpuSec, "profile-seconds", 15, "number of seconds to run a CPU profile for, when --cpu-profile is non-empty")
|
||||||
ShortUsage: "tailscale debug derp-map",
|
return fs
|
||||||
Exec: runDERPMap,
|
})(),
|
||||||
ShortHelp: "Print DERP map",
|
Subcommands: nonNilCmds([]*ffcli.Command{
|
||||||
},
|
{
|
||||||
{
|
Name: "derp-map",
|
||||||
Name: "component-logs",
|
ShortUsage: "tailscale debug derp-map",
|
||||||
ShortUsage: "tailscale debug component-logs [" + strings.Join(ipn.DebuggableComponents, "|") + "]",
|
Exec: runDERPMap,
|
||||||
Exec: runDebugComponentLogs,
|
ShortHelp: "Print DERP map",
|
||||||
ShortHelp: "Enable/disable debug logs for a component",
|
},
|
||||||
FlagSet: (func() *flag.FlagSet {
|
{
|
||||||
fs := newFlagSet("component-logs")
|
Name: "component-logs",
|
||||||
fs.DurationVar(&debugComponentLogsArgs.forDur, "for", time.Hour, "how long to enable debug logs for; zero or negative means to disable")
|
ShortUsage: "tailscale debug component-logs [" + strings.Join(ipn.DebuggableComponents, "|") + "]",
|
||||||
return fs
|
Exec: runDebugComponentLogs,
|
||||||
})(),
|
ShortHelp: "Enable/disable debug logs for a component",
|
||||||
},
|
FlagSet: (func() *flag.FlagSet {
|
||||||
{
|
fs := newFlagSet("component-logs")
|
||||||
Name: "daemon-goroutines",
|
fs.DurationVar(&debugComponentLogsArgs.forDur, "for", time.Hour, "how long to enable debug logs for; zero or negative means to disable")
|
||||||
ShortUsage: "tailscale debug daemon-goroutines",
|
return fs
|
||||||
Exec: runDaemonGoroutines,
|
})(),
|
||||||
ShortHelp: "Print tailscaled's goroutines",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "daemon-goroutines",
|
||||||
Name: "daemon-logs",
|
ShortUsage: "tailscale debug daemon-goroutines",
|
||||||
ShortUsage: "tailscale debug daemon-logs",
|
Exec: runDaemonGoroutines,
|
||||||
Exec: runDaemonLogs,
|
ShortHelp: "Print tailscaled's goroutines",
|
||||||
ShortHelp: "Watch tailscaled's server logs",
|
},
|
||||||
FlagSet: (func() *flag.FlagSet {
|
{
|
||||||
fs := newFlagSet("daemon-logs")
|
Name: "daemon-logs",
|
||||||
fs.IntVar(&daemonLogsArgs.verbose, "verbose", 0, "verbosity level")
|
ShortUsage: "tailscale debug daemon-logs",
|
||||||
fs.BoolVar(&daemonLogsArgs.time, "time", false, "include client time")
|
Exec: runDaemonLogs,
|
||||||
return fs
|
ShortHelp: "Watch tailscaled's server logs",
|
||||||
})(),
|
FlagSet: (func() *flag.FlagSet {
|
||||||
},
|
fs := newFlagSet("daemon-logs")
|
||||||
{
|
fs.IntVar(&daemonLogsArgs.verbose, "verbose", 0, "verbosity level")
|
||||||
Name: "metrics",
|
fs.BoolVar(&daemonLogsArgs.time, "time", false, "include client time")
|
||||||
ShortUsage: "tailscale debug metrics",
|
return fs
|
||||||
Exec: runDaemonMetrics,
|
})(),
|
||||||
ShortHelp: "Print tailscaled's metrics",
|
},
|
||||||
FlagSet: (func() *flag.FlagSet {
|
{
|
||||||
fs := newFlagSet("metrics")
|
Name: "metrics",
|
||||||
fs.BoolVar(&metricsArgs.watch, "watch", false, "print JSON dump of delta values")
|
ShortUsage: "tailscale debug metrics",
|
||||||
return fs
|
Exec: runDaemonMetrics,
|
||||||
})(),
|
ShortHelp: "Print tailscaled's metrics",
|
||||||
},
|
FlagSet: (func() *flag.FlagSet {
|
||||||
{
|
fs := newFlagSet("metrics")
|
||||||
Name: "env",
|
fs.BoolVar(&metricsArgs.watch, "watch", false, "print JSON dump of delta values")
|
||||||
ShortUsage: "tailscale debug env",
|
return fs
|
||||||
Exec: runEnv,
|
})(),
|
||||||
ShortHelp: "Print cmd/tailscale environment",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "env",
|
||||||
Name: "stat",
|
ShortUsage: "tailscale debug env",
|
||||||
ShortUsage: "tailscale debug stat <files...>",
|
Exec: runEnv,
|
||||||
Exec: runStat,
|
ShortHelp: "Print cmd/tailscale environment",
|
||||||
ShortHelp: "Stat a file",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "stat",
|
||||||
Name: "hostinfo",
|
ShortUsage: "tailscale debug stat <files...>",
|
||||||
ShortUsage: "tailscale debug hostinfo",
|
Exec: runStat,
|
||||||
Exec: runHostinfo,
|
ShortHelp: "Stat a file",
|
||||||
ShortHelp: "Print hostinfo",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "hostinfo",
|
||||||
Name: "local-creds",
|
ShortUsage: "tailscale debug hostinfo",
|
||||||
ShortUsage: "tailscale debug local-creds",
|
Exec: runHostinfo,
|
||||||
Exec: runLocalCreds,
|
ShortHelp: "Print hostinfo",
|
||||||
ShortHelp: "Print how to access Tailscale LocalAPI",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "local-creds",
|
||||||
Name: "restun",
|
ShortUsage: "tailscale debug local-creds",
|
||||||
ShortUsage: "tailscale debug restun",
|
Exec: runLocalCreds,
|
||||||
Exec: localAPIAction("restun"),
|
ShortHelp: "Print how to access Tailscale LocalAPI",
|
||||||
ShortHelp: "Force a magicsock restun",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "restun",
|
||||||
Name: "rebind",
|
ShortUsage: "tailscale debug restun",
|
||||||
ShortUsage: "tailscale debug rebind",
|
Exec: localAPIAction("restun"),
|
||||||
Exec: localAPIAction("rebind"),
|
ShortHelp: "Force a magicsock restun",
|
||||||
ShortHelp: "Force a magicsock rebind",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "rebind",
|
||||||
Name: "derp-set-on-demand",
|
ShortUsage: "tailscale debug rebind",
|
||||||
ShortUsage: "tailscale debug derp-set-on-demand",
|
Exec: localAPIAction("rebind"),
|
||||||
Exec: localAPIAction("derp-set-homeless"),
|
ShortHelp: "Force a magicsock rebind",
|
||||||
ShortHelp: "Enable DERP on-demand mode (breaks reachability)",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "derp-set-on-demand",
|
||||||
Name: "derp-unset-on-demand",
|
ShortUsage: "tailscale debug derp-set-on-demand",
|
||||||
ShortUsage: "tailscale debug derp-unset-on-demand",
|
Exec: localAPIAction("derp-set-homeless"),
|
||||||
Exec: localAPIAction("derp-unset-homeless"),
|
ShortHelp: "Enable DERP on-demand mode (breaks reachability)",
|
||||||
ShortHelp: "Disable DERP on-demand mode",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "derp-unset-on-demand",
|
||||||
Name: "break-tcp-conns",
|
ShortUsage: "tailscale debug derp-unset-on-demand",
|
||||||
ShortUsage: "tailscale debug break-tcp-conns",
|
Exec: localAPIAction("derp-unset-homeless"),
|
||||||
Exec: localAPIAction("break-tcp-conns"),
|
ShortHelp: "Disable DERP on-demand mode",
|
||||||
ShortHelp: "Break any open TCP connections from the daemon",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "break-tcp-conns",
|
||||||
Name: "break-derp-conns",
|
ShortUsage: "tailscale debug break-tcp-conns",
|
||||||
ShortUsage: "tailscale debug break-derp-conns",
|
Exec: localAPIAction("break-tcp-conns"),
|
||||||
Exec: localAPIAction("break-derp-conns"),
|
ShortHelp: "Break any open TCP connections from the daemon",
|
||||||
ShortHelp: "Break any open DERP connections from the daemon",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "break-derp-conns",
|
||||||
Name: "pick-new-derp",
|
ShortUsage: "tailscale debug break-derp-conns",
|
||||||
ShortUsage: "tailscale debug pick-new-derp",
|
Exec: localAPIAction("break-derp-conns"),
|
||||||
Exec: localAPIAction("pick-new-derp"),
|
ShortHelp: "Break any open DERP connections from the daemon",
|
||||||
ShortHelp: "Switch to some other random DERP home region for a short time",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "pick-new-derp",
|
||||||
Name: "force-prefer-derp",
|
ShortUsage: "tailscale debug pick-new-derp",
|
||||||
ShortUsage: "tailscale debug force-prefer-derp",
|
Exec: localAPIAction("pick-new-derp"),
|
||||||
Exec: forcePreferDERP,
|
ShortHelp: "Switch to some other random DERP home region for a short time",
|
||||||
ShortHelp: "Prefer the given region ID if reachable (until restart, or 0 to clear)",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "force-prefer-derp",
|
||||||
Name: "force-netmap-update",
|
ShortUsage: "tailscale debug force-prefer-derp",
|
||||||
ShortUsage: "tailscale debug force-netmap-update",
|
Exec: forcePreferDERP,
|
||||||
Exec: localAPIAction("force-netmap-update"),
|
ShortHelp: "Prefer the given region ID if reachable (until restart, or 0 to clear)",
|
||||||
ShortHelp: "Force a full no-op netmap update (for load testing)",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "force-netmap-update",
|
||||||
// TODO(bradfitz,maisem): eventually promote this out of debug
|
ShortUsage: "tailscale debug force-netmap-update",
|
||||||
Name: "reload-config",
|
Exec: localAPIAction("force-netmap-update"),
|
||||||
ShortUsage: "tailscale debug reload-config",
|
ShortHelp: "Force a full no-op netmap update (for load testing)",
|
||||||
Exec: reloadConfig,
|
},
|
||||||
ShortHelp: "Reload config",
|
{
|
||||||
},
|
// TODO(bradfitz,maisem): eventually promote this out of debug
|
||||||
{
|
Name: "reload-config",
|
||||||
Name: "control-knobs",
|
ShortUsage: "tailscale debug reload-config",
|
||||||
ShortUsage: "tailscale debug control-knobs",
|
Exec: reloadConfig,
|
||||||
Exec: debugControlKnobs,
|
ShortHelp: "Reload config",
|
||||||
ShortHelp: "See current control knobs",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "control-knobs",
|
||||||
Name: "prefs",
|
ShortUsage: "tailscale debug control-knobs",
|
||||||
ShortUsage: "tailscale debug prefs",
|
Exec: debugControlKnobs,
|
||||||
Exec: runPrefs,
|
ShortHelp: "See current control knobs",
|
||||||
ShortHelp: "Print prefs",
|
},
|
||||||
FlagSet: (func() *flag.FlagSet {
|
{
|
||||||
fs := newFlagSet("prefs")
|
Name: "prefs",
|
||||||
fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output")
|
ShortUsage: "tailscale debug prefs",
|
||||||
return fs
|
Exec: runPrefs,
|
||||||
})(),
|
ShortHelp: "Print prefs",
|
||||||
},
|
FlagSet: (func() *flag.FlagSet {
|
||||||
{
|
fs := newFlagSet("prefs")
|
||||||
Name: "watch-ipn",
|
fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output")
|
||||||
ShortUsage: "tailscale debug watch-ipn",
|
return fs
|
||||||
Exec: runWatchIPN,
|
})(),
|
||||||
ShortHelp: "Subscribe to IPN message bus",
|
},
|
||||||
FlagSet: (func() *flag.FlagSet {
|
{
|
||||||
fs := newFlagSet("watch-ipn")
|
Name: "watch-ipn",
|
||||||
fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages")
|
ShortUsage: "tailscale debug watch-ipn",
|
||||||
fs.BoolVar(&watchIPNArgs.initial, "initial", false, "include initial status")
|
Exec: runWatchIPN,
|
||||||
fs.BoolVar(&watchIPNArgs.rateLimit, "rate-limit", true, "rate limit messags")
|
ShortHelp: "Subscribe to IPN message bus",
|
||||||
fs.BoolVar(&watchIPNArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap")
|
FlagSet: (func() *flag.FlagSet {
|
||||||
fs.IntVar(&watchIPNArgs.count, "count", 0, "exit after printing this many statuses, or 0 to keep going forever")
|
fs := newFlagSet("watch-ipn")
|
||||||
return fs
|
fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages")
|
||||||
})(),
|
fs.BoolVar(&watchIPNArgs.initial, "initial", false, "include initial status")
|
||||||
},
|
fs.BoolVar(&watchIPNArgs.rateLimit, "rate-limit", true, "rate limit messags")
|
||||||
{
|
fs.BoolVar(&watchIPNArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap")
|
||||||
Name: "netmap",
|
fs.IntVar(&watchIPNArgs.count, "count", 0, "exit after printing this many statuses, or 0 to keep going forever")
|
||||||
ShortUsage: "tailscale debug netmap",
|
return fs
|
||||||
Exec: runNetmap,
|
})(),
|
||||||
ShortHelp: "Print the current network map",
|
},
|
||||||
FlagSet: (func() *flag.FlagSet {
|
{
|
||||||
fs := newFlagSet("netmap")
|
Name: "netmap",
|
||||||
fs.BoolVar(&netmapArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap")
|
ShortUsage: "tailscale debug netmap",
|
||||||
return fs
|
Exec: runNetmap,
|
||||||
})(),
|
ShortHelp: "Print the current network map",
|
||||||
},
|
FlagSet: (func() *flag.FlagSet {
|
||||||
{
|
fs := newFlagSet("netmap")
|
||||||
Name: "via",
|
fs.BoolVar(&netmapArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap")
|
||||||
ShortUsage: "tailscale debug via <site-id> <v4-cidr>\n" +
|
return fs
|
||||||
"tailscale debug via <v6-route>",
|
})(),
|
||||||
Exec: runVia,
|
},
|
||||||
ShortHelp: "Convert between site-specific IPv4 CIDRs and IPv6 'via' routes",
|
{
|
||||||
},
|
Name: "via",
|
||||||
{
|
ShortUsage: "tailscale debug via <site-id> <v4-cidr>\n" +
|
||||||
Name: "ts2021",
|
"tailscale debug via <v6-route>",
|
||||||
ShortUsage: "tailscale debug ts2021",
|
Exec: runVia,
|
||||||
Exec: runTS2021,
|
ShortHelp: "Convert between site-specific IPv4 CIDRs and IPv6 'via' routes",
|
||||||
ShortHelp: "Debug ts2021 protocol connectivity",
|
},
|
||||||
FlagSet: (func() *flag.FlagSet {
|
{
|
||||||
fs := newFlagSet("ts2021")
|
Name: "ts2021",
|
||||||
fs.StringVar(&ts2021Args.host, "host", "controlplane.tailscale.com", "hostname of control plane")
|
ShortUsage: "tailscale debug ts2021",
|
||||||
fs.IntVar(&ts2021Args.version, "version", int(tailcfg.CurrentCapabilityVersion), "protocol version")
|
Exec: runTS2021,
|
||||||
fs.BoolVar(&ts2021Args.verbose, "verbose", false, "be extra verbose")
|
ShortHelp: "Debug ts2021 protocol connectivity",
|
||||||
return fs
|
FlagSet: (func() *flag.FlagSet {
|
||||||
})(),
|
fs := newFlagSet("ts2021")
|
||||||
},
|
fs.StringVar(&ts2021Args.host, "host", "controlplane.tailscale.com", "hostname of control plane")
|
||||||
{
|
fs.IntVar(&ts2021Args.version, "version", int(tailcfg.CurrentCapabilityVersion), "protocol version")
|
||||||
Name: "set-expire",
|
fs.BoolVar(&ts2021Args.verbose, "verbose", false, "be extra verbose")
|
||||||
ShortUsage: "tailscale debug set-expire --in=1m",
|
return fs
|
||||||
Exec: runSetExpire,
|
})(),
|
||||||
ShortHelp: "Manipulate node key expiry for testing",
|
},
|
||||||
FlagSet: (func() *flag.FlagSet {
|
{
|
||||||
fs := newFlagSet("set-expire")
|
Name: "set-expire",
|
||||||
fs.DurationVar(&setExpireArgs.in, "in", 0, "if non-zero, set node key to expire this duration from now")
|
ShortUsage: "tailscale debug set-expire --in=1m",
|
||||||
return fs
|
Exec: runSetExpire,
|
||||||
})(),
|
ShortHelp: "Manipulate node key expiry for testing",
|
||||||
},
|
FlagSet: (func() *flag.FlagSet {
|
||||||
{
|
fs := newFlagSet("set-expire")
|
||||||
Name: "dev-store-set",
|
fs.DurationVar(&setExpireArgs.in, "in", 0, "if non-zero, set node key to expire this duration from now")
|
||||||
ShortUsage: "tailscale debug dev-store-set",
|
return fs
|
||||||
Exec: runDevStoreSet,
|
})(),
|
||||||
ShortHelp: "Set a key/value pair during development",
|
},
|
||||||
FlagSet: (func() *flag.FlagSet {
|
{
|
||||||
fs := newFlagSet("store-set")
|
Name: "dev-store-set",
|
||||||
fs.BoolVar(&devStoreSetArgs.danger, "danger", false, "accept danger")
|
ShortUsage: "tailscale debug dev-store-set",
|
||||||
return fs
|
Exec: runDevStoreSet,
|
||||||
})(),
|
ShortHelp: "Set a key/value pair during development",
|
||||||
},
|
FlagSet: (func() *flag.FlagSet {
|
||||||
{
|
fs := newFlagSet("store-set")
|
||||||
Name: "derp",
|
fs.BoolVar(&devStoreSetArgs.danger, "danger", false, "accept danger")
|
||||||
ShortUsage: "tailscale debug derp",
|
return fs
|
||||||
Exec: runDebugDERP,
|
})(),
|
||||||
ShortHelp: "Test a DERP configuration",
|
},
|
||||||
},
|
{
|
||||||
{
|
Name: "derp",
|
||||||
Name: "capture",
|
ShortUsage: "tailscale debug derp",
|
||||||
ShortUsage: "tailscale debug capture",
|
Exec: runDebugDERP,
|
||||||
Exec: runCapture,
|
ShortHelp: "Test a DERP configuration",
|
||||||
ShortHelp: "Stream pcaps for debugging",
|
},
|
||||||
FlagSet: (func() *flag.FlagSet {
|
ccall(debugCaptureCmd),
|
||||||
fs := newFlagSet("capture")
|
{
|
||||||
fs.StringVar(&captureArgs.outFile, "o", "", "path to stream the pcap (or - for stdout), leave empty to start wireshark")
|
Name: "portmap",
|
||||||
return fs
|
ShortUsage: "tailscale debug portmap",
|
||||||
})(),
|
Exec: debugPortmap,
|
||||||
},
|
ShortHelp: "Run portmap debugging",
|
||||||
{
|
FlagSet: (func() *flag.FlagSet {
|
||||||
Name: "portmap",
|
fs := newFlagSet("portmap")
|
||||||
ShortUsage: "tailscale debug portmap",
|
fs.DurationVar(&debugPortmapArgs.duration, "duration", 5*time.Second, "timeout for port mapping")
|
||||||
Exec: debugPortmap,
|
fs.StringVar(&debugPortmapArgs.ty, "type", "", `portmap debug type (one of "", "pmp", "pcp", or "upnp")`)
|
||||||
ShortHelp: "Run portmap debugging",
|
fs.StringVar(&debugPortmapArgs.gatewayAddr, "gateway-addr", "", `override gateway IP (must also pass --self-addr)`)
|
||||||
FlagSet: (func() *flag.FlagSet {
|
fs.StringVar(&debugPortmapArgs.selfAddr, "self-addr", "", `override self IP (must also pass --gateway-addr)`)
|
||||||
fs := newFlagSet("portmap")
|
fs.BoolVar(&debugPortmapArgs.logHTTP, "log-http", false, `print all HTTP requests and responses to the log`)
|
||||||
fs.DurationVar(&debugPortmapArgs.duration, "duration", 5*time.Second, "timeout for port mapping")
|
return fs
|
||||||
fs.StringVar(&debugPortmapArgs.ty, "type", "", `portmap debug type (one of "", "pmp", "pcp", or "upnp")`)
|
})(),
|
||||||
fs.StringVar(&debugPortmapArgs.gatewayAddr, "gateway-addr", "", `override gateway IP (must also pass --self-addr)`)
|
},
|
||||||
fs.StringVar(&debugPortmapArgs.selfAddr, "self-addr", "", `override self IP (must also pass --gateway-addr)`)
|
{
|
||||||
fs.BoolVar(&debugPortmapArgs.logHTTP, "log-http", false, `print all HTTP requests and responses to the log`)
|
Name: "peer-endpoint-changes",
|
||||||
return fs
|
ShortUsage: "tailscale debug peer-endpoint-changes <hostname-or-IP>",
|
||||||
})(),
|
Exec: runPeerEndpointChanges,
|
||||||
},
|
ShortHelp: "Print debug information about a peer's endpoint changes",
|
||||||
{
|
},
|
||||||
Name: "peer-endpoint-changes",
|
{
|
||||||
ShortUsage: "tailscale debug peer-endpoint-changes <hostname-or-IP>",
|
Name: "dial-types",
|
||||||
Exec: runPeerEndpointChanges,
|
ShortUsage: "tailscale debug dial-types <hostname-or-IP> <port>",
|
||||||
ShortHelp: "Print debug information about a peer's endpoint changes",
|
Exec: runDebugDialTypes,
|
||||||
},
|
ShortHelp: "Print debug information about connecting to a given host or IP",
|
||||||
{
|
FlagSet: (func() *flag.FlagSet {
|
||||||
Name: "dial-types",
|
fs := newFlagSet("dial-types")
|
||||||
ShortUsage: "tailscale debug dial-types <hostname-or-IP> <port>",
|
fs.StringVar(&debugDialTypesArgs.network, "network", "tcp", `network type to dial ("tcp", "udp", etc.)`)
|
||||||
Exec: runDebugDialTypes,
|
return fs
|
||||||
ShortHelp: "Print debug information about connecting to a given host or IP",
|
})(),
|
||||||
FlagSet: (func() *flag.FlagSet {
|
},
|
||||||
fs := newFlagSet("dial-types")
|
{
|
||||||
fs.StringVar(&debugDialTypesArgs.network, "network", "tcp", `network type to dial ("tcp", "udp", etc.)`)
|
Name: "resolve",
|
||||||
return fs
|
ShortUsage: "tailscale debug resolve <hostname>",
|
||||||
})(),
|
Exec: runDebugResolve,
|
||||||
},
|
ShortHelp: "Does a DNS lookup",
|
||||||
{
|
FlagSet: (func() *flag.FlagSet {
|
||||||
Name: "resolve",
|
fs := newFlagSet("resolve")
|
||||||
ShortUsage: "tailscale debug resolve <hostname>",
|
fs.StringVar(&resolveArgs.net, "net", "ip", "network type to resolve (ip, ip4, ip6)")
|
||||||
Exec: runDebugResolve,
|
return fs
|
||||||
ShortHelp: "Does a DNS lookup",
|
})(),
|
||||||
FlagSet: (func() *flag.FlagSet {
|
},
|
||||||
fs := newFlagSet("resolve")
|
{
|
||||||
fs.StringVar(&resolveArgs.net, "net", "ip", "network type to resolve (ip, ip4, ip6)")
|
Name: "go-buildinfo",
|
||||||
return fs
|
ShortUsage: "tailscale debug go-buildinfo",
|
||||||
})(),
|
ShortHelp: "Print Go's runtime/debug.BuildInfo",
|
||||||
},
|
Exec: runGoBuildInfo,
|
||||||
{
|
},
|
||||||
Name: "go-buildinfo",
|
}...),
|
||||||
ShortUsage: "tailscale debug go-buildinfo",
|
}
|
||||||
ShortHelp: "Print Go's runtime/debug.BuildInfo",
|
|
||||||
Exec: runGoBuildInfo,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGoBuildInfo(ctx context.Context, args []string) error {
|
func runGoBuildInfo(ctx context.Context, args []string) error {
|
||||||
@ -1036,50 +1030,6 @@ func runSetExpire(ctx context.Context, args []string) error {
|
|||||||
return localClient.DebugSetExpireIn(ctx, setExpireArgs.in)
|
return localClient.DebugSetExpireIn(ctx, setExpireArgs.in)
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
|
||||||
lua.Write([]byte(capture.DissectorLua))
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
var debugPortmapArgs struct {
|
var debugPortmapArgs struct {
|
||||||
duration time.Duration
|
duration time.Duration
|
||||||
gatewayAddr string
|
gatewayAddr string
|
||||||
|
@ -88,6 +88,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/drive from tailscale.com/client/tailscale+
|
tailscale.com/drive from tailscale.com/client/tailscale+
|
||||||
tailscale.com/envknob from tailscale.com/client/tailscale+
|
tailscale.com/envknob from tailscale.com/client/tailscale+
|
||||||
tailscale.com/envknob/featureknob from tailscale.com/client/web
|
tailscale.com/envknob/featureknob from tailscale.com/client/web
|
||||||
|
tailscale.com/feature/capture/dissector from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/health from tailscale.com/net/tlsdial+
|
tailscale.com/health from tailscale.com/net/tlsdial+
|
||||||
tailscale.com/health/healthmsg from tailscale.com/cmd/tailscale/cli
|
tailscale.com/health/healthmsg from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/hostinfo from tailscale.com/client/web+
|
tailscale.com/hostinfo from tailscale.com/client/web+
|
||||||
@ -102,7 +103,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/net/dns/recursive from tailscale.com/net/dnsfallback
|
tailscale.com/net/dns/recursive from tailscale.com/net/dnsfallback
|
||||||
tailscale.com/net/dnscache from tailscale.com/control/controlhttp+
|
tailscale.com/net/dnscache from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/net/dnsfallback from tailscale.com/control/controlhttp+
|
tailscale.com/net/dnsfallback from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/net/flowtrack from tailscale.com/net/packet
|
|
||||||
tailscale.com/net/netaddr from tailscale.com/ipn+
|
tailscale.com/net/netaddr from tailscale.com/ipn+
|
||||||
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
|
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
tailscale.com/net/neterror from tailscale.com/net/netcheck+
|
||||||
@ -110,7 +110,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
💣 tailscale.com/net/netmon from tailscale.com/cmd/tailscale/cli+
|
💣 tailscale.com/net/netmon from tailscale.com/cmd/tailscale/cli+
|
||||||
💣 tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
💣 tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/net/netutil from tailscale.com/client/tailscale+
|
tailscale.com/net/netutil from tailscale.com/client/tailscale+
|
||||||
tailscale.com/net/packet from tailscale.com/wgengine/capture
|
|
||||||
tailscale.com/net/ping from tailscale.com/net/netcheck
|
tailscale.com/net/ping from tailscale.com/net/netcheck
|
||||||
tailscale.com/net/portmapper from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/net/portmapper from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/net/sockstats from tailscale.com/control/controlhttp+
|
tailscale.com/net/sockstats from tailscale.com/control/controlhttp+
|
||||||
@ -133,7 +132,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/tsweb/varz from tailscale.com/util/usermetric
|
tailscale.com/tsweb/varz from tailscale.com/util/usermetric
|
||||||
tailscale.com/types/dnstype from tailscale.com/tailcfg+
|
tailscale.com/types/dnstype from tailscale.com/tailcfg+
|
||||||
tailscale.com/types/empty from tailscale.com/ipn
|
tailscale.com/types/empty from tailscale.com/ipn
|
||||||
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
|
tailscale.com/types/ipproto from tailscale.com/ipn+
|
||||||
tailscale.com/types/key from tailscale.com/client/tailscale+
|
tailscale.com/types/key from tailscale.com/client/tailscale+
|
||||||
tailscale.com/types/lazy from tailscale.com/util/testenv+
|
tailscale.com/types/lazy from tailscale.com/util/testenv+
|
||||||
tailscale.com/types/logger from tailscale.com/client/web+
|
tailscale.com/types/logger from tailscale.com/client/web+
|
||||||
@ -185,7 +184,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
|
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
|
||||||
tailscale.com/version from tailscale.com/client/web+
|
tailscale.com/version from tailscale.com/client/web+
|
||||||
tailscale.com/version/distro from tailscale.com/client/web+
|
tailscale.com/version/distro from tailscale.com/client/web+
|
||||||
tailscale.com/wgengine/capture from tailscale.com/cmd/tailscale/cli
|
|
||||||
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap
|
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap
|
||||||
golang.org/x/crypto/argon2 from tailscale.com/tka
|
golang.org/x/crypto/argon2 from tailscale.com/tka
|
||||||
golang.org/x/crypto/blake2b from golang.org/x/crypto/argon2+
|
golang.org/x/crypto/blake2b from golang.org/x/crypto/argon2+
|
||||||
|
@ -260,6 +260,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/envknob from tailscale.com/client/tailscale+
|
tailscale.com/envknob from tailscale.com/client/tailscale+
|
||||||
tailscale.com/envknob/featureknob from tailscale.com/client/web+
|
tailscale.com/envknob/featureknob from tailscale.com/client/web+
|
||||||
tailscale.com/feature from tailscale.com/feature/wakeonlan+
|
tailscale.com/feature from tailscale.com/feature/wakeonlan+
|
||||||
|
tailscale.com/feature/capture from tailscale.com/feature/condregister
|
||||||
tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled
|
tailscale.com/feature/condregister from tailscale.com/cmd/tailscaled
|
||||||
L tailscale.com/feature/tap from tailscale.com/feature/condregister
|
L tailscale.com/feature/tap from tailscale.com/feature/condregister
|
||||||
tailscale.com/feature/wakeonlan from tailscale.com/feature/condregister
|
tailscale.com/feature/wakeonlan from tailscale.com/feature/condregister
|
||||||
@ -273,7 +274,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+
|
tailscale.com/ipn/ipnlocal from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled
|
tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled
|
||||||
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
|
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
|
||||||
tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver
|
tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver+
|
||||||
tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal
|
tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal
|
||||||
tailscale.com/ipn/store from tailscale.com/cmd/tailscaled+
|
tailscale.com/ipn/store from tailscale.com/cmd/tailscaled+
|
||||||
L tailscale.com/ipn/store/awsstore from tailscale.com/ipn/store
|
L tailscale.com/ipn/store/awsstore from tailscale.com/ipn/store
|
||||||
@ -422,7 +423,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/version/distro from tailscale.com/client/web+
|
tailscale.com/version/distro from tailscale.com/client/web+
|
||||||
W tailscale.com/wf from tailscale.com/cmd/tailscaled
|
W tailscale.com/wf from tailscale.com/cmd/tailscaled
|
||||||
tailscale.com/wgengine from tailscale.com/cmd/tailscaled+
|
tailscale.com/wgengine from tailscale.com/cmd/tailscaled+
|
||||||
tailscale.com/wgengine/capture from tailscale.com/ipn/ipnlocal+
|
|
||||||
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
|
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
|
||||||
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
|
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
|
||||||
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
|
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
|
||||||
|
@ -13,21 +13,44 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "embed"
|
"tailscale.com/feature"
|
||||||
|
"tailscale.com/ipn/localapi"
|
||||||
"tailscale.com/net/packet"
|
"tailscale.com/net/packet"
|
||||||
"tailscale.com/util/set"
|
"tailscale.com/util/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed ts-dissector.lua
|
func init() {
|
||||||
var DissectorLua string
|
feature.Register("capture")
|
||||||
|
localapi.Register("debug-capture", serveLocalAPIDebugCapture)
|
||||||
|
}
|
||||||
|
|
||||||
// Callback describes a function which is called to
|
func serveLocalAPIDebugCapture(h *localapi.Handler, w http.ResponseWriter, r *http.Request) {
|
||||||
// record packets when debugging packet-capture.
|
ctx := r.Context()
|
||||||
// Such callbacks must not take ownership of the
|
if !h.PermitWrite {
|
||||||
// provided data slice: it may only copy out of it
|
http.Error(w, "debug access denied", http.StatusForbidden)
|
||||||
// within the lifetime of the function.
|
return
|
||||||
type Callback func(Path, time.Time, []byte, packet.CaptureMeta)
|
}
|
||||||
|
if r.Method != "POST" {
|
||||||
|
http.Error(w, "POST required", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
|
||||||
|
b := h.LocalBackend()
|
||||||
|
s := b.GetOrSetCaptureSink(newSink)
|
||||||
|
|
||||||
|
unregister := s.RegisterOutput(w)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-s.WaitCh():
|
||||||
|
}
|
||||||
|
unregister()
|
||||||
|
|
||||||
|
b.ClearCaptureSink()
|
||||||
|
}
|
||||||
|
|
||||||
var bufferPool = sync.Pool{
|
var bufferPool = sync.Pool{
|
||||||
New: func() any {
|
New: func() any {
|
||||||
@ -57,29 +80,8 @@ func writePktHeader(w *bytes.Buffer, when time.Time, length int) {
|
|||||||
binary.Write(w, binary.LittleEndian, uint32(length)) // total length
|
binary.Write(w, binary.LittleEndian, uint32(length)) // total length
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path describes where in the data path the packet was captured.
|
// newSink creates a new capture sink.
|
||||||
type Path uint8
|
func newSink() packet.CaptureSink {
|
||||||
|
|
||||||
// Valid Path 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 Path = 0
|
|
||||||
// FromPeer indicates the packet was logged upon reception from a remote peer.
|
|
||||||
FromPeer Path = 1
|
|
||||||
// SynthesizedToLocal indicates the packet was generated from within tailscaled,
|
|
||||||
// and is being routed to the local machine's network stack.
|
|
||||||
SynthesizedToLocal Path = 2
|
|
||||||
// SynthesizedToPeer indicates the packet was generated from within tailscaled,
|
|
||||||
// and is being routed to a remote Wireguard peer.
|
|
||||||
SynthesizedToPeer Path = 3
|
|
||||||
|
|
||||||
// PathDisco indicates the packet is information about a disco frame.
|
|
||||||
PathDisco Path = 254
|
|
||||||
)
|
|
||||||
|
|
||||||
// New creates a new capture sink.
|
|
||||||
func New() *Sink {
|
|
||||||
ctx, c := context.WithCancel(context.Background())
|
ctx, c := context.WithCancel(context.Background())
|
||||||
return &Sink{
|
return &Sink{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@ -126,6 +128,10 @@ func (s *Sink) RegisterOutput(w io.Writer) (unregister func()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Sink) CaptureCallback() packet.CaptureCallback {
|
||||||
|
return s.LogPacket
|
||||||
|
}
|
||||||
|
|
||||||
// NumOutputs returns the number of outputs registered with the sink.
|
// NumOutputs returns the number of outputs registered with the sink.
|
||||||
func (s *Sink) NumOutputs() int {
|
func (s *Sink) NumOutputs() int {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
@ -174,7 +180,7 @@ func customDataLen(meta packet.CaptureMeta) int {
|
|||||||
// LogPacket is called to insert a packet into the capture.
|
// LogPacket is called to insert a packet into the capture.
|
||||||
//
|
//
|
||||||
// This function does not take ownership of the provided data slice.
|
// This function does not take ownership of the provided data slice.
|
||||||
func (s *Sink) LogPacket(path Path, when time.Time, data []byte, meta packet.CaptureMeta) {
|
func (s *Sink) LogPacket(path packet.CapturePath, when time.Time, data []byte, meta packet.CaptureMeta) {
|
||||||
select {
|
select {
|
||||||
case <-s.ctx.Done():
|
case <-s.ctx.Done():
|
||||||
return
|
return
|
12
feature/capture/dissector/dissector.go
Normal file
12
feature/capture/dissector/dissector.go
Normal file
@ -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
|
8
feature/condregister/maybe_capture.go
Normal file
8
feature/condregister/maybe_capture.go
Normal file
@ -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"
|
@ -73,6 +73,7 @@ import (
|
|||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/netns"
|
"tailscale.com/net/netns"
|
||||||
"tailscale.com/net/netutil"
|
"tailscale.com/net/netutil"
|
||||||
|
"tailscale.com/net/packet"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/net/tsdial"
|
"tailscale.com/net/tsdial"
|
||||||
"tailscale.com/paths"
|
"tailscale.com/paths"
|
||||||
@ -115,7 +116,6 @@ import (
|
|||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
"tailscale.com/version/distro"
|
"tailscale.com/version/distro"
|
||||||
"tailscale.com/wgengine"
|
"tailscale.com/wgengine"
|
||||||
"tailscale.com/wgengine/capture"
|
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
"tailscale.com/wgengine/magicsock"
|
"tailscale.com/wgengine/magicsock"
|
||||||
"tailscale.com/wgengine/router"
|
"tailscale.com/wgengine/router"
|
||||||
@ -209,7 +209,7 @@ type LocalBackend struct {
|
|||||||
// Tailscale on port 5252.
|
// Tailscale on port 5252.
|
||||||
exposeRemoteWebClientAtomicBool atomic.Bool
|
exposeRemoteWebClientAtomicBool atomic.Bool
|
||||||
shutdownCalled bool // if Shutdown has been called
|
shutdownCalled bool // if Shutdown has been called
|
||||||
debugSink *capture.Sink
|
debugSink packet.CaptureSink
|
||||||
sockstatLogger *sockstatlog.Logger
|
sockstatLogger *sockstatlog.Logger
|
||||||
|
|
||||||
// getTCPHandlerForFunnelFlow returns a handler for an incoming TCP flow for
|
// 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
|
// Shutdown halts the backend and all its sub-components. The backend
|
||||||
// can no longer be used after Shutdown returns.
|
// can no longer be used after Shutdown returns.
|
||||||
func (b *LocalBackend) Shutdown() {
|
func (b *LocalBackend) Shutdown() {
|
||||||
@ -7154,48 +7188,6 @@ func (b *LocalBackend) ResetAuth() error {
|
|||||||
return b.resetForProfileChangeLockedOnEntry(unlock)
|
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) {
|
func (b *LocalBackend) GetPeerEndpointChanges(ctx context.Context, ip netip.Addr) ([]magicsock.EndpointChange, error) {
|
||||||
pip, ok := b.e.PeerForIP(ip)
|
pip, ok := b.e.PeerForIP(ip)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -68,12 +68,12 @@ import (
|
|||||||
"tailscale.com/wgengine/magicsock"
|
"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
|
// 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
|
// Request.URL.Path after "/localapi/v0/". If the key ends with a trailing slash
|
||||||
// then it's a prefix match.
|
// then it's a prefix match.
|
||||||
var handler = map[string]localAPIHandler{
|
var handler = map[string]LocalAPIHandler{
|
||||||
// The prefix match handlers end with a slash:
|
// The prefix match handlers end with a slash:
|
||||||
"cert/": (*Handler).serveCert,
|
"cert/": (*Handler).serveCert,
|
||||||
"file-put/": (*Handler).serveFilePut,
|
"file-put/": (*Handler).serveFilePut,
|
||||||
@ -90,7 +90,6 @@ var handler = map[string]localAPIHandler{
|
|||||||
"check-udp-gro-forwarding": (*Handler).serveCheckUDPGROForwarding,
|
"check-udp-gro-forwarding": (*Handler).serveCheckUDPGROForwarding,
|
||||||
"component-debug-logging": (*Handler).serveComponentDebugLogging,
|
"component-debug-logging": (*Handler).serveComponentDebugLogging,
|
||||||
"debug": (*Handler).serveDebug,
|
"debug": (*Handler).serveDebug,
|
||||||
"debug-capture": (*Handler).serveDebugCapture,
|
|
||||||
"debug-derp-region": (*Handler).serveDebugDERPRegion,
|
"debug-derp-region": (*Handler).serveDebugDERPRegion,
|
||||||
"debug-dial-types": (*Handler).serveDebugDialTypes,
|
"debug-dial-types": (*Handler).serveDebugDialTypes,
|
||||||
"debug-log": (*Handler).serveDebugLog,
|
"debug-log": (*Handler).serveDebugLog,
|
||||||
@ -152,6 +151,14 @@ var handler = map[string]localAPIHandler{
|
|||||||
"whois": (*Handler).serveWhoIs,
|
"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 (
|
var (
|
||||||
// The clientmetrics package is stateful, but we want to expose a simple
|
// The clientmetrics package is stateful, but we want to expose a simple
|
||||||
// imperative API to local clients, so we need to keep track of
|
// imperative API to local clients, so we need to keep track of
|
||||||
@ -196,6 +203,10 @@ type Handler struct {
|
|||||||
clock tstime.Clock
|
clock tstime.Clock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) LocalBackend() *ipnlocal.LocalBackend {
|
||||||
|
return h.b
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if h.b == nil {
|
if h.b == nil {
|
||||||
http.Error(w, "server has no local backend", http.StatusInternalServerError)
|
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.
|
// handlerForPath returns the LocalAPI handler for the provided Request.URI.Path.
|
||||||
// (the path doesn't include any query parameters)
|
// (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 == "/" {
|
if urlPath == "/" {
|
||||||
return (*Handler).serveLocalAPIRoot, true
|
return (*Handler).serveLocalAPIRoot, true
|
||||||
}
|
}
|
||||||
@ -2689,21 +2700,6 @@ func defBool(a string, def bool) bool {
|
|||||||
return v
|
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) {
|
func (h *Handler) serveDebugLog(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.PermitRead {
|
if !h.PermitRead {
|
||||||
http.Error(w, "debug-log access denied", http.StatusForbidden)
|
http.Error(w, "debug-log access denied", http.StatusForbidden)
|
||||||
|
75
net/packet/capture.go
Normal file
75
net/packet/capture.go
Normal file
@ -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
|
||||||
|
)
|
@ -34,14 +34,6 @@ const (
|
|||||||
TCPECNBits TCPFlag = TCPECNEcho | TCPCWR
|
TCPECNBits TCPFlag = TCPECNEcho | TCPCWR
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parsed is a minimal decoding of a packet suitable for use in filters.
|
// Parsed is a minimal decoding of a packet suitable for use in filters.
|
||||||
type Parsed struct {
|
type Parsed struct {
|
||||||
// b is the byte buffer that this decodes.
|
// b is the byte buffer that this decodes.
|
||||||
|
@ -36,7 +36,6 @@ import (
|
|||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/clientmetric"
|
"tailscale.com/util/clientmetric"
|
||||||
"tailscale.com/util/usermetric"
|
"tailscale.com/util/usermetric"
|
||||||
"tailscale.com/wgengine/capture"
|
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
"tailscale.com/wgengine/netstack/gro"
|
"tailscale.com/wgengine/netstack/gro"
|
||||||
"tailscale.com/wgengine/wgcfg"
|
"tailscale.com/wgengine/wgcfg"
|
||||||
@ -208,7 +207,7 @@ type Wrapper struct {
|
|||||||
// stats maintains per-connection counters.
|
// stats maintains per-connection counters.
|
||||||
stats atomic.Pointer[connstats.Statistics]
|
stats atomic.Pointer[connstats.Statistics]
|
||||||
|
|
||||||
captureHook syncs.AtomicValue[capture.Callback]
|
captureHook syncs.AtomicValue[packet.CaptureCallback]
|
||||||
|
|
||||||
metrics *metrics
|
metrics *metrics
|
||||||
}
|
}
|
||||||
@ -955,7 +954,7 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if captHook != nil {
|
if captHook != nil {
|
||||||
captHook(capture.FromLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
captHook(packet.FromLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||||
}
|
}
|
||||||
if !t.disableFilter {
|
if !t.disableFilter {
|
||||||
var response filter.Response
|
var response filter.Response
|
||||||
@ -1101,9 +1100,9 @@ func (t *Wrapper) injectedRead(res tunInjectedRead, outBuffs [][]byte, sizes []i
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook capture.Callback, pc *peerConfigTable, gro *gro.GRO) (filter.Response, *gro.GRO) {
|
func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook packet.CaptureCallback, pc *peerConfigTable, gro *gro.GRO) (filter.Response, *gro.GRO) {
|
||||||
if captHook != nil {
|
if captHook != nil {
|
||||||
captHook(capture.FromPeer, t.now(), p.Buffer(), p.CaptureMeta)
|
captHook(packet.FromPeer, t.now(), p.Buffer(), p.CaptureMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.IPProto == ipproto.TSMP {
|
if p.IPProto == ipproto.TSMP {
|
||||||
@ -1317,7 +1316,7 @@ func (t *Wrapper) InjectInboundPacketBuffer(pkt *stack.PacketBuffer, buffs [][]b
|
|||||||
p.Decode(buf)
|
p.Decode(buf)
|
||||||
captHook := t.captureHook.Load()
|
captHook := t.captureHook.Load()
|
||||||
if captHook != nil {
|
if captHook != nil {
|
||||||
captHook(capture.SynthesizedToLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
captHook(packet.SynthesizedToLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
invertGSOChecksum(buf, pkt.GSOOptions)
|
invertGSOChecksum(buf, pkt.GSOOptions)
|
||||||
@ -1449,7 +1448,7 @@ func (t *Wrapper) InjectOutboundPacketBuffer(pkt *stack.PacketBuffer) error {
|
|||||||
}
|
}
|
||||||
if capt := t.captureHook.Load(); capt != nil {
|
if capt := t.captureHook.Load(); capt != nil {
|
||||||
b := pkt.ToBuffer()
|
b := pkt.ToBuffer()
|
||||||
capt(capture.SynthesizedToPeer, t.now(), b.Flatten(), packet.CaptureMeta{})
|
capt(packet.SynthesizedToPeer, t.now(), b.Flatten(), packet.CaptureMeta{})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.injectOutbound(tunInjectedRead{packet: pkt})
|
t.injectOutbound(tunInjectedRead{packet: pkt})
|
||||||
@ -1491,6 +1490,6 @@ var (
|
|||||||
metricPacketOutDropSelfDisco = clientmetric.NewCounter("tstun_out_to_wg_drop_self_disco")
|
metricPacketOutDropSelfDisco = clientmetric.NewCounter("tstun_out_to_wg_drop_self_disco")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *Wrapper) InstallCaptureHook(cb capture.Callback) {
|
func (t *Wrapper) InstallCaptureHook(cb packet.CaptureCallback) {
|
||||||
t.captureHook.Store(cb)
|
t.captureHook.Store(cb)
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ import (
|
|||||||
"tailscale.com/types/views"
|
"tailscale.com/types/views"
|
||||||
"tailscale.com/util/must"
|
"tailscale.com/util/must"
|
||||||
"tailscale.com/util/usermetric"
|
"tailscale.com/util/usermetric"
|
||||||
"tailscale.com/wgengine/capture"
|
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
"tailscale.com/wgengine/wgcfg"
|
"tailscale.com/wgengine/wgcfg"
|
||||||
)
|
)
|
||||||
@ -871,14 +870,14 @@ func TestPeerCfg_NAT(t *testing.T) {
|
|||||||
// with the correct parameters when various packet operations are performed.
|
// with the correct parameters when various packet operations are performed.
|
||||||
func TestCaptureHook(t *testing.T) {
|
func TestCaptureHook(t *testing.T) {
|
||||||
type captureRecord struct {
|
type captureRecord struct {
|
||||||
path capture.Path
|
path packet.CapturePath
|
||||||
now time.Time
|
now time.Time
|
||||||
pkt []byte
|
pkt []byte
|
||||||
meta packet.CaptureMeta
|
meta packet.CaptureMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
var captured []captureRecord
|
var captured []captureRecord
|
||||||
hook := func(path capture.Path, now time.Time, pkt []byte, meta packet.CaptureMeta) {
|
hook := func(path packet.CapturePath, now time.Time, pkt []byte, meta packet.CaptureMeta) {
|
||||||
captured = append(captured, captureRecord{
|
captured = append(captured, captureRecord{
|
||||||
path: path,
|
path: path,
|
||||||
now: now,
|
now: now,
|
||||||
@ -935,19 +934,19 @@ func TestCaptureHook(t *testing.T) {
|
|||||||
// Assert that the right packets are captured.
|
// Assert that the right packets are captured.
|
||||||
want := []captureRecord{
|
want := []captureRecord{
|
||||||
{
|
{
|
||||||
path: capture.FromPeer,
|
path: packet.FromPeer,
|
||||||
pkt: []byte("Write1"),
|
pkt: []byte("Write1"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: capture.FromPeer,
|
path: packet.FromPeer,
|
||||||
pkt: []byte("Write2"),
|
pkt: []byte("Write2"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: capture.SynthesizedToLocal,
|
path: packet.SynthesizedToLocal,
|
||||||
pkt: []byte("InjectInboundPacketBuffer"),
|
pkt: []byte("InjectInboundPacketBuffer"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: capture.SynthesizedToPeer,
|
path: packet.SynthesizedToPeer,
|
||||||
pkt: []byte("InjectOutboundPacketBuffer"),
|
pkt: []byte("InjectOutboundPacketBuffer"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ func TestDeps(t *testing.T) {
|
|||||||
"github.com/google/uuid": "see tailscale/tailscale#13760",
|
"github.com/google/uuid": "see tailscale/tailscale#13760",
|
||||||
"tailscale.com/clientupdate/distsign": "downloads via AppStore, not distsign",
|
"tailscale.com/clientupdate/distsign": "downloads via AppStore, not distsign",
|
||||||
"github.com/tailscale/hujson": "no config file support on iOS",
|
"github.com/tailscale/hujson": "no config file support on iOS",
|
||||||
|
"tailscale.com/feature/capture": "no debug packet capture on iOS",
|
||||||
},
|
},
|
||||||
}.Check(t)
|
}.Check(t)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,6 @@ import (
|
|||||||
"tailscale.com/util/set"
|
"tailscale.com/util/set"
|
||||||
"tailscale.com/util/testenv"
|
"tailscale.com/util/testenv"
|
||||||
"tailscale.com/util/usermetric"
|
"tailscale.com/util/usermetric"
|
||||||
"tailscale.com/wgengine/capture"
|
|
||||||
"tailscale.com/wgengine/wgint"
|
"tailscale.com/wgengine/wgint"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -238,7 +237,7 @@ type Conn struct {
|
|||||||
stats atomic.Pointer[connstats.Statistics]
|
stats atomic.Pointer[connstats.Statistics]
|
||||||
|
|
||||||
// captureHook, if non-nil, is the pcap logging callback when capturing.
|
// captureHook, if non-nil, is the pcap logging callback when capturing.
|
||||||
captureHook syncs.AtomicValue[capture.Callback]
|
captureHook syncs.AtomicValue[packet.CaptureCallback]
|
||||||
|
|
||||||
// discoPrivate is the private naclbox key used for active
|
// discoPrivate is the private naclbox key used for active
|
||||||
// discovery traffic. It is always present, and immutable.
|
// discovery traffic. It is always present, and immutable.
|
||||||
@ -655,7 +654,7 @@ func deregisterMetrics(m *metrics) {
|
|||||||
// log debug information into the pcap stream. This function
|
// log debug information into the pcap stream. This function
|
||||||
// can be called with a nil argument to uninstall the capture
|
// can be called with a nil argument to uninstall the capture
|
||||||
// hook.
|
// hook.
|
||||||
func (c *Conn) InstallCaptureHook(cb capture.Callback) {
|
func (c *Conn) InstallCaptureHook(cb packet.CaptureCallback) {
|
||||||
c.captureHook.Store(cb)
|
c.captureHook.Store(cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1709,7 +1708,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
|
|||||||
// Emit information about the disco frame into the pcap stream
|
// Emit information about the disco frame into the pcap stream
|
||||||
// if a capture hook is installed.
|
// if a capture hook is installed.
|
||||||
if cb := c.captureHook.Load(); cb != nil {
|
if cb := c.captureHook.Load(); cb != nil {
|
||||||
cb(capture.PathDisco, time.Now(), disco.ToPCAPFrame(src, derpNodeSrc, payload), packet.CaptureMeta{})
|
cb(packet.PathDisco, time.Now(), disco.ToPCAPFrame(src, derpNodeSrc, payload), packet.CaptureMeta{})
|
||||||
}
|
}
|
||||||
|
|
||||||
dm, err := disco.Parse(payload)
|
dm, err := disco.Parse(payload)
|
||||||
|
@ -51,7 +51,6 @@ import (
|
|||||||
"tailscale.com/util/testenv"
|
"tailscale.com/util/testenv"
|
||||||
"tailscale.com/util/usermetric"
|
"tailscale.com/util/usermetric"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
"tailscale.com/wgengine/capture"
|
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
"tailscale.com/wgengine/magicsock"
|
"tailscale.com/wgengine/magicsock"
|
||||||
"tailscale.com/wgengine/netlog"
|
"tailscale.com/wgengine/netlog"
|
||||||
@ -1594,7 +1593,7 @@ var (
|
|||||||
metricNumMinorChanges = clientmetric.NewCounter("wgengine_minor_changes")
|
metricNumMinorChanges = clientmetric.NewCounter("wgengine_minor_changes")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e *userspaceEngine) InstallCaptureHook(cb capture.Callback) {
|
func (e *userspaceEngine) InstallCaptureHook(cb packet.CaptureCallback) {
|
||||||
e.tundev.InstallCaptureHook(cb)
|
e.tundev.InstallCaptureHook(cb)
|
||||||
e.magicConn.InstallCaptureHook(cb)
|
e.magicConn.InstallCaptureHook(cb)
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,10 @@ import (
|
|||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/dns"
|
"tailscale.com/net/dns"
|
||||||
|
"tailscale.com/net/packet"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/netmap"
|
"tailscale.com/types/netmap"
|
||||||
"tailscale.com/wgengine/capture"
|
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
"tailscale.com/wgengine/router"
|
"tailscale.com/wgengine/router"
|
||||||
"tailscale.com/wgengine/wgcfg"
|
"tailscale.com/wgengine/wgcfg"
|
||||||
@ -162,7 +162,7 @@ func (e *watchdogEngine) Done() <-chan struct{} {
|
|||||||
return e.wrap.Done()
|
return e.wrap.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *watchdogEngine) InstallCaptureHook(cb capture.Callback) {
|
func (e *watchdogEngine) InstallCaptureHook(cb packet.CaptureCallback) {
|
||||||
e.wrap.InstallCaptureHook(cb)
|
e.wrap.InstallCaptureHook(cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ import (
|
|||||||
|
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/dns"
|
"tailscale.com/net/dns"
|
||||||
|
"tailscale.com/net/packet"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/netmap"
|
"tailscale.com/types/netmap"
|
||||||
"tailscale.com/wgengine/capture"
|
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
"tailscale.com/wgengine/router"
|
"tailscale.com/wgengine/router"
|
||||||
"tailscale.com/wgengine/wgcfg"
|
"tailscale.com/wgengine/wgcfg"
|
||||||
@ -129,5 +129,5 @@ type Engine interface {
|
|||||||
// InstallCaptureHook registers a function to be called to capture
|
// InstallCaptureHook registers a function to be called to capture
|
||||||
// packets traversing the data path. The hook can be uninstalled by
|
// packets traversing the data path. The hook can be uninstalled by
|
||||||
// calling this function with a nil value.
|
// calling this function with a nil value.
|
||||||
InstallCaptureHook(capture.Callback)
|
InstallCaptureHook(packet.CaptureCallback)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user