Compare commits
21 Commits
tom/iptabl
...
andrew/lin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d067c7cf41 | ||
|
|
91d28e7155 | ||
|
|
d8eb111ac8 | ||
|
|
832031d54b | ||
|
|
42f1d92ae0 | ||
|
|
41bb47de0e | ||
|
|
3562b5bdfa | ||
|
|
5c42990c2f | ||
|
|
65c24b6334 | ||
|
|
4bda41e701 | ||
|
|
9b71008ef2 | ||
|
|
5623ef0271 | ||
|
|
486eecc063 | ||
|
|
b830c9975f | ||
|
|
4a82b317b7 | ||
|
|
f0347e841f | ||
|
|
027111fb5a | ||
|
|
1ce0e558a7 | ||
|
|
74674b110d | ||
|
|
33ee2c058e | ||
|
|
d34dd43562 |
54
.github/workflows/cross-android.yml
vendored
Normal file
54
.github/workflows/cross-android.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Android-Cross
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
id: go
|
||||
|
||||
- name: Android smoke build
|
||||
# Super minimal Android build that doesn't even use CGO and doesn't build everything that's needed
|
||||
# and is only arm64. But it's a smoke build: it's not meant to catch everything. But it'll catch
|
||||
# some Android breakages early.
|
||||
# TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482
|
||||
env:
|
||||
GOOS: android
|
||||
GOARCH: arm64
|
||||
run: go install ./net/netns ./ipn/ipnlocal ./wgengine/magicsock/ ./wgengine/ ./wgengine/router/ ./wgengine/netstack ./util/dnsname/ ./ipn/ ./net/interfaces ./wgengine/router/ ./tailcfg/ ./types/logger/ ./net/dns ./hostinfo ./version
|
||||
|
||||
- uses: k0kubun/action-slack@v2.0.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"attachments": [{
|
||||
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
|
||||
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
|
||||
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
|
||||
"color": "danger"
|
||||
}]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
if: failure() && github.event_name == 'push'
|
||||
@@ -32,7 +32,7 @@
|
||||
# $ docker exec tailscaled tailscale status
|
||||
|
||||
|
||||
FROM golang:1.18-alpine AS build-env
|
||||
FROM golang:1.19-alpine AS build-env
|
||||
|
||||
WORKDIR /go/src/tailscale
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
package atomicfile // import "tailscale.com/atomicfile"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -18,7 +17,7 @@ import (
|
||||
// WriteFile writes data to filename+some suffix, then renames it
|
||||
// into filename. The perm argument is ignored on Windows.
|
||||
func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
|
||||
f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".tmp")
|
||||
f, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename)+".tmp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
@@ -137,7 +136,7 @@ func (lc *LocalClient) doLocalRequestNiceError(req *http.Request) (*http.Respons
|
||||
onVersionMismatch(ipn.IPCVersion(), server)
|
||||
}
|
||||
if res.StatusCode == 403 {
|
||||
all, _ := ioutil.ReadAll(res.Body)
|
||||
all, _ := io.ReadAll(res.Body)
|
||||
return nil, &AccessDeniedError{errors.New(errorMessageFromBody(all))}
|
||||
}
|
||||
return res, nil
|
||||
@@ -207,7 +206,7 @@ func (lc *LocalClient) send(ctx context.Context, method, path string, wantStatus
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
slurp, err := ioutil.ReadAll(res.Body)
|
||||
slurp, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -365,7 +364,7 @@ func (lc *LocalClient) GetWaitingFile(ctx context.Context, baseName string) (rc
|
||||
return nil, 0, fmt.Errorf("unexpected chunking")
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return nil, 0, fmt.Errorf("HTTP %s: %s", res.Status, body)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -131,7 +130,7 @@ func (c *Client) sendRequest(req *http.Request) ([]byte, *http.Response, error)
|
||||
|
||||
// Read response. Limit the response to 10MB.
|
||||
body := io.LimitReader(resp.Body, maxReadSize+1)
|
||||
b, err := ioutil.ReadAll(body)
|
||||
b, err := io.ReadAll(body)
|
||||
if len(b) > maxReadSize {
|
||||
err = errors.New("API response too large")
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
@@ -99,7 +98,7 @@ func loadConfig() config {
|
||||
}
|
||||
log.Printf("no config path specified; using %s", *configPath)
|
||||
}
|
||||
b, err := ioutil.ReadFile(*configPath)
|
||||
b, err := os.ReadFile(*configPath)
|
||||
switch {
|
||||
case errors.Is(err, os.ErrNotExist):
|
||||
return writeNewConfig()
|
||||
@@ -155,7 +154,7 @@ func main() {
|
||||
s.SetVerifyClient(*verifyClients)
|
||||
|
||||
if *meshPSKFile != "" {
|
||||
b, err := ioutil.ReadFile(*meshPSKFile)
|
||||
b, err := os.ReadFile(*meshPSKFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@ func addWebSocketSupport(s *derp.Server, base http.Handler) http.Handler {
|
||||
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
||||
Subprotocols: []string{"derp"},
|
||||
OriginPatterns: []string{"*"},
|
||||
// Disable compression because we transmit WireGuard messages that
|
||||
// are not compressible.
|
||||
// Additionally, Safari has a broken implementation of compression
|
||||
// (see https://github.com/nhooyr/websocket/issues/218) that makes
|
||||
// enabling it actively harmful.
|
||||
CompressionMode: websocket.CompressionDisabled,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("websocket.Accept: %v", err)
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -106,7 +105,7 @@ func devMode() bool { return *httpsAddr == "" && *httpAddr != "" }
|
||||
|
||||
func getTmpl() (*template.Template, error) {
|
||||
if devMode() {
|
||||
tmplData, err := ioutil.ReadFile("hello.tmpl.html")
|
||||
tmplData, err := os.ReadFile("hello.tmpl.html")
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("using baked-in template in dev mode; can't find hello.tmpl.html in current directory")
|
||||
return tmpl, nil
|
||||
|
||||
@@ -489,7 +489,15 @@ func runTS2021(ctx context.Context, args []string) error {
|
||||
return c, err
|
||||
}
|
||||
|
||||
conn, err := controlhttp.Dial(ctx, ts2021Args.host, "80", "443", machinePrivate, keys.PublicKey, uint16(ts2021Args.version), dialFunc)
|
||||
conn, err := (&controlhttp.Dialer{
|
||||
Hostname: ts2021Args.host,
|
||||
HTTPPort: "80",
|
||||
HTTPSPort: "443",
|
||||
MachineKey: machinePrivate,
|
||||
ControlKey: keys.PublicKey,
|
||||
ProtocolVersion: uint16(ts2021Args.version),
|
||||
Dialer: dialFunc,
|
||||
}).Dial(ctx)
|
||||
log.Printf("controlhttp.Dial = %p, %v", conn, err)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
@@ -202,7 +201,7 @@ func prodDERPMap(ctx context.Context, httpc *http.Client) (*tailcfg.DERPMap, err
|
||||
return nil, fmt.Errorf("fetch prodDERPMap failed: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
|
||||
b, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetch prodDERPMap failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -254,7 +253,7 @@ func qnapAuthnFinish(user, url string) (string, *qnapAuthResponse, error) {
|
||||
return "", nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
out, err := ioutil.ReadAll(resp.Body)
|
||||
out, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -173,7 +172,7 @@ func checkDerp(ctx context.Context, derpRegion string) error {
|
||||
return fmt.Errorf("fetch derp map failed: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
|
||||
b, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch derp map failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -142,7 +141,7 @@ func installSystemDaemonDarwin(args []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil {
|
||||
if err := os.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -97,6 +98,20 @@ func defaultTunName() string {
|
||||
return "tailscale0"
|
||||
}
|
||||
|
||||
// defaultPort returns the default UDP port to listen on for disco+wireguard.
|
||||
// By default it returns 0, to pick one randomly from the kernel.
|
||||
// If the environment variable PORT is set, that's used instead.
|
||||
// The PORT environment variable is chosen to match what the Linux systemd
|
||||
// unit uses, to make documentation more consistent.
|
||||
func defaultPort() uint16 {
|
||||
if s := envknob.String("PORT"); s != "" {
|
||||
if p, err := strconv.ParseUint(s, 10, 16); err == nil {
|
||||
return uint16(p)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var args struct {
|
||||
// tunname is a /dev/net/tun tunnel name ("tailscale0"), the
|
||||
// string "userspace-networking", "tap:TAPNAME[:BRIDGENAME]"
|
||||
@@ -132,6 +147,9 @@ var subCommands = map[string]*func([]string) error{
|
||||
var beCLI func() // non-nil if CLI is linked in
|
||||
|
||||
func main() {
|
||||
envknob.PanicIfAnyEnvCheckedInInit()
|
||||
envknob.ApplyDiskConfig()
|
||||
|
||||
printVersion := false
|
||||
flag.IntVar(&args.verbose, "verbose", 0, "log verbosity level; 0 is default, 1 or higher are increasingly verbose")
|
||||
flag.BoolVar(&args.cleanup, "cleanup", false, "clean up system state and exit")
|
||||
@@ -139,7 +157,7 @@ func main() {
|
||||
flag.StringVar(&args.socksAddr, "socks5-server", "", `optional [ip]:port to run a SOCK5 server (e.g. "localhost:1080")`)
|
||||
flag.StringVar(&args.httpProxyAddr, "outbound-http-proxy-listen", "", `optional [ip]:port to run an outbound HTTP proxy (e.g. "localhost:8080")`)
|
||||
flag.StringVar(&args.tunname, "tun", defaultTunName(), `tunnel interface name; use "userspace-networking" (beta) to not use TUN`)
|
||||
flag.Var(flagtype.PortValue(&args.port, 0), "port", "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
|
||||
flag.Var(flagtype.PortValue(&args.port, defaultPort()), "port", "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
|
||||
flag.StringVar(&args.statepath, "state", "", "absolute path of state file; use 'kube:<secret-name>' to use Kubernetes secrets or 'arn:aws:ssm:...' to store in AWS SSM; use 'mem:' to not store state and register as an emphemeral node. If empty and --statedir is provided, the default is <statedir>/tailscaled.state. Default: "+paths.DefaultTailscaledStateFile())
|
||||
flag.StringVar(&args.statedir, "statedir", "", "path to directory for storage of config state, TLS certs, temporary incoming Taildrop files, etc. If empty, it's derived from --state when possible.")
|
||||
flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket")
|
||||
@@ -308,6 +326,10 @@ func run() error {
|
||||
pol.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
if err := envknob.ApplyDiskConfigError(); err != nil {
|
||||
log.Printf("Error reading environment config: %v", err)
|
||||
}
|
||||
|
||||
if isWindowsService() {
|
||||
// Run the IPN server from the Windows service manager.
|
||||
log.Printf("Running service...")
|
||||
@@ -376,7 +398,7 @@ func run() error {
|
||||
return fmt.Errorf("newNetstack: %w", err)
|
||||
}
|
||||
ns.ProcessLocalIPs = useNetstack
|
||||
ns.ProcessSubnets = useNetstack || wrapNetstack
|
||||
ns.ProcessSubnets = useNetstack || shouldWrapNetstack()
|
||||
|
||||
if useNetstack {
|
||||
dialer.UseNetstackForIP = func(ip netip.Addr) bool {
|
||||
@@ -477,8 +499,6 @@ func createEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer)
|
||||
return nil, false, multierr.New(errs...)
|
||||
}
|
||||
|
||||
var wrapNetstack = shouldWrapNetstack()
|
||||
|
||||
func shouldWrapNetstack() bool {
|
||||
if v, ok := envknob.LookupBool("TS_DEBUG_WRAP_NETSTACK"); ok {
|
||||
return v
|
||||
@@ -549,7 +569,7 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer, na
|
||||
}
|
||||
conf.DNS = d
|
||||
conf.Router = r
|
||||
if wrapNetstack {
|
||||
if shouldWrapNetstack() {
|
||||
conf.Router = netstack.NewSubnetRouterWrapper(conf.Router)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,9 @@ func beWindowsSubprocess() bool {
|
||||
|
||||
log.Printf("Program starting: v%v: %#v", version.Long, os.Args)
|
||||
log.Printf("subproc mode: logid=%v", logid)
|
||||
if err := envknob.ApplyDiskConfigError(); err != nil {
|
||||
log.Printf("Error reading environment config: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
b := make([]byte, 16)
|
||||
@@ -274,7 +277,7 @@ func startIPNServer(ctx context.Context, logid string) error {
|
||||
dev.Close()
|
||||
return nil, nil, fmt.Errorf("router: %w", err)
|
||||
}
|
||||
if wrapNetstack {
|
||||
if shouldWrapNetstack() {
|
||||
r = netstack.NewSubnetRouterWrapper(r)
|
||||
}
|
||||
d, err := dns.NewOSConfigurator(logf, devName)
|
||||
@@ -301,7 +304,7 @@ func startIPNServer(ctx context.Context, logid string) error {
|
||||
return nil, nil, fmt.Errorf("newNetstack: %w", err)
|
||||
}
|
||||
ns.ProcessLocalIPs = false
|
||||
ns.ProcessSubnets = wrapNetstack
|
||||
ns.ProcessSubnets = shouldWrapNetstack()
|
||||
if err := ns.Start(); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to start netstack: %w", err)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
@@ -47,7 +46,7 @@ func runBuild() {
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot fix esbuild metadata paths: %v", err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(*distDir, "/esbuild-metadata.json"), metadataBytes, 0666); err != nil {
|
||||
if err := os.WriteFile(path.Join(*distDir, "/esbuild-metadata.json"), metadataBytes, 0666); err != nil {
|
||||
log.Fatalf("Cannot write metadata: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
@@ -183,7 +182,7 @@ func setupEsbuildWasm(build esbuild.PluginBuild, dev bool) {
|
||||
|
||||
func buildWasm(dev bool) ([]byte, error) {
|
||||
start := time.Now()
|
||||
outputFile, err := ioutil.TempFile("", "main.*.wasm")
|
||||
outputFile, err := os.CreateTemp("", "main.*.wasm")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot create main.wasm output file: %w", err)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -75,7 +74,7 @@ func generateServeIndex(distFS fs.FS) ([]byte, error) {
|
||||
return nil, fmt.Errorf("Could not open esbuild-metadata.json: %w", err)
|
||||
}
|
||||
defer esbuildMetadataFile.Close()
|
||||
esbuildMetadataBytes, err := ioutil.ReadAll(esbuildMetadataFile)
|
||||
esbuildMetadataBytes, err := io.ReadAll(esbuildMetadataFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not read esbuild-metadata.json: %w", err)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -490,7 +489,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
||||
c.logf("RegisterReq sign error: %v", err)
|
||||
}
|
||||
}
|
||||
if debugRegister {
|
||||
if debugRegister() {
|
||||
j, _ := json.MarshalIndent(request, "", "\t")
|
||||
c.logf("RegisterRequest: %s", j)
|
||||
}
|
||||
@@ -523,7 +522,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
||||
return regen, opt.URL, fmt.Errorf("register request: %w", err)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
msg, _ := ioutil.ReadAll(res.Body)
|
||||
msg, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return regen, opt.URL, fmt.Errorf("register request: http %d: %.200s",
|
||||
res.StatusCode, strings.TrimSpace(string(msg)))
|
||||
@@ -533,7 +532,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
|
||||
c.logf("error decoding RegisterResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err)
|
||||
return regen, opt.URL, fmt.Errorf("register request: %v", err)
|
||||
}
|
||||
if debugRegister {
|
||||
if debugRegister() {
|
||||
j, _ := json.MarshalIndent(resp, "", "\t")
|
||||
c.logf("RegisterResponse: %s", j)
|
||||
}
|
||||
@@ -715,7 +714,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
|
||||
c.logf("[v1] PollNetMap: stream=%v ep=%v", allowStream, epStrs)
|
||||
|
||||
vlogf := logger.Discard
|
||||
if DevKnob.DumpNetMaps {
|
||||
if DevKnob.DumpNetMaps() {
|
||||
// TODO(bradfitz): update this to use "[v2]" prefix perhaps? but we don't
|
||||
// want to upload it always.
|
||||
vlogf = c.logf
|
||||
@@ -804,7 +803,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
|
||||
}
|
||||
vlogf("netmap: Do = %v after %v", res.StatusCode, time.Since(t0).Round(time.Millisecond))
|
||||
if res.StatusCode != 200 {
|
||||
msg, _ := ioutil.ReadAll(res.Body)
|
||||
msg, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return fmt.Errorf("initial fetch failed %d: %.200s",
|
||||
res.StatusCode, strings.TrimSpace(string(msg)))
|
||||
@@ -814,7 +813,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
|
||||
health.NoteMapRequestHeard(request)
|
||||
|
||||
if cb == nil {
|
||||
io.Copy(ioutil.Discard, res.Body)
|
||||
io.Copy(io.Discard, res.Body)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -963,12 +962,12 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
|
||||
controlTrimWGConfig.Store(d.TrimWGConfig)
|
||||
}
|
||||
|
||||
if DevKnob.StripEndpoints {
|
||||
if DevKnob.StripEndpoints() {
|
||||
for _, p := range resp.Peers {
|
||||
p.Endpoints = nil
|
||||
}
|
||||
}
|
||||
if DevKnob.StripCaps {
|
||||
if DevKnob.StripCaps() {
|
||||
nm.SelfNode.Capabilities = nil
|
||||
}
|
||||
|
||||
@@ -998,7 +997,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
|
||||
// it uses the serverKey and mkey to decode the message from the NaCl-crypto-box.
|
||||
func decode(res *http.Response, v any, serverKey, serverNoiseKey key.MachinePublic, mkey key.MachinePrivate) error {
|
||||
defer res.Body.Close()
|
||||
msg, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
|
||||
msg, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1012,8 +1011,8 @@ func decode(res *http.Response, v any, serverKey, serverNoiseKey key.MachinePubl
|
||||
}
|
||||
|
||||
var (
|
||||
debugMap = envknob.Bool("TS_DEBUG_MAP")
|
||||
debugRegister = envknob.Bool("TS_DEBUG_REGISTER")
|
||||
debugMap = envknob.RegisterBool("TS_DEBUG_MAP")
|
||||
debugRegister = envknob.RegisterBool("TS_DEBUG_REGISTER")
|
||||
)
|
||||
|
||||
var jsonEscapedZero = []byte(`\u0000`)
|
||||
@@ -1051,7 +1050,7 @@ func (c *Direct) decodeMsg(msg []byte, v any, mkey key.MachinePrivate) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if debugMap {
|
||||
if debugMap() {
|
||||
var buf bytes.Buffer
|
||||
json.Indent(&buf, b, "", " ")
|
||||
log.Printf("MapResponse: %s", buf.Bytes())
|
||||
@@ -1088,7 +1087,7 @@ func encode(v any, serverKey, serverNoiseKey key.MachinePublic, mkey key.Machine
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if debugMap {
|
||||
if debugMap() {
|
||||
if _, ok := v.(*tailcfg.MapRequest); ok {
|
||||
log.Printf("MapRequest: %s", b)
|
||||
}
|
||||
@@ -1110,7 +1109,7 @@ func loadServerPubKeys(ctx context.Context, httpc *http.Client, serverURL string
|
||||
return nil, fmt.Errorf("fetch control key: %v", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 64<<10))
|
||||
b, err := io.ReadAll(io.LimitReader(res.Body, 64<<10))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetch control key response: %v", err)
|
||||
}
|
||||
@@ -1139,18 +1138,18 @@ func loadServerPubKeys(ctx context.Context, httpc *http.Client, serverURL string
|
||||
var DevKnob = initDevKnob()
|
||||
|
||||
type devKnobs struct {
|
||||
DumpNetMaps bool
|
||||
ForceProxyDNS bool
|
||||
StripEndpoints bool // strip endpoints from control (only use disco messages)
|
||||
StripCaps bool // strip all local node's control-provided capabilities
|
||||
DumpNetMaps func() bool
|
||||
ForceProxyDNS func() bool
|
||||
StripEndpoints func() bool // strip endpoints from control (only use disco messages)
|
||||
StripCaps func() bool // strip all local node's control-provided capabilities
|
||||
}
|
||||
|
||||
func initDevKnob() devKnobs {
|
||||
return devKnobs{
|
||||
DumpNetMaps: envknob.Bool("TS_DEBUG_NETMAP"),
|
||||
ForceProxyDNS: envknob.Bool("TS_DEBUG_PROXY_DNS"),
|
||||
StripEndpoints: envknob.Bool("TS_DEBUG_STRIP_ENDPOINTS"),
|
||||
StripCaps: envknob.Bool("TS_DEBUG_STRIP_CAPS"),
|
||||
DumpNetMaps: envknob.RegisterBool("TS_DEBUG_NETMAP"),
|
||||
ForceProxyDNS: envknob.RegisterBool("TS_DEBUG_PROXY_DNS"),
|
||||
StripEndpoints: envknob.RegisterBool("TS_DEBUG_STRIP_ENDPOINTS"),
|
||||
StripCaps: envknob.RegisterBool("TS_DEBUG_STRIP_CAPS"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1398,7 +1397,7 @@ func (c *Direct) setDNSNoise(ctx context.Context, req *tailcfg.SetDNSRequest) er
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
msg, _ := ioutil.ReadAll(res.Body)
|
||||
msg, _ := io.ReadAll(res.Body)
|
||||
return fmt.Errorf("set-dns response: %v, %.200s", res.Status, strings.TrimSpace(string(msg)))
|
||||
}
|
||||
var setDNSRes tailcfg.SetDNSResponse
|
||||
@@ -1464,7 +1463,7 @@ func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err er
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
msg, _ := ioutil.ReadAll(res.Body)
|
||||
msg, _ := io.ReadAll(res.Body)
|
||||
return fmt.Errorf("set-dns response: %v, %.200s", res.Status, strings.TrimSpace(string(msg)))
|
||||
}
|
||||
var setDNSRes tailcfg.SetDNSResponse
|
||||
|
||||
@@ -190,7 +190,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
|
||||
}
|
||||
ms.addUserProfile(peer.User)
|
||||
}
|
||||
if DevKnob.ForceProxyDNS {
|
||||
if DevKnob.ForceProxyDNS() {
|
||||
nm.DNS.Proxied = true
|
||||
}
|
||||
ms.netMapBuilding = nil
|
||||
@@ -356,13 +356,13 @@ func cloneNodes(v1 []*tailcfg.Node) []*tailcfg.Node {
|
||||
return v2
|
||||
}
|
||||
|
||||
var debugSelfIPv6Only = envknob.Bool("TS_DEBUG_SELF_V6_ONLY")
|
||||
var debugSelfIPv6Only = envknob.RegisterBool("TS_DEBUG_SELF_V6_ONLY")
|
||||
|
||||
func filterSelfAddresses(in []netip.Prefix) (ret []netip.Prefix) {
|
||||
switch {
|
||||
default:
|
||||
return in
|
||||
case debugSelfIPv6Only:
|
||||
case debugSelfIPv6Only():
|
||||
for _, a := range in {
|
||||
if a.Addr().Is6() {
|
||||
ret = append(ret, a)
|
||||
|
||||
@@ -165,7 +165,15 @@ func (nc *noiseClient) dial(_, _ string, _ *tls.Config) (net.Conn, error) {
|
||||
// thousand version numbers before getting to this point.
|
||||
panic("capability version is too high to fit in the wire protocol")
|
||||
}
|
||||
conn, err := controlhttp.Dial(ctx, nc.host, nc.httpPort, nc.httpsPort, nc.privKey, nc.serverPubKey, uint16(tailcfg.CurrentCapabilityVersion), nc.dialer.SystemDial)
|
||||
conn, err := (&controlhttp.Dialer{
|
||||
Hostname: nc.host,
|
||||
HTTPPort: nc.httpPort,
|
||||
HTTPSPort: nc.httpsPort,
|
||||
MachineKey: nc.privKey,
|
||||
ControlKey: nc.serverPubKey,
|
||||
ProtocolVersion: uint16(tailcfg.CurrentCapabilityVersion),
|
||||
Dialer: nc.dialer.SystemDial,
|
||||
}).Dial(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -40,57 +40,49 @@ import (
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/tlsdial"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
// Dial connects to the HTTP server at host:httpPort, requests to switch to the
|
||||
// Tailscale control protocol, and returns an established control
|
||||
var stdDialer net.Dialer
|
||||
|
||||
// Dial connects to the HTTP server at this Dialer's Host:HTTPPort, requests to
|
||||
// switch to the Tailscale control protocol, and returns an established control
|
||||
// protocol connection.
|
||||
//
|
||||
// If Dial fails to connect using addr, it also tries to tunnel over
|
||||
// TLS to host:httpsPort as a compatibility fallback.
|
||||
// If Dial fails to connect using HTTP, it also tries to tunnel over TLS to the
|
||||
// Dialer's Host:HTTPSPort as a compatibility fallback.
|
||||
//
|
||||
// The provided ctx is only used for the initial connection, until
|
||||
// Dial returns. It does not affect the connection once established.
|
||||
func Dial(ctx context.Context, host string, httpPort string, httpsPort string, machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16, dialer dnscache.DialContextFunc) (*controlbase.Conn, error) {
|
||||
a := &dialParams{
|
||||
host: host,
|
||||
httpPort: httpPort,
|
||||
httpsPort: httpsPort,
|
||||
machineKey: machineKey,
|
||||
controlKey: controlKey,
|
||||
version: protocolVersion,
|
||||
proxyFunc: tshttpproxy.ProxyFromEnvironment,
|
||||
dialer: dialer,
|
||||
func (a *Dialer) Dial(ctx context.Context) (*controlbase.Conn, error) {
|
||||
if a.Hostname == "" {
|
||||
return nil, errors.New("required Dialer.Hostname empty")
|
||||
}
|
||||
return a.dial(ctx)
|
||||
}
|
||||
|
||||
type dialParams struct {
|
||||
host string
|
||||
httpPort string
|
||||
httpsPort string
|
||||
machineKey key.MachinePrivate
|
||||
controlKey key.MachinePublic
|
||||
version uint16
|
||||
proxyFunc func(*http.Request) (*url.URL, error) // or nil
|
||||
dialer dnscache.DialContextFunc
|
||||
|
||||
// For tests only
|
||||
insecureTLS bool
|
||||
testFallbackDelay time.Duration
|
||||
func (a *Dialer) logf(format string, args ...any) {
|
||||
if a.Logf != nil {
|
||||
a.Logf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// httpsFallbackDelay is how long we'll wait for a.httpPort to work before
|
||||
// starting to try a.httpsPort.
|
||||
func (a *dialParams) httpsFallbackDelay() time.Duration {
|
||||
func (a *Dialer) getProxyFunc() func(*http.Request) (*url.URL, error) {
|
||||
if a.proxyFunc != nil {
|
||||
return a.proxyFunc
|
||||
}
|
||||
return tshttpproxy.ProxyFromEnvironment
|
||||
}
|
||||
|
||||
// httpsFallbackDelay is how long we'll wait for a.HTTPPort to work before
|
||||
// starting to try a.HTTPSPort.
|
||||
func (a *Dialer) httpsFallbackDelay() time.Duration {
|
||||
if v := a.testFallbackDelay; v != 0 {
|
||||
return v
|
||||
}
|
||||
return 500 * time.Millisecond
|
||||
}
|
||||
|
||||
func (a *dialParams) dial(ctx context.Context) (*controlbase.Conn, error) {
|
||||
func (a *Dialer) dial(ctx context.Context) (*controlbase.Conn, error) {
|
||||
// Create one shared context used by both port 80 and port 443 dials.
|
||||
// If port 80 is still in flight when 443 returns, this deferred cancel
|
||||
// will stop the port 80 dial.
|
||||
@@ -102,12 +94,12 @@ func (a *dialParams) dial(ctx context.Context) (*controlbase.Conn, error) {
|
||||
// we'll speak Noise.
|
||||
u80 := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: net.JoinHostPort(a.host, a.httpPort),
|
||||
Host: net.JoinHostPort(a.Hostname, strDef(a.HTTPPort, "80")),
|
||||
Path: serverUpgradePath,
|
||||
}
|
||||
u443 := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort(a.host, a.httpsPort),
|
||||
Host: net.JoinHostPort(a.Hostname, strDef(a.HTTPSPort, "443")),
|
||||
Path: serverUpgradePath,
|
||||
}
|
||||
|
||||
@@ -169,8 +161,8 @@ func (a *dialParams) dial(ctx context.Context) (*controlbase.Conn, error) {
|
||||
}
|
||||
|
||||
// dialURL attempts to connect to the given URL.
|
||||
func (a *dialParams) dialURL(ctx context.Context, u *url.URL) (*controlbase.Conn, error) {
|
||||
init, cont, err := controlbase.ClientDeferred(a.machineKey, a.controlKey, a.version)
|
||||
func (a *Dialer) dialURL(ctx context.Context, u *url.URL) (*controlbase.Conn, error) {
|
||||
init, cont, err := controlbase.ClientDeferred(a.MachineKey, a.ControlKey, a.ProtocolVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -189,26 +181,34 @@ func (a *dialParams) dialURL(ctx context.Context, u *url.URL) (*controlbase.Conn
|
||||
// tryURLUpgrade connects to u, and tries to upgrade it to a net.Conn.
|
||||
//
|
||||
// Only the provided ctx is used, not a.ctx.
|
||||
func (a *dialParams) tryURLUpgrade(ctx context.Context, u *url.URL, init []byte) (net.Conn, error) {
|
||||
func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, init []byte) (net.Conn, error) {
|
||||
dns := &dnscache.Resolver{
|
||||
Forward: dnscache.Get().Forward,
|
||||
LookupIPFallback: dnsfallback.Lookup,
|
||||
UseLastGood: true,
|
||||
}
|
||||
|
||||
var dialer dnscache.DialContextFunc
|
||||
if a.Dialer != nil {
|
||||
dialer = a.Dialer
|
||||
} else {
|
||||
dialer = stdDialer.DialContext
|
||||
}
|
||||
|
||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||
defer tr.CloseIdleConnections()
|
||||
tr.Proxy = a.proxyFunc
|
||||
tr.Proxy = a.getProxyFunc()
|
||||
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
|
||||
tr.DialContext = dnscache.Dialer(a.dialer, dns)
|
||||
tr.DialContext = dnscache.Dialer(dialer, dns)
|
||||
// Disable HTTP2, since h2 can't do protocol switching.
|
||||
tr.TLSClientConfig.NextProtos = []string{}
|
||||
tr.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
|
||||
tr.TLSClientConfig = tlsdial.Config(a.host, tr.TLSClientConfig)
|
||||
tr.TLSClientConfig = tlsdial.Config(a.Hostname, tr.TLSClientConfig)
|
||||
if a.insecureTLS {
|
||||
tr.TLSClientConfig.InsecureSkipVerify = true
|
||||
tr.TLSClientConfig.VerifyConnection = nil
|
||||
}
|
||||
tr.DialTLSContext = dnscache.TLSDialer(a.dialer, dns, tr.TLSClientConfig)
|
||||
tr.DialTLSContext = dnscache.TLSDialer(dialer, dns, tr.TLSClientConfig)
|
||||
tr.DisableCompression = true
|
||||
|
||||
// (mis)use httptrace to extract the underlying net.Conn from the
|
||||
|
||||
@@ -7,27 +7,31 @@ package controlhttp
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"nhooyr.io/websocket"
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/types/key"
|
||||
)
|
||||
|
||||
// Variant of Dial that tunnels the request over WebSockets, since we cannot do
|
||||
// bi-directional communication over an HTTP connection when in JS.
|
||||
func Dial(ctx context.Context, host string, httpPort string, httpsPort string, machineKey key.MachinePrivate, controlKey key.MachinePublic, protocolVersion uint16, dialer dnscache.DialContextFunc) (*controlbase.Conn, error) {
|
||||
init, cont, err := controlbase.ClientDeferred(machineKey, controlKey, protocolVersion)
|
||||
func (d *Dialer) Dial(ctx context.Context) (*controlbase.Conn, error) {
|
||||
if d.Hostname == "" {
|
||||
return nil, errors.New("required Dialer.Hostname empty")
|
||||
}
|
||||
|
||||
init, cont, err := controlbase.ClientDeferred(d.MachineKey, d.ControlKey, d.ProtocolVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wsScheme := "wss"
|
||||
host := d.Hostname
|
||||
if host == "localhost" {
|
||||
wsScheme = "ws"
|
||||
host = net.JoinHostPort(host, httpPort)
|
||||
host = net.JoinHostPort(host, strDef(d.HTTPPort, "80"))
|
||||
}
|
||||
wsURL := &url.URL{
|
||||
Scheme: wsScheme,
|
||||
@@ -52,5 +56,4 @@ func Dial(ctx context.Context, host string, httpPort string, httpsPort string, m
|
||||
return nil, err
|
||||
}
|
||||
return cbConn, nil
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,16 @@
|
||||
|
||||
package controlhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
// upgradeHeader is the value of the Upgrade HTTP header used to
|
||||
// indicate the Tailscale control protocol.
|
||||
@@ -18,3 +28,58 @@ const (
|
||||
// to do the protocol switch is located.
|
||||
serverUpgradePath = "/ts2021"
|
||||
)
|
||||
|
||||
// Dialer contains configuration on how to dial the Tailscale control server.
|
||||
type Dialer struct {
|
||||
// Hostname is the hostname to connect to, with no port number.
|
||||
//
|
||||
// This field is required.
|
||||
Hostname string
|
||||
|
||||
// MachineKey contains the current machine's private key.
|
||||
//
|
||||
// This field is required.
|
||||
MachineKey key.MachinePrivate
|
||||
|
||||
// ControlKey contains the expected public key for the control server.
|
||||
//
|
||||
// This field is required.
|
||||
ControlKey key.MachinePublic
|
||||
|
||||
// ProtocolVersion is the expected protocol version to negotiate.
|
||||
//
|
||||
// This field is required.
|
||||
ProtocolVersion uint16
|
||||
|
||||
// HTTPPort is the port number to use when making a HTTP connection.
|
||||
//
|
||||
// If not specified, this defaults to port 80.
|
||||
HTTPPort string
|
||||
|
||||
// HTTPSPort is the port number to use when making a HTTPS connection.
|
||||
//
|
||||
// If not specified, this defaults to port 443.
|
||||
HTTPSPort string
|
||||
|
||||
// Dialer is the dialer used to make outbound connections.
|
||||
//
|
||||
// If not specified, this defaults to net.Dialer.DialContext.
|
||||
Dialer dnscache.DialContextFunc
|
||||
|
||||
// Logf, if set, is a logging function to use; if unset, logs are
|
||||
// dropped.
|
||||
Logf logger.Logf
|
||||
|
||||
proxyFunc func(*http.Request) (*url.URL, error) // or nil
|
||||
|
||||
// For tests only
|
||||
insecureTLS bool
|
||||
testFallbackDelay time.Duration
|
||||
}
|
||||
|
||||
func strDef(v1, v2 string) string {
|
||||
if v1 != "" {
|
||||
return v1
|
||||
}
|
||||
return v2
|
||||
}
|
||||
|
||||
@@ -170,15 +170,16 @@ func testControlHTTP(t *testing.T, param httpTestParam) {
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
a := dialParams{
|
||||
host: "localhost",
|
||||
httpPort: strconv.Itoa(httpLn.Addr().(*net.TCPAddr).Port),
|
||||
httpsPort: strconv.Itoa(httpsLn.Addr().(*net.TCPAddr).Port),
|
||||
machineKey: client,
|
||||
controlKey: server.Public(),
|
||||
version: testProtocolVersion,
|
||||
a := &Dialer{
|
||||
Hostname: "localhost",
|
||||
HTTPPort: strconv.Itoa(httpLn.Addr().(*net.TCPAddr).Port),
|
||||
HTTPSPort: strconv.Itoa(httpsLn.Addr().(*net.TCPAddr).Port),
|
||||
MachineKey: client,
|
||||
ControlKey: server.Public(),
|
||||
ProtocolVersion: testProtocolVersion,
|
||||
Dialer: new(tsdial.Dialer).SystemDial,
|
||||
Logf: t.Logf,
|
||||
insecureTLS: true,
|
||||
dialer: new(tsdial.Dialer).SystemDial,
|
||||
testFallbackDelay: 50 * time.Millisecond,
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,12 @@ func acceptWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request
|
||||
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
||||
Subprotocols: []string{upgradeHeaderValue},
|
||||
OriginPatterns: []string{"*"},
|
||||
// Disable compression because we transmit Noise messages that are not
|
||||
// compressible.
|
||||
// Additionally, Safari has a broken implementation of compression
|
||||
// (see https://github.com/nhooyr/websocket/issues/218) that makes
|
||||
// enabling it actively harmful.
|
||||
CompressionMode: websocket.CompressionDisabled,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not accept WebSocket connection %v", err)
|
||||
|
||||
@@ -13,20 +13,18 @@ import (
|
||||
)
|
||||
|
||||
// disableUPnP indicates whether to attempt UPnP mapping.
|
||||
var disableUPnP atomic.Bool
|
||||
var disableUPnPControl atomic.Bool
|
||||
|
||||
func init() {
|
||||
SetDisableUPnP(envknob.Bool("TS_DISABLE_UPNP"))
|
||||
}
|
||||
var disableUPnpEnv = envknob.RegisterBool("TS_DISABLE_UPNP")
|
||||
|
||||
// DisableUPnP reports the last reported value from control
|
||||
// whether UPnP portmapping should be disabled.
|
||||
func DisableUPnP() bool {
|
||||
return disableUPnP.Load()
|
||||
return disableUPnPControl.Load() || disableUPnpEnv()
|
||||
}
|
||||
|
||||
// SetDisableUPnP sets whether control says that UPnP should be
|
||||
// disabled.
|
||||
func SetDisableUPnP(v bool) {
|
||||
disableUPnP.Store(v)
|
||||
disableUPnPControl.Store(v)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package derp implements DERP, the Detour Encrypted Routing Protocol.
|
||||
// Package derp implements the Designated Encrypted Relay for Packets (DERP)
|
||||
// protocol.
|
||||
//
|
||||
// DERP routes packets to clients using curve25519 keys as addresses.
|
||||
//
|
||||
@@ -18,7 +19,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -194,7 +194,7 @@ func readFrame(br *bufio.Reader, maxSize uint32, b []byte) (t frameType, frameLe
|
||||
}
|
||||
remain := frameLen - uint32(n)
|
||||
if remain > 0 {
|
||||
if _, err := io.CopyN(ioutil.Discard, br, int64(remain)); err != nil {
|
||||
if _, err := io.CopyN(io.Discard, br, int64(remain)); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
err = io.ErrShortBuffer
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"math/big"
|
||||
@@ -47,8 +46,6 @@ import (
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
var debug = envknob.Bool("DERP_DEBUG_LOGS")
|
||||
|
||||
// verboseDropKeys is the set of destination public keys that should
|
||||
// verbosely log whenever DERP drops a packet.
|
||||
var verboseDropKeys = map[key.NodePublic]bool{}
|
||||
@@ -106,6 +103,7 @@ type Server struct {
|
||||
limitedLogf logger.Logf
|
||||
metaCert []byte // the encoded x509 cert to send after LetsEncrypt cert+intermediate
|
||||
dupPolicy dupPolicy
|
||||
debug bool
|
||||
|
||||
// Counters:
|
||||
packetsSent, bytesSent expvar.Int
|
||||
@@ -299,6 +297,7 @@ func NewServer(privateKey key.NodePrivate, logf logger.Logf) *Server {
|
||||
runtime.ReadMemStats(&ms)
|
||||
|
||||
s := &Server{
|
||||
debug: envknob.Bool("DERP_DEBUG_LOGS"),
|
||||
privateKey: privateKey,
|
||||
publicKey: privateKey.Public(),
|
||||
logf: logf,
|
||||
@@ -758,7 +757,7 @@ func (c *sclient) run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *sclient) handleUnknownFrame(ft frameType, fl uint32) error {
|
||||
_, err := io.CopyN(ioutil.Discard, c.br, int64(fl))
|
||||
_, err := io.CopyN(io.Discard, c.br, int64(fl))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -801,7 +800,7 @@ func (c *sclient) handleFramePing(ft frameType, fl uint32) error {
|
||||
return err
|
||||
}
|
||||
if extra := int64(fl) - int64(len(m)); extra > 0 {
|
||||
_, err = io.CopyN(ioutil.Discard, c.br, extra)
|
||||
_, err = io.CopyN(io.Discard, c.br, extra)
|
||||
}
|
||||
select {
|
||||
case c.sendPongCh <- [8]byte(m):
|
||||
@@ -980,7 +979,7 @@ func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.NodePublic, r
|
||||
msg := fmt.Sprintf("drop (%s) %s -> %s", srcKey.ShortString(), reason, dstKey.ShortString())
|
||||
s.limitedLogf(msg)
|
||||
}
|
||||
if debug {
|
||||
if s.debug {
|
||||
s.logf("dropping packet reason=%s dst=%s disco=%v", reason, dstKey, disco.LooksLikeDiscoWrapper(packetBytes))
|
||||
}
|
||||
}
|
||||
@@ -1828,7 +1827,7 @@ func (s *Server) ServeDebugTraffic(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var bufioWriterPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return bufio.NewWriterSize(ioutil.Discard, 2<<10)
|
||||
return bufio.NewWriterSize(io.Discard, 2<<10)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1861,7 +1860,7 @@ func (w *lazyBufioWriter) Flush() error {
|
||||
}
|
||||
err := w.lbw.Flush()
|
||||
|
||||
w.lbw.Reset(ioutil.Discard)
|
||||
w.lbw.Reset(io.Discard)
|
||||
bufioWriterPool.Put(w.lbw)
|
||||
w.lbw = nil
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -1240,7 +1240,7 @@ func benchmarkSendRecvSize(b *testing.B, packetSize int) {
|
||||
}
|
||||
|
||||
func BenchmarkWriteUint32(b *testing.B) {
|
||||
w := bufio.NewWriter(ioutil.Discard)
|
||||
w := bufio.NewWriter(io.Discard)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -1279,9 +1279,9 @@ func waitConnect(t testing.TB, c *Client) {
|
||||
}
|
||||
|
||||
func TestParseSSOutput(t *testing.T) {
|
||||
contents, err := ioutil.ReadFile("testdata/example_ss.txt")
|
||||
contents, err := os.ReadFile("testdata/example_ss.txt")
|
||||
if err != nil {
|
||||
t.Errorf("ioutil.Readfile(example_ss.txt) failed: %v", err)
|
||||
t.Errorf("os.ReadFile(example_ss.txt) failed: %v", err)
|
||||
}
|
||||
seen := parseSSOutput(string(contents))
|
||||
if len(seen) == 0 {
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@@ -432,7 +431,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
|
||||
return nil, 0, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusSwitchingProtocols {
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
return nil, 0, fmt.Errorf("GET failed: %v: %s", err, b)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: tailscale-auth
|
||||
key: AUTH_KEY
|
||||
key: TS_AUTH_KEY
|
||||
optional: true
|
||||
securityContext:
|
||||
capabilities:
|
||||
|
||||
@@ -17,30 +17,43 @@
|
||||
package envknob
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
set = map[string]string{}
|
||||
list []string
|
||||
mu sync.Mutex
|
||||
set = map[string]string{}
|
||||
regStr = map[string]*string{}
|
||||
regBool = map[string]*bool{}
|
||||
regOptBool = map[string]*opt.Bool{}
|
||||
)
|
||||
|
||||
func noteEnv(k, v string) {
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if _, ok := set[k]; !ok {
|
||||
list = append(list, k)
|
||||
noteEnvLocked(k, v)
|
||||
}
|
||||
|
||||
func noteEnvLocked(k, v string) {
|
||||
if v != "" {
|
||||
set[k] = v
|
||||
} else {
|
||||
delete(set, k)
|
||||
}
|
||||
set[k] = v
|
||||
}
|
||||
|
||||
// logf is logger.Logf, but logger depends on envknob, so for circular
|
||||
@@ -52,11 +65,39 @@ type logf = func(format string, args ...any)
|
||||
func LogCurrent(logf logf) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
list := make([]string, 0, len(set))
|
||||
for k := range set {
|
||||
list = append(list, k)
|
||||
}
|
||||
sort.Strings(list)
|
||||
for _, k := range list {
|
||||
logf("envknob: %s=%q", k, set[k])
|
||||
}
|
||||
}
|
||||
|
||||
// Setenv changes an environment variable.
|
||||
//
|
||||
// It is not safe for concurrent reading of environment variables via the
|
||||
// Register functions. All Setenv calls are meant to happen early in main before
|
||||
// any goroutines are started.
|
||||
func Setenv(envVar, val string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
os.Setenv(envVar, val)
|
||||
noteEnvLocked(envVar, val)
|
||||
|
||||
if p := regStr[envVar]; p != nil {
|
||||
*p = val
|
||||
}
|
||||
if p := regBool[envVar]; p != nil {
|
||||
setBoolLocked(p, envVar, val)
|
||||
}
|
||||
if p := regOptBool[envVar]; p != nil {
|
||||
setOptBoolLocked(p, envVar, val)
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the named environment variable, using os.Getenv.
|
||||
//
|
||||
// If the variable is non-empty, it's also tracked & logged as being
|
||||
@@ -67,6 +108,82 @@ func String(envVar string) string {
|
||||
return v
|
||||
}
|
||||
|
||||
// RegisterString returns a func that gets the named environment variable,
|
||||
// without a map lookup per call. It assumes that mutations happen via
|
||||
// envknob.Setenv.
|
||||
func RegisterString(envVar string) func() string {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
p, ok := regStr[envVar]
|
||||
if !ok {
|
||||
val := os.Getenv(envVar)
|
||||
if val != "" {
|
||||
noteEnvLocked(envVar, val)
|
||||
}
|
||||
p = &val
|
||||
regStr[envVar] = p
|
||||
}
|
||||
return func() string { return *p }
|
||||
}
|
||||
|
||||
// RegisterBool returns a func that gets the named environment variable,
|
||||
// without a map lookup per call. It assumes that mutations happen via
|
||||
// envknob.Setenv.
|
||||
func RegisterBool(envVar string) func() bool {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
p, ok := regBool[envVar]
|
||||
if !ok {
|
||||
var b bool
|
||||
p = &b
|
||||
setBoolLocked(p, envVar, os.Getenv(envVar))
|
||||
regBool[envVar] = p
|
||||
}
|
||||
return func() bool { return *p }
|
||||
}
|
||||
|
||||
// RegisterOptBool returns a func that gets the named environment variable,
|
||||
// without a map lookup per call. It assumes that mutations happen via
|
||||
// envknob.Setenv.
|
||||
func RegisterOptBool(envVar string) func() opt.Bool {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
p, ok := regOptBool[envVar]
|
||||
if !ok {
|
||||
var b opt.Bool
|
||||
p = &b
|
||||
setOptBoolLocked(p, envVar, os.Getenv(envVar))
|
||||
regOptBool[envVar] = p
|
||||
}
|
||||
return func() opt.Bool { return *p }
|
||||
}
|
||||
|
||||
func setBoolLocked(p *bool, envVar, val string) {
|
||||
noteEnvLocked(envVar, val)
|
||||
if val == "" {
|
||||
*p = false
|
||||
return
|
||||
}
|
||||
var err error
|
||||
*p, err = strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid boolean environment variable %s value %q", envVar, val)
|
||||
}
|
||||
}
|
||||
|
||||
func setOptBoolLocked(p *opt.Bool, envVar, val string) {
|
||||
noteEnvLocked(envVar, val)
|
||||
if val == "" {
|
||||
*p = ""
|
||||
return
|
||||
}
|
||||
b, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid boolean environment variable %s value %q", envVar, val)
|
||||
}
|
||||
p.Set(b)
|
||||
}
|
||||
|
||||
// Bool returns the boolean value of the named environment variable.
|
||||
// If the variable is not set, it returns false.
|
||||
// An invalid value exits the binary with a failure.
|
||||
@@ -81,6 +198,7 @@ func BoolDefaultTrue(envVar string) bool {
|
||||
}
|
||||
|
||||
func boolOr(envVar string, implicitValue bool) bool {
|
||||
assertNotInInit()
|
||||
val := os.Getenv(envVar)
|
||||
if val == "" {
|
||||
return implicitValue
|
||||
@@ -98,6 +216,7 @@ func boolOr(envVar string, implicitValue bool) bool {
|
||||
// The ok result is whether a value was set.
|
||||
// If the value isn't a valid int, it exits the program with a failure.
|
||||
func LookupBool(envVar string) (v bool, ok bool) {
|
||||
assertNotInInit()
|
||||
val := os.Getenv(envVar)
|
||||
if val == "" {
|
||||
return false, false
|
||||
@@ -113,6 +232,7 @@ func LookupBool(envVar string) (v bool, ok bool) {
|
||||
// OptBool is like Bool, but returns an opt.Bool, so the caller can
|
||||
// distinguish between implicitly and explicitly false.
|
||||
func OptBool(envVar string) opt.Bool {
|
||||
assertNotInInit()
|
||||
b, ok := LookupBool(envVar)
|
||||
if !ok {
|
||||
return ""
|
||||
@@ -126,6 +246,7 @@ func OptBool(envVar string) opt.Bool {
|
||||
// The ok result is whether a value was set.
|
||||
// If the value isn't a valid int, it exits the program with a failure.
|
||||
func LookupInt(envVar string) (v int, ok bool) {
|
||||
assertNotInInit()
|
||||
val := os.Getenv(envVar)
|
||||
if val == "" {
|
||||
return 0, false
|
||||
@@ -164,5 +285,142 @@ func NoLogsNoSupport() bool {
|
||||
|
||||
// SetNoLogsNoSupport enables no-logs-no-support mode.
|
||||
func SetNoLogsNoSupport() {
|
||||
os.Setenv("TS_NO_LOGS_NO_SUPPORT", "true")
|
||||
Setenv("TS_NO_LOGS_NO_SUPPORT", "true")
|
||||
}
|
||||
|
||||
// notInInit is set true the first time we've seen a non-init stack trace.
|
||||
var notInInit atomic.Bool
|
||||
|
||||
func assertNotInInit() {
|
||||
if notInInit.Load() {
|
||||
return
|
||||
}
|
||||
skip := 0
|
||||
for {
|
||||
pc, _, _, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
notInInit.Store(true)
|
||||
return
|
||||
}
|
||||
fu := runtime.FuncForPC(pc)
|
||||
if fu == nil {
|
||||
return
|
||||
}
|
||||
name := fu.Name()
|
||||
name = strings.TrimRightFunc(name, func(r rune) bool { return r >= '0' && r <= '9' })
|
||||
if strings.HasSuffix(name, ".init") || strings.HasSuffix(name, ".init.") {
|
||||
stack := make([]byte, 1<<10)
|
||||
stack = stack[:runtime.Stack(stack, false)]
|
||||
envCheckedInInitStack = stack
|
||||
}
|
||||
skip++
|
||||
}
|
||||
}
|
||||
|
||||
var envCheckedInInitStack []byte
|
||||
|
||||
// PanicIfAnyEnvCheckedInInit panics if environment variables were read during
|
||||
// init.
|
||||
func PanicIfAnyEnvCheckedInInit() {
|
||||
if envCheckedInInitStack != nil {
|
||||
panic("envknob check of called from init function: " + string(envCheckedInInitStack))
|
||||
}
|
||||
}
|
||||
|
||||
var applyDiskConfigErr error
|
||||
|
||||
// ApplyDiskConfigError returns the most recent result of ApplyDiskConfig.
|
||||
func ApplyDiskConfigError() error { return applyDiskConfigErr }
|
||||
|
||||
// ApplyDiskConfig returns a platform-specific config file of environment keys/values and
|
||||
// applies them. On Linux and Unix operating systems, it's a no-op and always returns nil.
|
||||
// If no platform-specific config file is found, it also returns nil.
|
||||
//
|
||||
// It exists primarily for Windows to make it easy to apply environment variables to
|
||||
// a running service in a way similar to modifying /etc/default/tailscaled on Linux.
|
||||
// On Windows, you use %ProgramData%\Tailscale\tailscaled-env.txt instead.
|
||||
func ApplyDiskConfig() (err error) {
|
||||
var f *os.File
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// Stash away our return error for the healthcheck package to use.
|
||||
applyDiskConfigErr = fmt.Errorf("error parsing %s: %w", f.Name(), err)
|
||||
}
|
||||
}()
|
||||
|
||||
// First try the explicitly-provided value for development testing. Not
|
||||
// useful for users to use on their own. (if they can set this, they can set
|
||||
// any environment variable anyway)
|
||||
if name := os.Getenv("TS_DEBUG_ENV_FILE"); name != "" {
|
||||
f, err = os.Open(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening explicitly configured TS_DEBUG_ENV_FILE: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
return applyKeyValueEnv(f)
|
||||
}
|
||||
|
||||
name := getPlatformEnvFile()
|
||||
if name == "" {
|
||||
return nil
|
||||
}
|
||||
f, err = os.Open(name)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return applyKeyValueEnv(f)
|
||||
}
|
||||
|
||||
// getPlatformEnvFile returns the current platform's path to an optional
|
||||
// tailscaled-env.txt file. It returns an empty string if none is defined
|
||||
// for the platform.
|
||||
func getPlatformEnvFile() string {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return filepath.Join(os.Getenv("ProgramData"), "Tailscale", "tailscaled-env.txt")
|
||||
case "linux":
|
||||
if distro.Get() == distro.Synology {
|
||||
return "/etc/tailscale/tailscaled-env.txt"
|
||||
}
|
||||
case "darwin":
|
||||
// TODO(bradfitz): figure this out. There are three ways to run
|
||||
// Tailscale on macOS (tailscaled, GUI App Store, GUI System Extension)
|
||||
// and we should deal with all three.
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// applyKeyValueEnv reads key=value lines r and calls Setenv for each.
|
||||
//
|
||||
// Empty lines and lines beginning with '#' are skipped.
|
||||
//
|
||||
// Values can be double quoted, in which case they're unquoted using
|
||||
// strconv.Unquote.
|
||||
func applyKeyValueEnv(r io.Reader) error {
|
||||
bs := bufio.NewScanner(r)
|
||||
for bs.Scan() {
|
||||
line := strings.TrimSpace(bs.Text())
|
||||
if line == "" || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
k, v, ok := strings.Cut(line, "=")
|
||||
k = strings.TrimSpace(k)
|
||||
if !ok || k == "" {
|
||||
continue
|
||||
}
|
||||
v = strings.TrimSpace(v)
|
||||
if strings.HasPrefix(v, `"`) {
|
||||
var err error
|
||||
v, err = strconv.Unquote(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value in line %q: %v", line, err)
|
||||
}
|
||||
}
|
||||
Setenv(k, v)
|
||||
}
|
||||
return bs.Err()
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ func OverallError() error {
|
||||
return overallErrorLocked()
|
||||
}
|
||||
|
||||
var fakeErrForTesting = envknob.String("TS_DEBUG_FAKE_HEALTH_ERROR")
|
||||
var fakeErrForTesting = envknob.RegisterString("TS_DEBUG_FAKE_HEALTH_ERROR")
|
||||
|
||||
func overallErrorLocked() error {
|
||||
if !anyInterfaceUp {
|
||||
@@ -383,7 +383,10 @@ func overallErrorLocked() error {
|
||||
for _, s := range controlHealth {
|
||||
errs = append(errs, errors.New(s))
|
||||
}
|
||||
if e := fakeErrForTesting; len(errs) == 0 && e != "" {
|
||||
if err := envknob.ApplyDiskConfigError(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if e := fakeErrForTesting(); len(errs) == 0 && e != "" {
|
||||
return errors.New(e)
|
||||
}
|
||||
sort.Slice(errs, func(i, j int) bool {
|
||||
|
||||
@@ -9,7 +9,6 @@ package hostinfo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -99,11 +98,11 @@ func linuxVersionMeta() (meta versionMeta) {
|
||||
case distro.OpenWrt:
|
||||
propFile = "/etc/openwrt_release"
|
||||
case distro.WDMyCloud:
|
||||
slurp, _ := ioutil.ReadFile("/etc/version")
|
||||
slurp, _ := os.ReadFile("/etc/version")
|
||||
meta.DistroVersion = string(bytes.TrimSpace(slurp))
|
||||
return
|
||||
case distro.QNAP:
|
||||
slurp, _ := ioutil.ReadFile("/etc/version_info")
|
||||
slurp, _ := os.ReadFile("/etc/version_info")
|
||||
meta.DistroVersion = getQnapQtsVersion(string(slurp))
|
||||
return
|
||||
}
|
||||
@@ -133,7 +132,7 @@ func linuxVersionMeta() (meta versionMeta) {
|
||||
case "debian":
|
||||
// Debian's VERSION_ID is just like "11". But /etc/debian_version has "11.5" normally.
|
||||
// Or "bookworm/sid" on sid/testing.
|
||||
slurp, _ := ioutil.ReadFile("/etc/debian_version")
|
||||
slurp, _ := os.ReadFile("/etc/debian_version")
|
||||
if v := string(bytes.TrimSpace(slurp)); v != "" {
|
||||
if '0' <= v[0] && v[0] <= '9' {
|
||||
meta.DistroVersion = v
|
||||
@@ -143,7 +142,7 @@ func linuxVersionMeta() (meta versionMeta) {
|
||||
}
|
||||
case "", "centos": // CentOS 6 has no /etc/os-release, so its id is ""
|
||||
if meta.DistroVersion == "" {
|
||||
if cr, _ := ioutil.ReadFile("/etc/centos-release"); len(cr) > 0 { // "CentOS release 6.10 (Final)
|
||||
if cr, _ := os.ReadFile("/etc/centos-release"); len(cr) > 0 { // "CentOS release 6.10 (Final)
|
||||
meta.DistroVersion = string(bytes.TrimSpace(cr))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,6 @@ import (
|
||||
)
|
||||
|
||||
var controlDebugFlags = getControlDebugFlags()
|
||||
var canSSH = envknob.CanSSHD()
|
||||
|
||||
func getControlDebugFlags() []string {
|
||||
if e := envknob.String("TS_DEBUG_CONTROL_FLAGS"); e != "" {
|
||||
@@ -1510,12 +1509,12 @@ func (b *LocalBackend) tellClientToBrowseToURL(url string) {
|
||||
}
|
||||
|
||||
// For testing lazy machine key generation.
|
||||
var panicOnMachineKeyGeneration = envknob.Bool("TS_DEBUG_PANIC_MACHINE_KEY")
|
||||
var panicOnMachineKeyGeneration = envknob.RegisterBool("TS_DEBUG_PANIC_MACHINE_KEY")
|
||||
|
||||
func (b *LocalBackend) createGetMachinePrivateKeyFunc() func() (key.MachinePrivate, error) {
|
||||
var cache syncs.AtomicValue[key.MachinePrivate]
|
||||
return func() (key.MachinePrivate, error) {
|
||||
if panicOnMachineKeyGeneration {
|
||||
if panicOnMachineKeyGeneration() {
|
||||
panic("machine key generated")
|
||||
}
|
||||
if v, ok := cache.LoadOk(); ok {
|
||||
@@ -1752,7 +1751,7 @@ func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs) (err
|
||||
// setAtomicValuesFromPrefs populates sshAtomicBool and containsViaIPFuncAtomic
|
||||
// from the prefs p, which may be nil.
|
||||
func (b *LocalBackend) setAtomicValuesFromPrefs(p *ipn.Prefs) {
|
||||
b.sshAtomicBool.Store(p != nil && p.RunSSH && canSSH)
|
||||
b.sshAtomicBool.Store(p != nil && p.RunSSH && envknob.CanSSHD())
|
||||
|
||||
if p == nil {
|
||||
b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(nil))
|
||||
@@ -1967,7 +1966,7 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
|
||||
default:
|
||||
return errors.New("The Tailscale SSH server is not supported on " + runtime.GOOS)
|
||||
}
|
||||
if !canSSH {
|
||||
if !envknob.CanSSHD() {
|
||||
return errors.New("The Tailscale SSH server has been administratively disabled.")
|
||||
}
|
||||
if envknob.SSHIgnoreTailnetPolicy() || envknob.SSHPolicyFile() != "" {
|
||||
@@ -2032,7 +2031,7 @@ func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
|
||||
b.logf("EditPrefs check error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if p1.RunSSH && !canSSH {
|
||||
if p1.RunSSH && !envknob.CanSSHD() {
|
||||
b.mu.Unlock()
|
||||
b.logf("EditPrefs requests SSH, but disabled by envknob; returning error")
|
||||
return nil, errors.New("Tailscale SSH server administratively disabled.")
|
||||
@@ -2854,7 +2853,7 @@ func (b *LocalBackend) applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs *ipn.Pre
|
||||
hi.ShieldsUp = prefs.ShieldsUp
|
||||
|
||||
var sshHostKeys []string
|
||||
if prefs.RunSSH && canSSH {
|
||||
if prefs.RunSSH && envknob.CanSSHD() {
|
||||
// TODO(bradfitz): this is called with b.mu held. Not ideal.
|
||||
// If the filesystem gets wedged or something we could block for
|
||||
// a long time. But probably fine.
|
||||
@@ -3073,7 +3072,7 @@ func (b *LocalBackend) ResetForClientDisconnect() {
|
||||
b.setAtomicValuesFromPrefs(nil)
|
||||
}
|
||||
|
||||
func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && canSSH }
|
||||
func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Load() && envknob.CanSSHD() }
|
||||
|
||||
// ShouldHandleViaIP reports whether whether ip is an IPv6 address in the
|
||||
// Tailscale ULA's v6 "via" range embedding an IPv4 address to be forwarded to
|
||||
|
||||
@@ -478,8 +478,8 @@ func (panicOnUseTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
||||
|
||||
// Issue 1573: don't generate a machine key if we don't want to be running.
|
||||
func TestLazyMachineKeyGeneration(t *testing.T) {
|
||||
defer func(old bool) { panicOnMachineKeyGeneration = old }(panicOnMachineKeyGeneration)
|
||||
panicOnMachineKeyGeneration = true
|
||||
defer func(old func() bool) { panicOnMachineKeyGeneration = old }(panicOnMachineKeyGeneration)
|
||||
panicOnMachineKeyGeneration = func() bool { return true }
|
||||
|
||||
var logf logger.Logf = logger.Discard
|
||||
store := new(mem.Store)
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"tailscale.com/types/tkatype"
|
||||
)
|
||||
|
||||
var networkLockAvailable = envknob.Bool("TS_EXPERIMENTAL_NETWORK_LOCK")
|
||||
var networkLockAvailable = envknob.RegisterBool("TS_EXPERIMENTAL_NETWORK_LOCK")
|
||||
|
||||
type tkaState struct {
|
||||
authority *tka.Authority
|
||||
@@ -82,7 +82,7 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key) error {
|
||||
if b.tka != nil {
|
||||
return errors.New("network-lock is already initialized")
|
||||
}
|
||||
if !networkLockAvailable {
|
||||
if !networkLockAvailable() {
|
||||
return errors.New("this is an experimental feature in your version of tailscale - Please upgrade to the latest to use this.")
|
||||
}
|
||||
if !b.CanSupportNetworkLock() {
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/strs"
|
||||
"tailscale.com/wgengine"
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
@@ -720,8 +721,8 @@ func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
rawPath := r.URL.EscapedPath()
|
||||
suffix := strings.TrimPrefix(rawPath, "/v0/put/")
|
||||
if suffix == rawPath {
|
||||
suffix, ok := strs.CutPrefix(rawPath, "/v0/put/")
|
||||
if !ok {
|
||||
http.Error(w, "misconfigured internals", 500)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -87,7 +86,7 @@ func fileHasContents(name string, want string) check {
|
||||
return
|
||||
}
|
||||
path := filepath.Join(root, name)
|
||||
got, err := ioutil.ReadFile(path)
|
||||
got, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Errorf("fileHasContents: %v", err)
|
||||
return
|
||||
@@ -517,7 +516,7 @@ func TestDeletedMarkers(t *testing.T) {
|
||||
}
|
||||
wantEmptyTempDir := func() {
|
||||
t.Helper()
|
||||
if fis, err := ioutil.ReadDir(dir); err != nil {
|
||||
if fis, err := os.ReadDir(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(fis) > 0 && runtime.GOOS != "windows" {
|
||||
for _, fi := range fis {
|
||||
|
||||
@@ -18,19 +18,15 @@ import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/tailscale/golang-x-crypto/ssh"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
var useHostKeys = envknob.Bool("TS_USE_SYSTEM_SSH_HOST_KEYS")
|
||||
|
||||
// keyTypes are the SSH key types that we either try to read from the
|
||||
// system's OpenSSH keys or try to generate for ourselves when not
|
||||
// running as root.
|
||||
@@ -83,7 +79,7 @@ func (b *LocalBackend) hostKeyFileOrCreate(keyDir, typ string) ([]byte, error) {
|
||||
defer keyGenMu.Unlock()
|
||||
|
||||
path := filepath.Join(keyDir, "ssh_host_"+typ+"_key")
|
||||
v, err := ioutil.ReadFile(path)
|
||||
v, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
}
|
||||
@@ -124,7 +120,7 @@ func (b *LocalBackend) hostKeyFileOrCreate(keyDir, typ string) ([]byte, error) {
|
||||
func (b *LocalBackend) getSystemSSH_HostKeys() (ret map[string]ssh.Signer) {
|
||||
for _, typ := range keyTypes {
|
||||
filename := "/etc/ssh/ssh_host_" + typ + "_key"
|
||||
hostKey, err := ioutil.ReadFile(filename)
|
||||
hostKey, err := os.ReadFile(filename)
|
||||
if err != nil || len(bytes.TrimSpace(hostKey)) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -215,7 +214,7 @@ func (s *Server) blockWhileInUse(conn io.Reader, ci connIdentity) {
|
||||
s.logf("blocking client while server in use; connIdentity=%v", ci)
|
||||
connDone := make(chan struct{})
|
||||
go func() {
|
||||
io.Copy(ioutil.Discard, conn)
|
||||
io.Copy(io.Discard, conn)
|
||||
close(connDone)
|
||||
}()
|
||||
ch := make(chan struct{}, 1)
|
||||
@@ -933,14 +932,6 @@ func BabysitProc(ctx context.Context, args []string, logf logger.Logf) {
|
||||
startTime := time.Now()
|
||||
log.Printf("exec: %#v %v", executable, args)
|
||||
cmd := exec.Command(executable, args...)
|
||||
if runtime.GOOS == "windows" {
|
||||
extraEnv, err := loadExtraEnv()
|
||||
if err != nil {
|
||||
logf("errors loading extra env file; ignoring: %v", err)
|
||||
} else {
|
||||
cmd.Env = append(os.Environ(), extraEnv...)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a pipe object to use as the subproc's stdin.
|
||||
// When the writer goes away, the reader gets EOF.
|
||||
@@ -1175,7 +1166,7 @@ func findTrueNASTaildropDir(name string) (dir string, err error) {
|
||||
}
|
||||
|
||||
// but if running on the host, it may be something like /mnt/Primary/Taildrop
|
||||
fis, err := ioutil.ReadDir("/mnt")
|
||||
fis, err := os.ReadDir("/mnt")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading /mnt: %w", err)
|
||||
}
|
||||
@@ -1209,38 +1200,3 @@ func findQnapTaildropDir(name string) (string, error) {
|
||||
}
|
||||
return "", fmt.Errorf("shared folder %q not found", name)
|
||||
}
|
||||
|
||||
func loadExtraEnv() (env []string, err error) {
|
||||
if runtime.GOOS != "windows" {
|
||||
return nil, nil
|
||||
}
|
||||
name := filepath.Join(os.Getenv("ProgramData"), "Tailscale", "tailscaled-env.txt")
|
||||
contents, err := os.ReadFile(name)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, line := range strings.Split(string(contents), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
k, v, ok := strings.Cut(line, "=")
|
||||
if !ok || k == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(v, `"`) {
|
||||
var err error
|
||||
v, err = strconv.Unquote(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid value in line %q: %v", line, err)
|
||||
}
|
||||
env = append(env, k+"="+v)
|
||||
} else {
|
||||
env = append(env, line)
|
||||
}
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -38,6 +37,7 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/util/strs"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
@@ -73,7 +73,7 @@ func (h *Handler) certDir() (string, error) {
|
||||
return full, nil
|
||||
}
|
||||
|
||||
var acmeDebug = envknob.Bool("TS_DEBUG_ACME")
|
||||
var acmeDebug = envknob.RegisterBool("TS_DEBUG_ACME")
|
||||
|
||||
func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite && !h.PermitCert {
|
||||
@@ -87,16 +87,19 @@ func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
domain := strings.TrimPrefix(r.URL.Path, "/localapi/v0/cert/")
|
||||
if domain == r.URL.Path {
|
||||
domain, ok := strs.CutPrefix(r.URL.Path, "/localapi/v0/cert/")
|
||||
if !ok {
|
||||
http.Error(w, "internal handler config wired wrong", 500)
|
||||
return
|
||||
}
|
||||
|
||||
if !validLookingCertDomain(domain) {
|
||||
http.Error(w, "invalid domain", 400)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
logf := logger.WithPrefix(h.logf, fmt.Sprintf("cert(%q): ", domain))
|
||||
traceACME := func(v any) {
|
||||
if !acmeDebug {
|
||||
if !acmeDebug() {
|
||||
return
|
||||
}
|
||||
j, _ := json.MarshalIndent(v, "", "\t")
|
||||
@@ -165,6 +168,11 @@ func certFile(dir, domain string) string { return filepath.Join(dir, domain+".cr
|
||||
// keypair for domain exists on disk in dir that is valid at the
|
||||
// provided now time.
|
||||
func (h *Handler) getCertPEMCached(dir, domain string, now time.Time) (p *keyPair, ok bool) {
|
||||
if !validLookingCertDomain(domain) {
|
||||
// Before we read files from disk using it, validate it's halfway
|
||||
// reasonable looking.
|
||||
return nil, false
|
||||
}
|
||||
if keyPEM, err := os.ReadFile(keyFile(dir, domain)); err == nil {
|
||||
certPEM, _ := os.ReadFile(certFile(dir, domain))
|
||||
if validCertPEM(domain, keyPEM, certPEM, now) {
|
||||
@@ -293,7 +301,7 @@ func (h *Handler) getCertPEM(ctx context.Context, logf logger.Logf, traceACME fu
|
||||
if err := encodeECDSAKey(&privPEM, certPrivKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ioutil.WriteFile(keyFile(dir, domain), privPEM.Bytes(), 0600); err != nil {
|
||||
if err := os.WriteFile(keyFile(dir, domain), privPEM.Bytes(), 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -316,7 +324,7 @@ func (h *Handler) getCertPEM(ctx context.Context, logf logger.Logf, traceACME fu
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := ioutil.WriteFile(certFile(dir, domain), certPEM.Bytes(), 0644); err != nil {
|
||||
if err := os.WriteFile(certFile(dir, domain), certPEM.Bytes(), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -372,7 +380,7 @@ func parsePrivateKey(der []byte) (crypto.Signer, error) {
|
||||
|
||||
func acmeKey(dir string) (crypto.Signer, error) {
|
||||
pemName := filepath.Join(dir, "acme-account.key.pem")
|
||||
if v, err := ioutil.ReadFile(pemName); err == nil {
|
||||
if v, err := os.ReadFile(pemName); err == nil {
|
||||
priv, _ := pem.Decode(v)
|
||||
if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
|
||||
return nil, errors.New("acme/autocert: invalid account key found in cache")
|
||||
@@ -388,7 +396,7 @@ func acmeKey(dir string) (crypto.Signer, error) {
|
||||
if err := encodeECDSAKey(&pemBuf, privKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ioutil.WriteFile(pemName, pemBuf.Bytes(), 0600); err != nil {
|
||||
if err := os.WriteFile(pemName, pemBuf.Bytes(), 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return privKey, nil
|
||||
@@ -426,6 +434,21 @@ func validCertPEM(domain string, keyPEM, certPEM []byte, now time.Time) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// validLookingCertDomain reports whether name looks like a valid domain name that
|
||||
// we might be able to get a cert for.
|
||||
//
|
||||
// It's a light check primarily for double checking before it's used
|
||||
// as part of a filesystem path. The actual validation happens in checkCertDomain.
|
||||
func validLookingCertDomain(name string) bool {
|
||||
if name == "" ||
|
||||
strings.Contains(name, "..") ||
|
||||
strings.ContainsAny(name, ":/\\\x00") ||
|
||||
!strings.Contains(name, ".") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkCertDomain(st *ipnstate.Status, domain string) error {
|
||||
if domain == "" {
|
||||
return errors.New("missing domain name")
|
||||
|
||||
30
ipn/localapi/cert_test.go
Normal file
30
ipn/localapi/cert_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !ios && !android && !js
|
||||
// +build !ios,!android,!js
|
||||
|
||||
package localapi
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValidLookingCertDomain(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want bool
|
||||
}{
|
||||
{"foo.com", true},
|
||||
{"foo..com", false},
|
||||
{"foo/com.com", false},
|
||||
{"NUL", false},
|
||||
{"", false},
|
||||
{"foo\\bar.com", false},
|
||||
{"foo\x00bar.com", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := validLookingCertDomain(tt.in); got != tt.want {
|
||||
t.Errorf("validLookingCertDomain(%q) = %v, want %v", tt.in, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/netip"
|
||||
"os"
|
||||
@@ -618,7 +617,7 @@ func PrefsFromBytes(b []byte) (*Prefs, error) {
|
||||
// LoadPrefs loads a legacy relaynode config file into Prefs
|
||||
// with sensible migration defaults set.
|
||||
func LoadPrefs(filename string) (*Prefs, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("LoadPrefs open: %w", err) // err includes path
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/netip"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -474,7 +473,7 @@ func TestLoadPrefsNotExist(t *testing.T) {
|
||||
// TestLoadPrefsFileWithZeroInIt verifies that LoadPrefs hanldes corrupted input files.
|
||||
// See issue #954 for details.
|
||||
func TestLoadPrefsFileWithZeroInIt(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "TestLoadPrefsFileWithZeroInIt")
|
||||
f, err := os.CreateTemp("", "TestLoadPrefsFileWithZeroInIt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -128,7 +127,7 @@ func NewFileStore(logf logger.Logf, path string) (ipn.StateStore, error) {
|
||||
return nil, fmt.Errorf("creating state directory: %w", err)
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadFile(path)
|
||||
bs, err := os.ReadFile(path)
|
||||
|
||||
// Treat an empty file as a missing file.
|
||||
// (https://github.com/tailscale/tailscale/issues/895#issuecomment-723255589)
|
||||
|
||||
@@ -9,7 +9,6 @@ package filelogger
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -186,12 +185,18 @@ func (w *logFileWriter) startNewFileLocked() {
|
||||
//
|
||||
// w.mu must be held.
|
||||
func (w *logFileWriter) cleanLocked() {
|
||||
fis, _ := ioutil.ReadDir(w.dir)
|
||||
entries, _ := os.ReadDir(w.dir)
|
||||
prefix := w.fileBasePrefix + "-"
|
||||
fileSize := map[string]int64{}
|
||||
var files []string
|
||||
var sumSize int64
|
||||
for _, fi := range fis {
|
||||
for _, entry := range entries {
|
||||
fi, err := entry.Info()
|
||||
if err != nil {
|
||||
w.wrappedLogf("error getting log file info: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
baseName := filepath.Base(fi.Name())
|
||||
if !strings.HasPrefix(baseName, prefix) {
|
||||
continue
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -248,7 +247,7 @@ func logsDir(logf logger.Logf) string {
|
||||
// No idea where to put stuff. Try to create a temp dir. It'll
|
||||
// mean we might lose some logs and rotate through log IDs, but
|
||||
// it's something.
|
||||
tmp, err := ioutil.TempDir("", "tailscaled-log-*")
|
||||
tmp, err := os.MkdirTemp("", "tailscaled-log-*")
|
||||
if err != nil {
|
||||
panic("no safe place found to store log state")
|
||||
}
|
||||
@@ -259,7 +258,7 @@ func logsDir(logf logger.Logf) string {
|
||||
// runningUnderSystemd reports whether we're running under systemd.
|
||||
func runningUnderSystemd() bool {
|
||||
if runtime.GOOS == "linux" && os.Getppid() == 1 {
|
||||
slurp, _ := ioutil.ReadFile("/proc/1/stat")
|
||||
slurp, _ := os.ReadFile("/proc/1/stat")
|
||||
return bytes.HasPrefix(slurp, []byte("1 (systemd) "))
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -6,7 +6,7 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -39,7 +39,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("logadopt: response read failed %d: %v", resp.StatusCode, err)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -50,7 +50,7 @@ func main() {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("logreprocess: read error %d: %v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ package filch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -195,7 +195,7 @@ func TestFilchStderr(t *testing.T) {
|
||||
f.close(t)
|
||||
|
||||
pipeW.Close()
|
||||
b, err := ioutil.ReadAll(pipeR)
|
||||
b, err := io.ReadAll(pipeR)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -430,7 +429,7 @@ func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
uploaded = resp.StatusCode == 400 // the server saved the logs anyway
|
||||
b, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
return uploaded, fmt.Errorf("log upload of %d bytes %s failed %d: %q", len(body), compressedNote, resp.StatusCode, b)
|
||||
}
|
||||
|
||||
@@ -654,7 +653,7 @@ func (l *Logger) Write(buf []byte) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
level, buf := parseAndRemoveLogLevel(buf)
|
||||
if l.stderr != nil && l.stderr != ioutil.Discard && int64(level) <= atomic.LoadInt64(&l.stderrLevel) {
|
||||
if l.stderr != nil && l.stderr != io.Discard && int64(level) <= atomic.LoadInt64(&l.stderrLevel) {
|
||||
if buf[len(buf)-1] == '\n' {
|
||||
l.stderr.Write(buf)
|
||||
} else {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -52,7 +51,7 @@ func NewLogtailTestHarness(t *testing.T) (*LogtailTestServer, *Logger) {
|
||||
|
||||
ts.srv = httptest.NewServer(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Error("failed to read HTTP request")
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -158,7 +157,7 @@ func (m *resolvconfManager) GetBaseConfig() (OSConfig, error) {
|
||||
if sc.Text() == resolvconfConfigName {
|
||||
continue
|
||||
}
|
||||
bs, err := ioutil.ReadFile(filepath.Join(m.interfacesDir, sc.Text()))
|
||||
bs, err := os.ReadFile(filepath.Join(m.interfacesDir, sc.Text()))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Probably raced with a deletion, that's okay.
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -452,7 +451,7 @@ func (fs directFS) Rename(oldName, newName string) error {
|
||||
func (fs directFS) Remove(name string) error { return os.Remove(fs.path(name)) }
|
||||
|
||||
func (fs directFS) ReadFile(name string) ([]byte, error) {
|
||||
return ioutil.ReadFile(fs.path(name))
|
||||
return os.ReadFile(fs.path(name))
|
||||
}
|
||||
|
||||
func (fs directFS) Truncate(name string) error {
|
||||
@@ -460,7 +459,7 @@ func (fs directFS) Truncate(name string) error {
|
||||
}
|
||||
|
||||
func (fs directFS) WriteFile(name string, contents []byte, perm os.FileMode) error {
|
||||
return ioutil.WriteFile(fs.path(name), contents, perm)
|
||||
return os.WriteFile(fs.path(name), contents, perm)
|
||||
}
|
||||
|
||||
// runningAsGUIDesktopUser reports whether it seems that this code is
|
||||
|
||||
@@ -6,14 +6,13 @@ package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func NewOSConfigurator(logf logger.Logf, _ string) (OSConfigurator, error) {
|
||||
bs, err := ioutil.ReadFile("/etc/resolv.conf")
|
||||
bs, err := os.ReadFile("/etc/resolv.conf")
|
||||
if os.IsNotExist(err) {
|
||||
return newDirectManager(logf), nil
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ const (
|
||||
versionKey = `SOFTWARE\Microsoft\Windows NT\CurrentVersion`
|
||||
)
|
||||
|
||||
var configureWSL = envknob.Bool("TS_DEBUG_CONFIGURE_WSL")
|
||||
var configureWSL = envknob.RegisterBool("TS_DEBUG_CONFIGURE_WSL")
|
||||
|
||||
type windowsManager struct {
|
||||
logf logger.Logf
|
||||
@@ -359,7 +359,7 @@ func (m windowsManager) SetDNS(cfg OSConfig) error {
|
||||
|
||||
// On initial setup of WSL, the restart caused by --shutdown is slow,
|
||||
// so we do it out-of-line.
|
||||
if configureWSL {
|
||||
if configureWSL() {
|
||||
go func() {
|
||||
if err := m.wslManager.SetDNS(cfg); err != nil {
|
||||
m.logf("WSL SetDNS: %v", err) // continue
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -474,7 +473,7 @@ func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client,
|
||||
metricDNSFwdDoHErrorCT.Add(1)
|
||||
return nil, fmt.Errorf("unexpected response Content-Type %q", ct)
|
||||
}
|
||||
res, err := ioutil.ReadAll(hres.Body)
|
||||
res, err := io.ReadAll(hres.Body)
|
||||
if err != nil {
|
||||
metricDNSFwdDoHErrorBody.Add(1)
|
||||
}
|
||||
@@ -484,13 +483,13 @@ func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client,
|
||||
return res, err
|
||||
}
|
||||
|
||||
var verboseDNSForward = envknob.Bool("TS_DEBUG_DNS_FORWARD_SEND")
|
||||
var verboseDNSForward = envknob.RegisterBool("TS_DEBUG_DNS_FORWARD_SEND")
|
||||
|
||||
// send sends packet to dst. It is best effort.
|
||||
//
|
||||
// send expects the reply to have the same txid as txidOut.
|
||||
func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) (ret []byte, err error) {
|
||||
if verboseDNSForward {
|
||||
if verboseDNSForward() {
|
||||
f.logf("forwarder.send(%q) ...", rr.name.Addr)
|
||||
defer func() {
|
||||
f.logf("forwarder.send(%q) = %v, %v", rr.name.Addr, len(ret), err)
|
||||
|
||||
@@ -141,7 +141,7 @@ func (r *Resolver) ttl() time.Duration {
|
||||
return 10 * time.Minute
|
||||
}
|
||||
|
||||
var debug = envknob.Bool("TS_DEBUG_DNS_CACHE")
|
||||
var debug = envknob.RegisterBool("TS_DEBUG_DNS_CACHE")
|
||||
|
||||
// LookupIP returns the host's primary IP address (either IPv4 or
|
||||
// IPv6, but preferring IPv4) and optionally its IPv6 address, if
|
||||
@@ -167,14 +167,14 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 netip.Addr
|
||||
}
|
||||
if ip, err := netip.ParseAddr(host); err == nil {
|
||||
ip = ip.Unmap()
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: %q is an IP", host)
|
||||
}
|
||||
return ip, zaddr, []netip.Addr{ip}, nil
|
||||
}
|
||||
|
||||
if ip, ip6, allIPs, ok := r.lookupIPCache(host); ok {
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: %q = %v (cached)", host, ip)
|
||||
}
|
||||
return ip, ip6, allIPs, nil
|
||||
@@ -192,13 +192,13 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 netip.Addr
|
||||
if res.Err != nil {
|
||||
if r.UseLastGood {
|
||||
if ip, ip6, allIPs, ok := r.lookupIPCacheExpired(host); ok {
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: %q using %v after error", host, ip)
|
||||
}
|
||||
return ip, ip6, allIPs, nil
|
||||
}
|
||||
}
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: error resolving %q: %v", host, res.Err)
|
||||
}
|
||||
return zaddr, zaddr, nil, res.Err
|
||||
@@ -206,7 +206,7 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 netip.Addr
|
||||
r := res.Val
|
||||
return r.ip, r.ip6, r.allIPs, nil
|
||||
case <-ctx.Done():
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: context done while resolving %q: %v", host, ctx.Err())
|
||||
}
|
||||
return zaddr, zaddr, nil, ctx.Err()
|
||||
@@ -250,7 +250,7 @@ func (r *Resolver) lookupTimeoutForHost(host string) time.Duration {
|
||||
|
||||
func (r *Resolver) lookupIP(host string) (ip, ip6 netip.Addr, allIPs []netip.Addr, err error) {
|
||||
if ip, ip6, allIPs, ok := r.lookupIPCache(host); ok {
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: %q found in cache as %v", host, ip)
|
||||
}
|
||||
return ip, ip6, allIPs, nil
|
||||
@@ -300,13 +300,13 @@ func (r *Resolver) addIPCache(host string, ip, ip6 netip.Addr, allIPs []netip.Ad
|
||||
if ip.IsPrivate() {
|
||||
// Don't cache obviously wrong entries from captive portals.
|
||||
// TODO: use DoH or DoT for the forwarding resolver?
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: %q resolved to private IP %v; using but not caching", host, ip)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: %q resolved to IP %v; caching", host, ip)
|
||||
}
|
||||
|
||||
@@ -382,7 +382,7 @@ func (d *dialer) DialContext(ctx context.Context, network, address string) (retC
|
||||
}
|
||||
i4s := v4addrs(allIPs)
|
||||
if len(i4s) < 2 {
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: dialing %s, %s for %s", network, ip, address)
|
||||
}
|
||||
c, err := dc.dialOne(ctx, ip.Unmap())
|
||||
@@ -406,7 +406,7 @@ func (d *dialer) shouldTryBootstrap(ctx context.Context, err error, dc *dialCall
|
||||
|
||||
// Can't try bootstrap DNS if we don't have a fallback function
|
||||
if d.dnsCache.LookupIPFallback == nil {
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: not using bootstrap DNS: no fallback")
|
||||
}
|
||||
return false
|
||||
@@ -415,7 +415,7 @@ func (d *dialer) shouldTryBootstrap(ctx context.Context, err error, dc *dialCall
|
||||
// We can't retry if the context is canceled, since any further
|
||||
// operations with this context will fail.
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: not using bootstrap DNS: context error: %v", ctxErr)
|
||||
}
|
||||
return false
|
||||
@@ -423,7 +423,7 @@ func (d *dialer) shouldTryBootstrap(ctx context.Context, err error, dc *dialCall
|
||||
|
||||
wasTrustworthy := dc.dnsWasTrustworthy()
|
||||
if wasTrustworthy {
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("dnscache: not using bootstrap DNS: DNS was trustworthy")
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -167,10 +167,8 @@ func TestInterleaveSlices(t *testing.T) {
|
||||
|
||||
func TestShouldTryBootstrap(t *testing.T) {
|
||||
oldDebug := debug
|
||||
t.Cleanup(func() {
|
||||
debug = oldDebug
|
||||
})
|
||||
debug = true
|
||||
t.Cleanup(func() { debug = oldDebug })
|
||||
debug = func() bool { return true }
|
||||
|
||||
type step struct {
|
||||
ip netip.Addr // IP we pretended to dial
|
||||
|
||||
@@ -10,7 +10,6 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -42,7 +41,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile("dns-fallback-servers.json", out, 0644); err != nil {
|
||||
if err := os.WriteFile("dns-fallback-servers.json", out, 0644); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -23,7 +22,7 @@ func TestGoogleCloudRunDefaultRouteInterface(t *testing.T) {
|
||||
buf := []byte("Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT\n" +
|
||||
"eth0\t8008FEA9\t00000000\t0001\t0\t0\t0\t01FFFFFF\t0\t0\t0\n" +
|
||||
"eth1\t00000000\t00000000\t0001\t0\t0\t0\t00000000\t0\t0\t0\n")
|
||||
err := ioutil.WriteFile(procNetRoutePath, buf, 0644)
|
||||
err := os.WriteFile(procNetRoutePath, buf, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -87,7 +86,7 @@ func TestAwsAppRunnerDefaultRouteInterface(t *testing.T) {
|
||||
"ecs-eth0\t02AAFEA9\t01ACFEA9\t0007\t0\t0\t0\tFFFFFFFF\t0\t0\t0\n" +
|
||||
"ecs-eth0\t00ACFEA9\t00000000\t0001\t0\t0\t0\t00FFFFFF\t0\t0\t0\n" +
|
||||
"eth0\t00AFFEA9\t00000000\t0001\t0\t0\t0\t00FFFFFF\t0\t0\t0\n")
|
||||
err := ioutil.WriteFile(procNetRoutePath, buf, 0644)
|
||||
err := os.WriteFile(procNetRoutePath, buf, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -43,7 +42,7 @@ import (
|
||||
|
||||
// Debugging and experimentation tweakables.
|
||||
var (
|
||||
debugNetcheck = envknob.Bool("TS_DEBUG_NETCHECK")
|
||||
debugNetcheck = envknob.RegisterBool("TS_DEBUG_NETCHECK")
|
||||
)
|
||||
|
||||
// The various default timeouts for things.
|
||||
@@ -210,7 +209,7 @@ func (c *Client) logf(format string, a ...any) {
|
||||
}
|
||||
|
||||
func (c *Client) vlogf(format string, a ...any) {
|
||||
if c.Verbose || debugNetcheck {
|
||||
if c.Verbose || debugNetcheck() {
|
||||
c.logf(format, a...)
|
||||
}
|
||||
}
|
||||
@@ -1096,7 +1095,7 @@ func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegio
|
||||
return 0, ip, fmt.Errorf("unexpected status code: %d (%s)", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
_, err = io.Copy(ioutil.Discard, io.LimitReader(resp.Body, 8<<10))
|
||||
_, err = io.Copy(io.Discard, io.LimitReader(resp.Body, 8<<10))
|
||||
if err != nil {
|
||||
return 0, ip, err
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ var (
|
||||
androidProtectFunc func(fd int) error
|
||||
)
|
||||
|
||||
// UseSocketMark reports whether SO_MARK is in use. Android does not use SO_MARK.
|
||||
func UseSocketMark() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAndroidProtectFunc register a func that Android provides that JNI calls into
|
||||
// https://developer.android.com/reference/android/net/VpnService#protect(int)
|
||||
// which is documented as:
|
||||
|
||||
@@ -63,12 +63,12 @@ func socketMarkWorks() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var forceBindToDevice = envknob.Bool("TS_FORCE_LINUX_BIND_TO_DEVICE")
|
||||
var forceBindToDevice = envknob.RegisterBool("TS_FORCE_LINUX_BIND_TO_DEVICE")
|
||||
|
||||
// UseSocketMark reports whether SO_MARK is in use.
|
||||
// If it doesn't, we have to use SO_BINDTODEVICE on our sockets instead.
|
||||
func UseSocketMark() bool {
|
||||
if forceBindToDevice {
|
||||
if forceBindToDevice() {
|
||||
return false
|
||||
}
|
||||
socketMarkWorksOnce.Do(func() {
|
||||
|
||||
@@ -32,7 +32,7 @@ var counterFallbackOK int32 // atomic
|
||||
// See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
|
||||
var sslKeyLogFile = os.Getenv("SSLKEYLOGFILE")
|
||||
|
||||
var debug = envknob.Bool("TS_DEBUG_TLS_DIAL")
|
||||
var debug = envknob.RegisterBool("TS_DEBUG_TLS_DIAL")
|
||||
|
||||
// Config returns a tls.Config for connecting to a server.
|
||||
// If base is non-nil, it's cloned as the base config before
|
||||
@@ -77,7 +77,7 @@ func Config(host string, base *tls.Config) *tls.Config {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
_, errSys := cs.PeerCertificates[0].Verify(opts)
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("tlsdial(sys %q): %v", host, errSys)
|
||||
}
|
||||
if errSys == nil {
|
||||
@@ -88,7 +88,7 @@ func Config(host string, base *tls.Config) *tls.Config {
|
||||
// or broken, fall back to trying LetsEncrypt at least.
|
||||
opts.Roots = bakedInRoots()
|
||||
_, err := cs.PeerCertificates[0].Verify(opts)
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("tlsdial(bake %q): %v", host, err)
|
||||
}
|
||||
if err == nil {
|
||||
@@ -142,7 +142,7 @@ func SetConfigExpectedCert(c *tls.Config, certDNSName string) {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
_, errSys := certs[0].Verify(opts)
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("tlsdial(sys %q/%q): %v", c.ServerName, certDNSName, errSys)
|
||||
}
|
||||
if errSys == nil {
|
||||
@@ -150,7 +150,7 @@ func SetConfigExpectedCert(c *tls.Config, certDNSName string) {
|
||||
}
|
||||
opts.Roots = bakedInRoots()
|
||||
_, err := certs[0].Verify(opts)
|
||||
if debug {
|
||||
if debug() {
|
||||
log.Printf("tlsdial(bake %q/%q): %v", c.ServerName, certDNSName, err)
|
||||
}
|
||||
if err == nil {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -60,7 +59,7 @@ func TestSynologyProxyFromConfigCached(t *testing.T) {
|
||||
cache.httpProxy = nil
|
||||
cache.httpsProxy = nil
|
||||
|
||||
if err := ioutil.WriteFile(synologyProxyConfigPath, []byte(`
|
||||
if err := os.WriteFile(synologyProxyConfigPath, []byte(`
|
||||
proxy_enabled=yes
|
||||
http_host=10.0.0.55
|
||||
http_port=80
|
||||
@@ -116,7 +115,7 @@ https_port=443
|
||||
cache.httpProxy = nil
|
||||
cache.httpsProxy = nil
|
||||
|
||||
if err := ioutil.WriteFile(synologyProxyConfigPath, []byte(`
|
||||
if err := os.WriteFile(synologyProxyConfigPath, []byte(`
|
||||
proxy_enabled=yes
|
||||
http_host=10.0.0.55
|
||||
http_port=80
|
||||
|
||||
@@ -20,14 +20,6 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
var tunMTU = DefaultMTU
|
||||
|
||||
func init() {
|
||||
if mtu, ok := envknob.LookupInt("TS_DEBUG_MTU"); ok {
|
||||
tunMTU = mtu
|
||||
}
|
||||
}
|
||||
|
||||
// createTAP is non-nil on Linux.
|
||||
var createTAP func(tapName, bridgeName string) (tun.Device, error)
|
||||
|
||||
@@ -52,6 +44,10 @@ func New(logf logger.Logf, tunName string) (tun.Device, string, error) {
|
||||
}
|
||||
dev, err = createTAP(tapName, bridgeName)
|
||||
} else {
|
||||
tunMTU := DefaultMTU
|
||||
if mtu, ok := envknob.LookupInt("TS_DEBUG_MTU"); ok {
|
||||
tunMTU = mtu
|
||||
}
|
||||
dev, err = tun.CreateTUN(tunName, tunMTU)
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -74,7 +73,7 @@ func Read(r io.Reader) (*Info, error) {
|
||||
}
|
||||
|
||||
// Exhaust the remainder of r, so that the summers see the entire file.
|
||||
if _, err := io.Copy(ioutil.Discard, r); err != nil {
|
||||
if _, err := io.Copy(io.Discard, r); err != nil {
|
||||
return nil, fmt.Errorf("hashing file: %w", err)
|
||||
}
|
||||
|
||||
@@ -117,7 +116,7 @@ func findControlTar(r io.Reader) (tarReader io.Reader, err error) {
|
||||
if size%2 == 1 {
|
||||
size++
|
||||
}
|
||||
if _, err := io.CopyN(ioutil.Discard, r, size); err != nil {
|
||||
if _, err := io.CopyN(io.Discard, r, size); err != nil {
|
||||
return nil, fmt.Errorf("seeking past file %q: %w", filename, err)
|
||||
}
|
||||
}
|
||||
@@ -150,7 +149,7 @@ func findControlFile(r io.Reader) (control []byte, err error) {
|
||||
break
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadAll(tr)
|
||||
bs, err := io.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading control file: %w", err)
|
||||
}
|
||||
|
||||
@@ -74,10 +74,10 @@ func (pl List) String() string {
|
||||
return strings.TrimRight(sb.String(), "\n")
|
||||
}
|
||||
|
||||
var debugDisablePortlist = envknob.Bool("TS_DEBUG_DISABLE_PORTLIST")
|
||||
var debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
|
||||
|
||||
func GetList(prev List) (List, error) {
|
||||
if debugDisablePortlist {
|
||||
if debugDisablePortlist() {
|
||||
return nil, nil
|
||||
}
|
||||
pl, err := listPorts()
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -230,7 +229,7 @@ func addProcesses(pl []Port) ([]Port, error) {
|
||||
|
||||
pe := pm[string(targetBuf[:n])] // m[string([]byte)] avoids alloc
|
||||
if pe != nil {
|
||||
bs, err := ioutil.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid))
|
||||
bs, err := os.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid))
|
||||
if err != nil {
|
||||
// Usually shouldn't happen. One possibility is
|
||||
// the process has gone away, so let's skip it.
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -70,7 +69,7 @@ func localTCPPortAndTokenDarwin() (port int, token string, err error) {
|
||||
// The current binary (this process) is sandboxed. The user is
|
||||
// running the CLI via /Applications/Tailscale.app/Contents/MacOS/Tailscale
|
||||
// which sets the TS_MACOS_CLI_SHARED_DIR environment variable.
|
||||
fis, err := ioutil.ReadDir(dir)
|
||||
fis, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package smallzstd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
@@ -113,7 +113,7 @@ func benchDecoderWithConstruction(b *testing.B, mk func() (*zstd.Decoder, error)
|
||||
|
||||
func testdata(b *testing.B) []byte {
|
||||
b.Helper()
|
||||
in, err := ioutil.ReadFile("testdata")
|
||||
in, err := os.ReadFile("testdata")
|
||||
if err != nil {
|
||||
b.Fatalf("reading testdata: %v", err)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
@@ -46,9 +45,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
debugPolicyFile = envknob.SSHPolicyFile()
|
||||
debugIgnoreTailnetSSHPolicy = envknob.SSHIgnoreTailnetPolicy()
|
||||
sshVerboseLogging = envknob.Bool("TS_DEBUG_SSH_VLOG")
|
||||
sshVerboseLogging = envknob.RegisterBool("TS_DEBUG_SSH_VLOG")
|
||||
)
|
||||
|
||||
type server struct {
|
||||
@@ -384,9 +381,10 @@ func (c *conn) sshPolicy() (_ *tailcfg.SSHPolicy, ok bool) {
|
||||
if nm == nil {
|
||||
return nil, false
|
||||
}
|
||||
if pol := nm.SSHPolicy; pol != nil && !debugIgnoreTailnetSSHPolicy {
|
||||
if pol := nm.SSHPolicy; pol != nil && !envknob.SSHIgnoreTailnetPolicy() {
|
||||
return pol, true
|
||||
}
|
||||
debugPolicyFile := envknob.SSHPolicyFile()
|
||||
if debugPolicyFile != "" {
|
||||
c.logf("reading debug SSH policy file: %v", debugPolicyFile)
|
||||
f, err := os.ReadFile(debugPolicyFile)
|
||||
@@ -769,7 +767,7 @@ type sshSession struct {
|
||||
}
|
||||
|
||||
func (ss *sshSession) vlogf(format string, args ...interface{}) {
|
||||
if sshVerboseLogging {
|
||||
if sshVerboseLogging() {
|
||||
ss.logf(format, args...)
|
||||
}
|
||||
}
|
||||
@@ -952,7 +950,7 @@ func (ss *sshSession) handleSSHAgentForwarding(s ssh.Session, lu *user.User) err
|
||||
// functionality and support off-node streaming.
|
||||
//
|
||||
// TODO(bradfitz,maisem): move this to SSHPolicy.
|
||||
var recordSSH = envknob.Bool("TS_DEBUG_LOG_SSH")
|
||||
var recordSSH = envknob.RegisterBool("TS_DEBUG_LOG_SSH")
|
||||
|
||||
// run is the entrypoint for a newly accepted SSH session.
|
||||
//
|
||||
@@ -1092,7 +1090,7 @@ func (ss *sshSession) shouldRecord() bool {
|
||||
// TODO(bradfitz,maisem): make configurable on SSHPolicy and
|
||||
// support recording non-pty stuff too.
|
||||
_, _, isPtyReq := ss.Pty()
|
||||
return recordSSH && isPtyReq
|
||||
return recordSSH() && isPtyReq
|
||||
}
|
||||
|
||||
type sshConnInfo struct {
|
||||
@@ -1307,7 +1305,7 @@ func (ss *sshSession) startNewRecording() (*recording, error) {
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := ioutil.TempFile(dir, fmt.Sprintf("ssh-session-%v-*.cast", now.UnixNano()))
|
||||
f, err := os.CreateTemp(dir, fmt.Sprintf("ssh-session-%v-*.cast", now.UnixNano()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1456,6 +1456,10 @@ type Debug struct {
|
||||
// re-enabled for the lifetime of the process.
|
||||
DisableLogTail bool `json:",omitempty"`
|
||||
|
||||
// EnableSilentDisco disables the use of heartBeatTimer in magicsock and attempts to
|
||||
// handle disco silently. See issue #540 for details.
|
||||
EnableSilentDisco bool `json:",omitempty"`
|
||||
|
||||
// Exit optionally specifies that the client should os.Exit
|
||||
// with this code.
|
||||
Exit *int `json:",omitempty"`
|
||||
|
||||
@@ -2,8 +2,8 @@ package ssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
@@ -36,7 +36,7 @@ func AgentRequested(sess Session) bool {
|
||||
// NewAgentListener sets up a temporary Unix socket that can be communicated
|
||||
// to the session environment and used for forwarding connections.
|
||||
func NewAgentListener() (net.Listener, error) {
|
||||
dir, err := ioutil.TempDir("", agentTempDir)
|
||||
dir, err := os.MkdirTemp("", agentTempDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package ssh_test
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"tailscale.com/tempfork/gliderlabs/ssh"
|
||||
)
|
||||
@@ -29,7 +29,7 @@ func ExampleNoPty() {
|
||||
func ExamplePublicKeyAuth() {
|
||||
ssh.ListenAndServe(":2222", nil,
|
||||
ssh.PublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) error {
|
||||
data, err := ioutil.ReadFile("/path/to/allowed/key.pub")
|
||||
data, err := os.ReadFile("/path/to/allowed/key.pub")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
gossh "github.com/tailscale/golang-x-crypto/ssh"
|
||||
)
|
||||
@@ -26,7 +26,7 @@ func PublicKeyAuth(fn PublicKeyHandler) Option {
|
||||
// from a PEM file at filepath.
|
||||
func HostKeyFile(filepath string) Option {
|
||||
return func(srv *Server) error {
|
||||
pemBytes, err := ioutil.ReadFile(filepath)
|
||||
pemBytes, err := os.ReadFile(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -61,7 +60,7 @@ func TestLocalPortForwardingWorks(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Error connecting to %v: %v", l.Addr().String(), err)
|
||||
}
|
||||
result, err := ioutil.ReadAll(conn)
|
||||
result, err := io.ReadAll(conn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ package pprof
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"runtime/pprof"
|
||||
@@ -52,7 +52,7 @@ func TestHandlers(t *testing.T) {
|
||||
t.Errorf("status code: got %d; want %d", got, want)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("when reading response body, expected non-nil err; got %v", err)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ package tka
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
@@ -345,7 +344,7 @@ func (c *FS) LastActiveAncestor() (*AUMHash, error) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
hash, err := ioutil.ReadFile(filepath.Join(c.base, "last_active_ancestor"))
|
||||
hash, err := os.ReadFile(filepath.Join(c.base, "last_active_ancestor"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil // Not exist == none set.
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -252,7 +251,7 @@ func (s *Server) start() (reterr error) {
|
||||
c := logtail.Config{
|
||||
Collection: lpc.Collection,
|
||||
PrivateID: lpc.PrivateID,
|
||||
Stderr: ioutil.Discard, // log everything to Buffer
|
||||
Stderr: io.Discard, // log everything to Buffer
|
||||
Buffer: s.logbuffer,
|
||||
NewZstdEncoder: func() logtail.Encoder {
|
||||
w, err := smallzstd.NewEncoder(nil)
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -62,7 +61,7 @@ import (
|
||||
fmt.Fprintf(&out, ")\n")
|
||||
|
||||
filename := fmt.Sprintf("tailscaled_deps_test_%s.go", goos)
|
||||
err = ioutil.WriteFile(filename, out.Bytes(), 0644)
|
||||
err = os.WriteFile(filename, out.Bytes(), 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -88,7 +87,7 @@ var (
|
||||
// buildTestBinaries builds tailscale and tailscaled.
|
||||
// It returns the dir containing the binaries.
|
||||
func buildTestBinaries() (string, error) {
|
||||
bindir, err := ioutil.TempDir("", "")
|
||||
bindir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -288,7 +287,7 @@ func (lc *LogCatcher) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer dec.Close()
|
||||
body = dec
|
||||
}
|
||||
bodyBytes, _ := ioutil.ReadAll(body)
|
||||
bodyBytes, _ := io.ReadAll(body)
|
||||
|
||||
type Entry struct {
|
||||
Logtail struct {
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -653,7 +652,7 @@ func newTestNode(t *testing.T, env *testEnv) *testNode {
|
||||
func (n *testNode) diskPrefs() *ipn.Prefs {
|
||||
t := n.env.t
|
||||
t.Helper()
|
||||
if _, err := ioutil.ReadFile(n.stateFile); err != nil {
|
||||
if _, err := os.ReadFile(n.stateFile); err != nil {
|
||||
t.Fatalf("reading prefs: %v", err)
|
||||
}
|
||||
fs, err := store.NewFileStore(nil, n.stateFile)
|
||||
@@ -768,7 +767,7 @@ func (op *nodeOutputParser) parseLines() {
|
||||
if len(buf) == 0 {
|
||||
op.buf.Reset()
|
||||
} else {
|
||||
io.CopyN(ioutil.Discard, &op.buf, int64(op.buf.Len()-len(buf)))
|
||||
io.CopyN(io.Discard, &op.buf, int64(op.buf.Len()-len(buf)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
@@ -426,7 +425,7 @@ func (s *Server) CompleteAuth(authPathOrURL string) bool {
|
||||
}
|
||||
|
||||
func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey key.MachinePublic) {
|
||||
msg, err := ioutil.ReadAll(io.LimitReader(r.Body, msgLimit))
|
||||
msg, err := io.ReadAll(io.LimitReader(r.Body, msgLimit))
|
||||
r.Body.Close()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("bad map request read: %v", err), 400)
|
||||
@@ -597,7 +596,7 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey key.Machi
|
||||
defer s.incrInServeMap(-1)
|
||||
ctx := r.Context()
|
||||
|
||||
msg, err := ioutil.ReadAll(io.LimitReader(r.Body, msgLimit))
|
||||
msg, err := io.ReadAll(io.LimitReader(r.Body, msgLimit))
|
||||
if err != nil {
|
||||
r.Body.Close()
|
||||
http.Error(w, fmt.Sprintf("bad map request read: %v", err), 400)
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
@@ -81,7 +80,7 @@ func AllowDebugAccess(r *http.Request) bool {
|
||||
urlKey := r.FormValue("debugkey")
|
||||
keyPath := envknob.String("TS_DEBUG_KEY_PATH")
|
||||
if urlKey != "" && keyPath != "" {
|
||||
slurp, err := ioutil.ReadFile(keyPath)
|
||||
slurp, err := os.ReadFile(keyPath)
|
||||
if err == nil && string(bytes.TrimSpace(slurp)) == urlKey {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -129,8 +128,6 @@ type limitData struct {
|
||||
ele *list.Element // list element used to access this string in the cache
|
||||
}
|
||||
|
||||
var disableRateLimit = envknob.String("TS_DEBUG_LOG_RATE") == "all"
|
||||
|
||||
// rateFree are format string substrings that are exempt from rate limiting.
|
||||
// Things should not be added to this unless they're already limited otherwise
|
||||
// or are critical for generating important stats from the logs.
|
||||
@@ -156,7 +153,7 @@ func RateLimitedFn(logf Logf, f time.Duration, burst int, maxCache int) Logf {
|
||||
// timeNow is a function that returns the current time, used for calculating
|
||||
// rate limits.
|
||||
func RateLimitedFnWithClock(logf Logf, f time.Duration, burst int, maxCache int, timeNow func() time.Time) Logf {
|
||||
if disableRateLimit {
|
||||
if envknob.String("TS_DEBUG_LOG_RATE") == "all" {
|
||||
return logf
|
||||
}
|
||||
var (
|
||||
@@ -270,7 +267,7 @@ func (fn ArgWriter) Format(f fmt.State, _ rune) {
|
||||
argBufioPool.Put(bw)
|
||||
}
|
||||
|
||||
var argBufioPool = &sync.Pool{New: func() any { return bufio.NewWriterSize(ioutil.Discard, 1024) }}
|
||||
var argBufioPool = &sync.Pool{New: func() any { return bufio.NewWriterSize(io.Discard, 1024) }}
|
||||
|
||||
// Filtered returns a Logf that silently swallows some log lines.
|
||||
// Each inbound format and args is evaluated and printed to a string s.
|
||||
|
||||
34
version_test.go
Normal file
34
version_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & 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 tailscaleroot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDockerfileVersion(t *testing.T) {
|
||||
goMod, err := os.ReadFile("go.mod")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := regexp.MustCompile(`(?m)^go (\d\.\d+)\r?$`).FindStringSubmatch(string(goMod))
|
||||
if m == nil {
|
||||
t.Fatalf("didn't find go version in go.mod")
|
||||
}
|
||||
goVersion := m[1]
|
||||
|
||||
dockerFile, err := os.ReadFile("Dockerfile")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantSub := fmt.Sprintf("FROM golang:%s-alpine AS build-env", goVersion)
|
||||
if !strings.Contains(string(dockerFile), wantSub) {
|
||||
t.Errorf("didn't find %q in Dockerfile", wantSub)
|
||||
}
|
||||
}
|
||||
@@ -11,28 +11,33 @@ import (
|
||||
"tailscale.com/envknob"
|
||||
)
|
||||
|
||||
const linkDebug = true
|
||||
|
||||
// Various debugging and experimental tweakables, set by environment
|
||||
// variable.
|
||||
var (
|
||||
// debugDisco prints verbose logs of active discovery events as
|
||||
// they happen.
|
||||
debugDisco = envknob.Bool("TS_DEBUG_DISCO")
|
||||
debugDisco = envknob.RegisterBool("TS_DEBUG_DISCO")
|
||||
// debugOmitLocalAddresses removes all local interface addresses
|
||||
// from magicsock's discovered local endpoints. Used in some tests.
|
||||
debugOmitLocalAddresses = envknob.Bool("TS_DEBUG_OMIT_LOCAL_ADDRS")
|
||||
debugOmitLocalAddresses = envknob.RegisterBool("TS_DEBUG_OMIT_LOCAL_ADDRS")
|
||||
// debugUseDerpRoute temporarily (2020-03-22) controls whether DERP
|
||||
// reverse routing is enabled (Issue 150).
|
||||
debugUseDerpRoute = envknob.OptBool("TS_DEBUG_ENABLE_DERP_ROUTE")
|
||||
debugUseDerpRoute = envknob.RegisterOptBool("TS_DEBUG_ENABLE_DERP_ROUTE")
|
||||
// logDerpVerbose logs all received DERP packets, including their
|
||||
// full payload.
|
||||
logDerpVerbose = envknob.Bool("TS_DEBUG_DERP")
|
||||
logDerpVerbose = envknob.RegisterBool("TS_DEBUG_DERP")
|
||||
// debugReSTUNStopOnIdle unconditionally enables the "shut down
|
||||
// STUN if magicsock is idle" behavior that normally only triggers
|
||||
// on mobile devices, lowers the shutdown interval, and logs more
|
||||
// verbosely about idle measurements.
|
||||
debugReSTUNStopOnIdle = envknob.Bool("TS_DEBUG_RESTUN_STOP_ON_IDLE")
|
||||
debugReSTUNStopOnIdle = envknob.RegisterBool("TS_DEBUG_RESTUN_STOP_ON_IDLE")
|
||||
// debugAlwaysDERP disables the use of UDP, forcing all peer communication over DERP.
|
||||
debugAlwaysDERP = envknob.Bool("TS_DEBUG_ALWAYS_USE_DERP")
|
||||
debugAlwaysDERP = envknob.RegisterBool("TS_DEBUG_ALWAYS_USE_DERP")
|
||||
// debugEnableSilentDisco disables the use of heartbeatTimer on the endpoint struct
|
||||
// and attempts to handle disco silently. See issue #540 for details.
|
||||
debugEnableSilentDisco = envknob.RegisterBool("TS_DEBUG_ENABLE_SILENT_DISCO")
|
||||
)
|
||||
|
||||
// inTest reports whether the running program is a test that set the
|
||||
|
||||
@@ -10,15 +10,16 @@ package magicsock
|
||||
import "tailscale.com/types/opt"
|
||||
|
||||
// All knobs are disabled on iOS and Wasm.
|
||||
// Further, they're const, so the toolchain can produce smaller binaries.
|
||||
const (
|
||||
debugDisco = false
|
||||
debugOmitLocalAddresses = false
|
||||
debugUseDerpRouteEnv = ""
|
||||
debugUseDerpRoute opt.Bool = ""
|
||||
logDerpVerbose = false
|
||||
debugReSTUNStopOnIdle = false
|
||||
debugAlwaysDERP = false
|
||||
)
|
||||
//
|
||||
// They're inlinable and the linker can deadcode that's guarded by them to make
|
||||
// smaller binaries.
|
||||
func debugDisco() bool { return false }
|
||||
func debugOmitLocalAddresses() bool { return false }
|
||||
func logDerpVerbose() bool { return false }
|
||||
func debugReSTUNStopOnIdle() bool { return false }
|
||||
func debugAlwaysDERP() bool { return false }
|
||||
func debugEnableSilentDisco() bool { return false }
|
||||
func debugUseDerpRouteEnv() string { return "" }
|
||||
func debugUseDerpRoute() opt.Bool { return "" }
|
||||
|
||||
func inTest() bool { return false }
|
||||
|
||||
@@ -74,7 +74,7 @@ const (
|
||||
// useDerpRoute reports whether magicsock should enable the DERP
|
||||
// return path optimization (Issue 150).
|
||||
func useDerpRoute() bool {
|
||||
if b, ok := debugUseDerpRoute.Get(); ok {
|
||||
if b, ok := debugUseDerpRoute().Get(); ok {
|
||||
return b
|
||||
}
|
||||
ob := controlclient.DERPRouteFlag()
|
||||
@@ -638,18 +638,18 @@ func (c *Conn) updateEndpoints(why string) {
|
||||
// etc)
|
||||
d := tstime.RandomDurationBetween(20*time.Second, 26*time.Second)
|
||||
if t := c.periodicReSTUNTimer; t != nil {
|
||||
if debugReSTUNStopOnIdle {
|
||||
if debugReSTUNStopOnIdle() {
|
||||
c.logf("resetting existing periodicSTUN to run in %v", d)
|
||||
}
|
||||
t.Reset(d)
|
||||
} else {
|
||||
if debugReSTUNStopOnIdle {
|
||||
if debugReSTUNStopOnIdle() {
|
||||
c.logf("scheduling periodicSTUN to run in %v", d)
|
||||
}
|
||||
c.periodicReSTUNTimer = time.AfterFunc(d, c.doPeriodicSTUN)
|
||||
}
|
||||
} else {
|
||||
if debugReSTUNStopOnIdle {
|
||||
if debugReSTUNStopOnIdle() {
|
||||
c.logf("periodic STUN idle")
|
||||
}
|
||||
c.stopPeriodicReSTUNTimerLocked()
|
||||
@@ -1074,7 +1074,7 @@ func (c *Conn) determineEndpoints(ctx context.Context) ([]tailcfg.Endpoint, erro
|
||||
return
|
||||
}
|
||||
addAddr := func(ipp netip.AddrPort, et tailcfg.EndpointType) {
|
||||
if !ipp.IsValid() || (debugOmitLocalAddresses && et == tailcfg.EndpointLocal) {
|
||||
if !ipp.IsValid() || (debugOmitLocalAddresses() && et == tailcfg.EndpointLocal) {
|
||||
return
|
||||
}
|
||||
if _, ok := already[ipp]; !ok {
|
||||
@@ -1575,7 +1575,7 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netip.AddrPort, d
|
||||
pkt = m
|
||||
res.n = len(m.Data)
|
||||
res.src = m.Source
|
||||
if logDerpVerbose {
|
||||
if logDerpVerbose() {
|
||||
c.logf("magicsock: got derp-%v packet: %q", regionID, m.Data)
|
||||
}
|
||||
// If this is a new sender we hadn't seen before, remember it and
|
||||
@@ -1826,7 +1826,7 @@ func (c *Conn) sendDiscoMessage(dst netip.AddrPort, dstKey key.NodePublic, dstDi
|
||||
pkt = append(pkt, box...)
|
||||
sent, err = c.sendAddr(dst, dstKey, pkt)
|
||||
if sent {
|
||||
if logLevel == discoLog || (logLevel == discoVerboseLog && debugDisco) {
|
||||
if logLevel == discoLog || (logLevel == discoVerboseLog && debugDisco()) {
|
||||
node := "?"
|
||||
if !dstKey.IsZero() {
|
||||
node = dstKey.ShortString()
|
||||
@@ -1890,7 +1890,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
|
||||
if c.closed {
|
||||
return
|
||||
}
|
||||
if debugDisco {
|
||||
if debugDisco() {
|
||||
c.logf("magicsock: disco: got disco-looking frame from %v", sender.ShortString())
|
||||
}
|
||||
if c.privateKey.IsZero() {
|
||||
@@ -1899,7 +1899,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
|
||||
return
|
||||
}
|
||||
if c.discoPrivate.IsZero() {
|
||||
if debugDisco {
|
||||
if debugDisco() {
|
||||
c.logf("magicsock: disco: ignoring disco-looking frame, no local key")
|
||||
}
|
||||
return
|
||||
@@ -1907,7 +1907,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
|
||||
|
||||
if !c.peerMap.anyEndpointForDiscoKey(sender) {
|
||||
metricRecvDiscoBadPeer.Add(1)
|
||||
if debugDisco {
|
||||
if debugDisco() {
|
||||
c.logf("magicsock: disco: ignoring disco-looking frame, don't know endpoint for %v", sender.ShortString())
|
||||
}
|
||||
return
|
||||
@@ -1931,7 +1931,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
|
||||
// Don't log in normal case. Pass on to wireguard, in case
|
||||
// it's actually a wireguard packet (super unlikely,
|
||||
// but).
|
||||
if debugDisco {
|
||||
if debugDisco() {
|
||||
c.logf("magicsock: disco: failed to open naclbox from %v (wrong rcpt?)", sender)
|
||||
}
|
||||
metricRecvDiscoBadKey.Add(1)
|
||||
@@ -1939,7 +1939,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
|
||||
}
|
||||
|
||||
dm, err := disco.Parse(payload)
|
||||
if debugDisco {
|
||||
if debugDisco() {
|
||||
c.logf("magicsock: disco: disco.Parse = %T, %v", dm, err)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -2094,7 +2094,7 @@ func (c *Conn) handlePingLocked(dm *disco.Ping, src netip.AddrPort, di *discoInf
|
||||
return
|
||||
}
|
||||
|
||||
if !likelyHeartBeat || debugDisco {
|
||||
if !likelyHeartBeat || debugDisco() {
|
||||
pingNodeSrcStr := dstKey.ShortString()
|
||||
if numNodes > 1 {
|
||||
pingNodeSrcStr = "[one-of-multi]"
|
||||
@@ -2356,6 +2356,8 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
|
||||
}
|
||||
c.netMap = nm
|
||||
|
||||
heartbeatDisabled := debugEnableSilentDisco() || (c.netMap != nil && c.netMap.Debug != nil && c.netMap.Debug.EnableSilentDisco)
|
||||
|
||||
// Try a pass of just upserting nodes and creating missing
|
||||
// endpoints. If the set of nodes is the same, this is an
|
||||
// efficient alloc-free update. If the set of nodes is different,
|
||||
@@ -2364,16 +2366,17 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
|
||||
for _, n := range nm.Peers {
|
||||
if ep, ok := c.peerMap.endpointForNodeKey(n.Key); ok {
|
||||
oldDiscoKey := ep.discoKey
|
||||
ep.updateFromNode(n)
|
||||
ep.updateFromNode(n, heartbeatDisabled)
|
||||
c.peerMap.upsertEndpoint(ep, oldDiscoKey) // maybe update discokey mappings in peerMap
|
||||
continue
|
||||
}
|
||||
|
||||
ep := &endpoint{
|
||||
c: c,
|
||||
publicKey: n.Key,
|
||||
sentPing: map[stun.TxID]sentPing{},
|
||||
endpointState: map[netip.AddrPort]*endpointState{},
|
||||
c: c,
|
||||
publicKey: n.Key,
|
||||
sentPing: map[stun.TxID]sentPing{},
|
||||
endpointState: map[netip.AddrPort]*endpointState{},
|
||||
heartbeatDisabled: heartbeatDisabled,
|
||||
}
|
||||
if !n.DiscoKey.IsZero() {
|
||||
ep.discoKey = n.DiscoKey
|
||||
@@ -2381,7 +2384,7 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
|
||||
}
|
||||
ep.wgEndpoint = n.Key.UntypedHexString()
|
||||
ep.initFakeUDPAddr()
|
||||
if debugDisco { // rather than making a new knob
|
||||
if debugDisco() { // rather than making a new knob
|
||||
c.logf("magicsock: created endpoint key=%s: disco=%s; %v", n.Key.ShortString(), n.DiscoKey.ShortString(), logger.ArgWriter(func(w *bufio.Writer) {
|
||||
const derpPrefix = "127.3.3.40:"
|
||||
if strings.HasPrefix(n.DERP, derpPrefix) {
|
||||
@@ -2406,7 +2409,7 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
|
||||
}
|
||||
}))
|
||||
}
|
||||
ep.updateFromNode(n)
|
||||
ep.updateFromNode(n, heartbeatDisabled)
|
||||
c.peerMap.upsertEndpoint(ep, key.DiscoPublic{})
|
||||
}
|
||||
|
||||
@@ -2736,7 +2739,7 @@ func (c *Conn) goroutinesRunningLocked() bool {
|
||||
}
|
||||
|
||||
func maxIdleBeforeSTUNShutdown() time.Duration {
|
||||
if debugReSTUNStopOnIdle {
|
||||
if debugReSTUNStopOnIdle() {
|
||||
return 45 * time.Second
|
||||
}
|
||||
return sessionActiveTimeout
|
||||
@@ -2753,7 +2756,7 @@ func (c *Conn) shouldDoPeriodicReSTUNLocked() bool {
|
||||
}
|
||||
if f := c.idleFunc; f != nil {
|
||||
idleFor := f()
|
||||
if debugReSTUNStopOnIdle {
|
||||
if debugReSTUNStopOnIdle() {
|
||||
c.logf("magicsock: periodicReSTUN: idle for %v", idleFor.Round(time.Second))
|
||||
}
|
||||
if idleFor > maxIdleBeforeSTUNShutdown() {
|
||||
@@ -2834,7 +2837,7 @@ func (c *Conn) bindSocket(ruc *RebindingUDPConn, network string, curPortFate cur
|
||||
return nil
|
||||
}
|
||||
|
||||
if debugAlwaysDERP {
|
||||
if debugAlwaysDERP() {
|
||||
c.logf("disabled %v per TS_DEBUG_ALWAYS_USE_DERP", network)
|
||||
ruc.setConnLocked(newBlockForeverConn())
|
||||
return nil
|
||||
@@ -3307,6 +3310,8 @@ type endpoint struct {
|
||||
isCallMeMaybeEP map[netip.AddrPort]bool
|
||||
|
||||
pendingCLIPings []pendingCLIPing // any outstanding "tailscale ping" commands running
|
||||
|
||||
heartbeatDisabled bool // heartBeatTimer disabled for silent disco. See issue #540.
|
||||
}
|
||||
|
||||
type pendingCLIPing struct {
|
||||
@@ -3502,6 +3507,11 @@ func (de *endpoint) heartbeat() {
|
||||
|
||||
de.heartBeatTimer = nil
|
||||
|
||||
if de.heartbeatDisabled {
|
||||
// If control override to disable heartBeatTimer set, return early.
|
||||
return
|
||||
}
|
||||
|
||||
if !de.canP2P() {
|
||||
// Cannot form p2p connections, no heartbeating necessary.
|
||||
return
|
||||
@@ -3560,7 +3570,7 @@ func (de *endpoint) wantFullPingLocked(now mono.Time) bool {
|
||||
|
||||
func (de *endpoint) noteActiveLocked() {
|
||||
de.lastSend = mono.Now()
|
||||
if de.heartBeatTimer == nil && de.canP2P() {
|
||||
if de.heartBeatTimer == nil && de.canP2P() && !de.heartbeatDisabled {
|
||||
de.heartBeatTimer = time.AfterFunc(heartbeatInterval, de.heartbeat)
|
||||
}
|
||||
}
|
||||
@@ -3626,7 +3636,7 @@ func (de *endpoint) pingTimeout(txid stun.TxID) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if debugDisco || !de.bestAddr.IsValid() || mono.Now().After(de.trustBestAddrUntil) {
|
||||
if debugDisco() || !de.bestAddr.IsValid() || mono.Now().After(de.trustBestAddrUntil) {
|
||||
de.c.logf("[v1] magicsock: disco: timeout waiting for pong %x from %v (%v, %v)", txid[:6], sp.to, de.publicKey.ShortString(), de.discoShort)
|
||||
}
|
||||
de.removeSentPingLocked(txid, sp)
|
||||
@@ -3751,13 +3761,15 @@ func (de *endpoint) sendPingsLocked(now mono.Time, sendCallMeMaybe bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (de *endpoint) updateFromNode(n *tailcfg.Node) {
|
||||
func (de *endpoint) updateFromNode(n *tailcfg.Node, heartbeatDisabled bool) {
|
||||
if n == nil {
|
||||
panic("nil node when updating disco ep")
|
||||
}
|
||||
de.mu.Lock()
|
||||
defer de.mu.Unlock()
|
||||
|
||||
de.heartbeatDisabled = heartbeatDisabled
|
||||
|
||||
if de.discoKey != n.DiscoKey {
|
||||
de.c.logf("[v1] magicsock: disco: node %s changed from discokey %s to %s", de.publicKey.ShortString(), de.discoKey, n.DiscoKey)
|
||||
de.discoKey = n.DiscoKey
|
||||
|
||||
@@ -28,7 +28,7 @@ const (
|
||||
)
|
||||
|
||||
// Enable/disable using raw sockets to receive disco traffic.
|
||||
var debugDisableRawDisco = envknob.Bool("TS_DEBUG_DISABLE_RAW_DISCO")
|
||||
var debugDisableRawDisco = envknob.RegisterBool("TS_DEBUG_DISABLE_RAW_DISCO")
|
||||
|
||||
// These are our BPF filters that we use for testing packets.
|
||||
var (
|
||||
@@ -125,7 +125,7 @@ var (
|
||||
// and BPF filter.
|
||||
// https://github.com/tailscale/tailscale/issues/3824
|
||||
func (c *Conn) listenRawDisco(family string) (io.Closer, error) {
|
||||
if debugDisableRawDisco {
|
||||
if debugDisableRawDisco() {
|
||||
return nil, errors.New("raw disco listening disabled by debug flag")
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -1174,7 +1174,7 @@ func TestDiscoStringLogRace(t *testing.T) {
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
fmt.Fprintf(ioutil.Discard, "%v", de)
|
||||
fmt.Fprintf(io.Discard, "%v", de)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
var debugNetlinkMessages = envknob.Bool("TS_DEBUG_NETLINK")
|
||||
var debugNetlinkMessages = envknob.RegisterBool("TS_DEBUG_NETLINK")
|
||||
|
||||
// unspecifiedMessage is a minimal message implementation that should not
|
||||
// be ignored. In general, OS-specific implementations should use better
|
||||
@@ -96,7 +96,7 @@ func (c *nlConn) Receive() (message, error) {
|
||||
|
||||
nip := netaddrIP(rmsg.Attributes.Address)
|
||||
|
||||
if debugNetlinkMessages {
|
||||
if debugNetlinkMessages() {
|
||||
typ := "RTM_NEWADDR"
|
||||
if msg.Header.Type == unix.RTM_DELADDR {
|
||||
typ = "RTM_DELADDR"
|
||||
@@ -125,7 +125,7 @@ func (c *nlConn) Receive() (message, error) {
|
||||
}
|
||||
|
||||
if addrs[nip] {
|
||||
if debugNetlinkMessages {
|
||||
if debugNetlinkMessages() {
|
||||
c.logf("ignored duplicate RTM_NEWADDR for %s", nip)
|
||||
}
|
||||
return ignoreMessage{}, nil
|
||||
@@ -147,7 +147,7 @@ func (c *nlConn) Receive() (message, error) {
|
||||
Addr: nip,
|
||||
Delete: msg.Header.Type == unix.RTM_DELADDR,
|
||||
}
|
||||
if debugNetlinkMessages {
|
||||
if debugNetlinkMessages() {
|
||||
c.logf("%+v", nam)
|
||||
}
|
||||
return nam, nil
|
||||
@@ -169,7 +169,7 @@ func (c *nlConn) Receive() (message, error) {
|
||||
(rmsg.Attributes.Table == 255 || rmsg.Attributes.Table == 254) &&
|
||||
(dst.Addr().IsMulticast() || dst.Addr().IsLinkLocalUnicast()) {
|
||||
|
||||
if debugNetlinkMessages {
|
||||
if debugNetlinkMessages() {
|
||||
c.logf("%s ignored", typeStr)
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ func (c *nlConn) Receive() (message, error) {
|
||||
Dst: dst,
|
||||
Gateway: gw,
|
||||
}
|
||||
if debugNetlinkMessages {
|
||||
if debugNetlinkMessages() {
|
||||
c.logf("%+v", nrm)
|
||||
}
|
||||
return nrm, nil
|
||||
@@ -225,7 +225,7 @@ func (c *nlConn) Receive() (message, error) {
|
||||
table: rmsg.Table,
|
||||
priority: rmsg.Attributes.Priority,
|
||||
}
|
||||
if debugNetlinkMessages {
|
||||
if debugNetlinkMessages() {
|
||||
c.logf("%+v", rdm)
|
||||
}
|
||||
return rdm, nil
|
||||
|
||||
@@ -55,7 +55,7 @@ import (
|
||||
|
||||
const debugPackets = false
|
||||
|
||||
var debugNetstack = envknob.Bool("TS_DEBUG_NETSTACK")
|
||||
var debugNetstack = envknob.RegisterBool("TS_DEBUG_NETSTACK")
|
||||
|
||||
var (
|
||||
magicDNSIP = tsaddr.TailscaleServiceIP()
|
||||
@@ -638,7 +638,7 @@ func (ns *Impl) userPing(dstIP netip.Addr, pingResPkt []byte) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if debugNetstack {
|
||||
if debugNetstack() {
|
||||
ns.logf("exec pinged %v in %v", dstIP, time.Since(t0))
|
||||
}
|
||||
if err := ns.tundev.InjectOutbound(pingResPkt); err != nil {
|
||||
@@ -718,7 +718,7 @@ func netaddrIPFromNetstackIP(s tcpip.Address) netip.Addr {
|
||||
|
||||
func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
reqDetails := r.ID()
|
||||
if debugNetstack {
|
||||
if debugNetstack() {
|
||||
ns.logf("[v2] TCP ForwarderRequest: %s", stringifyTEI(reqDetails))
|
||||
}
|
||||
clientRemoteIP := netaddrIPFromNetstackIP(reqDetails.RemoteAddress)
|
||||
@@ -849,7 +849,7 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
|
||||
func (ns *Impl) forwardTCP(getClient func() *gonet.TCPConn, clientRemoteIP netip.Addr, wq *waiter.Queue, dialAddr netip.AddrPort) (handled bool) {
|
||||
dialAddrStr := dialAddr.String()
|
||||
if debugNetstack {
|
||||
if debugNetstack() {
|
||||
ns.logf("[v2] netstack: forwarding incoming connection to %s", dialAddrStr)
|
||||
}
|
||||
|
||||
@@ -866,7 +866,7 @@ func (ns *Impl) forwardTCP(getClient func() *gonet.TCPConn, clientRemoteIP netip
|
||||
go func() {
|
||||
select {
|
||||
case <-notifyCh:
|
||||
if debugNetstack {
|
||||
if debugNetstack() {
|
||||
ns.logf("[v2] netstack: forwardTCP notifyCh fired; canceling context for %s", dialAddrStr)
|
||||
}
|
||||
case <-done:
|
||||
@@ -919,7 +919,7 @@ func (ns *Impl) forwardTCP(getClient func() *gonet.TCPConn, clientRemoteIP netip
|
||||
|
||||
func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) {
|
||||
sess := r.ID()
|
||||
if debugNetstack {
|
||||
if debugNetstack() {
|
||||
ns.logf("[v2] UDP ForwarderRequest: %v", stringifyTEI(sess))
|
||||
}
|
||||
var wq waiter.Queue
|
||||
@@ -995,7 +995,7 @@ func (ns *Impl) handleMagicDNSUDP(srcAddr netip.AddrPort, c *gonet.UDPConn) {
|
||||
// proxy to it directly.
|
||||
func (ns *Impl) forwardUDP(client *gonet.UDPConn, wq *waiter.Queue, clientAddr, dstAddr netip.AddrPort) {
|
||||
port, srcPort := dstAddr.Port(), clientAddr.Port()
|
||||
if debugNetstack {
|
||||
if debugNetstack() {
|
||||
ns.logf("[v2] netstack: forwarding incoming UDP connection on port %v", port)
|
||||
}
|
||||
|
||||
@@ -1071,7 +1071,7 @@ func (ns *Impl) forwardUDP(client *gonet.UDPConn, wq *waiter.Queue, clientAddr,
|
||||
}
|
||||
|
||||
func startPacketCopy(ctx context.Context, cancel context.CancelFunc, dst net.PacketConn, dstAddr net.Addr, src net.PacketConn, logf logger.Logf, extend func()) {
|
||||
if debugNetstack {
|
||||
if debugNetstack() {
|
||||
logf("[v2] netstack: startPacketCopy to %v (%T) from %T", dstAddr, dst, src)
|
||||
}
|
||||
go func() {
|
||||
@@ -1096,7 +1096,7 @@ func startPacketCopy(ctx context.Context, cancel context.CancelFunc, dst net.Pac
|
||||
}
|
||||
return
|
||||
}
|
||||
if debugNetstack {
|
||||
if debugNetstack() {
|
||||
logf("[v2] wrote UDP packet %s -> %s", srcAddr, dstAddr)
|
||||
}
|
||||
extend()
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -113,6 +112,7 @@ type linuxRouter struct {
|
||||
v6Available bool
|
||||
v6NATAvailable bool
|
||||
fwmaskWorks bool // whether we can use 'ip rule...fwmark <mark>/<mask>'
|
||||
hasV4Prefix bool // false when 'DisableIPv4' is set on the tailnet
|
||||
|
||||
// ipPolicyPrefBase is the base priority at which ip rules are installed.
|
||||
ipPolicyPrefBase int
|
||||
@@ -316,7 +316,7 @@ func useAmbientCaps() bool {
|
||||
return distro.DSMVersion() >= 7
|
||||
}
|
||||
|
||||
var forceIPCommand = envknob.Bool("TS_DEBUG_USE_IP_COMMAND")
|
||||
var forceIPCommand = envknob.RegisterBool("TS_DEBUG_USE_IP_COMMAND")
|
||||
|
||||
// useIPCommand reports whether r should use the "ip" command (or its
|
||||
// fake commandRunner for tests) instead of netlink.
|
||||
@@ -324,7 +324,7 @@ func (r *linuxRouter) useIPCommand() bool {
|
||||
if r.cmd == nil {
|
||||
panic("invalid init")
|
||||
}
|
||||
if forceIPCommand {
|
||||
if forceIPCommand() {
|
||||
return true
|
||||
}
|
||||
// In the future we might need to fall back to using the "ip"
|
||||
@@ -377,7 +377,7 @@ func (r *linuxRouter) Up() error {
|
||||
if err := r.addIPRules(); err != nil {
|
||||
return fmt.Errorf("adding IP rules: %w", err)
|
||||
}
|
||||
if err := r.setNetfilterMode(netfilterOff); err != nil {
|
||||
if err := r.setNetfilterMode(netfilterOff, true); err != nil {
|
||||
return fmt.Errorf("setting netfilter mode: %w", err)
|
||||
}
|
||||
if err := r.upInterface(); err != nil {
|
||||
@@ -398,7 +398,7 @@ func (r *linuxRouter) Close() error {
|
||||
if err := r.delIPRules(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.setNetfilterMode(netfilterOff); err != nil {
|
||||
if err := r.setNetfilterMode(netfilterOff, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.delRoutes(); err != nil {
|
||||
@@ -419,7 +419,38 @@ func (r *linuxRouter) Set(cfg *Config) error {
|
||||
cfg = &shutdownConfig
|
||||
}
|
||||
|
||||
if err := r.setNetfilterMode(cfg.NetfilterMode); err != nil {
|
||||
// Because the tailnet may have IPv4 disabled, check if we have any v4
|
||||
// prefixes from addresses, routes, or local routes.
|
||||
var foundV4 bool
|
||||
findV4 := func(arr []netip.Prefix) {
|
||||
// Skip useless loop if we've already found a v4 prefix
|
||||
if foundV4 {
|
||||
return
|
||||
}
|
||||
for _, pref := range arr {
|
||||
if pref.Addr().Is4() {
|
||||
foundV4 = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
findV4(cfg.LocalAddrs)
|
||||
findV4(cfg.Routes)
|
||||
findV4(cfg.LocalRoutes)
|
||||
findV4(cfg.SubnetRoutes)
|
||||
|
||||
if !foundV4 && !r.v6Available {
|
||||
return fmt.Errorf("No IPv4 routes and IPv6 isn't available")
|
||||
}
|
||||
|
||||
// Update our "have IPv4" setting after we're done with this function;
|
||||
// we can't do this earlier because both 'setNetfilterMode' and the
|
||||
// SNAT rules need to know what the previous value was.
|
||||
defer func() {
|
||||
r.hasV4Prefix = foundV4
|
||||
}()
|
||||
|
||||
if err := r.setNetfilterMode(cfg.NetfilterMode, foundV4); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
@@ -445,7 +476,7 @@ func (r *linuxRouter) Set(cfg *Config) error {
|
||||
case cfg.SNATSubnetRoutes == r.snatSubnetRoutes:
|
||||
// state already correct, nothing to do.
|
||||
case cfg.SNATSubnetRoutes:
|
||||
if err := r.addSNATRule(); err != nil {
|
||||
if err := r.addSNATRule(foundV4); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
default:
|
||||
@@ -462,14 +493,27 @@ func (r *linuxRouter) Set(cfg *Config) error {
|
||||
// mode. Netfilter state is created or deleted appropriately to
|
||||
// reflect the new mode, and r.snatSubnetRoutes is updated to reflect
|
||||
// the current state of subnet SNATing.
|
||||
func (r *linuxRouter) setNetfilterMode(mode preftype.NetfilterMode) error {
|
||||
func (r *linuxRouter) setNetfilterMode(mode preftype.NetfilterMode, foundV4 bool) error {
|
||||
if distro.Get() == distro.Synology {
|
||||
mode = netfilterOff
|
||||
}
|
||||
if r.netfilterMode == mode {
|
||||
if r.netfilterMode == mode && r.hasV4Prefix == foundV4 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// In the (unlikely) case where we change whether or not we install
|
||||
// IPv4 rules, recreate everything rather than complicating our diff
|
||||
// logic. This should only happen if the user changes the tailnet-wide
|
||||
// "DisableIPv4" setting.
|
||||
if r.hasV4Prefix != foundV4 {
|
||||
// Turn off netfilter (recursively) to force the code below to
|
||||
// recreate things in the "correct" mode.
|
||||
// TODO(andrew): this is a bit janky; think about this before merging
|
||||
if err := r.setNetfilterMode(netfilterOff, r.hasV4Prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on the netfilter mode we switch from and to, we may
|
||||
// have created the Tailscale netfilter chains. If so, we have to
|
||||
// go back through existing router state, and add the netfilter
|
||||
@@ -1479,14 +1523,16 @@ func (r *linuxRouter) delNetfilterHooks() error {
|
||||
|
||||
// addSNATRule adds a netfilter rule to SNAT traffic destined for
|
||||
// local subnets.
|
||||
func (r *linuxRouter) addSNATRule() error {
|
||||
func (r *linuxRouter) addSNATRule(foundV4 bool) error {
|
||||
if r.netfilterMode == netfilterOff {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark + "/" + tailscaleFwmarkMask, "-j", "MASQUERADE"}
|
||||
if err := r.ipt4.Append("nat", "ts-postrouting", args...); err != nil {
|
||||
return fmt.Errorf("adding %v in v4/nat/ts-postrouting: %w", args, err)
|
||||
if foundV4 {
|
||||
if err := r.ipt4.Append("nat", "ts-postrouting", args...); err != nil {
|
||||
return fmt.Errorf("adding %v in v4/nat/ts-postrouting: %w", args, err)
|
||||
}
|
||||
}
|
||||
if r.v6NATAvailable {
|
||||
if err := r.ipt6.Append("nat", "ts-postrouting", args...); err != nil {
|
||||
@@ -1504,8 +1550,10 @@ func (r *linuxRouter) delSNATRule() error {
|
||||
}
|
||||
|
||||
args := []string{"-m", "mark", "--mark", tailscaleSubnetRouteMark + "/" + tailscaleFwmarkMask, "-j", "MASQUERADE"}
|
||||
if err := r.ipt4.Delete("nat", "ts-postrouting", args...); err != nil {
|
||||
return fmt.Errorf("deleting %v in v4/nat/ts-postrouting: %w", args, err)
|
||||
if r.hasV4Prefix {
|
||||
if err := r.ipt4.Delete("nat", "ts-postrouting", args...); err != nil {
|
||||
return fmt.Errorf("deleting %v in v4/nat/ts-postrouting: %w", args, err)
|
||||
}
|
||||
}
|
||||
if r.v6NATAvailable {
|
||||
if err := r.ipt6.Delete("nat", "ts-postrouting", args...); err != nil {
|
||||
@@ -1601,7 +1649,7 @@ func checkIPv6(logf logger.Logf) error {
|
||||
if os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
bs, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6")
|
||||
bs, err := os.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6")
|
||||
if err != nil {
|
||||
// Be conservative if we can't find the ipv6 configuration knob.
|
||||
return err
|
||||
@@ -1617,7 +1665,7 @@ func checkIPv6(logf logger.Logf) error {
|
||||
// Older kernels don't support IPv6 policy routing. Some kernels
|
||||
// support policy routing but don't have this knob, so absence of
|
||||
// the knob is not fatal.
|
||||
bs, err = ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
|
||||
bs, err = os.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
|
||||
if err == nil {
|
||||
disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs)))
|
||||
if err != nil {
|
||||
@@ -1647,7 +1695,7 @@ func checkIPv6(logf logger.Logf) error {
|
||||
// netfilter, so some older distros ship a kernel that can't NAT IPv6
|
||||
// traffic.
|
||||
func supportsV6NAT() bool {
|
||||
bs, err := ioutil.ReadFile("/proc/net/ip6_tables_names")
|
||||
bs, err := os.ReadFile("/proc/net/ip6_tables_names")
|
||||
if err != nil {
|
||||
// Can't read the file. Assume SNAT works.
|
||||
return true
|
||||
|
||||
@@ -44,7 +44,6 @@ import (
|
||||
"tailscale.com/util/clientmetric"
|
||||
"tailscale.com/util/deephash"
|
||||
"tailscale.com/util/mak"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/magicsock"
|
||||
"tailscale.com/wgengine/monitor"
|
||||
@@ -535,29 +534,28 @@ func (e *userspaceEngine) pollResolver() {
|
||||
}
|
||||
}
|
||||
|
||||
var debugTrimWireguard = envknob.OptBool("TS_DEBUG_TRIM_WIREGUARD")
|
||||
var debugTrimWireguard = envknob.RegisterOptBool("TS_DEBUG_TRIM_WIREGUARD")
|
||||
|
||||
// forceFullWireguardConfig reports whether we should give wireguard
|
||||
// our full network map, even for inactive peers
|
||||
// forceFullWireguardConfig reports whether we should give wireguard our full
|
||||
// network map, even for inactive peers.
|
||||
//
|
||||
// TODO(bradfitz): remove this after our 1.0 launch; we don't want to
|
||||
// enable wireguard config trimming quite yet because it just landed
|
||||
// and we haven't got enough time testing it.
|
||||
// TODO(bradfitz): remove this at some point. We had a TODO to do it before 1.0
|
||||
// but it's still there as of 1.30. Really we should not do this wireguard lazy
|
||||
// peer config at all and just fix wireguard-go to not have so much extra memory
|
||||
// usage per peer. That would simplify a lot of Tailscale code. OTOH, we have 50
|
||||
// MB of memory on iOS now instead of 15 MB, so the other option is to just give
|
||||
// up on lazy wireguard config and blow the memory and hope for the best on iOS.
|
||||
// That's sad too. Or we get rid of these knobs (lazy wireguard config has been
|
||||
// stable!) but I'm worried that a future regression would be easier to debug
|
||||
// with these knobs in place.
|
||||
func forceFullWireguardConfig(numPeers int) bool {
|
||||
// Did the user explicitly enable trimmming via the environment variable knob?
|
||||
if b, ok := debugTrimWireguard.Get(); ok {
|
||||
if b, ok := debugTrimWireguard().Get(); ok {
|
||||
return !b
|
||||
}
|
||||
if opt := controlclient.TrimWGConfig(); opt != "" {
|
||||
return !opt.EqualBool(true)
|
||||
}
|
||||
|
||||
// On iOS with large networks, it's critical, so turn on trimming.
|
||||
// Otherwise we run out of memory from wireguard-go goroutine stacks+buffers.
|
||||
// This will be the default later for all platforms and network sizes.
|
||||
if numPeers > 50 && version.OS() == "iOS" {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user