Compare commits
14 Commits
andrew/exe
...
knyar/inst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
290d23ff83 | ||
|
|
bce05ec6c3 | ||
|
|
8c925899e1 | ||
|
|
04029b857f | ||
|
|
e701fde6b3 | ||
|
|
66b2e9fd07 | ||
|
|
68a66ee81b | ||
|
|
2c98c44d9a | ||
|
|
82e41ddc42 | ||
|
|
2089f4b603 | ||
|
|
ca39c4e150 | ||
|
|
1a7274fccb | ||
|
|
cbf1a9abe1 | ||
|
|
716e4fcc97 |
1
.github/workflows/installer.yml
vendored
1
.github/workflows/installer.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: test installer.sh
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -481,7 +481,7 @@ jobs:
|
||||
- name: check depaware
|
||||
run: |
|
||||
export PATH=$(./tool/go env GOROOT)/bin:$PATH
|
||||
find . -name 'depaware.txt' | xargs -n1 dirname | xargs ./tool/go run github.com/tailscale/depaware --check
|
||||
find . -name 'depaware.txt' | xargs -n1 dirname | xargs ./tool/go run github.com/tailscale/depaware --check --internal
|
||||
|
||||
go_generate:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
4
Makefile
4
Makefile
@@ -17,7 +17,7 @@ lint: ## Run golangci-lint
|
||||
updatedeps: ## Update depaware deps
|
||||
# depaware (via x/tools/go/packages) shells back to "go", so make sure the "go"
|
||||
# it finds in its $$PATH is the right one.
|
||||
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --update \
|
||||
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --update --internal \
|
||||
tailscale.com/cmd/tailscaled \
|
||||
tailscale.com/cmd/tailscale \
|
||||
tailscale.com/cmd/derper \
|
||||
@@ -27,7 +27,7 @@ updatedeps: ## Update depaware deps
|
||||
depaware: ## Run depaware checks
|
||||
# depaware (via x/tools/go/packages) shells back to "go", so make sure the "go"
|
||||
# it finds in its $$PATH is the right one.
|
||||
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --check \
|
||||
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --check --internal \
|
||||
tailscale.com/cmd/tailscaled \
|
||||
tailscale.com/cmd/tailscale \
|
||||
tailscale.com/cmd/derper \
|
||||
|
||||
@@ -37,7 +37,7 @@ while [ "$#" -gt 1 ]; do
|
||||
--extra-small)
|
||||
shift
|
||||
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)
|
||||
shift
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import React, { useState } from "react"
|
||||
import React from "react"
|
||||
import { useAPI } from "src/api"
|
||||
import TailscaleIcon from "src/assets/icons/tailscale-icon.svg?react"
|
||||
import { NodeData } from "src/types"
|
||||
import Button from "src/ui/button"
|
||||
import Collapsible from "src/ui/collapsible"
|
||||
import Input from "src/ui/input"
|
||||
|
||||
/**
|
||||
* LoginView is rendered when the client is not authenticated
|
||||
@@ -15,8 +13,6 @@ import Input from "src/ui/input"
|
||||
*/
|
||||
export default function LoginView({ data }: { data: NodeData }) {
|
||||
const api = useAPI()
|
||||
const [controlURL, setControlURL] = useState<string>("")
|
||||
const [authKey, setAuthKey] = useState<string>("")
|
||||
|
||||
return (
|
||||
<div className="mb-8 py-6 px-8 bg-white rounded-md shadow-2xl">
|
||||
@@ -88,8 +84,6 @@ export default function LoginView({ data }: { data: NodeData }) {
|
||||
action: "up",
|
||||
data: {
|
||||
Reauthenticate: true,
|
||||
ControlURL: controlURL,
|
||||
AuthKey: authKey,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -98,34 +92,6 @@ export default function LoginView({ data }: { data: NodeData }) {
|
||||
>
|
||||
Log In
|
||||
</Button>
|
||||
<Collapsible trigger="Advanced options">
|
||||
<h4 className="font-medium mb-1 mt-2">Auth Key</h4>
|
||||
<p className="text-sm text-gray-500">
|
||||
Connect with a pre-authenticated key.{" "}
|
||||
<a
|
||||
href="https://tailscale.com/kb/1085/auth-keys/"
|
||||
className="link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more →
|
||||
</a>
|
||||
</p>
|
||||
<Input
|
||||
className="mt-2"
|
||||
value={authKey}
|
||||
onChange={(e) => setAuthKey(e.target.value)}
|
||||
placeholder="tskey-auth-XXX"
|
||||
/>
|
||||
<h4 className="font-medium mt-3 mb-1">Server URL</h4>
|
||||
<p className="text-sm text-gray-500">Base URL of control server.</p>
|
||||
<Input
|
||||
className="mt-2"
|
||||
value={controlURL}
|
||||
onChange={(e) => setControlURL(e.target.value)}
|
||||
placeholder="https://login.tailscale.com/"
|
||||
/>
|
||||
</Collapsible>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -189,6 +189,8 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
||||
golang.org/x/crypto/curve25519 from golang.org/x/crypto/nacl/box+
|
||||
golang.org/x/crypto/hkdf from crypto/tls+
|
||||
golang.org/x/crypto/internal/alias from golang.org/x/crypto/chacha20+
|
||||
golang.org/x/crypto/internal/poly1305 from golang.org/x/crypto/chacha20poly1305+
|
||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
@@ -201,6 +203,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
golang.org/x/net/http/httpproxy from net/http+
|
||||
golang.org/x/net/http2/hpack from net/http
|
||||
golang.org/x/net/idna from golang.org/x/crypto/acme/autocert+
|
||||
golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||
D golang.org/x/net/route from net+
|
||||
golang.org/x/sync/errgroup from github.com/mdlayher/socket+
|
||||
@@ -232,6 +235,18 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
crypto/ed25519 from crypto/tls+
|
||||
crypto/elliptic from crypto/ecdsa+
|
||||
crypto/hmac from crypto/tls+
|
||||
crypto/internal/alias from crypto/aes+
|
||||
crypto/internal/bigmod from crypto/ecdsa+
|
||||
crypto/internal/boring from crypto/aes+
|
||||
crypto/internal/boring/bbig from crypto/ecdsa+
|
||||
crypto/internal/boring/sig from crypto/internal/boring
|
||||
crypto/internal/edwards25519 from crypto/ed25519
|
||||
crypto/internal/edwards25519/field from crypto/ecdh+
|
||||
crypto/internal/hpke from crypto/tls
|
||||
crypto/internal/mlkem768 from crypto/tls
|
||||
crypto/internal/nistec from crypto/ecdh+
|
||||
crypto/internal/nistec/fiat from crypto/internal/nistec
|
||||
crypto/internal/randutil from crypto/dsa+
|
||||
crypto/md5 from crypto/tls+
|
||||
crypto/rand from crypto/ed25519+
|
||||
crypto/rc4 from crypto/tls
|
||||
@@ -242,6 +257,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
crypto/subtle from crypto/aes+
|
||||
crypto/tls from golang.org/x/crypto/acme+
|
||||
crypto/x509 from crypto/tls+
|
||||
D crypto/x509/internal/macos from crypto/x509
|
||||
crypto/x509/pkix from crypto/x509+
|
||||
embed from crypto/internal/nistec+
|
||||
encoding from encoding/json+
|
||||
@@ -263,6 +279,44 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
hash/maphash from go4.org/mem
|
||||
html from net/http/pprof+
|
||||
html/template from tailscale.com/cmd/derper
|
||||
internal/abi from crypto/x509/internal/macos+
|
||||
internal/asan from syscall
|
||||
internal/bisect from internal/godebug
|
||||
internal/bytealg from bytes+
|
||||
internal/byteorder from crypto/aes+
|
||||
internal/chacha8rand from math/rand/v2+
|
||||
internal/concurrent from unique
|
||||
internal/coverage/rtcov from runtime
|
||||
internal/cpu from crypto/aes+
|
||||
internal/filepathlite from os+
|
||||
internal/fmtsort from fmt+
|
||||
internal/goarch from crypto/aes+
|
||||
internal/godebug from crypto/tls+
|
||||
internal/godebugs from internal/godebug+
|
||||
internal/goexperiment from runtime
|
||||
internal/goos from crypto/x509+
|
||||
internal/itoa from internal/poll+
|
||||
internal/msan from syscall
|
||||
internal/nettrace from net+
|
||||
internal/oserror from io/fs+
|
||||
internal/poll from net+
|
||||
internal/profile from net/http/pprof
|
||||
internal/profilerecord from runtime+
|
||||
internal/race from internal/poll+
|
||||
internal/reflectlite from context+
|
||||
internal/runtime/atomic from internal/runtime/exithook+
|
||||
internal/runtime/exithook from runtime
|
||||
L internal/runtime/syscall from runtime+
|
||||
internal/singleflight from net
|
||||
internal/stringslite from embed+
|
||||
internal/syscall/execenv from os+
|
||||
LD internal/syscall/unix from crypto/rand+
|
||||
W internal/syscall/windows from crypto/rand+
|
||||
W internal/syscall/windows/registry from mime+
|
||||
W internal/syscall/windows/sysdll from internal/syscall/windows+
|
||||
internal/testlog from os
|
||||
internal/unsafeheader from internal/reflectlite+
|
||||
internal/weak from unique
|
||||
io from bufio+
|
||||
io/fs from crypto/x509+
|
||||
L io/ioutil from github.com/mitchellh/go-ps+
|
||||
@@ -282,6 +336,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
net/http from expvar+
|
||||
net/http/httptrace from net/http+
|
||||
net/http/internal from net/http
|
||||
net/http/internal/ascii from net/http
|
||||
net/http/pprof from tailscale.com/tsweb
|
||||
net/netip from go4.org/netipx+
|
||||
net/textproto from golang.org/x/net/http/httpguts+
|
||||
@@ -295,7 +350,10 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
reflect from crypto/x509+
|
||||
regexp from github.com/coreos/go-iptables/iptables+
|
||||
regexp/syntax from regexp
|
||||
runtime from crypto/internal/nistec+
|
||||
runtime/debug from github.com/prometheus/client_golang/prometheus+
|
||||
runtime/internal/math from runtime
|
||||
runtime/internal/sys from runtime
|
||||
runtime/metrics from github.com/prometheus/client_golang/prometheus+
|
||||
runtime/pprof from net/http/pprof
|
||||
runtime/trace from net/http/pprof
|
||||
@@ -314,3 +372,4 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
unicode/utf16 from crypto/x509+
|
||||
unicode/utf8 from bufio+
|
||||
unique from net/netip
|
||||
unsafe from bytes+
|
||||
|
||||
@@ -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/featureknob from tailscale.com/client/web+
|
||||
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
|
||||
L tailscale.com/feature/tap 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/ipnlocal from tailscale.com/ipn/localapi+
|
||||
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/store from tailscale.com/ipn/ipnlocal+
|
||||
L tailscale.com/ipn/store/awsstore from tailscale.com/ipn/store
|
||||
@@ -888,6 +889,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
||||
tailscale.com/taildrop from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/tempfork/httprec from tailscale.com/control/controlclient
|
||||
tailscale.com/tka from tailscale.com/client/tailscale+
|
||||
tailscale.com/tsconst from tailscale.com/net/netmon+
|
||||
tailscale.com/tsd from tailscale.com/ipn/ipnlocal+
|
||||
@@ -969,7 +971,6 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
tailscale.com/version 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/capture from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
|
||||
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
|
||||
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
|
||||
@@ -992,6 +993,8 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
||||
golang.org/x/crypto/curve25519 from github.com/tailscale/golang-x-crypto/ssh+
|
||||
golang.org/x/crypto/hkdf from crypto/tls+
|
||||
golang.org/x/crypto/internal/alias from golang.org/x/crypto/chacha20+
|
||||
golang.org/x/crypto/internal/poly1305 from golang.org/x/crypto/chacha20poly1305+
|
||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device
|
||||
@@ -1009,6 +1012,9 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
|
||||
golang.org/x/net/icmp from github.com/prometheus-community/pro-bing+
|
||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
||||
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
||||
golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
||||
golang.org/x/net/ipv4 from github.com/miekg/dns+
|
||||
golang.org/x/net/ipv6 from github.com/miekg/dns+
|
||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||
@@ -1050,6 +1056,18 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
crypto/ed25519 from crypto/tls+
|
||||
crypto/elliptic from crypto/ecdsa+
|
||||
crypto/hmac from crypto/tls+
|
||||
crypto/internal/alias from crypto/aes+
|
||||
crypto/internal/bigmod from crypto/ecdsa+
|
||||
crypto/internal/boring from crypto/aes+
|
||||
crypto/internal/boring/bbig from crypto/ecdsa+
|
||||
crypto/internal/boring/sig from crypto/internal/boring
|
||||
crypto/internal/edwards25519 from crypto/ed25519
|
||||
crypto/internal/edwards25519/field from crypto/ecdh+
|
||||
crypto/internal/hpke from crypto/tls
|
||||
crypto/internal/mlkem768 from crypto/tls
|
||||
crypto/internal/nistec from crypto/ecdh+
|
||||
crypto/internal/nistec/fiat from crypto/internal/nistec
|
||||
crypto/internal/randutil from crypto/dsa+
|
||||
crypto/md5 from crypto/tls+
|
||||
crypto/rand from crypto/ed25519+
|
||||
crypto/rc4 from crypto/tls+
|
||||
@@ -1060,6 +1078,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
crypto/subtle from crypto/aes+
|
||||
crypto/tls from github.com/aws/aws-sdk-go-v2/aws/transport/http+
|
||||
crypto/x509 from crypto/tls+
|
||||
D crypto/x509/internal/macos from crypto/x509
|
||||
crypto/x509/pkix from crypto/x509+
|
||||
database/sql from github.com/prometheus/client_golang/prometheus/collectors
|
||||
database/sql/driver from database/sql+
|
||||
@@ -1085,6 +1104,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
go/build/constraint from go/parser
|
||||
go/doc from k8s.io/apimachinery/pkg/runtime
|
||||
go/doc/comment from go/doc
|
||||
go/internal/typeparams from go/parser
|
||||
go/parser from k8s.io/apimachinery/pkg/runtime
|
||||
go/scanner from go/ast+
|
||||
go/token from go/ast+
|
||||
@@ -1095,6 +1115,46 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
hash/maphash from go4.org/mem
|
||||
html from html/template+
|
||||
html/template from github.com/gorilla/csrf
|
||||
internal/abi from crypto/x509/internal/macos+
|
||||
internal/asan from syscall
|
||||
internal/bisect from internal/godebug
|
||||
internal/bytealg from bytes+
|
||||
internal/byteorder from crypto/aes+
|
||||
internal/chacha8rand from math/rand/v2+
|
||||
internal/concurrent from unique
|
||||
internal/coverage/rtcov from runtime
|
||||
internal/cpu from crypto/aes+
|
||||
internal/filepathlite from os+
|
||||
internal/fmtsort from fmt+
|
||||
internal/goarch from crypto/aes+
|
||||
internal/godebug from archive/tar+
|
||||
internal/godebugs from internal/godebug+
|
||||
internal/goexperiment from runtime
|
||||
internal/goos from crypto/x509+
|
||||
internal/itoa from internal/poll+
|
||||
internal/lazyregexp from go/doc
|
||||
internal/msan from syscall
|
||||
internal/nettrace from net+
|
||||
internal/oserror from io/fs+
|
||||
internal/poll from net+
|
||||
internal/profile from net/http/pprof
|
||||
internal/profilerecord from runtime+
|
||||
internal/race from internal/poll+
|
||||
internal/reflectlite from context+
|
||||
internal/runtime/atomic from internal/runtime/exithook+
|
||||
internal/runtime/exithook from runtime
|
||||
L internal/runtime/syscall from runtime+
|
||||
internal/saferio from debug/pe+
|
||||
internal/singleflight from net
|
||||
internal/stringslite from embed+
|
||||
internal/syscall/execenv from os+
|
||||
LD internal/syscall/unix from crypto/rand+
|
||||
W internal/syscall/windows from crypto/rand+
|
||||
W internal/syscall/windows/registry from mime+
|
||||
W internal/syscall/windows/sysdll from internal/syscall/windows+
|
||||
internal/testlog from os
|
||||
internal/unsafeheader from internal/reflectlite+
|
||||
internal/weak from unique
|
||||
io from archive/tar+
|
||||
io/fs from archive/tar+
|
||||
io/ioutil from github.com/aws/aws-sdk-go-v2/aws/protocol/query+
|
||||
@@ -1103,6 +1163,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
log/internal from log+
|
||||
log/slog from github.com/go-logr/logr+
|
||||
log/slog/internal from log/slog
|
||||
log/slog/internal/buffer from log/slog
|
||||
maps from sigs.k8s.io/controller-runtime/pkg/predicate+
|
||||
math from archive/tar+
|
||||
math/big from crypto/dsa+
|
||||
@@ -1114,10 +1175,10 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
mime/quotedprintable from mime/multipart
|
||||
net from crypto/tls+
|
||||
net/http from expvar+
|
||||
net/http/httptest from tailscale.com/control/controlclient
|
||||
net/http/httptrace from github.com/prometheus-community/pro-bing+
|
||||
net/http/httputil from github.com/aws/smithy-go/transport/http+
|
||||
net/http/internal from net/http+
|
||||
net/http/internal/ascii from net/http+
|
||||
net/http/pprof from sigs.k8s.io/controller-runtime/pkg/manager+
|
||||
net/netip from github.com/gaissmai/bart+
|
||||
net/textproto from github.com/aws/aws-sdk-go-v2/aws/signer/v4+
|
||||
@@ -1131,7 +1192,10 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
reflect from archive/tar+
|
||||
regexp from github.com/aws/aws-sdk-go-v2/internal/endpoints+
|
||||
regexp/syntax from regexp
|
||||
runtime from archive/tar+
|
||||
runtime/debug from github.com/aws/aws-sdk-go-v2/internal/sync/singleflight+
|
||||
runtime/internal/math from runtime
|
||||
runtime/internal/sys from runtime
|
||||
runtime/metrics from github.com/prometheus/client_golang/prometheus+
|
||||
runtime/pprof from net/http/pprof+
|
||||
runtime/trace from net/http/pprof
|
||||
@@ -1150,3 +1214,4 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
unicode/utf16 from crypto/x509+
|
||||
unicode/utf8 from bufio+
|
||||
unique from net/netip
|
||||
unsafe from bytes+
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"expvar"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -26,6 +27,8 @@ import (
|
||||
"github.com/inetaf/tcpproxy"
|
||||
"github.com/peterbourgon/ff/v3"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/hostinfo"
|
||||
@@ -37,6 +40,7 @@ import (
|
||||
"tailscale.com/tsweb"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/wgengine/netstack"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -112,6 +116,7 @@ func main() {
|
||||
ts.Port = uint16(*wgPort)
|
||||
}
|
||||
defer ts.Close()
|
||||
|
||||
if *verboseTSNet {
|
||||
ts.Logf = log.Printf
|
||||
}
|
||||
@@ -129,6 +134,36 @@ func main() {
|
||||
log.Fatalf("debug serve: %v", http.Serve(dln, mux))
|
||||
}()
|
||||
}
|
||||
|
||||
if err := ts.Start(); err != nil {
|
||||
log.Fatalf("ts.Start: %v", err)
|
||||
}
|
||||
// TODO(raggi): this is not a public interface or guarantee.
|
||||
ns := ts.Sys().Netstack.Get().(*netstack.Impl)
|
||||
tcpRXBufOpt := tcpip.TCPReceiveBufferSizeRangeOption{
|
||||
Min: tcp.MinBufferSize,
|
||||
Default: tcp.DefaultReceiveBufferSize,
|
||||
Max: tcp.MaxBufferSize,
|
||||
}
|
||||
if err := ns.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpRXBufOpt); err != nil {
|
||||
log.Fatalf("could not set TCP RX buf size: %v", err)
|
||||
}
|
||||
tcpTXBufOpt := tcpip.TCPSendBufferSizeRangeOption{
|
||||
Min: tcp.MinBufferSize,
|
||||
Default: tcp.DefaultSendBufferSize,
|
||||
Max: tcp.MaxBufferSize,
|
||||
}
|
||||
if err := ns.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpTXBufOpt); err != nil {
|
||||
log.Fatalf("could not set TCP TX buf size: %v", err)
|
||||
}
|
||||
mslOpt := tcpip.TCPTimeWaitTimeoutOption(5 * time.Second)
|
||||
if err := ns.SetTransportProtocolOption(tcp.ProtocolNumber, &mslOpt); err != nil {
|
||||
log.Fatalf("could not set TCP MSL: %v", err)
|
||||
}
|
||||
if *debugPort != 0 {
|
||||
expvar.Publish("netstack", ns.ExpVar())
|
||||
}
|
||||
|
||||
lc, err := ts.LocalClient()
|
||||
if err != nil {
|
||||
log.Fatalf("LocalClient() failed: %v", err)
|
||||
|
||||
@@ -89,6 +89,8 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
||||
golang.org/x/crypto/curve25519 from golang.org/x/crypto/nacl/box+
|
||||
golang.org/x/crypto/hkdf from crypto/tls+
|
||||
golang.org/x/crypto/internal/alias from golang.org/x/crypto/chacha20+
|
||||
golang.org/x/crypto/internal/poly1305 from golang.org/x/crypto/chacha20poly1305+
|
||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
@@ -123,6 +125,18 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
crypto/ed25519 from crypto/tls+
|
||||
crypto/elliptic from crypto/ecdsa+
|
||||
crypto/hmac from crypto/tls+
|
||||
crypto/internal/alias from crypto/aes+
|
||||
crypto/internal/bigmod from crypto/ecdsa+
|
||||
crypto/internal/boring from crypto/aes+
|
||||
crypto/internal/boring/bbig from crypto/ecdsa+
|
||||
crypto/internal/boring/sig from crypto/internal/boring
|
||||
crypto/internal/edwards25519 from crypto/ed25519
|
||||
crypto/internal/edwards25519/field from crypto/ecdh+
|
||||
crypto/internal/hpke from crypto/tls
|
||||
crypto/internal/mlkem768 from crypto/tls
|
||||
crypto/internal/nistec from crypto/ecdh+
|
||||
crypto/internal/nistec/fiat from crypto/internal/nistec
|
||||
crypto/internal/randutil from crypto/dsa+
|
||||
crypto/md5 from crypto/tls+
|
||||
crypto/rand from crypto/ed25519+
|
||||
crypto/rc4 from crypto/tls
|
||||
@@ -133,6 +147,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
crypto/subtle from crypto/aes+
|
||||
crypto/tls from net/http+
|
||||
crypto/x509 from crypto/tls
|
||||
D crypto/x509/internal/macos from crypto/x509
|
||||
crypto/x509/pkix from crypto/x509
|
||||
embed from crypto/internal/nistec+
|
||||
encoding from encoding/json+
|
||||
@@ -153,6 +168,44 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
hash/fnv from google.golang.org/protobuf/internal/detrand
|
||||
hash/maphash from go4.org/mem
|
||||
html from net/http/pprof+
|
||||
internal/abi from crypto/x509/internal/macos+
|
||||
internal/asan from syscall
|
||||
internal/bisect from internal/godebug
|
||||
internal/bytealg from bytes+
|
||||
internal/byteorder from crypto/aes+
|
||||
internal/chacha8rand from math/rand/v2+
|
||||
internal/concurrent from unique
|
||||
internal/coverage/rtcov from runtime
|
||||
internal/cpu from crypto/aes+
|
||||
internal/filepathlite from os+
|
||||
internal/fmtsort from fmt
|
||||
internal/goarch from crypto/aes+
|
||||
internal/godebug from crypto/tls+
|
||||
internal/godebugs from internal/godebug+
|
||||
internal/goexperiment from runtime
|
||||
internal/goos from crypto/x509+
|
||||
internal/itoa from internal/poll+
|
||||
internal/msan from syscall
|
||||
internal/nettrace from net+
|
||||
internal/oserror from io/fs+
|
||||
internal/poll from net+
|
||||
internal/profile from net/http/pprof
|
||||
internal/profilerecord from runtime+
|
||||
internal/race from internal/poll+
|
||||
internal/reflectlite from context+
|
||||
internal/runtime/atomic from internal/runtime/exithook+
|
||||
internal/runtime/exithook from runtime
|
||||
L internal/runtime/syscall from runtime+
|
||||
internal/singleflight from net
|
||||
internal/stringslite from embed+
|
||||
internal/syscall/execenv from os
|
||||
LD internal/syscall/unix from crypto/rand+
|
||||
W internal/syscall/windows from crypto/rand+
|
||||
W internal/syscall/windows/registry from mime+
|
||||
W internal/syscall/windows/sysdll from internal/syscall/windows+
|
||||
internal/testlog from os
|
||||
internal/unsafeheader from internal/reflectlite+
|
||||
internal/weak from unique
|
||||
io from bufio+
|
||||
io/fs from crypto/x509+
|
||||
iter from maps+
|
||||
@@ -171,6 +224,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
net/http from expvar+
|
||||
net/http/httptrace from net/http
|
||||
net/http/internal from net/http
|
||||
net/http/internal/ascii from net/http
|
||||
net/http/pprof from tailscale.com/tsweb
|
||||
net/netip from go4.org/netipx+
|
||||
net/textproto from golang.org/x/net/http/httpguts+
|
||||
@@ -182,7 +236,10 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
reflect from crypto/x509+
|
||||
regexp from github.com/prometheus/client_golang/prometheus/internal+
|
||||
regexp/syntax from regexp
|
||||
runtime from crypto/internal/nistec+
|
||||
runtime/debug from github.com/prometheus/client_golang/prometheus+
|
||||
runtime/internal/math from runtime
|
||||
runtime/internal/sys from runtime
|
||||
runtime/metrics from github.com/prometheus/client_golang/prometheus+
|
||||
runtime/pprof from net/http/pprof
|
||||
runtime/trace from net/http/pprof
|
||||
@@ -199,3 +256,4 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
unicode/utf16 from crypto/x509+
|
||||
unicode/utf8 from bufio+
|
||||
unique from net/netip
|
||||
unsafe from bytes+
|
||||
|
||||
@@ -212,7 +212,7 @@ change in the future.
|
||||
exitNodeCmd(),
|
||||
updateCmd,
|
||||
whoisCmd,
|
||||
debugCmd,
|
||||
debugCmd(),
|
||||
driveCmd,
|
||||
idTokenCmd,
|
||||
advertiseCmd(),
|
||||
|
||||
@@ -25,10 +25,12 @@ import (
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/tstest/deptest"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/persist"
|
||||
"tailscale.com/types/preftype"
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
|
||||
@@ -1568,3 +1570,31 @@ func TestDocs(t *testing.T) {
|
||||
}
|
||||
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/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
@@ -45,307 +44,302 @@ import (
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/must"
|
||||
"tailscale.com/wgengine/capture"
|
||||
)
|
||||
|
||||
var debugCmd = &ffcli.Command{
|
||||
Name: "debug",
|
||||
Exec: runDebug,
|
||||
ShortUsage: "tailscale debug <debug-flags | subcommand>",
|
||||
ShortHelp: "Debug commands",
|
||||
LongHelp: hidden + `"tailscale debug" contains misc debug facilities; it is not a stable interface.`,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("debug")
|
||||
fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME")
|
||||
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")
|
||||
fs.IntVar(&debugArgs.cpuSec, "profile-seconds", 15, "number of seconds to run a CPU profile for, when --cpu-profile is non-empty")
|
||||
return fs
|
||||
})(),
|
||||
Subcommands: []*ffcli.Command{
|
||||
{
|
||||
Name: "derp-map",
|
||||
ShortUsage: "tailscale debug derp-map",
|
||||
Exec: runDERPMap,
|
||||
ShortHelp: "Print DERP map",
|
||||
},
|
||||
{
|
||||
Name: "component-logs",
|
||||
ShortUsage: "tailscale debug component-logs [" + strings.Join(ipn.DebuggableComponents, "|") + "]",
|
||||
Exec: runDebugComponentLogs,
|
||||
ShortHelp: "Enable/disable debug logs for a component",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("component-logs")
|
||||
fs.DurationVar(&debugComponentLogsArgs.forDur, "for", time.Hour, "how long to enable debug logs for; zero or negative means to disable")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "daemon-goroutines",
|
||||
ShortUsage: "tailscale debug daemon-goroutines",
|
||||
Exec: runDaemonGoroutines,
|
||||
ShortHelp: "Print tailscaled's goroutines",
|
||||
},
|
||||
{
|
||||
Name: "daemon-logs",
|
||||
ShortUsage: "tailscale debug daemon-logs",
|
||||
Exec: runDaemonLogs,
|
||||
ShortHelp: "Watch tailscaled's server logs",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("daemon-logs")
|
||||
fs.IntVar(&daemonLogsArgs.verbose, "verbose", 0, "verbosity level")
|
||||
fs.BoolVar(&daemonLogsArgs.time, "time", false, "include client time")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "metrics",
|
||||
ShortUsage: "tailscale debug metrics",
|
||||
Exec: runDaemonMetrics,
|
||||
ShortHelp: "Print tailscaled's metrics",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("metrics")
|
||||
fs.BoolVar(&metricsArgs.watch, "watch", false, "print JSON dump of delta values")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "env",
|
||||
ShortUsage: "tailscale debug env",
|
||||
Exec: runEnv,
|
||||
ShortHelp: "Print cmd/tailscale environment",
|
||||
},
|
||||
{
|
||||
Name: "stat",
|
||||
ShortUsage: "tailscale debug stat <files...>",
|
||||
Exec: runStat,
|
||||
ShortHelp: "Stat a file",
|
||||
},
|
||||
{
|
||||
Name: "hostinfo",
|
||||
ShortUsage: "tailscale debug hostinfo",
|
||||
Exec: runHostinfo,
|
||||
ShortHelp: "Print hostinfo",
|
||||
},
|
||||
{
|
||||
Name: "local-creds",
|
||||
ShortUsage: "tailscale debug local-creds",
|
||||
Exec: runLocalCreds,
|
||||
ShortHelp: "Print how to access Tailscale LocalAPI",
|
||||
},
|
||||
{
|
||||
Name: "restun",
|
||||
ShortUsage: "tailscale debug restun",
|
||||
Exec: localAPIAction("restun"),
|
||||
ShortHelp: "Force a magicsock restun",
|
||||
},
|
||||
{
|
||||
Name: "rebind",
|
||||
ShortUsage: "tailscale debug rebind",
|
||||
Exec: localAPIAction("rebind"),
|
||||
ShortHelp: "Force a magicsock rebind",
|
||||
},
|
||||
{
|
||||
Name: "derp-set-on-demand",
|
||||
ShortUsage: "tailscale debug derp-set-on-demand",
|
||||
Exec: localAPIAction("derp-set-homeless"),
|
||||
ShortHelp: "Enable DERP on-demand mode (breaks reachability)",
|
||||
},
|
||||
{
|
||||
Name: "derp-unset-on-demand",
|
||||
ShortUsage: "tailscale debug derp-unset-on-demand",
|
||||
Exec: localAPIAction("derp-unset-homeless"),
|
||||
ShortHelp: "Disable DERP on-demand mode",
|
||||
},
|
||||
{
|
||||
Name: "break-tcp-conns",
|
||||
ShortUsage: "tailscale debug break-tcp-conns",
|
||||
Exec: localAPIAction("break-tcp-conns"),
|
||||
ShortHelp: "Break any open TCP connections from the daemon",
|
||||
},
|
||||
{
|
||||
Name: "break-derp-conns",
|
||||
ShortUsage: "tailscale debug break-derp-conns",
|
||||
Exec: localAPIAction("break-derp-conns"),
|
||||
ShortHelp: "Break any open DERP connections from the daemon",
|
||||
},
|
||||
{
|
||||
Name: "pick-new-derp",
|
||||
ShortUsage: "tailscale debug pick-new-derp",
|
||||
Exec: localAPIAction("pick-new-derp"),
|
||||
ShortHelp: "Switch to some other random DERP home region for a short time",
|
||||
},
|
||||
{
|
||||
Name: "force-prefer-derp",
|
||||
ShortUsage: "tailscale debug force-prefer-derp",
|
||||
Exec: forcePreferDERP,
|
||||
ShortHelp: "Prefer the given region ID if reachable (until restart, or 0 to clear)",
|
||||
},
|
||||
{
|
||||
Name: "force-netmap-update",
|
||||
ShortUsage: "tailscale debug force-netmap-update",
|
||||
Exec: localAPIAction("force-netmap-update"),
|
||||
ShortHelp: "Force a full no-op netmap update (for load testing)",
|
||||
},
|
||||
{
|
||||
// TODO(bradfitz,maisem): eventually promote this out of debug
|
||||
Name: "reload-config",
|
||||
ShortUsage: "tailscale debug reload-config",
|
||||
Exec: reloadConfig,
|
||||
ShortHelp: "Reload config",
|
||||
},
|
||||
{
|
||||
Name: "control-knobs",
|
||||
ShortUsage: "tailscale debug control-knobs",
|
||||
Exec: debugControlKnobs,
|
||||
ShortHelp: "See current control knobs",
|
||||
},
|
||||
{
|
||||
Name: "prefs",
|
||||
ShortUsage: "tailscale debug prefs",
|
||||
Exec: runPrefs,
|
||||
ShortHelp: "Print prefs",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("prefs")
|
||||
fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "watch-ipn",
|
||||
ShortUsage: "tailscale debug watch-ipn",
|
||||
Exec: runWatchIPN,
|
||||
ShortHelp: "Subscribe to IPN message bus",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("watch-ipn")
|
||||
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")
|
||||
fs.IntVar(&watchIPNArgs.count, "count", 0, "exit after printing this many statuses, or 0 to keep going forever")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "netmap",
|
||||
ShortUsage: "tailscale debug netmap",
|
||||
Exec: runNetmap,
|
||||
ShortHelp: "Print the current network map",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("netmap")
|
||||
fs.BoolVar(&netmapArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "via",
|
||||
ShortUsage: "tailscale debug via <site-id> <v4-cidr>\n" +
|
||||
"tailscale debug via <v6-route>",
|
||||
Exec: runVia,
|
||||
ShortHelp: "Convert between site-specific IPv4 CIDRs and IPv6 'via' routes",
|
||||
},
|
||||
{
|
||||
Name: "ts2021",
|
||||
ShortUsage: "tailscale debug ts2021",
|
||||
Exec: runTS2021,
|
||||
ShortHelp: "Debug ts2021 protocol connectivity",
|
||||
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")
|
||||
fs.BoolVar(&ts2021Args.verbose, "verbose", false, "be extra verbose")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "set-expire",
|
||||
ShortUsage: "tailscale debug set-expire --in=1m",
|
||||
Exec: runSetExpire,
|
||||
ShortHelp: "Manipulate node key expiry for testing",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("set-expire")
|
||||
fs.DurationVar(&setExpireArgs.in, "in", 0, "if non-zero, set node key to expire this duration from now")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "dev-store-set",
|
||||
ShortUsage: "tailscale debug dev-store-set",
|
||||
Exec: runDevStoreSet,
|
||||
ShortHelp: "Set a key/value pair during development",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("store-set")
|
||||
fs.BoolVar(&devStoreSetArgs.danger, "danger", false, "accept danger")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "derp",
|
||||
ShortUsage: "tailscale debug derp",
|
||||
Exec: runDebugDERP,
|
||||
ShortHelp: "Test a DERP configuration",
|
||||
},
|
||||
{
|
||||
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
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "portmap",
|
||||
ShortUsage: "tailscale debug portmap",
|
||||
Exec: debugPortmap,
|
||||
ShortHelp: "Run portmap debugging",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("portmap")
|
||||
fs.DurationVar(&debugPortmapArgs.duration, "duration", 5*time.Second, "timeout for port mapping")
|
||||
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`)
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "peer-endpoint-changes",
|
||||
ShortUsage: "tailscale debug peer-endpoint-changes <hostname-or-IP>",
|
||||
Exec: runPeerEndpointChanges,
|
||||
ShortHelp: "Print debug information about a peer's endpoint changes",
|
||||
},
|
||||
{
|
||||
Name: "dial-types",
|
||||
ShortUsage: "tailscale debug dial-types <hostname-or-IP> <port>",
|
||||
Exec: runDebugDialTypes,
|
||||
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.)`)
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "resolve",
|
||||
ShortUsage: "tailscale debug resolve <hostname>",
|
||||
Exec: runDebugResolve,
|
||||
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)")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "go-buildinfo",
|
||||
ShortUsage: "tailscale debug go-buildinfo",
|
||||
ShortHelp: "Print Go's runtime/debug.BuildInfo",
|
||||
Exec: runGoBuildInfo,
|
||||
},
|
||||
},
|
||||
var (
|
||||
debugCaptureCmd func() *ffcli.Command // or nil
|
||||
)
|
||||
|
||||
func debugCmd() *ffcli.Command {
|
||||
return &ffcli.Command{
|
||||
Name: "debug",
|
||||
Exec: runDebug,
|
||||
ShortUsage: "tailscale debug <debug-flags | subcommand>",
|
||||
ShortHelp: "Debug commands",
|
||||
LongHelp: hidden + `"tailscale debug" contains misc debug facilities; it is not a stable interface.`,
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("debug")
|
||||
fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME")
|
||||
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")
|
||||
fs.IntVar(&debugArgs.cpuSec, "profile-seconds", 15, "number of seconds to run a CPU profile for, when --cpu-profile is non-empty")
|
||||
return fs
|
||||
})(),
|
||||
Subcommands: nonNilCmds([]*ffcli.Command{
|
||||
{
|
||||
Name: "derp-map",
|
||||
ShortUsage: "tailscale debug derp-map",
|
||||
Exec: runDERPMap,
|
||||
ShortHelp: "Print DERP map",
|
||||
},
|
||||
{
|
||||
Name: "component-logs",
|
||||
ShortUsage: "tailscale debug component-logs [" + strings.Join(ipn.DebuggableComponents, "|") + "]",
|
||||
Exec: runDebugComponentLogs,
|
||||
ShortHelp: "Enable/disable debug logs for a component",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("component-logs")
|
||||
fs.DurationVar(&debugComponentLogsArgs.forDur, "for", time.Hour, "how long to enable debug logs for; zero or negative means to disable")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "daemon-goroutines",
|
||||
ShortUsage: "tailscale debug daemon-goroutines",
|
||||
Exec: runDaemonGoroutines,
|
||||
ShortHelp: "Print tailscaled's goroutines",
|
||||
},
|
||||
{
|
||||
Name: "daemon-logs",
|
||||
ShortUsage: "tailscale debug daemon-logs",
|
||||
Exec: runDaemonLogs,
|
||||
ShortHelp: "Watch tailscaled's server logs",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("daemon-logs")
|
||||
fs.IntVar(&daemonLogsArgs.verbose, "verbose", 0, "verbosity level")
|
||||
fs.BoolVar(&daemonLogsArgs.time, "time", false, "include client time")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "metrics",
|
||||
ShortUsage: "tailscale debug metrics",
|
||||
Exec: runDaemonMetrics,
|
||||
ShortHelp: "Print tailscaled's metrics",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("metrics")
|
||||
fs.BoolVar(&metricsArgs.watch, "watch", false, "print JSON dump of delta values")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "env",
|
||||
ShortUsage: "tailscale debug env",
|
||||
Exec: runEnv,
|
||||
ShortHelp: "Print cmd/tailscale environment",
|
||||
},
|
||||
{
|
||||
Name: "stat",
|
||||
ShortUsage: "tailscale debug stat <files...>",
|
||||
Exec: runStat,
|
||||
ShortHelp: "Stat a file",
|
||||
},
|
||||
{
|
||||
Name: "hostinfo",
|
||||
ShortUsage: "tailscale debug hostinfo",
|
||||
Exec: runHostinfo,
|
||||
ShortHelp: "Print hostinfo",
|
||||
},
|
||||
{
|
||||
Name: "local-creds",
|
||||
ShortUsage: "tailscale debug local-creds",
|
||||
Exec: runLocalCreds,
|
||||
ShortHelp: "Print how to access Tailscale LocalAPI",
|
||||
},
|
||||
{
|
||||
Name: "restun",
|
||||
ShortUsage: "tailscale debug restun",
|
||||
Exec: localAPIAction("restun"),
|
||||
ShortHelp: "Force a magicsock restun",
|
||||
},
|
||||
{
|
||||
Name: "rebind",
|
||||
ShortUsage: "tailscale debug rebind",
|
||||
Exec: localAPIAction("rebind"),
|
||||
ShortHelp: "Force a magicsock rebind",
|
||||
},
|
||||
{
|
||||
Name: "derp-set-on-demand",
|
||||
ShortUsage: "tailscale debug derp-set-on-demand",
|
||||
Exec: localAPIAction("derp-set-homeless"),
|
||||
ShortHelp: "Enable DERP on-demand mode (breaks reachability)",
|
||||
},
|
||||
{
|
||||
Name: "derp-unset-on-demand",
|
||||
ShortUsage: "tailscale debug derp-unset-on-demand",
|
||||
Exec: localAPIAction("derp-unset-homeless"),
|
||||
ShortHelp: "Disable DERP on-demand mode",
|
||||
},
|
||||
{
|
||||
Name: "break-tcp-conns",
|
||||
ShortUsage: "tailscale debug break-tcp-conns",
|
||||
Exec: localAPIAction("break-tcp-conns"),
|
||||
ShortHelp: "Break any open TCP connections from the daemon",
|
||||
},
|
||||
{
|
||||
Name: "break-derp-conns",
|
||||
ShortUsage: "tailscale debug break-derp-conns",
|
||||
Exec: localAPIAction("break-derp-conns"),
|
||||
ShortHelp: "Break any open DERP connections from the daemon",
|
||||
},
|
||||
{
|
||||
Name: "pick-new-derp",
|
||||
ShortUsage: "tailscale debug pick-new-derp",
|
||||
Exec: localAPIAction("pick-new-derp"),
|
||||
ShortHelp: "Switch to some other random DERP home region for a short time",
|
||||
},
|
||||
{
|
||||
Name: "force-prefer-derp",
|
||||
ShortUsage: "tailscale debug force-prefer-derp",
|
||||
Exec: forcePreferDERP,
|
||||
ShortHelp: "Prefer the given region ID if reachable (until restart, or 0 to clear)",
|
||||
},
|
||||
{
|
||||
Name: "force-netmap-update",
|
||||
ShortUsage: "tailscale debug force-netmap-update",
|
||||
Exec: localAPIAction("force-netmap-update"),
|
||||
ShortHelp: "Force a full no-op netmap update (for load testing)",
|
||||
},
|
||||
{
|
||||
// TODO(bradfitz,maisem): eventually promote this out of debug
|
||||
Name: "reload-config",
|
||||
ShortUsage: "tailscale debug reload-config",
|
||||
Exec: reloadConfig,
|
||||
ShortHelp: "Reload config",
|
||||
},
|
||||
{
|
||||
Name: "control-knobs",
|
||||
ShortUsage: "tailscale debug control-knobs",
|
||||
Exec: debugControlKnobs,
|
||||
ShortHelp: "See current control knobs",
|
||||
},
|
||||
{
|
||||
Name: "prefs",
|
||||
ShortUsage: "tailscale debug prefs",
|
||||
Exec: runPrefs,
|
||||
ShortHelp: "Print prefs",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("prefs")
|
||||
fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "watch-ipn",
|
||||
ShortUsage: "tailscale debug watch-ipn",
|
||||
Exec: runWatchIPN,
|
||||
ShortHelp: "Subscribe to IPN message bus",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("watch-ipn")
|
||||
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")
|
||||
fs.IntVar(&watchIPNArgs.count, "count", 0, "exit after printing this many statuses, or 0 to keep going forever")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "netmap",
|
||||
ShortUsage: "tailscale debug netmap",
|
||||
Exec: runNetmap,
|
||||
ShortHelp: "Print the current network map",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("netmap")
|
||||
fs.BoolVar(&netmapArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "via",
|
||||
ShortUsage: "tailscale debug via <site-id> <v4-cidr>\n" +
|
||||
"tailscale debug via <v6-route>",
|
||||
Exec: runVia,
|
||||
ShortHelp: "Convert between site-specific IPv4 CIDRs and IPv6 'via' routes",
|
||||
},
|
||||
{
|
||||
Name: "ts2021",
|
||||
ShortUsage: "tailscale debug ts2021",
|
||||
Exec: runTS2021,
|
||||
ShortHelp: "Debug ts2021 protocol connectivity",
|
||||
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")
|
||||
fs.BoolVar(&ts2021Args.verbose, "verbose", false, "be extra verbose")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "set-expire",
|
||||
ShortUsage: "tailscale debug set-expire --in=1m",
|
||||
Exec: runSetExpire,
|
||||
ShortHelp: "Manipulate node key expiry for testing",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("set-expire")
|
||||
fs.DurationVar(&setExpireArgs.in, "in", 0, "if non-zero, set node key to expire this duration from now")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "dev-store-set",
|
||||
ShortUsage: "tailscale debug dev-store-set",
|
||||
Exec: runDevStoreSet,
|
||||
ShortHelp: "Set a key/value pair during development",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("store-set")
|
||||
fs.BoolVar(&devStoreSetArgs.danger, "danger", false, "accept danger")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "derp",
|
||||
ShortUsage: "tailscale debug derp",
|
||||
Exec: runDebugDERP,
|
||||
ShortHelp: "Test a DERP configuration",
|
||||
},
|
||||
ccall(debugCaptureCmd),
|
||||
{
|
||||
Name: "portmap",
|
||||
ShortUsage: "tailscale debug portmap",
|
||||
Exec: debugPortmap,
|
||||
ShortHelp: "Run portmap debugging",
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := newFlagSet("portmap")
|
||||
fs.DurationVar(&debugPortmapArgs.duration, "duration", 5*time.Second, "timeout for port mapping")
|
||||
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`)
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "peer-endpoint-changes",
|
||||
ShortUsage: "tailscale debug peer-endpoint-changes <hostname-or-IP>",
|
||||
Exec: runPeerEndpointChanges,
|
||||
ShortHelp: "Print debug information about a peer's endpoint changes",
|
||||
},
|
||||
{
|
||||
Name: "dial-types",
|
||||
ShortUsage: "tailscale debug dial-types <hostname-or-IP> <port>",
|
||||
Exec: runDebugDialTypes,
|
||||
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.)`)
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
Name: "resolve",
|
||||
ShortUsage: "tailscale debug resolve <hostname>",
|
||||
Exec: runDebugResolve,
|
||||
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)")
|
||||
return fs
|
||||
})(),
|
||||
},
|
||||
{
|
||||
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 {
|
||||
@@ -1036,50 +1030,6 @@ func runSetExpire(ctx context.Context, args []string) error {
|
||||
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 {
|
||||
duration time.Duration
|
||||
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/envknob from tailscale.com/client/tailscale+
|
||||
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/healthmsg from tailscale.com/cmd/tailscale/cli
|
||||
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/dnscache 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/netcheck from tailscale.com/cmd/tailscale/cli
|
||||
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/netns from tailscale.com/derp/derphttp+
|
||||
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/portmapper from tailscale.com/cmd/tailscale/cli+
|
||||
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/types/dnstype from tailscale.com/tailcfg+
|
||||
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/lazy from tailscale.com/util/testenv+
|
||||
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+
|
||||
tailscale.com/version 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
|
||||
golang.org/x/crypto/argon2 from tailscale.com/tka
|
||||
golang.org/x/crypto/blake2b from golang.org/x/crypto/argon2+
|
||||
@@ -196,6 +194,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
||||
golang.org/x/crypto/curve25519 from golang.org/x/crypto/nacl/box+
|
||||
golang.org/x/crypto/hkdf from crypto/tls+
|
||||
golang.org/x/crypto/internal/alias from golang.org/x/crypto/chacha20+
|
||||
golang.org/x/crypto/internal/poly1305 from golang.org/x/crypto/chacha20poly1305+
|
||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/pbkdf2 from software.sslmate.com/src/go-pkcs12
|
||||
@@ -211,6 +211,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
golang.org/x/net/http2/hpack from net/http+
|
||||
golang.org/x/net/icmp from tailscale.com/net/ping
|
||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
||||
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
||||
golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
||||
golang.org/x/net/ipv4 from github.com/miekg/dns+
|
||||
golang.org/x/net/ipv6 from github.com/miekg/dns+
|
||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||
@@ -249,6 +252,18 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
crypto/ed25519 from crypto/tls+
|
||||
crypto/elliptic from crypto/ecdsa+
|
||||
crypto/hmac from crypto/tls+
|
||||
crypto/internal/alias from crypto/aes+
|
||||
crypto/internal/bigmod from crypto/ecdsa+
|
||||
crypto/internal/boring from crypto/aes+
|
||||
crypto/internal/boring/bbig from crypto/ecdsa+
|
||||
crypto/internal/boring/sig from crypto/internal/boring
|
||||
crypto/internal/edwards25519 from crypto/ed25519
|
||||
crypto/internal/edwards25519/field from crypto/ecdh+
|
||||
crypto/internal/hpke from crypto/tls
|
||||
crypto/internal/mlkem768 from crypto/tls
|
||||
crypto/internal/nistec from crypto/ecdh+
|
||||
crypto/internal/nistec/fiat from crypto/internal/nistec
|
||||
crypto/internal/randutil from crypto/dsa+
|
||||
crypto/md5 from crypto/tls+
|
||||
crypto/rand from crypto/ed25519+
|
||||
crypto/rc4 from crypto/tls
|
||||
@@ -259,6 +274,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
crypto/subtle from crypto/aes+
|
||||
crypto/tls from github.com/miekg/dns+
|
||||
crypto/x509 from crypto/tls+
|
||||
D crypto/x509/internal/macos from crypto/x509
|
||||
crypto/x509/pkix from crypto/x509+
|
||||
DW database/sql/driver from github.com/google/uuid
|
||||
W debug/dwarf from debug/pe
|
||||
@@ -287,6 +303,44 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
image from github.com/skip2/go-qrcode+
|
||||
image/color from github.com/skip2/go-qrcode+
|
||||
image/png from github.com/skip2/go-qrcode
|
||||
internal/abi from crypto/x509/internal/macos+
|
||||
internal/asan from syscall
|
||||
internal/bisect from internal/godebug
|
||||
internal/bytealg from bytes+
|
||||
internal/byteorder from crypto/aes+
|
||||
internal/chacha8rand from math/rand/v2+
|
||||
internal/concurrent from unique
|
||||
internal/coverage/rtcov from runtime
|
||||
internal/cpu from crypto/aes+
|
||||
internal/filepathlite from os+
|
||||
internal/fmtsort from fmt+
|
||||
internal/goarch from crypto/aes+
|
||||
internal/godebug from archive/tar+
|
||||
internal/godebugs from internal/godebug+
|
||||
internal/goexperiment from runtime
|
||||
internal/goos from crypto/x509+
|
||||
internal/itoa from internal/poll+
|
||||
internal/msan from syscall
|
||||
internal/nettrace from net+
|
||||
internal/oserror from io/fs+
|
||||
internal/poll from net+
|
||||
internal/profilerecord from runtime
|
||||
internal/race from internal/poll+
|
||||
internal/reflectlite from context+
|
||||
internal/runtime/atomic from internal/runtime/exithook+
|
||||
internal/runtime/exithook from runtime
|
||||
L internal/runtime/syscall from runtime+
|
||||
internal/saferio from debug/pe+
|
||||
internal/singleflight from net
|
||||
internal/stringslite from embed+
|
||||
internal/syscall/execenv from os+
|
||||
LD internal/syscall/unix from crypto/rand+
|
||||
W internal/syscall/windows from crypto/rand+
|
||||
W internal/syscall/windows/registry from mime+
|
||||
W internal/syscall/windows/sysdll from internal/syscall/windows+
|
||||
internal/testlog from os
|
||||
internal/unsafeheader from internal/reflectlite+
|
||||
internal/weak from unique
|
||||
io from archive/tar+
|
||||
io/fs from archive/tar+
|
||||
io/ioutil from github.com/mitchellh/go-ps+
|
||||
@@ -308,6 +362,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
net/http/httptrace from golang.org/x/net/http2+
|
||||
net/http/httputil from tailscale.com/client/web+
|
||||
net/http/internal from net/http+
|
||||
net/http/internal/ascii from net/http+
|
||||
net/netip from go4.org/netipx+
|
||||
net/textproto from golang.org/x/net/http/httpguts+
|
||||
net/url from crypto/x509+
|
||||
@@ -320,7 +375,10 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
reflect from archive/tar+
|
||||
regexp from github.com/coreos/go-iptables/iptables+
|
||||
regexp/syntax from regexp
|
||||
runtime from archive/tar+
|
||||
runtime/debug from tailscale.com+
|
||||
runtime/internal/math from runtime
|
||||
runtime/internal/sys from runtime
|
||||
slices from tailscale.com/client/web+
|
||||
sort from compress/flate+
|
||||
strconv from archive/tar+
|
||||
@@ -336,3 +394,4 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
unicode/utf16 from crypto/x509+
|
||||
unicode/utf8 from bufio+
|
||||
unique from net/netip
|
||||
unsafe from bytes+
|
||||
|
||||
@@ -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/featureknob from tailscale.com/client/web+
|
||||
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
|
||||
L tailscale.com/feature/tap 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/ipnserver from tailscale.com/cmd/tailscaled
|
||||
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/store from tailscale.com/cmd/tailscaled+
|
||||
L tailscale.com/ipn/store/awsstore from tailscale.com/ipn/store
|
||||
@@ -340,6 +341,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/taildrop from tailscale.com/ipn/ipnlocal+
|
||||
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
|
||||
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/tempfork/httprec from tailscale.com/control/controlclient
|
||||
tailscale.com/tka from tailscale.com/client/tailscale+
|
||||
tailscale.com/tsconst from tailscale.com/net/netmon+
|
||||
tailscale.com/tsd from tailscale.com/cmd/tailscaled+
|
||||
@@ -422,7 +424,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/version/distro from tailscale.com/client/web+
|
||||
W tailscale.com/wf 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/filtertype from tailscale.com/types/netmap+
|
||||
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
|
||||
@@ -445,12 +446,15 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
|
||||
golang.org/x/crypto/curve25519 from github.com/tailscale/golang-x-crypto/ssh+
|
||||
golang.org/x/crypto/hkdf from crypto/tls+
|
||||
golang.org/x/crypto/internal/alias from golang.org/x/crypto/chacha20+
|
||||
golang.org/x/crypto/internal/poly1305 from golang.org/x/crypto/chacha20poly1305+
|
||||
golang.org/x/crypto/nacl/box from tailscale.com/types/key
|
||||
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
|
||||
golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device
|
||||
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
|
||||
golang.org/x/crypto/sha3 from crypto/internal/mlkem768+
|
||||
LD golang.org/x/crypto/ssh from github.com/pkg/sftp+
|
||||
LD golang.org/x/crypto/ssh/internal/bcrypt_pbkdf from golang.org/x/crypto/ssh
|
||||
golang.org/x/exp/constraints from github.com/dblohm7/wingoes/pe+
|
||||
golang.org/x/exp/maps from tailscale.com/ipn/store/mem+
|
||||
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
|
||||
@@ -462,6 +466,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
|
||||
golang.org/x/net/icmp from tailscale.com/net/ping+
|
||||
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
|
||||
golang.org/x/net/internal/iana from golang.org/x/net/icmp+
|
||||
golang.org/x/net/internal/socket from golang.org/x/net/icmp+
|
||||
golang.org/x/net/internal/socks from golang.org/x/net/proxy
|
||||
golang.org/x/net/ipv4 from github.com/miekg/dns+
|
||||
golang.org/x/net/ipv6 from github.com/miekg/dns+
|
||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||
@@ -501,6 +508,18 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
crypto/ed25519 from crypto/tls+
|
||||
crypto/elliptic from crypto/ecdsa+
|
||||
crypto/hmac from crypto/tls+
|
||||
crypto/internal/alias from crypto/aes+
|
||||
crypto/internal/bigmod from crypto/ecdsa+
|
||||
crypto/internal/boring from crypto/aes+
|
||||
crypto/internal/boring/bbig from crypto/ecdsa+
|
||||
crypto/internal/boring/sig from crypto/internal/boring
|
||||
crypto/internal/edwards25519 from crypto/ed25519
|
||||
crypto/internal/edwards25519/field from crypto/ecdh+
|
||||
crypto/internal/hpke from crypto/tls
|
||||
crypto/internal/mlkem768 from crypto/tls
|
||||
crypto/internal/nistec from crypto/ecdh+
|
||||
crypto/internal/nistec/fiat from crypto/internal/nistec
|
||||
crypto/internal/randutil from crypto/dsa+
|
||||
crypto/md5 from crypto/tls+
|
||||
crypto/rand from crypto/ed25519+
|
||||
crypto/rc4 from crypto/tls+
|
||||
@@ -511,6 +530,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
crypto/subtle from crypto/aes+
|
||||
crypto/tls from github.com/aws/aws-sdk-go-v2/aws/transport/http+
|
||||
crypto/x509 from crypto/tls+
|
||||
D crypto/x509/internal/macos from crypto/x509
|
||||
crypto/x509/pkix from crypto/x509+
|
||||
DW database/sql/driver from github.com/google/uuid
|
||||
W debug/dwarf from debug/pe
|
||||
@@ -528,7 +548,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
encoding/xml from github.com/aws/aws-sdk-go-v2/aws/protocol/xml+
|
||||
errors from archive/tar+
|
||||
expvar from tailscale.com/derp+
|
||||
flag from net/http/httptest+
|
||||
flag from tailscale.com/cmd/tailscaled+
|
||||
fmt from archive/tar+
|
||||
hash from compress/zlib+
|
||||
hash/adler32 from compress/zlib+
|
||||
@@ -536,6 +556,45 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
hash/maphash from go4.org/mem
|
||||
html from html/template+
|
||||
html/template from github.com/gorilla/csrf
|
||||
internal/abi from crypto/x509/internal/macos+
|
||||
internal/asan from syscall
|
||||
internal/bisect from internal/godebug
|
||||
internal/bytealg from bytes+
|
||||
internal/byteorder from crypto/aes+
|
||||
internal/chacha8rand from math/rand/v2+
|
||||
internal/concurrent from unique
|
||||
internal/coverage/rtcov from runtime
|
||||
internal/cpu from crypto/aes+
|
||||
internal/filepathlite from os+
|
||||
internal/fmtsort from fmt+
|
||||
internal/goarch from crypto/aes+
|
||||
internal/godebug from archive/tar+
|
||||
internal/godebugs from internal/godebug+
|
||||
internal/goexperiment from runtime
|
||||
internal/goos from crypto/x509+
|
||||
internal/itoa from internal/poll+
|
||||
internal/msan from syscall
|
||||
internal/nettrace from net+
|
||||
internal/oserror from io/fs+
|
||||
internal/poll from net+
|
||||
internal/profile from net/http/pprof
|
||||
internal/profilerecord from runtime+
|
||||
internal/race from internal/poll+
|
||||
internal/reflectlite from context+
|
||||
internal/runtime/atomic from internal/runtime/exithook+
|
||||
internal/runtime/exithook from runtime
|
||||
L internal/runtime/syscall from runtime+
|
||||
internal/saferio from debug/pe+
|
||||
internal/singleflight from net
|
||||
internal/stringslite from embed+
|
||||
internal/syscall/execenv from os+
|
||||
LD internal/syscall/unix from crypto/rand+
|
||||
W internal/syscall/windows from crypto/rand+
|
||||
W internal/syscall/windows/registry from mime+
|
||||
W internal/syscall/windows/sysdll from internal/syscall/windows+
|
||||
internal/testlog from os
|
||||
internal/unsafeheader from internal/reflectlite+
|
||||
internal/weak from unique
|
||||
io from archive/tar+
|
||||
io/fs from archive/tar+
|
||||
io/ioutil from github.com/aws/aws-sdk-go-v2/aws/protocol/query+
|
||||
@@ -554,10 +613,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
mime/quotedprintable from mime/multipart
|
||||
net from crypto/tls+
|
||||
net/http from expvar+
|
||||
net/http/httptest from tailscale.com/control/controlclient
|
||||
net/http/httptrace from github.com/prometheus-community/pro-bing+
|
||||
net/http/httputil from github.com/aws/smithy-go/transport/http+
|
||||
net/http/internal from net/http+
|
||||
net/http/internal/ascii from net/http+
|
||||
net/http/pprof from tailscale.com/cmd/tailscaled+
|
||||
net/netip from github.com/tailscale/wireguard-go/conn+
|
||||
net/textproto from github.com/aws/aws-sdk-go-v2/aws/signer/v4+
|
||||
@@ -571,7 +630,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
reflect from archive/tar+
|
||||
regexp from github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn+
|
||||
regexp/syntax from regexp
|
||||
runtime from archive/tar+
|
||||
runtime/debug from github.com/aws/aws-sdk-go-v2/internal/sync/singleflight+
|
||||
runtime/internal/math from runtime
|
||||
runtime/internal/sys from runtime
|
||||
runtime/pprof from net/http/pprof+
|
||||
runtime/trace from net/http/pprof
|
||||
slices from tailscale.com/appc+
|
||||
@@ -589,3 +651,4 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
unicode/utf16 from crypto/x509+
|
||||
unicode/utf8 from bufio+
|
||||
unique from net/netip
|
||||
unsafe from bytes+
|
||||
|
||||
@@ -22,6 +22,8 @@ func TestDeps(t *testing.T) {
|
||||
BadDeps: map[string]string{
|
||||
"testing": "do not use testing package in production code",
|
||||
"gvisor.dev/gvisor/pkg/hostarch": "will crash on non-4K page sizes; see https://github.com/tailscale/tailscale/issues/8658",
|
||||
"net/http/httptest": "do not use httptest in production code",
|
||||
"net/http/internal/testcert": "do not use httptest in production code",
|
||||
},
|
||||
}.Check(t)
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/persist"
|
||||
"tailscale.com/types/structs"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/execqueue"
|
||||
)
|
||||
|
||||
@@ -131,6 +132,8 @@ type Auto struct {
|
||||
// the server.
|
||||
lastUpdateGen updateGen
|
||||
|
||||
lastStatus atomic.Pointer[Status]
|
||||
|
||||
paused bool // whether we should stop making HTTP requests
|
||||
unpauseWaiters []chan bool // chans that gets sent true (once) on wake, or false on Shutdown
|
||||
loggedIn bool // true if currently logged in
|
||||
@@ -596,21 +599,85 @@ func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkM
|
||||
// not logged in.
|
||||
nm = nil
|
||||
}
|
||||
new := Status{
|
||||
newSt := &Status{
|
||||
URL: url,
|
||||
Persist: p,
|
||||
NetMap: nm,
|
||||
Err: err,
|
||||
state: state,
|
||||
}
|
||||
c.lastStatus.Store(newSt)
|
||||
|
||||
// Launch a new goroutine to avoid blocking the caller while the observer
|
||||
// does its thing, which may result in a call back into the client.
|
||||
metricQueued.Add(1)
|
||||
c.observerQueue.Add(func() {
|
||||
c.observer.SetControlClientStatus(c, new)
|
||||
if canSkipStatus(newSt, c.lastStatus.Load()) {
|
||||
metricSkippable.Add(1)
|
||||
if !c.direct.controlKnobs.DisableSkipStatusQueue.Load() {
|
||||
metricSkipped.Add(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.observer.SetControlClientStatus(c, *newSt)
|
||||
// Best effort stop retaining the memory now that
|
||||
// we've sent it to the observer (LocalBackend).
|
||||
// We CAS here because the caller goroutine is
|
||||
// doing a Store which we want to want to win
|
||||
// a race. This is only a memory optimization
|
||||
// and is for correctness:
|
||||
c.lastStatus.CompareAndSwap(newSt, nil)
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
metricQueued = clientmetric.NewCounter("controlclient_auto_status_queued")
|
||||
metricSkippable = clientmetric.NewCounter("controlclient_auto_status_queue_skippable")
|
||||
metricSkipped = clientmetric.NewCounter("controlclient_auto_status_queue_skipped")
|
||||
)
|
||||
|
||||
// canSkipStatus reports whether we can skip sending s1, knowing
|
||||
// that s2 is enqueued sometime in the future after s1.
|
||||
//
|
||||
// s1 must be non-nil. s2 may be nil.
|
||||
func canSkipStatus(s1, s2 *Status) bool {
|
||||
if s2 == nil {
|
||||
// Nothing in the future.
|
||||
return false
|
||||
}
|
||||
if s1 == s2 {
|
||||
// If the last item in the queue is the same as s1,
|
||||
// we can't skip it.
|
||||
return false
|
||||
}
|
||||
if s1.Err != nil || s1.URL != "" {
|
||||
// If s1 has an error or a URL, we shouldn't skip it, lest the error go
|
||||
// away in s2 or in-between. We want to make sure all the subsystems see
|
||||
// it. Plus there aren't many of these, so not worth skipping.
|
||||
return false
|
||||
}
|
||||
if !s1.Persist.Equals(s2.Persist) || s1.state != s2.state {
|
||||
// If s1 has a different Persist or state than s2,
|
||||
// don't skip it. We only care about skipping the typical
|
||||
// entries where the only difference is the NetMap.
|
||||
return false
|
||||
}
|
||||
// If nothing above precludes it, and both s1 and s2 have NetMaps, then
|
||||
// we can skip it, because s2's NetMap is a newer version and we can
|
||||
// jump straight from whatever state we had before to s2's state,
|
||||
// without passing through s1's state first. A NetMap is regrettably a
|
||||
// full snapshot of the state, not an incremental delta. We're slowly
|
||||
// moving towards passing around only deltas around internally at all
|
||||
// layers, but this is explicitly the case where we didn't have a delta
|
||||
// path for the message we received over the wire and had to resort
|
||||
// to the legacy full NetMap path. And then we can get behind processing
|
||||
// these full NetMap snapshots in LocalBackend/wgengine/magicsock/netstack
|
||||
// and this path (when it returns true) lets us skip over useless work
|
||||
// and not get behind in the queue. This matters in particular for tailnets
|
||||
// that are both very large + very churny.
|
||||
return s1.NetMap != nil && s2.NetMap != nil
|
||||
}
|
||||
|
||||
func (c *Auto) Login(flags LoginFlags) {
|
||||
c.logf("client.Login(%v)", flags)
|
||||
|
||||
|
||||
@@ -4,8 +4,13 @@
|
||||
package controlclient
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/persist"
|
||||
)
|
||||
|
||||
func fieldsOf(t reflect.Type) (fields []string) {
|
||||
@@ -62,3 +67,83 @@ func TestStatusEqual(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tests [canSkipStatus].
|
||||
func TestCanSkipStatus(t *testing.T) {
|
||||
st := new(Status)
|
||||
nm1 := &netmap.NetworkMap{}
|
||||
nm2 := &netmap.NetworkMap{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
s1, s2 *Status
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nil-s2",
|
||||
s1: st,
|
||||
s2: nil,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "equal",
|
||||
s1: st,
|
||||
s2: st,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "s1-error",
|
||||
s1: &Status{Err: io.EOF, NetMap: nm1},
|
||||
s2: &Status{NetMap: nm2},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "s1-url",
|
||||
s1: &Status{URL: "foo", NetMap: nm1},
|
||||
s2: &Status{NetMap: nm2},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "s1-persist-diff",
|
||||
s1: &Status{Persist: new(persist.Persist).View(), NetMap: nm1},
|
||||
s2: &Status{NetMap: nm2},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "s1-state-diff",
|
||||
s1: &Status{state: 123, NetMap: nm1},
|
||||
s2: &Status{NetMap: nm2},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "s1-no-netmap1",
|
||||
s1: &Status{NetMap: nil},
|
||||
s2: &Status{NetMap: nm2},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "s1-no-netmap2",
|
||||
s1: &Status{NetMap: nm1},
|
||||
s2: &Status{NetMap: nil},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "skip",
|
||||
s1: &Status{NetMap: nm1},
|
||||
s2: &Status{NetMap: nm2},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := canSkipStatus(tt.s1, tt.s2); got != tt.want {
|
||||
t.Errorf("canSkipStatus = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
want := []string{"Err", "URL", "NetMap", "Persist", "state"}
|
||||
if f := fieldsOf(reflect.TypeFor[Status]()); !slices.Equal(f, want) {
|
||||
t.Errorf("Status fields = %q; this code was only written to handle fields %q", f, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -42,6 +41,7 @@ import (
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tempfork/httprec"
|
||||
"tailscale.com/tka"
|
||||
"tailscale.com/tstime"
|
||||
"tailscale.com/types/key"
|
||||
@@ -1384,7 +1384,7 @@ func answerC2NPing(logf logger.Logf, c2nHandler http.Handler, c *http.Client, pr
|
||||
handlerCtx, cancel := context.WithTimeout(context.Background(), handlerTimeout)
|
||||
defer cancel()
|
||||
hreq = hreq.WithContext(handlerCtx)
|
||||
rec := httptest.NewRecorder()
|
||||
rec := httprec.NewRecorder()
|
||||
c2nHandler.ServeHTTP(rec, hreq)
|
||||
cancel()
|
||||
|
||||
|
||||
@@ -300,6 +300,15 @@ func (ms *mapSession) updateStateFromResponse(resp *tailcfg.MapResponse) {
|
||||
if dm := resp.DERPMap; dm != nil {
|
||||
ms.vlogf("netmap: new map contains DERP map")
|
||||
|
||||
// Guard against the control server accidentally sending
|
||||
// a nil region definition, which at least Headscale was
|
||||
// observed to send.
|
||||
for rid, r := range dm.Regions {
|
||||
if r == nil {
|
||||
delete(dm.Regions, rid)
|
||||
}
|
||||
}
|
||||
|
||||
// Zero-valued fields in a DERPMap mean that we're not changing
|
||||
// anything and are using the previous value(s).
|
||||
if ldm := ms.lastDERPMap; ldm != nil {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
package controlknobs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
|
||||
"tailscale.com/syncs"
|
||||
@@ -103,6 +105,11 @@ type Knobs struct {
|
||||
// DisableCaptivePortalDetection is whether the node should not perform captive portal detection
|
||||
// automatically when the network state changes.
|
||||
DisableCaptivePortalDetection atomic.Bool
|
||||
|
||||
// DisableSkipStatusQueue is whether the node should disable skipping
|
||||
// of queued netmap.NetworkMap between the controlclient and LocalBackend.
|
||||
// See tailscale/tailscale#14768.
|
||||
DisableSkipStatusQueue atomic.Bool
|
||||
}
|
||||
|
||||
// UpdateFromNodeAttributes updates k (if non-nil) based on the provided self
|
||||
@@ -132,6 +139,7 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) {
|
||||
disableLocalDNSOverrideViaNRPT = has(tailcfg.NodeAttrDisableLocalDNSOverrideViaNRPT)
|
||||
disableCryptorouting = has(tailcfg.NodeAttrDisableMagicSockCryptoRouting)
|
||||
disableCaptivePortalDetection = has(tailcfg.NodeAttrDisableCaptivePortalDetection)
|
||||
disableSkipStatusQueue = has(tailcfg.NodeAttrDisableSkipStatusQueue)
|
||||
)
|
||||
|
||||
if has(tailcfg.NodeAttrOneCGNATEnable) {
|
||||
@@ -159,6 +167,7 @@ func (k *Knobs) UpdateFromNodeAttributes(capMap tailcfg.NodeCapMap) {
|
||||
k.DisableLocalDNSOverrideViaNRPT.Store(disableLocalDNSOverrideViaNRPT)
|
||||
k.DisableCryptorouting.Store(disableCryptorouting)
|
||||
k.DisableCaptivePortalDetection.Store(disableCaptivePortalDetection)
|
||||
k.DisableSkipStatusQueue.Store(disableSkipStatusQueue)
|
||||
}
|
||||
|
||||
// AsDebugJSON returns k as something that can be marshalled with json.Marshal
|
||||
@@ -167,25 +176,19 @@ func (k *Knobs) AsDebugJSON() map[string]any {
|
||||
if k == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"DisableUPnP": k.DisableUPnP.Load(),
|
||||
"KeepFullWGConfig": k.KeepFullWGConfig.Load(),
|
||||
"RandomizeClientPort": k.RandomizeClientPort.Load(),
|
||||
"OneCGNAT": k.OneCGNAT.Load(),
|
||||
"ForceBackgroundSTUN": k.ForceBackgroundSTUN.Load(),
|
||||
"DisableDeltaUpdates": k.DisableDeltaUpdates.Load(),
|
||||
"PeerMTUEnable": k.PeerMTUEnable.Load(),
|
||||
"DisableDNSForwarderTCPRetries": k.DisableDNSForwarderTCPRetries.Load(),
|
||||
"SilentDisco": k.SilentDisco.Load(),
|
||||
"LinuxForceIPTables": k.LinuxForceIPTables.Load(),
|
||||
"LinuxForceNfTables": k.LinuxForceNfTables.Load(),
|
||||
"SeamlessKeyRenewal": k.SeamlessKeyRenewal.Load(),
|
||||
"ProbeUDPLifetime": k.ProbeUDPLifetime.Load(),
|
||||
"AppCStoreRoutes": k.AppCStoreRoutes.Load(),
|
||||
"UserDialUseRoutes": k.UserDialUseRoutes.Load(),
|
||||
"DisableSplitDNSWhenNoCustomResolvers": k.DisableSplitDNSWhenNoCustomResolvers.Load(),
|
||||
"DisableLocalDNSOverrideViaNRPT": k.DisableLocalDNSOverrideViaNRPT.Load(),
|
||||
"DisableCryptorouting": k.DisableCryptorouting.Load(),
|
||||
"DisableCaptivePortalDetection": k.DisableCaptivePortalDetection.Load(),
|
||||
ret := map[string]any{}
|
||||
rt := reflect.TypeFor[Knobs]()
|
||||
rv := reflect.ValueOf(k).Elem() // of *k
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
name := rt.Field(i).Name
|
||||
switch v := rv.Field(i).Addr().Interface().(type) {
|
||||
case *atomic.Bool:
|
||||
ret[name] = v.Load()
|
||||
case *syncs.AtomicValue[opt.Bool]:
|
||||
ret[name] = v.Load()
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown field type %T for %v", v, name))
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ package controlknobs
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func TestAsDebugJSON(t *testing.T) {
|
||||
@@ -18,4 +20,5 @@ func TestAsDebugJSON(t *testing.T) {
|
||||
if want := reflect.TypeFor[Knobs]().NumField(); len(got) != want {
|
||||
t.Errorf("AsDebugJSON map has %d fields; want %v", len(got), want)
|
||||
}
|
||||
t.Logf("Got: %v", logger.AsJSON(got))
|
||||
}
|
||||
|
||||
@@ -55,8 +55,7 @@ func CanRunTailscaleSSH() error {
|
||||
func CanUseExitNode() error {
|
||||
switch dist := distro.Get(); dist {
|
||||
case distro.Synology, // see https://github.com/tailscale/tailscale/issues/1995
|
||||
distro.QNAP,
|
||||
distro.Unraid:
|
||||
distro.QNAP:
|
||||
return errors.New("Tailscale exit nodes cannot be used on " + string(dist))
|
||||
}
|
||||
|
||||
|
||||
@@ -13,21 +13,44 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/ipn/localapi"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
||||
//go:embed ts-dissector.lua
|
||||
var DissectorLua string
|
||||
func init() {
|
||||
feature.Register("capture")
|
||||
localapi.Register("debug-capture", serveLocalAPIDebugCapture)
|
||||
}
|
||||
|
||||
// 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 Callback func(Path, time.Time, []byte, packet.CaptureMeta)
|
||||
func serveLocalAPIDebugCapture(h *localapi.Handler, w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
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()
|
||||
|
||||
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{
|
||||
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
|
||||
}
|
||||
|
||||
// Path describes where in the data path the packet was captured.
|
||||
type Path uint8
|
||||
|
||||
// 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 {
|
||||
// newSink creates a new capture sink.
|
||||
func newSink() packet.CaptureSink {
|
||||
ctx, c := context.WithCancel(context.Background())
|
||||
return &Sink{
|
||||
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.
|
||||
func (s *Sink) NumOutputs() int {
|
||||
s.mu.Lock()
|
||||
@@ -174,7 +180,7 @@ func customDataLen(meta packet.CaptureMeta) int {
|
||||
// LogPacket is called to insert a packet into the capture.
|
||||
//
|
||||
// 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 {
|
||||
case <-s.ctx.Done():
|
||||
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"
|
||||
4
go.mod
4
go.mod
@@ -74,7 +74,7 @@ require (
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/studio-b12/gowebdav v0.9.0
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
|
||||
github.com/tailscale/depaware v0.0.0-20250112153213-b748de04d81b
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
|
||||
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
|
||||
@@ -82,7 +82,7 @@ require (
|
||||
github.com/tailscale/mkctr v0.0.0-20250110151924-54977352e4a6
|
||||
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7
|
||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6
|
||||
github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19
|
||||
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e
|
||||
|
||||
8
go.sum
8
go.sum
@@ -915,8 +915,8 @@ github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplB
|
||||
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 h1:34icjjmqJ2HPjrSuJYEkdZ+0ItmGQAQ75cRHIiftIyE=
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
||||
github.com/tailscale/depaware v0.0.0-20250112153213-b748de04d81b h1:ewWb4cA+YO9/3X+v5UhdV+eKFsNBOPcGRh39Glshx/4=
|
||||
github.com/tailscale/depaware v0.0.0-20250112153213-b748de04d81b/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
|
||||
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
|
||||
@@ -933,8 +933,8 @@ github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4
|
||||
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
|
||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
|
||||
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 h1:tdUdyPqJ0C97SJfjB9tW6EylTtreyee9C44de+UBG0g=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=
|
||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
|
||||
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
|
||||
github.com/tailscale/wireguard-go v0.0.0-20250107165329-0b8b35511f19 h1:BcEJP2ewTIK2ZCsqgl6YGpuO6+oKqqag5HHb7ehljKw=
|
||||
|
||||
@@ -659,8 +659,9 @@ func acmeClient(cs certStore) (*acme.Client, error) {
|
||||
// LetsEncrypt), we should make sure that they support ARI extension (see
|
||||
// shouldStartDomainRenewalARI).
|
||||
return &acme.Client{
|
||||
Key: key,
|
||||
UserAgent: "tailscaled/" + version.Long(),
|
||||
Key: key,
|
||||
UserAgent: "tailscaled/" + version.Long(),
|
||||
DirectoryURL: envknob.String("TS_DEBUG_ACME_DIRECTORY_URL"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -199,3 +199,19 @@ func TestShouldStartDomainRenewal(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebugACMEDirectoryURL(t *testing.T) {
|
||||
for _, tc := range []string{"", "https://acme-staging-v02.api.letsencrypt.org/directory"} {
|
||||
const setting = "TS_DEBUG_ACME_DIRECTORY_URL"
|
||||
t.Run(tc, func(t *testing.T) {
|
||||
t.Setenv(setting, tc)
|
||||
ac, err := acmeClient(certStateStore{StateStore: new(mem.Store)})
|
||||
if err != nil {
|
||||
t.Fatalf("acmeClient creation err: %v", err)
|
||||
}
|
||||
if ac.DirectoryURL != tc {
|
||||
t.Fatalf("acmeClient.DirectoryURL = %q, want %q", ac.DirectoryURL, tc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
// 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.
|
||||
type Parsed struct {
|
||||
// b is the byte buffer that this decodes.
|
||||
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/usermetric"
|
||||
"tailscale.com/wgengine/capture"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/netstack/gro"
|
||||
"tailscale.com/wgengine/wgcfg"
|
||||
@@ -208,7 +207,7 @@ type Wrapper struct {
|
||||
// stats maintains per-connection counters.
|
||||
stats atomic.Pointer[connstats.Statistics]
|
||||
|
||||
captureHook syncs.AtomicValue[capture.Callback]
|
||||
captureHook syncs.AtomicValue[packet.CaptureCallback]
|
||||
|
||||
metrics *metrics
|
||||
}
|
||||
@@ -955,7 +954,7 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
||||
}
|
||||
}
|
||||
if captHook != nil {
|
||||
captHook(capture.FromLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
captHook(packet.FromLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
}
|
||||
if !t.disableFilter {
|
||||
var response filter.Response
|
||||
@@ -1101,9 +1100,9 @@ func (t *Wrapper) injectedRead(res tunInjectedRead, outBuffs [][]byte, sizes []i
|
||||
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 {
|
||||
captHook(capture.FromPeer, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
captHook(packet.FromPeer, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
}
|
||||
|
||||
if p.IPProto == ipproto.TSMP {
|
||||
@@ -1317,7 +1316,7 @@ func (t *Wrapper) InjectInboundPacketBuffer(pkt *stack.PacketBuffer, buffs [][]b
|
||||
p.Decode(buf)
|
||||
captHook := t.captureHook.Load()
|
||||
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)
|
||||
@@ -1449,7 +1448,7 @@ func (t *Wrapper) InjectOutboundPacketBuffer(pkt *stack.PacketBuffer) error {
|
||||
}
|
||||
if capt := t.captureHook.Load(); capt != nil {
|
||||
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})
|
||||
@@ -1491,6 +1490,6 @@ var (
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ import (
|
||||
"tailscale.com/types/views"
|
||||
"tailscale.com/util/must"
|
||||
"tailscale.com/util/usermetric"
|
||||
"tailscale.com/wgengine/capture"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/wgcfg"
|
||||
)
|
||||
@@ -871,14 +870,14 @@ func TestPeerCfg_NAT(t *testing.T) {
|
||||
// with the correct parameters when various packet operations are performed.
|
||||
func TestCaptureHook(t *testing.T) {
|
||||
type captureRecord struct {
|
||||
path capture.Path
|
||||
path packet.CapturePath
|
||||
now time.Time
|
||||
pkt []byte
|
||||
meta packet.CaptureMeta
|
||||
}
|
||||
|
||||
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{
|
||||
path: path,
|
||||
now: now,
|
||||
@@ -935,19 +934,19 @@ func TestCaptureHook(t *testing.T) {
|
||||
// Assert that the right packets are captured.
|
||||
want := []captureRecord{
|
||||
{
|
||||
path: capture.FromPeer,
|
||||
path: packet.FromPeer,
|
||||
pkt: []byte("Write1"),
|
||||
},
|
||||
{
|
||||
path: capture.FromPeer,
|
||||
path: packet.FromPeer,
|
||||
pkt: []byte("Write2"),
|
||||
},
|
||||
{
|
||||
path: capture.SynthesizedToLocal,
|
||||
path: packet.SynthesizedToLocal,
|
||||
pkt: []byte("InjectInboundPacketBuffer"),
|
||||
},
|
||||
{
|
||||
path: capture.SynthesizedToPeer,
|
||||
path: packet.SynthesizedToPeer,
|
||||
pkt: []byte("InjectOutboundPacketBuffer"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2470,6 +2470,11 @@ const (
|
||||
// automatically when the network state changes.
|
||||
NodeAttrDisableCaptivePortalDetection NodeCapability = "disable-captive-portal-detection"
|
||||
|
||||
// NodeAttrDisableSkipStatusQueue is set when the node should disable skipping
|
||||
// of queued netmap.NetworkMap between the controlclient and LocalBackend.
|
||||
// See tailscale/tailscale#14768.
|
||||
NodeAttrDisableSkipStatusQueue NodeCapability = "disable-skip-status-queue"
|
||||
|
||||
// NodeAttrSSHEnvironmentVariables enables logic for handling environment variables sent
|
||||
// via SendEnv in the SSH server and applying them to the SSH session.
|
||||
NodeAttrSSHEnvironmentVariables NodeCapability = "ssh-env-vars"
|
||||
|
||||
258
tempfork/httprec/httprec.go
Normal file
258
tempfork/httprec/httprec.go
Normal file
@@ -0,0 +1,258 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package httprec is a copy of the Go standard library's httptest.ResponseRecorder
|
||||
// type, which we want to use in non-test code without pulling in the rest of
|
||||
// the httptest package and its test certs, etc.
|
||||
package httprec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// ResponseRecorder is an implementation of [http.ResponseWriter] that
|
||||
// records its mutations for later inspection in tests.
|
||||
type ResponseRecorder struct {
|
||||
// Code is the HTTP response code set by WriteHeader.
|
||||
//
|
||||
// Note that if a Handler never calls WriteHeader or Write,
|
||||
// this might end up being 0, rather than the implicit
|
||||
// http.StatusOK. To get the implicit value, use the Result
|
||||
// method.
|
||||
Code int
|
||||
|
||||
// HeaderMap contains the headers explicitly set by the Handler.
|
||||
// It is an internal detail.
|
||||
//
|
||||
// Deprecated: HeaderMap exists for historical compatibility
|
||||
// and should not be used. To access the headers returned by a handler,
|
||||
// use the Response.Header map as returned by the Result method.
|
||||
HeaderMap http.Header
|
||||
|
||||
// Body is the buffer to which the Handler's Write calls are sent.
|
||||
// If nil, the Writes are silently discarded.
|
||||
Body *bytes.Buffer
|
||||
|
||||
// Flushed is whether the Handler called Flush.
|
||||
Flushed bool
|
||||
|
||||
result *http.Response // cache of Result's return value
|
||||
snapHeader http.Header // snapshot of HeaderMap at first Write
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized [ResponseRecorder].
|
||||
func NewRecorder() *ResponseRecorder {
|
||||
return &ResponseRecorder{
|
||||
HeaderMap: make(http.Header),
|
||||
Body: new(bytes.Buffer),
|
||||
Code: 200,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
|
||||
// an explicit DefaultRemoteAddr isn't set on [ResponseRecorder].
|
||||
const DefaultRemoteAddr = "1.2.3.4"
|
||||
|
||||
// Header implements [http.ResponseWriter]. It returns the response
|
||||
// headers to mutate within a handler. To test the headers that were
|
||||
// written after a handler completes, use the [ResponseRecorder.Result] method and see
|
||||
// the returned Response value's Header.
|
||||
func (rw *ResponseRecorder) Header() http.Header {
|
||||
m := rw.HeaderMap
|
||||
if m == nil {
|
||||
m = make(http.Header)
|
||||
rw.HeaderMap = m
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// writeHeader writes a header if it was not written yet and
|
||||
// detects Content-Type if needed.
|
||||
//
|
||||
// bytes or str are the beginning of the response body.
|
||||
// We pass both to avoid unnecessarily generate garbage
|
||||
// in rw.WriteString which was created for performance reasons.
|
||||
// Non-nil bytes win.
|
||||
func (rw *ResponseRecorder) writeHeader(b []byte, str string) {
|
||||
if rw.wroteHeader {
|
||||
return
|
||||
}
|
||||
if len(str) > 512 {
|
||||
str = str[:512]
|
||||
}
|
||||
|
||||
m := rw.Header()
|
||||
|
||||
_, hasType := m["Content-Type"]
|
||||
hasTE := m.Get("Transfer-Encoding") != ""
|
||||
if !hasType && !hasTE {
|
||||
if b == nil {
|
||||
b = []byte(str)
|
||||
}
|
||||
m.Set("Content-Type", http.DetectContentType(b))
|
||||
}
|
||||
|
||||
rw.WriteHeader(200)
|
||||
}
|
||||
|
||||
// Write implements http.ResponseWriter. The data in buf is written to
|
||||
// rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
||||
rw.writeHeader(buf, "")
|
||||
if rw.Body != nil {
|
||||
rw.Body.Write(buf)
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// WriteString implements [io.StringWriter]. The data in str is written
|
||||
// to rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) WriteString(str string) (int, error) {
|
||||
rw.writeHeader(nil, str)
|
||||
if rw.Body != nil {
|
||||
rw.Body.WriteString(str)
|
||||
}
|
||||
return len(str), nil
|
||||
}
|
||||
|
||||
func checkWriteHeaderCode(code int) {
|
||||
// Issue 22880: require valid WriteHeader status codes.
|
||||
// For now we only enforce that it's three digits.
|
||||
// In the future we might block things over 599 (600 and above aren't defined
|
||||
// at https://httpwg.org/specs/rfc7231.html#status.codes)
|
||||
// and we might block under 200 (once we have more mature 1xx support).
|
||||
// But for now any three digits.
|
||||
//
|
||||
// We used to send "HTTP/1.1 000 0" on the wire in responses but there's
|
||||
// no equivalent bogus thing we can realistically send in HTTP/2,
|
||||
// so we'll consistently panic instead and help people find their bugs
|
||||
// early. (We can't return an error from WriteHeader even if we wanted to.)
|
||||
if code < 100 || code > 999 {
|
||||
panic(fmt.Sprintf("invalid WriteHeader code %v", code))
|
||||
}
|
||||
}
|
||||
|
||||
// WriteHeader implements [http.ResponseWriter].
|
||||
func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||
if rw.wroteHeader {
|
||||
return
|
||||
}
|
||||
|
||||
checkWriteHeaderCode(code)
|
||||
rw.Code = code
|
||||
rw.wroteHeader = true
|
||||
if rw.HeaderMap == nil {
|
||||
rw.HeaderMap = make(http.Header)
|
||||
}
|
||||
rw.snapHeader = rw.HeaderMap.Clone()
|
||||
}
|
||||
|
||||
// Flush implements [http.Flusher]. To test whether Flush was
|
||||
// called, see rw.Flushed.
|
||||
func (rw *ResponseRecorder) Flush() {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(200)
|
||||
}
|
||||
rw.Flushed = true
|
||||
}
|
||||
|
||||
// Result returns the response generated by the handler.
|
||||
//
|
||||
// The returned Response will have at least its StatusCode,
|
||||
// Header, Body, and optionally Trailer populated.
|
||||
// More fields may be populated in the future, so callers should
|
||||
// not DeepEqual the result in tests.
|
||||
//
|
||||
// The Response.Header is a snapshot of the headers at the time of the
|
||||
// first write call, or at the time of this call, if the handler never
|
||||
// did a write.
|
||||
//
|
||||
// The Response.Body is guaranteed to be non-nil and Body.Read call is
|
||||
// guaranteed to not return any error other than [io.EOF].
|
||||
//
|
||||
// Result must only be called after the handler has finished running.
|
||||
func (rw *ResponseRecorder) Result() *http.Response {
|
||||
if rw.result != nil {
|
||||
return rw.result
|
||||
}
|
||||
if rw.snapHeader == nil {
|
||||
rw.snapHeader = rw.HeaderMap.Clone()
|
||||
}
|
||||
res := &http.Response{
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
StatusCode: rw.Code,
|
||||
Header: rw.snapHeader,
|
||||
}
|
||||
rw.result = res
|
||||
if res.StatusCode == 0 {
|
||||
res.StatusCode = 200
|
||||
}
|
||||
res.Status = fmt.Sprintf("%03d %s", res.StatusCode, http.StatusText(res.StatusCode))
|
||||
if rw.Body != nil {
|
||||
res.Body = io.NopCloser(bytes.NewReader(rw.Body.Bytes()))
|
||||
} else {
|
||||
res.Body = http.NoBody
|
||||
}
|
||||
res.ContentLength = parseContentLength(res.Header.Get("Content-Length"))
|
||||
|
||||
if trailers, ok := rw.snapHeader["Trailer"]; ok {
|
||||
res.Trailer = make(http.Header, len(trailers))
|
||||
for _, k := range trailers {
|
||||
for _, k := range strings.Split(k, ",") {
|
||||
k = http.CanonicalHeaderKey(textproto.TrimString(k))
|
||||
if !httpguts.ValidTrailerHeader(k) {
|
||||
// Ignore since forbidden by RFC 7230, section 4.1.2.
|
||||
continue
|
||||
}
|
||||
vv, ok := rw.HeaderMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
vv2 := make([]string, len(vv))
|
||||
copy(vv2, vv)
|
||||
res.Trailer[k] = vv2
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, vv := range rw.HeaderMap {
|
||||
if !strings.HasPrefix(k, http.TrailerPrefix) {
|
||||
continue
|
||||
}
|
||||
if res.Trailer == nil {
|
||||
res.Trailer = make(http.Header)
|
||||
}
|
||||
for _, v := range vv {
|
||||
res.Trailer.Add(strings.TrimPrefix(k, http.TrailerPrefix), v)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// parseContentLength trims whitespace from s and returns -1 if no value
|
||||
// is set, or the value if it's >= 0.
|
||||
//
|
||||
// This a modified version of same function found in net/http/transfer.go. This
|
||||
// one just ignores an invalid header.
|
||||
func parseContentLength(cl string) int64 {
|
||||
cl = textproto.TrimString(cl)
|
||||
if cl == "" {
|
||||
return -1
|
||||
}
|
||||
n, err := strconv.ParseUint(cl, 10, 63)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return int64(n)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/util/set"
|
||||
@@ -54,11 +55,35 @@ func (c DepChecker) Check(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tsRoot := sync.OnceValue(func() string {
|
||||
out, err := exec.Command("go", "list", "-f", "{{.Dir}}", "tailscale.com").Output()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find tailscale.com root: %v", err)
|
||||
}
|
||||
return strings.TrimSpace(string(out))
|
||||
})
|
||||
|
||||
for _, dep := range res.Deps {
|
||||
if why, ok := c.BadDeps[dep]; ok {
|
||||
t.Errorf("package %q is not allowed as a dependency (env: %q); reason: %s", dep, extraEnv, why)
|
||||
}
|
||||
}
|
||||
// Make sure the BadDeps packages actually exists. If they got renamed or
|
||||
// moved around, we should update the test referencing the old name.
|
||||
// Doing this in the general case requires network access at runtime
|
||||
// (resolving a package path to its module, possibly doing the ?go-get=1
|
||||
// meta tag dance), so we just check the common case of
|
||||
// "tailscale.com/*" packages for now, with the assumption that all
|
||||
// "tailscale.com/*" packages are in the same module, which isn't
|
||||
// necessarily true in the general case.
|
||||
for dep := range c.BadDeps {
|
||||
if suf, ok := strings.CutPrefix(dep, "tailscale.com/"); ok {
|
||||
pkgDir := filepath.Join(tsRoot(), suf)
|
||||
if _, err := os.Stat(pkgDir); err != nil {
|
||||
t.Errorf("listed BadDep %q doesn't seem to exist anymore: %v", dep, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for dep := range c.WantDeps {
|
||||
if !slices.Contains(res.Deps, dep) {
|
||||
t.Errorf("expected package %q to be a dependency (env: %q)", dep, extraEnv)
|
||||
|
||||
@@ -24,6 +24,7 @@ func TestDeps(t *testing.T) {
|
||||
"github.com/google/uuid": "see tailscale/tailscale#13760",
|
||||
"tailscale.com/clientupdate/distsign": "downloads via AppStore, not distsign",
|
||||
"github.com/tailscale/hujson": "no config file support on iOS",
|
||||
"tailscale.com/feature/capture": "no debug packet capture on iOS",
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ import (
|
||||
"tailscale.com/util/set"
|
||||
"tailscale.com/util/testenv"
|
||||
"tailscale.com/util/usermetric"
|
||||
"tailscale.com/wgengine/capture"
|
||||
"tailscale.com/wgengine/wgint"
|
||||
)
|
||||
|
||||
@@ -238,7 +237,7 @@ type Conn struct {
|
||||
stats atomic.Pointer[connstats.Statistics]
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// can be called with a nil argument to uninstall the capture
|
||||
// hook.
|
||||
func (c *Conn) InstallCaptureHook(cb capture.Callback) {
|
||||
func (c *Conn) InstallCaptureHook(cb packet.CaptureCallback) {
|
||||
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
|
||||
// if a capture hook is installed.
|
||||
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)
|
||||
|
||||
@@ -405,6 +405,14 @@ func (ns *Impl) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTransportProtocolOption forwards to the underlying
|
||||
// [stack.Stack.SetTransportProtocolOption]. Callers are responsible for
|
||||
// ensuring that the options are valid, compatible and appropriate for their use
|
||||
// case. Compatibility may change at any version.
|
||||
func (ns *Impl) SetTransportProtocolOption(transport tcpip.TransportProtocolNumber, option tcpip.SettableTransportProtocolOption) tcpip.Error {
|
||||
return ns.ipstack.SetTransportProtocolOption(transport, option)
|
||||
}
|
||||
|
||||
// A single process might have several netstacks running at the same time.
|
||||
// Exported clientmetric counters will have a sum of counters of all of them.
|
||||
var stacksForMetrics syncs.Map[*Impl, struct{}]
|
||||
|
||||
@@ -51,7 +51,6 @@ import (
|
||||
"tailscale.com/util/testenv"
|
||||
"tailscale.com/util/usermetric"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/wgengine/capture"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/magicsock"
|
||||
"tailscale.com/wgengine/netlog"
|
||||
@@ -1594,7 +1593,7 @@ var (
|
||||
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.magicConn.InstallCaptureHook(cb)
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/wgengine/capture"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/router"
|
||||
"tailscale.com/wgengine/wgcfg"
|
||||
@@ -162,7 +162,7 @@ func (e *watchdogEngine) Done() <-chan struct{} {
|
||||
return e.wrap.Done()
|
||||
}
|
||||
|
||||
func (e *watchdogEngine) InstallCaptureHook(cb capture.Callback) {
|
||||
func (e *watchdogEngine) InstallCaptureHook(cb packet.CaptureCallback) {
|
||||
e.wrap.InstallCaptureHook(cb)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/wgengine/capture"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/router"
|
||||
"tailscale.com/wgengine/wgcfg"
|
||||
@@ -129,5 +129,5 @@ type Engine interface {
|
||||
// InstallCaptureHook registers a function to be called to capture
|
||||
// packets traversing the data path. The hook can be uninstalled by
|
||||
// calling this function with a nil value.
|
||||
InstallCaptureHook(capture.Callback)
|
||||
InstallCaptureHook(packet.CaptureCallback)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user