Compare commits

..

1 Commits

Author SHA1 Message Date
Brad Fitzpatrick
e6dbb4425c cmd/cloner, tailcfg: fix nil vs len 0 issues, add tests, use for Hostinfo
Also use go:generate and https://golang.org/s/generatedcode header style.
2020-07-27 10:41:06 -07:00
139 changed files with 2025 additions and 8006 deletions

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
go-version: 1.14
id: go
- name: Check out code into the Go module directory

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
go-version: 1.14
id: go
- name: Check out code into the Go module directory

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
go-version: 1.14
id: go
- name: Check out code into the Go module directory

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
go-version: 1.14
id: go
- name: Check out code into the Go module directory

View File

@@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
go-version: 1.14
- name: Check out code
uses: actions/checkout@v1

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
go-version: 1.14
id: go
- name: Check out code into the Go module directory

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
go-version: 1.14
id: go
- name: Check out code into the Go module directory

View File

@@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
go-version: 1.14
- name: Check out code
uses: actions/checkout@v1

View File

@@ -1,10 +1,7 @@
usage:
echo "See Makefile"
vet:
go vet ./...
check: staticcheck vet
check: staticcheck
staticcheck:
go run honnef.co/go/tools/cmd/staticcheck -- $$(go list ./... | grep -v tempfork)

View File

@@ -6,24 +6,17 @@ Private WireGuard® networks made easy
## Overview
This repository contains all the open source Tailscale client code and
the `tailscaled` daemon and `tailscale` CLI tool. The `tailscaled`
daemon runs primarily on Linux; it also works to varying degrees on
FreeBSD, OpenBSD, Darwin, and Windows.
This repository contains all the open source Tailscale code.
It currently includes the Linux client.
The Android app is at https://github.com/tailscale/tailscale-android
The Linux client is currently `cmd/relaynode`, but will
soon be replaced by `cmd/tailscaled`.
## Using
We serve packages for a variety of distros at
https://pkgs.tailscale.com .
## Other clients
The [macOS, iOS, and Windows clients](https://tailscale.com/download)
use the code in this repository but additionally include small GUI
wrappers that are not open source.
## Building
```
@@ -31,7 +24,7 @@ go install tailscale.com/cmd/tailscale{,d}
```
We only guarantee to support the latest Go release and any Go beta or
release candidate builds (currently Go 1.15) in module mode. It might
release candidate builds (currently Go 1.14) in module mode. It might
work in earlier Go versions or in GOPATH mode, but we're making no
effort to keep those working.
@@ -42,8 +35,10 @@ Please file any issues about this code or the hosted service on
## Contributing
PRs welcome! But please file bugs. Commit messages should [reference
bugs](https://docs.github.com/en/github/writing-on-github/autolinked-references-and-urls).
`under_construction.gif`
PRs welcome, but we are still working out our contribution process and
tooling.
We require [Developer Certificate of
Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin)
@@ -51,7 +46,7 @@ Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin)
## About Us
We are apenwarr, bradfitz, crawshaw, danderson, dfcarney, josharian
We are apenwarr, bradfitz, crawshaw, danderson, dfcarney,
from Tailscale Inc.
You can learn more about us from [our website](https://tailscale.com).

View File

@@ -7,7 +7,6 @@ package main // import "tailscale.com/cmd/derper"
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"expvar"
@@ -34,7 +33,6 @@ import (
"tailscale.com/net/stun"
"tailscale.com/tsweb"
"tailscale.com/types/key"
"tailscale.com/version"
)
var (
@@ -186,15 +184,6 @@ func main() {
certManager.Email = "security@tailscale.com"
}
httpsrv.TLSConfig = certManager.TLSConfig()
letsEncryptGetCert := httpsrv.TLSConfig.GetCertificate
httpsrv.TLSConfig.GetCertificate = func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := letsEncryptGetCert(hi)
if err != nil {
return nil, err
}
cert.Certificate = append(cert.Certificate, s.MetaCert())
return cert, nil
}
go func() {
err := http.ListenAndServe(":80", certManager.HTTPHandler(tsweb.Port80Handler{Main: mux}))
if err != nil {
@@ -232,7 +221,6 @@ func debugHandler(s *derp.Server) http.Handler {
f("<li><b>Hostname:</b> %v</li>\n", *hostname)
f("<li><b>Uptime:</b> %v</li>\n", tsweb.Uptime())
f("<li><b>Mesh Key:</b> %v</li>\n", s.HasMeshKey())
f("<li><b>Version:</b> %v</li>\n", version.LONG)
f(`<li><a href="/debug/vars">/debug/vars</a> (Go)</li>
<li><a href="/debug/varz">/debug/varz</a> (Prometheus)</li>

View File

@@ -19,7 +19,6 @@ import (
"net/http/httputil"
"net/url"
"path/filepath"
"strings"
"sync"
"time"
@@ -89,16 +88,7 @@ func promPrint(w io.Writer, prefix string, obj map[string]interface{}) {
case map[string]interface{}:
promPrint(w, k, v)
case float64:
const saveConfigReject = "control_save_config_rejected_"
const saveConfig = "control_save_config_"
switch {
case strings.HasPrefix(k, saveConfigReject):
fmt.Fprintf(w, "control_save_config_rejected{reason=%q} %f\n", k[len(saveConfigReject):], v)
case strings.HasPrefix(k, saveConfig):
fmt.Fprintf(w, "control_save_config{reason=%q} %f\n", k[len(saveConfig):], v)
default:
fmt.Fprintf(w, "%s %f\n", k, v)
}
fmt.Fprintf(w, "%s %f\n", k, v)
default:
fmt.Fprintf(w, "# Skipping key %q, unhandled type %T\n", k, v)
}

View File

@@ -0,0 +1 @@
# placeholder to work around redo bug

View File

@@ -31,8 +31,7 @@ func ActLikeCLI() bool {
return false
}
switch os.Args[1] {
case "up", "down", "status", "netcheck", "ping", "version",
"debug",
case "up", "status", "netcheck", "version",
"-V", "--version", "-h", "--help":
return true
}
@@ -58,21 +57,14 @@ change in the future.
`),
Subcommands: []*ffcli.Command{
upCmd,
downCmd,
netcheckCmd,
statusCmd,
pingCmd,
versionCmd,
},
FlagSet: rootfs,
Exec: func(context.Context, []string) error { return flag.ErrHelp },
}
// Don't advertise the debug command, but it exists.
if strSliceContains(args, "debug") {
rootCmd.Subcommands = append(rootCmd.Subcommands, debugCmd)
}
if err := rootCmd.Parse(args); err != nil {
return err
}
@@ -84,11 +76,6 @@ change in the future.
return err
}
func fatalf(format string, a ...interface{}) {
log.SetFlags(0)
log.Fatalf(format, a...)
}
var rootArgs struct {
socket string
}
@@ -97,9 +84,9 @@ func connect(ctx context.Context) (net.Conn, *ipn.BackendClient, context.Context
c, err := safesocket.Connect(rootArgs.socket, 41112)
if err != nil {
if runtime.GOOS != "windows" && rootArgs.socket == "" {
fatalf("--socket cannot be empty")
log.Fatalf("--socket cannot be empty")
}
fatalf("Failed to connect to connect to tailscaled. (safesocket.Connect: %v)\n", err)
log.Fatalf("Failed to connect to connect to tailscaled. (safesocket.Connect: %v)\n", err)
}
clientToServer := func(b []byte) {
ipn.WriteMsg(c, b)
@@ -134,12 +121,3 @@ func pump(ctx context.Context, bc *ipn.BackendClient, conn net.Conn) {
bc.GotNotifyMsg(msg)
}
}
func strSliceContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}

View File

@@ -1,175 +0,0 @@
// Copyright (c) 2020 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 cli
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"net/http"
"net/http/httptrace"
"net/url"
"os"
"time"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/derp/derphttp"
"tailscale.com/derp/derpmap"
"tailscale.com/net/interfaces"
"tailscale.com/net/tshttpproxy"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/wgengine/monitor"
)
var debugCmd = &ffcli.Command{
Name: "debug",
Exec: runDebug,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("debug", flag.ExitOnError)
fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run link monitor forever. Precludes all other options.")
fs.StringVar(&debugArgs.getURL, "get-url", "", "If non-empty, fetch provided URL.")
fs.StringVar(&debugArgs.derpCheck, "derp", "", "if non-empty, test a DERP ping via named region code")
return fs
})(),
}
var debugArgs struct {
monitor bool
getURL string
derpCheck string
}
func runDebug(ctx context.Context, args []string) error {
if len(args) > 0 {
return errors.New("unknown arguments")
}
if debugArgs.derpCheck != "" {
return checkDerp(ctx, debugArgs.derpCheck)
}
if debugArgs.monitor {
return runMonitor(ctx)
}
if debugArgs.getURL != "" {
return getURL(ctx, debugArgs.getURL)
}
return errors.New("only --monitor is available at the moment")
}
func runMonitor(ctx context.Context) error {
dump := func() {
st, err := interfaces.GetState()
if err != nil {
log.Printf("error getting state: %v", err)
return
}
j, _ := json.MarshalIndent(st, "", " ")
os.Stderr.Write(j)
}
mon, err := monitor.New(log.Printf, func() {
log.Printf("Link monitor fired. State:")
dump()
})
if err != nil {
return err
}
log.Printf("Starting link change monitor; initial state:")
dump()
mon.Start()
log.Printf("Started link change monitor; waiting...")
select {}
}
func getURL(ctx context.Context, urlStr string) error {
if urlStr == "login" {
urlStr = "https://login.tailscale.com"
}
log.SetOutput(os.Stdout)
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
GetConn: func(hostPort string) { log.Printf("GetConn(%q)", hostPort) },
GotConn: func(info httptrace.GotConnInfo) { log.Printf("GotConn: %+v", info) },
DNSStart: func(info httptrace.DNSStartInfo) { log.Printf("DNSStart: %+v", info) },
DNSDone: func(info httptrace.DNSDoneInfo) { log.Printf("DNSDoneInfo: %+v", info) },
TLSHandshakeStart: func() { log.Printf("TLSHandshakeStart") },
TLSHandshakeDone: func(cs tls.ConnectionState, err error) { log.Printf("TLSHandshakeDone: %+v, %v", cs, err) },
WroteRequest: func(info httptrace.WroteRequestInfo) { log.Printf("WroteRequest: %+v", info) },
})
req, err := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
if err != nil {
return fmt.Errorf("http.NewRequestWithContext: %v", err)
}
proxyURL, err := tshttpproxy.ProxyFromEnvironment(req)
if err != nil {
return fmt.Errorf("tshttpproxy.ProxyFromEnvironment: %v", err)
}
log.Printf("proxy: %v", proxyURL)
tr := &http.Transport{
Proxy: func(*http.Request) (*url.URL, error) { return proxyURL, nil },
ProxyConnectHeader: http.Header{},
DisableKeepAlives: true,
}
if proxyURL != nil {
auth, err := tshttpproxy.GetAuthHeader(proxyURL)
if err == nil && auth != "" {
tr.ProxyConnectHeader.Set("Proxy-Authorization", auth)
}
const truncLen = 20
if len(auth) > truncLen {
auth = fmt.Sprintf("%s...(%d total bytes)", auth[:truncLen], len(auth))
}
log.Printf("tshttpproxy.GetAuthHeader(%v) for Proxy-Auth: = %q, %v", proxyURL, auth, err)
}
res, err := tr.RoundTrip(req)
if err != nil {
return fmt.Errorf("Transport.RoundTrip: %v", err)
}
defer res.Body.Close()
return res.Write(os.Stdout)
}
func checkDerp(ctx context.Context, derpRegion string) error {
dmap := derpmap.Prod()
getRegion := func() *tailcfg.DERPRegion {
for _, r := range dmap.Regions {
if r.RegionCode == derpRegion {
return r
}
}
for _, r := range dmap.Regions {
log.Printf("Known region: %q", r.RegionCode)
}
log.Fatalf("unknown region %q", derpRegion)
panic("unreachable")
}
priv1 := key.NewPrivate()
priv2 := key.NewPrivate()
c1 := derphttp.NewRegionClient(priv1, log.Printf, getRegion)
c2 := derphttp.NewRegionClient(priv2, log.Printf, getRegion)
c2.NotePreferred(true) // just to open it
m, err := c2.Recv()
log.Printf("c2 got %T, %v", m, err)
t0 := time.Now()
if err := c1.Send(priv2.Public(), []byte("hello")); err != nil {
return err
}
fmt.Println(time.Since(t0))
m, err = c2.Recv()
log.Printf("c2 got %T, %v", m, err)
if err != nil {
return err
}
log.Printf("ok")
return err
}

View File

@@ -1,67 +0,0 @@
// Copyright (c) 2020 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 cli
import (
"context"
"log"
"time"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/ipn"
)
var downCmd = &ffcli.Command{
Name: "down",
ShortUsage: "down",
ShortHelp: "Disconnect from Tailscale",
Exec: runDown,
}
func runDown(ctx context.Context, args []string) error {
if len(args) > 0 {
log.Fatalf("too many non-flag arguments: %q", args)
}
c, bc, ctx, cancel := connect(ctx)
defer cancel()
timer := time.AfterFunc(5*time.Second, func() {
log.Fatalf("timeout running stop")
})
defer timer.Stop()
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
}
if n.Status != nil {
cur := n.Status.BackendState
switch cur {
case "Stopped":
log.Printf("already stopped")
cancel()
default:
log.Printf("was in state %q", cur)
}
return
}
if n.State != nil {
log.Printf("now in state %q", *n.State)
if *n.State == ipn.Stopped {
cancel()
}
return
}
log.Printf("Notify: %#v", n)
})
bc.RequestStatus()
bc.SetWantRunning(false)
pump(ctx, bc, c)
return nil
}

View File

@@ -125,35 +125,20 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
if len(report.RegionLatency) == 0 {
fmt.Printf("\t* Nearest DERP: unknown (no response to latency probes)\n")
} else {
fmt.Printf("\t* Nearest DERP: %v\n", dm.Regions[report.PreferredDERP].RegionName)
fmt.Printf("\t* Nearest DERP: %v (%v)\n", report.PreferredDERP, dm.Regions[report.PreferredDERP].RegionCode)
fmt.Printf("\t* DERP latency:\n")
var rids []int
for rid := range dm.Regions {
rids = append(rids, rid)
}
sort.Slice(rids, func(i, j int) bool {
l1, ok1 := report.RegionLatency[rids[i]]
l2, ok2 := report.RegionLatency[rids[j]]
if ok1 != ok2 {
return ok1 // defined things sort first
}
if !ok1 {
return rids[i] < rids[j]
}
return l1 < l2
})
sort.Ints(rids)
for _, rid := range rids {
d, ok := report.RegionLatency[rid]
var latency string
if ok {
latency = d.Round(time.Millisecond / 10).String()
}
r := dm.Regions[rid]
var derpNum string
if netcheckArgs.verbose {
derpNum = fmt.Sprintf("derp%d, ", rid)
}
fmt.Printf("\t\t- %3s: %-7s (%s%s)\n", r.RegionCode, latency, derpNum, r.RegionName)
fmt.Printf("\t\t- %v, %3s = %s\n", rid, dm.Regions[rid].RegionCode, latency)
}
}
return nil

View File

@@ -1,130 +0,0 @@
// Copyright (c) 2020 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 cli
import (
"context"
"errors"
"flag"
"fmt"
"log"
"net"
"strings"
"time"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
)
var pingCmd = &ffcli.Command{
Name: "ping",
ShortUsage: "ping <hostname-or-IP>",
ShortHelp: "Ping a host at the Tailscale layer, see how it routed",
LongHelp: strings.TrimSpace(`
The 'tailscale ping' command pings a peer node at the Tailscale layer
and reports which route it took for each response. The first ping or
so will likely go over DERP (Tailscale's TCP relay protocol) while NAT
traversal finds a direct path through.
If 'tailscale ping' works but a normal ping does not, that means one
side's operating system firewall is blocking packets; 'tailscale ping'
does not inject packets into either side's TUN devices.
By default, 'tailscale ping' stops after 10 pings or once a direct
(non-DERP) path has been established, whichever comes first.
The provided hostname must resolve to or be a Tailscale IP
(e.g. 100.x.y.z) or a subnet IP advertised by a Tailscale
relay node.
`),
Exec: runPing,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("ping", flag.ExitOnError)
fs.BoolVar(&pingArgs.verbose, "verbose", false, "verbose output")
fs.BoolVar(&pingArgs.untilDirect, "until-direct", true, "stop once a direct path is established")
fs.IntVar(&pingArgs.num, "c", 10, "max number of pings to send")
fs.DurationVar(&pingArgs.timeout, "timeout", 5*time.Second, "timeout before giving up on a ping")
return fs
})(),
}
var pingArgs struct {
num int
untilDirect bool
verbose bool
timeout time.Duration
}
func runPing(ctx context.Context, args []string) error {
c, bc, ctx, cancel := connect(ctx)
defer cancel()
if len(args) != 1 {
return errors.New("usage: ping <hostname-or-IP>")
}
hostOrIP := args[0]
var ip string
var res net.Resolver
if addrs, err := res.LookupHost(ctx, hostOrIP); err != nil {
return fmt.Errorf("error looking up IP of %q: %v", hostOrIP, err)
} else if len(addrs) == 0 {
return fmt.Errorf("no IPs found for %q", hostOrIP)
} else {
ip = addrs[0]
}
if pingArgs.verbose && ip != hostOrIP {
log.Printf("lookup %q => %q", hostOrIP, ip)
}
ch := make(chan *ipnstate.PingResult, 1)
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
}
if pr := n.PingResult; pr != nil && pr.IP == ip {
ch <- pr
}
})
go pump(ctx, bc, c)
n := 0
anyPong := false
for {
n++
bc.Ping(ip)
timer := time.NewTimer(pingArgs.timeout)
select {
case <-timer.C:
fmt.Printf("timeout waiting for ping reply\n")
case pr := <-ch:
timer.Stop()
if pr.Err != "" {
return errors.New(pr.Err)
}
latency := time.Duration(pr.LatencySeconds * float64(time.Second)).Round(time.Millisecond)
via := pr.Endpoint
if pr.DERPRegionID != 0 {
via = fmt.Sprintf("DERP(%s)", pr.DERPRegionCode)
}
anyPong = true
fmt.Printf("pong from %s (%s) via %v in %v\n", pr.NodeName, pr.NodeIP, via, latency)
if pr.Endpoint != "" && pingArgs.untilDirect {
return nil
}
time.Sleep(time.Second)
case <-ctx.Done():
return ctx.Err()
}
if n == pingArgs.num {
if !anyPong {
return errors.New("no reply")
}
return nil
}
}
}

View File

@@ -33,7 +33,6 @@ var statusCmd = &ffcli.Command{
fs.BoolVar(&statusArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
fs.BoolVar(&statusArgs.web, "web", false, "run webserver with HTML showing status")
fs.BoolVar(&statusArgs.active, "active", false, "filter output to only peers with active sessions (not applicable to web mode)")
fs.BoolVar(&statusArgs.self, "self", true, "show status of local machine")
fs.StringVar(&statusArgs.listen, "listen", "127.0.0.1:8384", "listen address; use port 0 for automatic")
fs.BoolVar(&statusArgs.browser, "browser", true, "Open a browser in web mode")
return fs
@@ -46,7 +45,6 @@ var statusArgs struct {
listen string // in web mode, webserver address to listen on, empty means auto
browser bool // in web mode, whether to open browser
active bool // in CLI mode, filter output to only peers with active sessions
self bool // in CLI mode, show status of local machine
}
func runStatus(ctx context.Context, args []string) error {
@@ -127,17 +125,16 @@ func runStatus(ctx context.Context, args []string) error {
return err
}
if st.BackendState == ipn.Stopped.String() {
fmt.Println("Tailscale is stopped.")
os.Exit(1)
}
var buf bytes.Buffer
f := func(format string, a ...interface{}) { fmt.Fprintf(&buf, format, a...) }
printPS := func(ps *ipnstate.PeerStatus) {
for _, peer := range st.Peers() {
ps := st.Peer[peer]
active := peerActive(ps)
if statusArgs.active && !active {
continue
}
f("%s %-7s %-15s %-18s tx=%8d rx=%8d ",
ps.PublicKey.ShortString(),
peer.ShortString(),
ps.OS,
ps.TailAddr,
ps.SimpleHostName(),
@@ -163,18 +160,6 @@ func runStatus(ctx context.Context, args []string) error {
}
f("\n")
}
if statusArgs.self && st.Self != nil {
printPS(st.Self)
}
for _, peer := range st.Peers() {
ps := st.Peer[peer]
active := peerActive(ps)
if statusArgs.active && !active {
continue
}
printPS(ps)
}
os.Stdout.Write(buf.Bytes())
return nil
}

View File

@@ -15,14 +15,11 @@ import (
"runtime"
"strconv"
"strings"
"sync"
"github.com/peterbourgon/ff/v2/ffcli"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/ipn"
"tailscale.com/tailcfg"
"tailscale.com/version"
"tailscale.com/wgengine/router"
)
@@ -54,12 +51,11 @@ specify any flags, options are reset to their default.
upf.BoolVar(&upArgs.acceptDNS, "accept-dns", true, "accept DNS configuration from the admin panel")
upf.BoolVar(&upArgs.singleRoutes, "host-routes", true, "install host routes to other Tailscale nodes")
upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
upf.BoolVar(&upArgs.forceReauth, "force-reauth", false, "force reauthentication")
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "ACL tags to request (comma-separated, e.g. eng,montreal,ssh)")
upf.StringVar(&upArgs.authKey, "authkey", "", "node authorization key")
upf.StringVar(&upArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
upf.BoolVar(&upArgs.enableDERP, "enable-derp", true, "enable the use of DERP servers")
if runtime.GOOS == "linux" || isBSD(runtime.GOOS) || version.OS() == "macOS" {
if runtime.GOOS == "linux" || isBSD(runtime.GOOS) {
upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. 10.0.0.0/8,192.168.0.0/24)")
}
if runtime.GOOS == "linux" {
@@ -77,7 +73,6 @@ var upArgs struct {
acceptDNS bool
singleRoutes bool
shieldsUp bool
forceReauth bool
advertiseRoutes string
advertiseTags string
enableDERP bool
@@ -114,18 +109,18 @@ func isBSD(s string) bool {
return s == "dragonfly" || s == "freebsd" || s == "netbsd" || s == "openbsd"
}
func warnf(format string, args ...interface{}) {
func warning(format string, args ...interface{}) {
fmt.Printf("Warning: "+format+"\n", args...)
}
// checkIPForwarding prints warnings if IP forwarding is not
// checkIPForwarding prints warnings on linux if IP forwarding is not
// enabled, or if we were unable to verify the state of IP forwarding.
func checkIPForwarding() {
var key string
if runtime.GOOS == "linux" {
key = "net.ipv4.ip_forward"
} else if isBSD(runtime.GOOS) || version.OS() == "macOS" {
} else if isBSD(runtime.GOOS) {
key = "net.inet.ip.forwarding"
} else {
return
@@ -133,16 +128,16 @@ func checkIPForwarding() {
bs, err := exec.Command("sysctl", "-n", key).Output()
if err != nil {
warnf("couldn't check %s (%v).\nSubnet routes won't work without IP forwarding.", key, err)
warning("couldn't check %s (%v).\nSubnet routes won't work without IP forwarding.", key, err)
return
}
on, err := strconv.ParseBool(string(bytes.TrimSpace(bs)))
if err != nil {
warnf("couldn't parse %s (%v).\nSubnet routes won't work without IP forwarding.", key, err)
warning("couldn't parse %s (%v).\nSubnet routes won't work without IP forwarding.", key, err)
return
}
if !on {
warnf("%s is disabled. Subnet routes won't work.", key)
warning("%s is disabled. Subnet routes won't work.", key)
}
}
@@ -153,19 +148,15 @@ func runUp(ctx context.Context, args []string) error {
var routes []wgcfg.CIDR
if upArgs.advertiseRoutes != "" {
checkIPForwarding()
advroutes := strings.Split(upArgs.advertiseRoutes, ",")
for _, s := range advroutes {
cidr, ok := parseIPOrCIDR(s)
ipp, err := netaddr.ParseIPPrefix(s) // parse it with other pawith both packages
if !ok || err != nil {
fatalf("%q is not a valid IP address or CIDR prefix", s)
}
if ipp != ipp.Masked() {
fatalf("%s has non-address bits set; expected %s", ipp, ipp.Masked())
if !ok {
log.Fatalf("%q is not a valid IP address or CIDR prefix", s)
}
routes = append(routes, cidr)
}
checkIPForwarding()
}
var tags []string
@@ -174,16 +165,17 @@ func runUp(ctx context.Context, args []string) error {
for _, tag := range tags {
err := tailcfg.CheckTag(tag)
if err != nil {
fatalf("tag: %q: %s", tag, err)
log.Fatalf("tag: %q: %s", tag, err)
}
}
}
if len(upArgs.hostname) > 256 {
fatalf("hostname too long: %d bytes (max 256)", len(upArgs.hostname))
log.Fatalf("hostname too long: %d bytes (max 256)", len(upArgs.hostname))
}
// TODO(apenwarr): fix different semantics between prefs and uflags
// TODO(apenwarr): allow setting/using CorpDNS
prefs := ipn.NewPrefs()
prefs.ControlURL = upArgs.server
prefs.WantRunning = true
@@ -202,12 +194,12 @@ func runUp(ctx context.Context, args []string) error {
prefs.NetfilterMode = router.NetfilterOn
case "nodivert":
prefs.NetfilterMode = router.NetfilterNoDivert
warnf("netfilter=nodivert; add iptables calls to ts-* chains manually.")
warning("netfilter=nodivert; add iptables calls to ts-* chains manually.")
case "off":
prefs.NetfilterMode = router.NetfilterOff
warnf("netfilter=off; configure iptables yourself.")
warning("netfilter=off; configure iptables yourself.")
default:
fatalf("invalid value --netfilter-mode: %q", upArgs.netfilterMode)
log.Fatalf("invalid value --netfilter-mode: %q", upArgs.netfilterMode)
}
}
@@ -215,8 +207,6 @@ func runUp(ctx context.Context, args []string) error {
defer cancel()
var printed bool
var loginOnce sync.Once
startLoginInteractive := func() { loginOnce.Do(func() { bc.StartLoginInteractive() }) }
bc.SetPrefs(prefs)
opts := ipn.Options{
@@ -224,13 +214,13 @@ func runUp(ctx context.Context, args []string) error {
AuthKey: upArgs.authKey,
Notify: func(n ipn.Notify) {
if n.ErrMessage != nil {
fatalf("backend error: %v\n", *n.ErrMessage)
log.Fatalf("backend error: %v\n", *n.ErrMessage)
}
if s := n.State; s != nil {
switch *s {
case ipn.NeedsLogin:
printed = true
startLoginInteractive()
bc.StartLoginInteractive()
case ipn.NeedsMachineAuth:
printed = true
fmt.Fprintf(os.Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s/admin/machines\n\n", upArgs.server)
@@ -256,10 +246,6 @@ func runUp(ctx context.Context, args []string) error {
// ephemeral frontends that read/modify/write state, once
// Windows/Mac state is moved into backend.
bc.Start(opts)
if upArgs.forceReauth {
printed = true
startLoginInteractive()
}
pump(ctx, bc, c)
return nil

View File

@@ -149,16 +149,11 @@ func run() error {
ctx, cancel := context.WithCancel(context.Background())
// Exit gracefully by cancelling the ipnserver context in most common cases:
// interrupted from the TTY or killed by a service manager.
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
// SIGPIPE sometimes gets generated when CLIs disconnect from
// tailscaled. The default action is to terminate the process, we
// want to keep running.
signal.Ignore(syscall.SIGPIPE)
go func() {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
select {
case s := <-interrupt:
logf("tailscaled got signal %v; shutting down", s)
case <-interrupt:
cancel()
case <-ctx.Done():
// continue
@@ -174,7 +169,7 @@ func run() error {
SurviveDisconnects: true,
DebugMux: debugMux,
}
err = ipnserver.Run(ctx, logf, pol.PublicID.String(), ipnserver.FixedEngine(e), opts)
err = ipnserver.Run(ctx, logf, pol.PublicID.String(), opts, e)
// Cancelation is not an error: it is the only way to stop ipnserver.
if err != nil && err != context.Canceled {
logf("ipnserver.Run: %v", err)

View File

@@ -3,6 +3,8 @@ Description=Tailscale node agent
Documentation=https://tailscale.com/kb/
Wants=network-pre.target
After=network-pre.target
StartLimitIntervalSec=0
StartLimitBurst=0
[Service]
EnvironmentFile=/etc/default/tailscaled

View File

@@ -117,15 +117,13 @@ type Client struct {
mu sync.Mutex // mutex guards the following fields
statusFunc func(Status) // called to update Client status
paused bool // whether we should stop making HTTP requests
unpauseWaiters []chan struct{}
loggedIn bool // true if currently logged in
loginGoal *LoginGoal // non-nil if some login activity is desired
synced bool // true if our netmap is up-to-date
hostinfo *tailcfg.Hostinfo
inPollNetMap bool // true if currently running a PollNetMap
inSendStatus int // number of sendStatus calls currently in progress
state State
loggedIn bool // true if currently logged in
loginGoal *LoginGoal // non-nil if some login activity is desired
synced bool // true if our netmap is up-to-date
hostinfo *tailcfg.Hostinfo
inPollNetMap bool // true if currently running a PollNetMap
inSendStatus int // number of sendStatus calls currently in progress
state State
authCtx context.Context // context used for auth requests
mapCtx context.Context // context used for netmap requests
@@ -171,27 +169,6 @@ func NewNoStart(opts Options) (*Client, error) {
return c, nil
}
// SetPaused controls whether HTTP activity should be paused.
//
// The client can be paused and unpaused repeatedly, unlike Start and Shutdown, which can only be used once.
func (c *Client) SetPaused(paused bool) {
c.mu.Lock()
defer c.mu.Unlock()
if paused == c.paused {
return
}
c.paused = paused
if paused {
// Just cancel the map routine. The auth routine isn't expensive.
c.cancelMapLocked()
} else {
for _, ch := range c.unpauseWaiters {
close(ch)
}
c.unpauseWaiters = nil
}
}
// Start starts the client's goroutines.
//
// It should only be called for clients created by NewNoStart.
@@ -264,7 +241,7 @@ func (c *Client) cancelMapSafely() {
func (c *Client) authRoutine() {
defer close(c.authDone)
bo := backoff.NewBackoff("authRoutine", c.logf, 30*time.Second)
bo := backoff.NewBackoff("authRoutine", c.logf)
for {
c.mu.Lock()
@@ -295,7 +272,6 @@ func (c *Client) authRoutine() {
if goal == nil {
// Wait for something interesting to happen
var exp <-chan time.Time
var expTimer *time.Timer
if expiry != nil && !expiry.IsZero() {
// if expiry is in the future, don't delay
// past that time.
@@ -308,15 +284,11 @@ func (c *Client) authRoutine() {
if delay > 5*time.Second {
delay = time.Second
}
expTimer = time.NewTimer(delay)
exp = expTimer.C
exp = time.After(delay)
}
}
select {
case <-ctx.Done():
if expTimer != nil {
expTimer.Stop()
}
c.logf("authRoutine: context done.")
case <-exp:
// Unfortunately the key expiry isn't provided
@@ -338,7 +310,7 @@ func (c *Client) authRoutine() {
}
}
} else if !goal.wantLoggedIn {
err := c.direct.TryLogout(ctx)
err := c.direct.TryLogout(c.authCtx)
if err != nil {
report(err, "TryLogout")
bo.BackOff(ctx, err)
@@ -427,35 +399,12 @@ func (c *Client) Direct() *Direct {
return c.direct
}
// unpausedChanLocked returns a new channel that is closed when the
// current Client pause is unpaused.
//
// c.mu must be held
func (c *Client) unpausedChanLocked() <-chan struct{} {
unpaused := make(chan struct{})
c.unpauseWaiters = append(c.unpauseWaiters, unpaused)
return unpaused
}
func (c *Client) mapRoutine() {
defer close(c.mapDone)
bo := backoff.NewBackoff("mapRoutine", c.logf, 30*time.Second)
bo := backoff.NewBackoff("mapRoutine", c.logf)
for {
c.mu.Lock()
if c.paused {
unpaused := c.unpausedChanLocked()
c.mu.Unlock()
c.logf("mapRoutine: awaiting unpause")
select {
case <-unpaused:
c.logf("mapRoutine: unpaused")
case <-c.quit:
c.logf("mapRoutine: quit")
return
}
continue
}
c.logf("mapRoutine: %s", c.state)
loggedIn := c.loggedIn
ctx := c.mapCtx
@@ -538,14 +487,8 @@ func (c *Client) mapRoutine() {
if c.state == StateSynchronized {
c.state = StateAuthenticated
}
paused := c.paused
c.mu.Unlock()
if paused {
c.logf("mapRoutine: paused")
continue
}
if err != nil {
report(err, "PollNetMap")
bo.BackOff(ctx, err)
@@ -574,7 +517,7 @@ func (c *Client) SetHostinfo(hi *tailcfg.Hostinfo) {
panic("nil Hostinfo")
}
if !c.direct.SetHostinfo(hi) {
// No changes. Don't log.
c.logf("[unexpected] duplicate Hostinfo: %v", hi)
return
}
c.logf("Hostinfo: %v", hi)

View File

@@ -70,10 +70,3 @@ func TestStatusEqual(t *testing.T) {
}
}
}
func TestOSVersion(t *testing.T) {
if osVersion == nil {
t.Skip("not available for OS")
}
t.Logf("Got: %#q", osVersion())
}

View File

@@ -4,8 +4,6 @@
package controlclient
//go:generate go run tailscale.com/cmd/cloner -type=Persist -output=direct_clone.go
import (
"bytes"
"context"
@@ -21,25 +19,19 @@ import (
"net/url"
"os"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/crypto/nacl/box"
"golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/log/logheap"
"tailscale.com/net/netns"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
"tailscale.com/version"
)
@@ -148,8 +140,6 @@ func NewDirect(opts Options) (*Direct, error) {
if httpc == nil {
dialer := netns.NewDialer()
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
tr.DialContext = dialer.DialContext
tr.ForceAttemptHTTP2 = true
tr.TLSClientConfig = tlsdial.Config(serverURL.Host, tr.TLSClientConfig)
@@ -175,20 +165,12 @@ func NewDirect(opts Options) (*Direct, error) {
return c, nil
}
var osVersion func() string // non-nil on some platforms
func NewHostinfo() *tailcfg.Hostinfo {
hostname, _ := os.Hostname()
var osv string
if osVersion != nil {
osv = osVersion()
}
return &tailcfg.Hostinfo{
IPNVersion: version.LONG,
Hostname: hostname,
OS: version.OS(),
OSVersion: osv,
GoArch: runtime.GOARCH,
}
}
@@ -495,7 +477,6 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
request := tailcfg.MapRequest{
Version: 4,
IncludeIPv6: true,
DeltaPeers: true,
KeepAlive: c.keepAlive,
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
DiscoKey: c.discoPubKey,
@@ -580,7 +561,6 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
// the same format before just closing the connection.
// We can use this same read loop either way.
var msg []byte
var previousPeers []*tailcfg.Node // for delta-purposes
for i := 0; i < maxPolls || maxPolls < 0; i++ {
vlogf("netmap: starting size read after %v (poll %v)", time.Since(t0).Round(time.Millisecond), i)
var siz [4]byte
@@ -602,36 +582,25 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
vlogf("netmap: decode error: %v")
return err
}
if resp.KeepAlive {
vlogf("netmap: got keep-alive")
} else {
vlogf("netmap: got new map")
}
select {
case timeoutReset <- struct{}{}:
vlogf("netmap: sent timer reset")
case <-ctx.Done():
c.logf("netmap: not resetting timer; context done: %v", ctx.Err())
return ctx.Err()
}
if resp.KeepAlive {
select {
case timeoutReset <- struct{}{}:
vlogf("netmap: sent keep-alive timer reset")
case <-ctx.Done():
c.logf("netmap: not resetting timer for keep-alive due to: %v", ctx.Err())
return ctx.Err()
}
continue
}
undeltaPeers(&resp, previousPeers)
previousPeers = cloneNodes(resp.Peers) // defensive/lazy clone, since this escapes to who knows where
vlogf("netmap: got new map")
if resp.DERPMap != nil {
vlogf("netmap: new map contains DERP map")
lastDERPMap = resp.DERPMap
}
if resp.Debug != nil {
if resp.Debug.LogHeapPprof {
go logheap.LogHeap(resp.Debug.LogHeapURL)
}
setControlAtomic(&controlUseDERPRoute, resp.Debug.DERPRoute)
setControlAtomic(&controlTrimWGConfig, resp.Debug.TrimWGConfig)
if resp.Debug != nil && resp.Debug.LogHeapPprof {
go logheap.LogHeap(resp.Debug.LogHeapURL)
}
// Temporarily (2020-06-29) support removing all but
// discovery-supporting nodes during development, for
@@ -650,7 +619,6 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
PrivateKey: persist.PrivateNodeKey,
Expiry: resp.Node.KeyExpiry,
Name: resp.Node.Name,
Addresses: resp.Node.Addresses,
Peers: resp.Peers,
LocalPort: localPort,
@@ -658,7 +626,8 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile),
Domain: resp.Domain,
Roles: resp.Roles,
DNS: resp.DNSConfig,
DNS: resp.DNS,
DNSDomains: resp.SearchPaths,
Hostinfo: resp.Node.Hostinfo,
PacketFilter: c.parsePacketFilter(resp.PacketFilter),
DERPMap: lastDERPMap,
@@ -672,15 +641,6 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
} else {
nm.MachineStatus = tailcfg.MachineUnauthorized
}
if len(resp.DNS) > 0 {
nm.DNS.Nameservers = wgIPToNetaddr(resp.DNS)
}
if len(resp.SearchPaths) > 0 {
nm.DNS.Domains = resp.SearchPaths
}
if Debug.ProxyDNS {
nm.DNS.Proxied = true
}
// Printing the netmap can be extremely verbose, but is very
// handy for debugging. Let's limit how often we do it.
@@ -820,24 +780,12 @@ func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (w
return key, nil
}
func wgIPToNetaddr(ips []wgcfg.IP) (ret []netaddr.IP) {
for _, ip := range ips {
nip, ok := netaddr.FromStdIP(ip.IP())
if !ok {
panic(fmt.Sprintf("conversion of %s from wgcfg to netaddr IP failed", ip))
}
ret = append(ret, nip.Unmap())
}
return ret
}
// Debug contains temporary internal-only debug knobs.
// They're unexported to not draw attention to them.
var Debug = initDebug()
type debug struct {
NetMap bool
ProxyDNS bool
OnlyDisco bool
Disco bool
ForceDisco bool // ask control server to not filter out our disco key
@@ -846,7 +794,6 @@ type debug struct {
func initDebug() debug {
d := debug{
NetMap: envBool("TS_DEBUG_NETMAP"),
ProxyDNS: envBool("TS_DEBUG_PROXY_DNS"),
OnlyDisco: os.Getenv("TS_DEBUG_USE_DISCO") == "only",
ForceDisco: os.Getenv("TS_DEBUG_USE_DISCO") == "only" || envBool("TS_DEBUG_USE_DISCO"),
}
@@ -868,124 +815,3 @@ func envBool(k string) bool {
}
return v
}
// undeltaPeers updates mapRes.Peers to be complete based on the provided previous peer list
// and the PeersRemoved and PeersChanged fields in mapRes.
// It then also nils out the delta fields.
func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
if len(mapRes.Peers) > 0 {
// Not delta encoded.
if !nodesSorted(mapRes.Peers) {
log.Printf("netmap: undeltaPeers: MapResponse.Peers not sorted; sorting")
sortNodes(mapRes.Peers)
}
return
}
var removed map[tailcfg.NodeID]bool
if pr := mapRes.PeersRemoved; len(pr) > 0 {
removed = make(map[tailcfg.NodeID]bool, len(pr))
for _, id := range pr {
removed[id] = true
}
}
changed := mapRes.PeersChanged
if len(removed) == 0 && len(changed) == 0 {
// No changes fast path.
mapRes.Peers = prev
return
}
if !nodesSorted(changed) {
log.Printf("netmap: undeltaPeers: MapResponse.PeersChanged not sorted; sorting")
sortNodes(changed)
}
if !nodesSorted(prev) {
// Internal error (unrelated to the network) if we get here.
log.Printf("netmap: undeltaPeers: [unexpected] prev not sorted; sorting")
sortNodes(prev)
}
newFull := make([]*tailcfg.Node, 0, len(prev)-len(removed))
for len(prev) > 0 && len(changed) > 0 {
pID := prev[0].ID
cID := changed[0].ID
if removed[pID] {
prev = prev[1:]
continue
}
switch {
case pID < cID:
newFull = append(newFull, prev[0])
prev = prev[1:]
case pID == cID:
newFull = append(newFull, changed[0])
prev, changed = prev[1:], changed[1:]
case cID < pID:
newFull = append(newFull, changed[0])
changed = changed[1:]
}
}
newFull = append(newFull, changed...)
for _, n := range prev {
if !removed[n.ID] {
newFull = append(newFull, n)
}
}
sortNodes(newFull)
mapRes.Peers = newFull
mapRes.PeersChanged = nil
mapRes.PeersRemoved = nil
}
func nodesSorted(v []*tailcfg.Node) bool {
for i, n := range v {
if i > 0 && n.ID <= v[i-1].ID {
return false
}
}
return true
}
func sortNodes(v []*tailcfg.Node) {
sort.Slice(v, func(i, j int) bool { return v[i].ID < v[j].ID })
}
func cloneNodes(v1 []*tailcfg.Node) []*tailcfg.Node {
if v1 == nil {
return nil
}
v2 := make([]*tailcfg.Node, len(v1))
for i, n := range v1 {
v2[i] = n.Clone()
}
return v2
}
// opt.Bool configs from control.
var (
controlUseDERPRoute atomic.Value
controlTrimWGConfig atomic.Value
)
func setControlAtomic(dst *atomic.Value, v opt.Bool) {
old, ok := dst.Load().(opt.Bool)
if !ok || old != v {
dst.Store(v)
}
}
// DERPRouteFlag reports the last reported value from control for whether
// DERP route optimization (Issue 150) should be enabled.
func DERPRouteFlag() opt.Bool {
v, _ := controlUseDERPRoute.Load().(opt.Bool)
return v
}
// TrimWGConfig reports the last reported value from control for whether
// we should do lazy wireguard configuration.
func TrimWGConfig() opt.Bool {
v, _ := controlTrimWGConfig.Load().(opt.Bool)
return v
}

View File

@@ -1,20 +0,0 @@
// Copyright (c) 2020 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.
// Code generated by tailscale.com/cmd/cloner -type Persist; DO NOT EDIT.
package controlclient
import ()
// Clone makes a deep copy of Persist.
// The result aliases no memory with the original.
func (src *Persist) Clone() *Persist {
if src == nil {
return nil
}
dst := new(Persist)
*dst = *src
return dst
}

View File

@@ -1,93 +0,0 @@
// Copyright (c) 2020 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 controlclient
import (
"fmt"
"reflect"
"strings"
"testing"
"tailscale.com/tailcfg"
)
func TestUndeltaPeers(t *testing.T) {
n := func(id tailcfg.NodeID, name string) *tailcfg.Node {
return &tailcfg.Node{ID: id, Name: name}
}
peers := func(nv ...*tailcfg.Node) []*tailcfg.Node { return nv }
tests := []struct {
name string
mapRes *tailcfg.MapResponse
prev []*tailcfg.Node
want []*tailcfg.Node
}{
{
name: "full_peers",
mapRes: &tailcfg.MapResponse{
Peers: peers(n(1, "foo"), n(2, "bar")),
},
want: peers(n(1, "foo"), n(2, "bar")),
},
{
name: "full_peers_ignores_deltas",
mapRes: &tailcfg.MapResponse{
Peers: peers(n(1, "foo"), n(2, "bar")),
PeersRemoved: []tailcfg.NodeID{2},
},
want: peers(n(1, "foo"), n(2, "bar")),
},
{
name: "add_and_update",
prev: peers(n(1, "foo"), n(2, "bar")),
mapRes: &tailcfg.MapResponse{
PeersChanged: peers(n(0, "zero"), n(2, "bar2"), n(3, "three")),
},
want: peers(n(0, "zero"), n(1, "foo"), n(2, "bar2"), n(3, "three")),
},
{
name: "remove",
prev: peers(n(1, "foo"), n(2, "bar")),
mapRes: &tailcfg.MapResponse{
PeersRemoved: []tailcfg.NodeID{1},
},
want: peers(n(2, "bar")),
},
{
name: "add_and_remove",
prev: peers(n(1, "foo"), n(2, "bar")),
mapRes: &tailcfg.MapResponse{
PeersChanged: peers(n(1, "foo2")),
PeersRemoved: []tailcfg.NodeID{2},
},
want: peers(n(1, "foo2")),
},
{
name: "unchanged",
prev: peers(n(1, "foo"), n(2, "bar")),
mapRes: &tailcfg.MapResponse{},
want: peers(n(1, "foo"), n(2, "bar")),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
undeltaPeers(tt.mapRes, tt.prev)
if !reflect.DeepEqual(tt.mapRes.Peers, tt.want) {
t.Errorf("wrong results\n got: %s\nwant: %s", formatNodes(tt.mapRes.Peers), formatNodes(tt.want))
}
})
}
}
func formatNodes(nodes []*tailcfg.Node) string {
var sb strings.Builder
for i, n := range nodes {
if i > 0 {
sb.WriteString(", ")
}
fmt.Fprintf(&sb, "(%d, %q)", n.ID, n.Name)
}
return sb.String()
}

View File

@@ -5,16 +5,80 @@
package controlclient
import (
"fmt"
"net"
"tailscale.com/tailcfg"
"tailscale.com/wgengine/filter"
)
func parseIP(host string, defaultBits int) (filter.Net, error) {
ip := net.ParseIP(host)
if ip != nil && ip.IsUnspecified() {
// For clarity, reject 0.0.0.0 as an input
return filter.NetNone, fmt.Errorf("ports=%#v: to allow all IP addresses, use *:port, not 0.0.0.0:port", host)
} else if ip == nil && host == "*" {
// User explicitly requested wildcard dst ip
return filter.NetAny, nil
} else {
if ip != nil {
ip = ip.To4()
}
if ip == nil || len(ip) != 4 {
return filter.NetNone, fmt.Errorf("ports=%#v: invalid IPv4 address", host)
}
return filter.Net{
IP: filter.NewIP(ip),
Mask: filter.Netmask(defaultBits),
}, nil
}
}
// Parse a backward-compatible FilterRule used by control's wire format,
// producing the most current filter.Matches format.
func (c *Direct) parsePacketFilter(pf []tailcfg.FilterRule) filter.Matches {
mm, err := filter.MatchesFromFilterRules(pf)
if err != nil {
c.logf("parsePacketFilter: %s\n", err)
mm := make([]filter.Match, 0, len(pf))
var erracc error
for _, r := range pf {
m := filter.Match{}
for i, s := range r.SrcIPs {
bits := 32
if len(r.SrcBits) > i {
bits = r.SrcBits[i]
}
net, err := parseIP(s, bits)
if err != nil && erracc == nil {
erracc = err
continue
}
m.Srcs = append(m.Srcs, net)
}
for _, d := range r.DstPorts {
bits := 32
if d.Bits != nil {
bits = *d.Bits
}
net, err := parseIP(d.IP, bits)
if err != nil && erracc == nil {
erracc = err
continue
}
m.Dsts = append(m.Dsts, filter.NetPortRange{
Net: net,
Ports: filter.PortRange{
First: d.Ports.First,
Last: d.Ports.Last,
},
})
}
mm = append(mm, m)
}
if erracc != nil {
c.logf("parsePacketFilter: %s\n", erracc)
}
return mm
}

View File

@@ -1,87 +0,0 @@
// Copyright (c) 2020 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.
// +build linux,!android
package controlclient
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"strings"
"syscall"
"go4.org/mem"
"tailscale.com/util/lineread"
)
func init() {
osVersion = osVersionLinux
}
func osVersionLinux() string {
m := map[string]string{}
lineread.File("/etc/os-release", func(line []byte) error {
eq := bytes.IndexByte(line, '=')
if eq == -1 {
return nil
}
k, v := string(line[:eq]), strings.Trim(string(line[eq+1:]), `"`)
m[k] = v
return nil
})
var un syscall.Utsname
syscall.Uname(&un)
var attrBuf strings.Builder
attrBuf.WriteString("; kernel=")
for _, b := range un.Release {
if b == 0 {
break
}
attrBuf.WriteByte(byte(b))
}
if inContainer() {
attrBuf.WriteString("; container")
}
attr := attrBuf.String()
id := m["ID"]
switch id {
case "debian":
slurp, _ := ioutil.ReadFile("/etc/debian_version")
return fmt.Sprintf("Debian %s (%s)%s", bytes.TrimSpace(slurp), m["VERSION_CODENAME"], attr)
case "ubuntu":
return fmt.Sprintf("Ubuntu %s%s", m["VERSION"], attr)
case "", "centos": // CentOS 6 has no /etc/os-release, so its id is ""
if cr, _ := ioutil.ReadFile("/etc/centos-release"); len(cr) > 0 { // "CentOS release 6.10 (Final)
return fmt.Sprintf("%s%s", bytes.TrimSpace(cr), attr)
}
fallthrough
case "fedora", "rhel", "alpine":
// Their PRETTY_NAME is fine as-is for all versions I tested.
fallthrough
default:
if v := m["PRETTY_NAME"]; v != "" {
return fmt.Sprintf("%s%s", v, attr)
}
}
return fmt.Sprintf("Other%s", attr)
}
func inContainer() (ret bool) {
lineread.File("/proc/1/cgroup", func(line []byte) error {
if mem.Contains(mem.B(line), mem.S("/docker/")) ||
mem.Contains(mem.B(line), mem.S("/lxc/")) {
ret = true
return io.EOF // arbitrary non-nil error to stop loop
}
return nil
})
return
}

View File

@@ -1,26 +0,0 @@
// Copyright (c) 2020 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 controlclient
import (
"os/exec"
"strings"
"syscall"
)
func init() {
osVersion = osVersionWindows
}
func osVersionWindows() string {
cmd := exec.Command("cmd", "/c", "ver")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
out, _ := cmd.Output() // "\nMicrosoft Windows [Version 10.0.19041.388]\n\n"
s := strings.TrimSpace(string(out))
s = strings.TrimPrefix(s, "Microsoft Windows [")
s = strings.TrimSuffix(s, "]")
s = strings.TrimPrefix(s, "Version ") // is this localized? do it last in case.
return s // "10.0.19041.388", ideally
}

View File

@@ -7,6 +7,7 @@ package controlclient
import (
"encoding/json"
"fmt"
"log"
"net"
"reflect"
"strconv"
@@ -22,16 +23,15 @@ import (
type NetworkMap struct {
// Core networking
NodeKey tailcfg.NodeKey
PrivateKey wgcfg.PrivateKey
Expiry time.Time
// Name is the DNS name assigned to this node.
Name string
NodeKey tailcfg.NodeKey
PrivateKey wgcfg.PrivateKey
Expiry time.Time
Addresses []wgcfg.CIDR
LocalPort uint16 // used for debugging
MachineStatus tailcfg.MachineStatus
Peers []*tailcfg.Node // sorted by Node.ID
DNS tailcfg.DNSConfig
DNS []wgcfg.IP
DNSDomains []string
Hostinfo tailcfg.Hostinfo
PacketFilter filter.Matches
@@ -131,18 +131,12 @@ func printPeerConcise(buf *strings.Builder, p *tailcfg.Node) {
if strings.HasPrefix(derp, derpPrefix) {
derp = "D" + derp[len(derpPrefix):]
}
var discoShort string
if !p.DiscoKey.IsZero() {
discoShort = p.DiscoKey.ShortString() + " "
}
// Most of the time, aip is just one element, so format the
// table to look good in that case. This will also make multi-
// subnet nodes stand out visually.
fmt.Fprintf(buf, " %v %s%-2v %-15v : %v\n",
p.Key.ShortString(),
discoShort,
derp,
fmt.Fprintf(buf, " %v %-2v %-15v : %v\n",
p.Key.ShortString(), derp,
strings.Join(aip, " "),
strings.Join(ep, " "))
}
@@ -151,7 +145,6 @@ func printPeerConcise(buf *strings.Builder, p *tailcfg.Node) {
func nodeConciseEqual(a, b *tailcfg.Node) bool {
return a.Key == b.Key &&
a.DERP == b.DERP &&
a.DiscoKey == b.DiscoKey &&
eqCIDRsIgnoreNil(a.AllowedIPs, b.AllowedIPs) &&
eqStringsIgnoreNil(a.Endpoints, b.Endpoints)
}
@@ -222,18 +215,33 @@ const (
HackDefaultRoute
)
// TODO(bradfitz): UAPI seems to only be used by the old confnode and
// pingnode; delete this when those are deleted/rewritten?
func (nm *NetworkMap) UAPI(flags WGConfigFlags, dnsOverride []wgcfg.IP) string {
wgcfg, err := nm.WGCfg(log.Printf, flags, dnsOverride)
if err != nil {
log.Fatalf("WGCfg() failed unexpectedly: %v", err)
}
s, err := wgcfg.ToUAPI()
if err != nil {
log.Fatalf("ToUAPI() failed unexpectedly: %v", err)
}
return s
}
// EndpointDiscoSuffix is appended to the hex representation of a peer's discovery key
// and is then the sole wireguard endpoint for peers with a non-zero discovery key.
// This form is then recognize by magicsock's CreateEndpoint.
const EndpointDiscoSuffix = ".disco.tailscale:12345"
// WGCfg returns the NetworkMaps's Wireguard configuration.
func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Config, error) {
func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags, dnsOverride []wgcfg.IP) (*wgcfg.Config, error) {
cfg := &wgcfg.Config{
Name: "tailscale",
PrivateKey: nm.PrivateKey,
Addresses: nm.Addresses,
ListenPort: nm.LocalPort,
DNS: append([]wgcfg.IP(nil), dnsOverride...),
Peers: make([]wgcfg.Peer, 0, len(nm.Peers)),
}

View File

@@ -5,10 +5,8 @@
package controlclient
import (
"encoding/hex"
"testing"
"github.com/tailscale/wireguard-go/wgcfg"
"tailscale.com/tailcfg"
)
@@ -19,15 +17,6 @@ func testNodeKey(b byte) (ret tailcfg.NodeKey) {
return
}
func testDiscoKey(hexPrefix string) (ret tailcfg.DiscoKey) {
b, err := hex.DecodeString(hexPrefix)
if err != nil {
panic(err)
}
copy(ret[:], b)
return
}
func TestNetworkMapConcise(t *testing.T) {
for _, tt := range []struct {
name string
@@ -213,62 +202,6 @@ func TestConciseDiffFrom(t *testing.T) {
},
want: "- [AQEBA] D1 : 192.168.0.100:12 192.168.0.100:12354\n- [AwMDA] D3 : 192.168.0.100:12 192.168.0.100:12354\n",
},
{
name: "peer_port_change",
a: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
{
ID: 2,
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:12", "1.1.1.1:1"},
},
},
},
b: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
{
ID: 2,
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:12", "1.1.1.1:2"},
},
},
},
want: "- [AgICA] D2 : 192.168.0.100:12 1.1.1.1:1 \n+ [AgICA] D2 : 192.168.0.100:12 1.1.1.1:2 \n",
},
{
name: "disco_key_only_change",
a: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
{
ID: 2,
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:41641", "1.1.1.1:41641"},
DiscoKey: testDiscoKey("f00f00f00f"),
AllowedIPs: []wgcfg.CIDR{{IP: wgcfg.IPv4(100, 102, 103, 104), Mask: 32}},
},
},
},
b: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
{
ID: 2,
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:41641", "1.1.1.1:41641"},
DiscoKey: testDiscoKey("ba4ba4ba4b"),
AllowedIPs: []wgcfg.CIDR{{IP: wgcfg.IPv4(100, 102, 103, 104), Mask: 32}},
},
},
},
want: "- [AgICA] d:f00f00f00f000000 D2 100.102.103.104 : 192.168.0.100:41641 1.1.1.1:41641\n+ [AgICA] d:ba4ba4ba4b000000 D2 100.102.103.104 : 192.168.0.100:41641 1.1.1.1:41641\n",
},
} {
t.Run(tt.name, func(t *testing.T) {
var got string

View File

@@ -39,10 +39,14 @@ const (
keepAlive = 60 * time.Second
)
// ProtocolVersion is bumped whenever there's a wire-incompatible change.
// protocolVersion is bumped whenever there's a wire-incompatible change.
// * version 1 (zero on wire): consistent box headers, in use by employee dev nodes a bit
// * version 2: received packets have src addrs in frameRecvPacket at beginning
const ProtocolVersion = 2
const protocolVersion = 2
const (
protocolSrcAddrs = 2 // protocol version at which client expects src addresses
)
// frameType is the one byte frame type at the beginning of the frame
// header. The second field is a big-endian uint32 describing the
@@ -104,31 +108,16 @@ var bin = binary.BigEndian
func writeUint32(bw *bufio.Writer, v uint32) error {
var b [4]byte
bin.PutUint32(b[:], v)
// Writing a byte at a time is a bit silly,
// but it causes b not to escape,
// which more than pays for the silliness.
for _, c := range &b {
err := bw.WriteByte(c)
if err != nil {
return err
}
}
return nil
_, err := bw.Write(b[:])
return err
}
func readUint32(br *bufio.Reader) (uint32, error) {
var b [4]byte
// Reading a byte at a time is a bit silly,
// but it causes b not to escape,
// which more than pays for the silliness.
for i := range &b {
c, err := br.ReadByte()
if err != nil {
return 0, err
}
b[i] = c
b := make([]byte, 4)
if _, err := io.ReadFull(br, b); err != nil {
return 0, err
}
return bin.Uint32(b[:]), nil
return bin.Uint32(b), nil
}
func readFrameTypeHeader(br *bufio.Reader, wantType frameType) (frameLen uint32, err error) {
@@ -205,6 +194,13 @@ func writeFrame(bw *bufio.Writer, t frameType, b []byte) error {
return bw.Flush()
}
func minInt(a, b int) int {
if a < b {
return a
}
return b
}
func minUint32(a, b uint32) uint32 {
if a < b {
return a

View File

@@ -21,13 +21,14 @@ import (
// Client is a DERP client.
type Client struct {
serverKey key.Public // of the DERP server; not a machine or node key
privateKey key.Private
publicKey key.Public // of privateKey
logf logger.Logf
nc Conn
br *bufio.Reader
meshKey string
serverKey key.Public // of the DERP server; not a machine or node key
privateKey key.Private
publicKey key.Public // of privateKey
protoVersion int // min of server+client
logf logger.Logf
nc Conn
br *bufio.Reader
meshKey string
wmu sync.Mutex // hold while writing to bw
bw *bufio.Writer
@@ -48,8 +49,7 @@ func (f clientOptFunc) update(o *clientOpt) { f(o) }
// clientOpt are the options passed to newClient.
type clientOpt struct {
MeshKey string
ServerPub key.Public
MeshKey string
}
// MeshKey returns a ClientOpt to pass to the DERP server during connect to get
@@ -58,12 +58,6 @@ type clientOpt struct {
// An empty key means to not use a mesh key.
func MeshKey(key string) ClientOpt { return clientOptFunc(func(o *clientOpt) { o.MeshKey = key }) }
// ServerPublicKey returns a ClientOpt to declare that the server's DERP public key is known.
// If key is the zero value, the returned ClientOpt is a no-op.
func ServerPublicKey(key key.Public) ClientOpt {
return clientOptFunc(func(o *clientOpt) { o.ServerPub = key })
}
func NewClient(privateKey key.Private, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opts ...ClientOpt) (*Client, error) {
var opt clientOpt
for _, o := range opts {
@@ -85,16 +79,17 @@ func newClient(privateKey key.Private, nc Conn, brw *bufio.ReadWriter, logf logg
bw: brw.Writer,
meshKey: opt.MeshKey,
}
if opt.ServerPub.IsZero() {
if err := c.recvServerKey(); err != nil {
return nil, fmt.Errorf("derp.Client: failed to receive server key: %v", err)
}
} else {
c.serverKey = opt.ServerPub
if err := c.recvServerKey(); err != nil {
return nil, fmt.Errorf("derp.Client: failed to receive server key: %v", err)
}
if err := c.sendClientKey(); err != nil {
return nil, fmt.Errorf("derp.Client: failed to send client key: %v", err)
}
info, err := c.recvServerInfo()
if err != nil {
return nil, fmt.Errorf("derp.Client: failed to receive server info: %v", err)
}
c.protoVersion = minInt(protocolVersion, info.Version)
return c, nil
}
@@ -115,9 +110,12 @@ func (c *Client) recvServerKey() error {
return nil
}
func (c *Client) parseServerInfo(b []byte) (*serverInfo, error) {
func (c *Client) recvServerInfo() (*serverInfo, error) {
fl, err := readFrameTypeHeader(c.br, frameServerInfo)
if err != nil {
return nil, err
}
const maxLength = nonceLen + maxInfoLen
fl := len(b)
if fl < nonceLen {
return nil, fmt.Errorf("short serverInfo frame")
}
@@ -126,27 +124,33 @@ func (c *Client) parseServerInfo(b []byte) (*serverInfo, error) {
}
// TODO: add a read-nonce-and-box helper
var nonce [nonceLen]byte
copy(nonce[:], b)
msgbox := b[nonceLen:]
if _, err := io.ReadFull(c.br, nonce[:]); err != nil {
return nil, fmt.Errorf("nonce: %v", err)
}
msgLen := fl - nonceLen
msgbox := make([]byte, msgLen)
if _, err := io.ReadFull(c.br, msgbox); err != nil {
return nil, fmt.Errorf("msgbox: %v", err)
}
msg, ok := box.Open(nil, msgbox, &nonce, c.serverKey.B32(), c.privateKey.B32())
if !ok {
return nil, fmt.Errorf("failed to open naclbox from server key %x", c.serverKey[:])
return nil, fmt.Errorf("msgbox: cannot open len=%d with server key %x", msgLen, c.serverKey[:])
}
info := new(serverInfo)
if err := json.Unmarshal(msg, info); err != nil {
return nil, fmt.Errorf("invalid JSON: %v", err)
return nil, fmt.Errorf("msg: %v", err)
}
return info, nil
}
type clientInfo struct {
Version int `json:"version,omitempty"`
Version int // `json:"version,omitempty"`
// MeshKey optionally specifies a pre-shared key used by
// trusted clients. It's required to subscribe to the
// connection list & forward packets. It's empty for regular
// users.
MeshKey string `json:"meshKey,omitempty"`
MeshKey string // `json:"meshKey,omitempty"`
}
func (c *Client) sendClientKey() error {
@@ -155,7 +159,7 @@ func (c *Client) sendClientKey() error {
return err
}
msg, err := json.Marshal(clientInfo{
Version: ProtocolVersion,
Version: protocolVersion,
MeshKey: c.meshKey,
})
if err != nil {
@@ -314,11 +318,6 @@ type PeerPresentMessage key.Public
func (PeerPresentMessage) msg() {}
// ServerInfoMessage is sent by the server upon first connect.
type ServerInfoMessage struct{}
func (ServerInfoMessage) msg() {}
// Recv reads a message from the DERP server.
//
// The returned message may alias memory owned by the Client; it
@@ -365,7 +364,7 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
// If the frame fits in our bufio.Reader buffer, just use it.
// In practice it's 4KB (from derphttp.Client's bufio.NewReader(httpConn)) and
// in practive, WireGuard packets (and thus DERP frames) are under 1.5KB.
// So this is the common path.
// So This is the common path.
if int(n) <= c.br.Size() {
b, err = c.br.Peek(int(n))
c.peeked = int(n)
@@ -383,19 +382,6 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
switch t {
default:
continue
case frameServerInfo:
// Server sends this at start-up. Currently unused.
// Just has a JSON message saying "version: 2",
// but the protocol seems extensible enough as-is without
// needing to wait an RTT to discover the version at startup.
// We'd prefer to give the connection to the client (magicsock)
// to start writing as soon as possible.
_, err := c.parseServerInfo(b)
if err != nil {
return nil, fmt.Errorf("invalid server info frame: %v", err)
}
// TODO: add the results of parseServerInfo to ServerInfoMessage if we ever need it.
return ServerInfoMessage{}, nil
case frameKeepAlive:
// TODO: eventually we'll have server->client pings that
// require ack pongs.
@@ -420,12 +406,16 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
case frameRecvPacket:
var rp ReceivedPacket
if n < keyLen {
c.logf("[unexpected] dropping short packet from DERP server")
continue
if c.protoVersion < protocolSrcAddrs {
rp.Data = b[:n]
} else {
if n < keyLen {
c.logf("[unexpected] dropping short packet from DERP server")
continue
}
copy(rp.Source[:], b[:keyLen])
rp.Data = b[keyLen:n]
}
copy(rp.Source[:], b[:keyLen])
rp.Data = b[keyLen:n]
return rp, nil
}
}

View File

@@ -9,19 +9,14 @@ package derp
import (
"bufio"
"context"
"crypto/ed25519"
crand "crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"errors"
"expvar"
"fmt"
"io"
"io/ioutil"
"log"
"math/big"
"math/rand"
"os"
"runtime"
"strconv"
@@ -29,41 +24,16 @@ import (
"sync"
"time"
"go4.org/mem"
"golang.org/x/crypto/nacl/box"
"golang.org/x/sync/errgroup"
"tailscale.com/disco"
"tailscale.com/metrics"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/version"
)
var debug, _ = strconv.ParseBool(os.Getenv("DERP_DEBUG_LOGS"))
// verboseDropKeys is the set of destination public keys that should
// verbosely log whenever DERP drops a packet.
var verboseDropKeys = map[key.Public]bool{}
func init() {
keys := os.Getenv("TS_DEBUG_VERBOSE_DROPS")
if keys == "" {
return
}
for _, keyStr := range strings.Split(keys, ",") {
k, err := key.NewPublicFromHexMem(mem.S(keyStr))
if err != nil {
log.Printf("ignoring invalid debug key %q: %v", keyStr, err)
} else {
verboseDropKeys[k] = true
}
}
}
func init() {
rand.Seed(time.Now().UnixNano())
}
const (
perClientSendQueueDepth = 32 // packets buffered for sending
writeTimeout = 2 * time.Second
@@ -82,22 +52,23 @@ type Server struct {
// before failing when writing to a client.
WriteTimeout time.Duration
privateKey key.Private
publicKey key.Public
logf logger.Logf
memSys0 uint64 // runtime.MemStats.Sys at start (or early-ish)
meshKey string
limitedLogf logger.Logf
metaCert []byte // the encoded x509 cert to send after LetsEncrypt cert+intermediate
// OnlyDisco controls whether, for tests, non-discovery packets
// are dropped. This is used by magicsock tests to verify that
// NAT traversal works (using DERP for out-of-band messaging)
// but the packets themselves aren't going via DERP.
OnlyDisco bool
_ [pad32bit]byte
privateKey key.Private
publicKey key.Public
logf logger.Logf
memSys0 uint64 // runtime.MemStats.Sys at start (or early-ish)
meshKey string
// Counters:
_ [pad32bit]byte
packetsSent, bytesSent expvar.Int
packetsRecv, bytesRecv expvar.Int
packetsRecvByKind metrics.LabelMap
packetsRecvDisco *expvar.Int
packetsRecvOther *expvar.Int
_ [pad32bit]byte
packetsDropped expvar.Int
packetsDroppedReason metrics.LabelMap
packetsDroppedUnknown *expvar.Int // unknown dst pubkey
@@ -172,8 +143,6 @@ func NewServer(privateKey key.Private, logf logger.Logf) *Server {
privateKey: privateKey,
publicKey: privateKey.Public(),
logf: logf,
limitedLogf: logger.RateLimitedFn(logf, 30*time.Second, 5, 100),
packetsRecvByKind: metrics.LabelMap{Label: "kind"},
packetsDroppedReason: metrics.LabelMap{Label: "reason"},
clients: map[key.Public]*sclient{},
clientsEver: map[key.Public]bool{},
@@ -183,9 +152,6 @@ func NewServer(privateKey key.Private, logf logger.Logf) *Server {
watchers: map[*sclient]bool{},
sentTo: map[key.Public]map[key.Public]int64{},
}
s.initMetacert()
s.packetsRecvDisco = s.packetsRecvByKind.Get("disco")
s.packetsRecvOther = s.packetsRecvByKind.Get("other")
s.packetsDroppedUnknown = s.packetsDroppedReason.Get("unknown_dest")
s.packetsDroppedFwdUnknown = s.packetsDroppedReason.Get("unknown_dest_on_fwd")
s.packetsDroppedGone = s.packetsDroppedReason.Get("gone")
@@ -277,50 +243,6 @@ func (s *Server) Accept(nc Conn, brw *bufio.ReadWriter, remoteAddr string) {
}
}
// initMetacert initialized s.metaCert with a self-signed x509 cert
// encoding this server's public key and protocol version. cmd/derper
// then sends this after the Let's Encrypt leaf + intermediate certs
// after the ServerHello (encrypted in TLS 1.3, not that it matters
// much).
//
// Then the client can save a round trip getting that and can start
// speaking DERP right away. (We don't use ALPN because that's sent in
// the clear and we're being paranoid to not look too weird to any
// middleboxes, given that DERP is an ultimate fallback path). But
// since the post-ServerHello certs are encrypted we can have the
// client also use them as a signal to be able to start speaking DERP
// right away, starting with its identity proof, encrypted to the
// server's public key.
//
// This RTT optimization fails where there's a corp-mandated
// TLS proxy with corp-mandated root certs on employee machines and
// and TLS proxy cleans up unnecessary certs. In that case we just fall
// back to the extra RTT.
func (s *Server) initMetacert() {
pub, priv, err := ed25519.GenerateKey(crand.Reader)
if err != nil {
log.Fatal(err)
}
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(ProtocolVersion),
Subject: pkix.Name{
CommonName: fmt.Sprintf("derpkey%x", s.publicKey[:]),
},
// Windows requires NotAfter and NotBefore set:
NotAfter: time.Now().Add(30 * 24 * time.Hour),
NotBefore: time.Now().Add(-30 * 24 * time.Hour),
}
cert, err := x509.CreateCertificate(crand.Reader, tmpl, tmpl, pub, priv)
if err != nil {
log.Fatalf("CreateCertificate: %v", err)
}
s.metaCert = cert
}
// MetaCert returns the server metadata cert that can be sent by the
// TLS server to let the client skip a round trip during start-up.
func (s *Server) MetaCert() []byte { return s.metaCert }
// registerClient notes that client c is now authenticated and ready for packets.
// If c's public key was already connected with a different connection, the prior one is closed.
func (s *Server) registerClient(c *sclient) {
@@ -637,6 +559,11 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
return fmt.Errorf("client %x: recvPacket: %v", c.key, err)
}
if s.OnlyDisco && !disco.LooksLikeDiscoWrapper(contents) {
s.packetsDropped.Add(1)
return nil
}
var fwd PacketForwarder
s.mu.Lock()
dst := s.clients[dstKey]
@@ -665,8 +592,10 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
}
p := pkt{
bs: contents,
src: c.key,
bs: contents,
}
if dst.info.Version >= protocolSrcAddrs {
p.src = c.key
}
return c.sendPkt(dst, p)
}
@@ -699,12 +628,6 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
case <-dst.sendQueue:
s.packetsDropped.Add(1)
s.packetsDroppedQueueHead.Add(1)
if verboseDropKeys[dstKey] {
// Generate a full string including src and dst, so
// the limiter kicks in once per src.
msg := fmt.Sprintf("tail drop %s -> %s", p.src.ShortString(), dstKey.ShortString())
c.s.limitedLogf(msg)
}
if debug {
c.logf("dropping packet from client %x queue head", dstKey)
}
@@ -716,12 +639,6 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
// this case to keep reader unblocked.
s.packetsDropped.Add(1)
s.packetsDroppedQueueTail.Add(1)
if verboseDropKeys[dstKey] {
// Generate a full string including src and dst, so
// the limiter kicks in once per src.
msg := fmt.Sprintf("head drop %s -> %s", p.src.ShortString(), dstKey.ShortString())
c.s.limitedLogf(msg)
}
if debug {
c.logf("dropping packet from client %x queue tail", dstKey)
}
@@ -763,7 +680,7 @@ func (s *Server) sendServerKey(bw *bufio.Writer) error {
}
type serverInfo struct {
Version int `json:"version,omitempty"`
Version int // `json:"version,omitempty"`
}
func (s *Server) sendServerInfo(bw *bufio.Writer, clientKey key.Public) error {
@@ -771,7 +688,7 @@ func (s *Server) sendServerInfo(bw *bufio.Writer, clientKey key.Public) error {
if _, err := crand.Read(nonce[:]); err != nil {
return err
}
msg, err := json.Marshal(serverInfo{Version: ProtocolVersion})
msg, err := json.Marshal(serverInfo{Version: protocolVersion})
if err != nil {
return err
}
@@ -833,7 +750,7 @@ func (s *Server) recvPacket(br *bufio.Reader, frameLen uint32) (dstKey key.Publi
if frameLen < keyLen {
return zpub, nil, errors.New("short send packet frame")
}
if err := readPublicKey(br, &dstKey); err != nil {
if _, err := io.ReadFull(br, dstKey[:]); err != nil {
return zpub, nil, err
}
packetLen := frameLen - keyLen
@@ -846,11 +763,6 @@ func (s *Server) recvPacket(br *bufio.Reader, frameLen uint32) (dstKey key.Publi
}
s.packetsRecv.Add(1)
s.bytesRecv.Add(int64(len(contents)))
if disco.LooksLikeDiscoWrapper(contents) {
s.packetsRecvDisco.Add(1)
} else {
s.packetsRecvOther.Add(1)
}
return dstKey, contents, nil
}
@@ -980,7 +892,11 @@ func (c *sclient) sendLoop(ctx context.Context) error {
}
}()
jitter := time.Duration(rand.Intn(5000)) * time.Millisecond
jitterMs, err := crand.Int(crand.Reader, big.NewInt(5000))
if err != nil {
panic(err)
}
jitter := time.Duration(jitterMs.Int64()) * time.Millisecond
keepAliveTick := time.NewTicker(keepAlive + jitter)
defer keepAliveTick.Stop()
@@ -1136,8 +1052,7 @@ func (c *sclient) sendPacket(srcKey key.Public, contents []byte) (err error) {
return err
}
if withKey {
err := writePublicKey(c.bw, &srcKey)
if err != nil {
if _, err = c.bw.Write(srcKey[:]); err != nil {
return err
}
}
@@ -1278,7 +1193,6 @@ func (s *Server) ExpVar() expvar.Var {
m.Set("bytes_sent", &s.bytesSent)
m.Set("packets_dropped", &s.packetsDropped)
m.Set("counter_packets_dropped_reason", &s.packetsDroppedReason)
m.Set("counter_packets_received_kind", &s.packetsRecvByKind)
m.Set("packets_sent", &s.packetsSent)
m.Set("packets_received", &s.packetsRecv)
m.Set("unknown_frames", &s.unknownFrames)
@@ -1290,9 +1204,6 @@ func (s *Server) ExpVar() expvar.Var {
m.Set("multiforwarder_created", &s.multiForwarderCreated)
m.Set("multiforwarder_deleted", &s.multiForwarderDeleted)
m.Set("packet_forwarder_delete_other_value", &s.removePktForwardOther)
var expvarVersion expvar.String
expvarVersion.Set(version.LONG)
m.Set("version", &expvarVersion)
return m
}
@@ -1334,34 +1245,3 @@ func (s *Server) ConsistencyCheck() error {
}
return errors.New(strings.Join(errs, ", "))
}
// readPublicKey reads key from br.
// It is ~4x slower than io.ReadFull(br, key),
// but it prevents key from escaping and thus being allocated.
// If io.ReadFull(br, key) does not cause key to escape, use that instead.
func readPublicKey(br *bufio.Reader, key *key.Public) error {
// Do io.ReadFull(br, key), but one byte at a time, to avoid allocation.
for i := range key {
b, err := br.ReadByte()
if err != nil {
return err
}
key[i] = b
}
return nil
}
// writePublicKey writes key to bw.
// It is ~3x slower than bw.Write(key[:]),
// but it prevents key from escaping and thus being allocated.
// If bw.Write(key[:]) does not cause key to escape, use that instead.
func writePublicKey(bw *bufio.Writer, key *key.Public) error {
// Do bw.Write(key[:]), but one byte at a time to avoid allocation.
for _, b := range key {
err := bw.WriteByte(b)
if err != nil {
return err
}
}
return nil
}

View File

@@ -8,14 +8,10 @@ import (
"bufio"
"context"
crand "crypto/rand"
"crypto/x509"
"encoding/json"
"errors"
"expvar"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"reflect"
"sync"
@@ -27,30 +23,14 @@ import (
"tailscale.com/types/logger"
)
func newPrivateKey(tb testing.TB) (k key.Private) {
tb.Helper()
func newPrivateKey(t *testing.T) (k key.Private) {
t.Helper()
if _, err := crand.Read(k[:]); err != nil {
tb.Fatal(err)
t.Fatal(err)
}
return
}
func TestClientInfoUnmarshal(t *testing.T) {
for i, in := range []string{
`{"Version":5,"MeshKey":"abc"}`,
`{"version":5,"meshKey":"abc"}`,
} {
var got clientInfo
if err := json.Unmarshal([]byte(in), &got); err != nil {
t.Fatalf("[%d]: %v", i, err)
}
want := clientInfo{Version: 5, MeshKey: "abc"}
if got != want {
t.Errorf("[%d]: got %+v; want %+v", i, got, want)
}
}
}
func TestSendRecv(t *testing.T) {
serverPrivateKey := newPrivateKey(t)
s := NewServer(serverPrivateKey, t.Logf)
@@ -99,8 +79,6 @@ func TestSendRecv(t *testing.T) {
if err != nil {
t.Fatalf("client %d: %v", i, err)
}
waitConnect(t, c)
clients = append(clients, c)
recvChs = append(recvChs, make(chan []byte))
t.Logf("Connected client %d.", i)
@@ -140,7 +118,7 @@ func TestSendRecv(t *testing.T) {
if got := string(b); got != want {
t.Errorf("client1.Recv=%q, want %q", got, want)
}
case <-time.After(5 * time.Second):
case <-time.After(1 * time.Second):
t.Errorf("client%d.Recv, got nothing, want %q", i, want)
}
}
@@ -246,7 +224,6 @@ func TestSendFreeze(t *testing.T) {
if err != nil {
t.Fatal(err)
}
waitConnect(t, c)
return c, c2
}
@@ -525,13 +502,7 @@ func newTestClient(t *testing.T, ts *testServer, name string, newClient func(net
func newRegularClient(t *testing.T, ts *testServer, name string) *testClient {
return newTestClient(t, ts, name, func(nc net.Conn, priv key.Private, logf logger.Logf) (*Client, error) {
brw := bufio.NewReadWriter(bufio.NewReader(nc), bufio.NewWriter(nc))
c, err := NewClient(priv, nc, brw, logf)
if err != nil {
return nil, err
}
waitConnect(t, c)
return c, nil
return NewClient(priv, nc, brw, logf)
})
}
@@ -542,7 +513,6 @@ func newTestWatcher(t *testing.T, ts *testServer, name string) *testClient {
if err != nil {
return nil, err
}
waitConnect(t, c)
if err := c.WatchConnectionChanges(); err != nil {
return nil, err
}
@@ -772,121 +742,3 @@ func TestForwarderRegistration(t *testing.T) {
u1: testFwd(3),
})
}
func TestMetaCert(t *testing.T) {
priv := newPrivateKey(t)
pub := priv.Public()
s := NewServer(priv, t.Logf)
certBytes := s.MetaCert()
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
log.Fatal(err)
}
if fmt.Sprint(cert.SerialNumber) != fmt.Sprint(ProtocolVersion) {
t.Errorf("serial = %v; want %v", cert.SerialNumber, ProtocolVersion)
}
if g, w := cert.Subject.CommonName, fmt.Sprintf("derpkey%x", pub[:]); g != w {
t.Errorf("CommonName = %q; want %q", g, w)
}
}
func BenchmarkSendRecv(b *testing.B) {
for _, size := range []int{10, 100, 1000, 10000} {
b.Run(fmt.Sprintf("msgsize=%d", size), func(b *testing.B) { benchmarkSendRecvSize(b, size) })
}
}
func benchmarkSendRecvSize(b *testing.B, packetSize int) {
serverPrivateKey := newPrivateKey(b)
s := NewServer(serverPrivateKey, logger.Discard)
defer s.Close()
key := newPrivateKey(b)
clientKey := key.Public()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
b.Fatal(err)
}
defer ln.Close()
connOut, err := net.Dial("tcp", ln.Addr().String())
if err != nil {
b.Fatal(err)
}
defer connOut.Close()
connIn, err := ln.Accept()
if err != nil {
b.Fatal(err)
}
defer connIn.Close()
brwServer := bufio.NewReadWriter(bufio.NewReader(connIn), bufio.NewWriter(connIn))
go s.Accept(connIn, brwServer, "test-client")
brw := bufio.NewReadWriter(bufio.NewReader(connOut), bufio.NewWriter(connOut))
client, err := NewClient(key, connOut, brw, logger.Discard)
if err != nil {
b.Fatalf("client: %v", err)
}
go func() {
for {
_, err := client.Recv()
if err != nil {
return
}
}
}()
msg := make([]byte, packetSize)
b.SetBytes(int64(len(msg)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := client.Send(clientKey, msg); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkWriteUint32(b *testing.B) {
w := bufio.NewWriter(ioutil.Discard)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
writeUint32(w, 0x0ba3a)
}
}
type nopRead struct{}
func (r nopRead) Read(p []byte) (int, error) {
return len(p), nil
}
var sinkU32 uint32
func BenchmarkReadUint32(b *testing.B) {
r := bufio.NewReader(nopRead{})
var err error
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
sinkU32, err = readUint32(r)
if err != nil {
b.Fatal(err)
}
}
}
func waitConnect(t testing.TB, c *Client) {
t.Helper()
if m, err := c.Recv(); err != nil {
t.Fatalf("client first Recv: %v", err)
} else if v, ok := m.(ServerInfoMessage); !ok {
t.Fatalf("client first Recv was unexpected type %T", v)
}
}

View File

@@ -14,27 +14,21 @@ import (
"bufio"
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"strings"
"sync"
"time"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/derp"
"tailscale.com/net/dnscache"
"tailscale.com/net/netns"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@@ -258,41 +252,14 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
}
}()
var httpConn net.Conn // a TCP conn or a TLS conn; what we speak HTTP to
var serverPub key.Public // or zero if unknown (if not using TLS or TLS middlebox eats it)
var serverProtoVersion int
var httpConn net.Conn // a TCP conn or a TLS conn; what we speak HTTP to
if c.useHTTPS() {
tlsConn := c.tlsClient(tcpConn, node)
httpConn = tlsConn
// Force a handshake now (instead of waiting for it to
// be done implicitly on read/write) so we can check
// the ConnectionState.
if err := tlsConn.Handshake(); err != nil {
return nil, 0, err
}
// We expect to be using TLS 1.3 to our own servers, and only
// starting at TLS 1.3 are the server's returned certificates
// encrypted, so only look for and use our "meta cert" if we're
// using TLS 1.3. If we're not using TLS 1.3, it might be a user
// running cmd/derper themselves with a different configuration,
// in which case we can avoid this fast-start optimization.
// (If a corporate proxy is MITM'ing TLS 1.3 connections with
// corp-mandated TLS root certs than all bets are off anyway.)
// Note that we're not specifically concerned about TLS downgrade
// attacks. TLS handles that fine:
// https://blog.gypsyengineer.com/en/security/how-does-tls-1-3-protect-against-downgrade-attacks.html
connState := tlsConn.ConnectionState()
if connState.Version >= tls.VersionTLS13 {
serverPub, serverProtoVersion = parseMetaCert(connState.PeerCertificates)
}
httpConn = c.tlsClient(tcpConn, node)
} else {
httpConn = tcpConn
}
brw := bufio.NewReadWriter(bufio.NewReader(httpConn), bufio.NewWriter(httpConn))
var derpClient *derp.Client
req, err := http.NewRequest("GET", c.urlString(node), nil)
if err != nil {
@@ -301,39 +268,24 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
req.Header.Set("Upgrade", "DERP")
req.Header.Set("Connection", "Upgrade")
if !serverPub.IsZero() && serverProtoVersion != 0 {
// parseMetaCert found the server's public key (no TLS
// middlebox was in the way), so skip the HTTP upgrade
// exchange. See https://github.com/tailscale/tailscale/issues/693
// for an overview. We still send the HTTP request
// just to get routed into the server's HTTP Handler so it
// can Hijack the request, but we signal with a special header
// that we don't want to deal with its HTTP response.
req.Header.Set(fastStartHeader, "1") // suppresses the server's HTTP response
if err := req.Write(brw); err != nil {
return nil, 0, err
}
// No need to flush the HTTP request. the derp.Client's initial
// client auth frame will flush it.
} else {
if err := req.Write(brw); err != nil {
return nil, 0, err
}
if err := brw.Flush(); err != nil {
return nil, 0, err
}
resp, err := http.ReadResponse(brw.Reader, req)
if err != nil {
return nil, 0, err
}
if resp.StatusCode != http.StatusSwitchingProtocols {
b, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
return nil, 0, fmt.Errorf("GET failed: %v: %s", err, b)
}
if err := req.Write(brw); err != nil {
return nil, 0, err
}
derpClient, err = derp.NewClient(c.privateKey, httpConn, brw, c.logf, derp.MeshKey(c.MeshKey), derp.ServerPublicKey(serverPub))
if err := brw.Flush(); err != nil {
return nil, 0, err
}
resp, err := http.ReadResponse(brw.Reader, req)
if err != nil {
return nil, 0, err
}
if resp.StatusCode != http.StatusSwitchingProtocols {
b, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
return nil, 0, fmt.Errorf("GET failed: %v: %s", err, b)
}
derpClient, err := derp.NewClient(c.privateKey, httpConn, brw, c.logf, derp.MeshKey(c.MeshKey))
if err != nil {
return nil, 0, err
}
@@ -412,14 +364,6 @@ func (c *Client) tlsClient(nc net.Conn, node *tailcfg.DERPNode) *tls.Conn {
tlsdial.SetConfigExpectedCert(tlsConf, node.CertName)
}
}
if n := os.Getenv("SSLKEYLOGFILE"); n != "" {
f, err := os.OpenFile(n, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatal(err)
}
log.Printf("WARNING: writing to SSLKEYLOGFILE %v", n)
tlsConf.KeyLogWriter = f
}
return tls.Client(nc, tlsConf)
}
@@ -476,19 +420,6 @@ const dialNodeTimeout = 1500 * time.Millisecond
// TODO(bradfitz): longer if no options remain perhaps? ... Or longer
// overall but have dialRegion start overlapping races?
func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, error) {
// First see if we need to use an HTTP proxy.
proxyReq := &http.Request{
Method: "GET", // doesn't really matter
URL: &url.URL{
Scheme: "https",
Host: c.tlsServerName(n),
Path: "/", // unused
},
}
if proxyURL, err := tshttpproxy.ProxyFromEnvironment(proxyReq); err == nil && proxyURL != nil {
return c.dialNodeUsingProxy(ctx, n, proxyURL)
}
type res struct {
c net.Conn
err error
@@ -549,77 +480,6 @@ func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, e
}
}
func firstStr(a, b string) string {
if a != "" {
return a
}
return b
}
// dialNodeUsingProxy connects to n using a CONNECT to the HTTP(s) proxy in proxyURL.
func (c *Client) dialNodeUsingProxy(ctx context.Context, n *tailcfg.DERPNode, proxyURL *url.URL) (proxyConn net.Conn, err error) {
pu := proxyURL
if pu.Scheme == "https" {
var d tls.Dialer
proxyConn, err = d.DialContext(ctx, "tcp", net.JoinHostPort(pu.Hostname(), firstStr(pu.Port(), "443")))
} else {
var d net.Dialer
proxyConn, err = d.DialContext(ctx, "tcp", net.JoinHostPort(pu.Hostname(), firstStr(pu.Port(), "80")))
}
defer func() {
if err != nil && proxyConn != nil {
// In a goroutine in case it's a *tls.Conn (that can block on Close)
// TODO(bradfitz): track the underlying tcp.Conn and just close that instead.
go proxyConn.Close()
}
}()
if err != nil {
return nil, err
}
done := make(chan struct{})
defer close(done)
go func() {
select {
case <-done:
return
case <-ctx.Done():
proxyConn.Close()
}
}()
target := net.JoinHostPort(n.HostName, "443")
var authHeader string
if v, err := tshttpproxy.GetAuthHeader(pu); err != nil {
c.logf("derphttp: error getting proxy auth header for %v: %v", proxyURL, err)
} else if v != "" {
authHeader = fmt.Sprintf("Proxy-Authorization: %s\r\n", v)
}
if _, err := fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n%s\r\n", target, pu.Hostname(), authHeader); err != nil {
if ctx.Err() != nil {
return nil, ctx.Err()
}
return nil, err
}
br := bufio.NewReader(proxyConn)
res, err := http.ReadResponse(br, nil)
if err != nil {
if ctx.Err() != nil {
return nil, ctx.Err()
}
c.logf("derphttp: CONNECT dial to %s: %v", target, err)
return nil, err
}
c.logf("derphttp: CONNECT dial to %s: %v", target, res.Status)
if res.StatusCode != 200 {
return nil, fmt.Errorf("invalid response status from HTTP proxy %s on CONNECT to %s: %v", pu, target, res.Status)
}
return proxyConn, nil
}
func (c *Client) Send(dstKey key.Public, b []byte) error {
client, _, err := c.connect(context.TODO(), "derphttp.Client.Send")
if err != nil {
@@ -754,16 +614,3 @@ func (c *Client) closeForReconnect(brokenClient *derp.Client) {
}
var ErrClientClosed = errors.New("derphttp.Client closed")
func parseMetaCert(certs []*x509.Certificate) (serverPub key.Public, serverProtoVersion int) {
for _, cert := range certs {
if cn := cert.Subject.CommonName; strings.HasPrefix(cn, "derpkey") {
var err error
serverPub, err = key.NewPublicFromHexMem(mem.S(strings.TrimPrefix(cn, "derpkey")))
if err == nil && cert.SerialNumber.BitLen() <= 8 { // supports up to version 255
return serverPub, int(cert.SerialNumber.Int64())
}
}
}
return key.Public{}, 0
}

View File

@@ -5,51 +5,33 @@
package derphttp
import (
"fmt"
"log"
"net/http"
"tailscale.com/derp"
)
// fastStartHeader is the header (with value "1") that signals to the HTTP
// server that the DERP HTTP client does not want the HTTP 101 response
// headers and it will begin writing & reading the DERP protocol immediately
// following its HTTP request.
const fastStartHeader = "Derp-Fast-Start"
func Handler(s *derp.Server) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if p := r.Header.Get("Upgrade"); p != "WebSocket" && p != "DERP" {
http.Error(w, "DERP requires connection upgrade", http.StatusUpgradeRequired)
return
}
fastStart := r.Header.Get(fastStartHeader) == "1"
w.Header().Set("Upgrade", "DERP")
w.Header().Set("Connection", "Upgrade")
w.WriteHeader(http.StatusSwitchingProtocols)
h, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "HTTP does not support general TCP support", 500)
return
}
netConn, conn, err := h.Hijack()
if err != nil {
log.Printf("Hijack failed: %v", err)
http.Error(w, "HTTP does not support general TCP support", 500)
return
}
if !fastStart {
pubKey := s.PublicKey()
fmt.Fprintf(conn, "HTTP/1.1 101 Switching Protocols\r\n"+
"Upgrade: DERP\r\n"+
"Connection: Upgrade\r\n"+
"Derp-Version: %v\r\n"+
"Derp-Public-Key: %x\r\n\r\n",
derp.ProtocolVersion,
pubKey[:])
}
s.Accept(netConn, conn, netConn.RemoteAddr().String())
})
}

View File

@@ -6,6 +6,7 @@ package derphttp
import (
"context"
crand "crypto/rand"
"crypto/tls"
"net"
"net/http"
@@ -18,15 +19,22 @@ import (
)
func TestSendRecv(t *testing.T) {
serverPrivateKey := key.NewPrivate()
const numClients = 3
var serverPrivateKey key.Private
if _, err := crand.Read(serverPrivateKey[:]); err != nil {
t.Fatal(err)
}
var clientPrivateKeys []key.Private
var clientKeys []key.Public
for i := 0; i < numClients; i++ {
priv := key.NewPrivate()
clientPrivateKeys = append(clientPrivateKeys, priv)
clientKeys = append(clientKeys, priv.Public())
var key key.Private
if _, err := crand.Read(key[:]); err != nil {
t.Fatal(err)
}
clientPrivateKeys = append(clientPrivateKeys, key)
}
var clientKeys []key.Public
for _, privKey := range clientPrivateKeys {
clientKeys = append(clientKeys, privKey.Public())
}
s := derp.NewServer(serverPrivateKey, t.Logf)
@@ -73,7 +81,6 @@ func TestSendRecv(t *testing.T) {
if err := c.Connect(context.Background()); err != nil {
t.Fatalf("client %d Connect: %v", i, err)
}
waitConnect(t, c)
clients = append(clients, c)
recvChs = append(recvChs, make(chan []byte))
@@ -88,11 +95,6 @@ func TestSendRecv(t *testing.T) {
}
m, err := c.Recv()
if err != nil {
select {
case <-done:
return
default:
}
t.Logf("client%d: %v", i, err)
break
}
@@ -116,7 +118,7 @@ func TestSendRecv(t *testing.T) {
if got := string(b); got != want {
t.Errorf("client1.Recv=%q, want %q", got, want)
}
case <-time.After(5 * time.Second):
case <-time.After(1 * time.Second):
t.Errorf("client%d.Recv, got nothing, want %q", i, want)
}
}
@@ -144,13 +146,5 @@ func TestSendRecv(t *testing.T) {
recv(2, string(msg2))
recvNothing(0)
recvNothing(1)
}
func waitConnect(t testing.TB, c *Client) {
t.Helper()
if m, err := c.Recv(); err != nil {
t.Fatalf("client first Recv: %v", err)
} else if v, ok := m.(derp.ServerInfoMessage); !ok {
t.Fatalf("client first Recv was unexpected type %T", v)
}
}

View File

@@ -3,13 +3,6 @@
// license that can be found in the LICENSE file.
// Package derpmap contains information about Tailscale.com's production DERP nodes.
//
// This package is only used by the "tailscale netcheck" command for debugging.
// In normal operation the Tailscale nodes get this sent to them from the control
// server.
//
// TODO: remove this package and make "tailscale netcheck" get the
// list from the control server too.
package derpmap
import (
@@ -28,10 +21,9 @@ func derpNode(suffix, v4, v6 string) *tailcfg.DERPNode {
}
}
func derpRegion(id int, code, name string, nodes ...*tailcfg.DERPNode) *tailcfg.DERPRegion {
func derpRegion(id int, code string, nodes ...*tailcfg.DERPNode) *tailcfg.DERPRegion {
region := &tailcfg.DERPRegion{
RegionID: id,
RegionName: name,
RegionCode: code,
Nodes: nodes,
}
@@ -53,36 +45,21 @@ func derpRegion(id int, code, name string, nodes ...*tailcfg.DERPNode) *tailcfg.
func Prod() *tailcfg.DERPMap {
return &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
1: derpRegion(1, "nyc", "New York City",
1: derpRegion(1, "nyc",
derpNode("a", "159.89.225.99", "2604:a880:400:d1::828:b001"),
),
2: derpRegion(2, "sfo", "San Francisco",
2: derpRegion(2, "sfo",
derpNode("a", "167.172.206.31", "2604:a880:2:d1::c5:7001"),
),
3: derpRegion(3, "sin", "Singapore",
3: derpRegion(3, "sin",
derpNode("a", "68.183.179.66", "2400:6180:0:d1::67d:8001"),
),
4: derpRegion(4, "fra", "Frankfurt",
4: derpRegion(4, "fra",
derpNode("a", "167.172.182.26", "2a03:b0c0:3:e0::36e:9001"),
),
5: derpRegion(5, "syd", "Sydney",
5: derpRegion(5, "syd",
derpNode("a", "103.43.75.49", "2001:19f0:5801:10b7:5400:2ff:feaa:284c"),
),
6: derpRegion(6, "blr", "Bangalore",
derpNode("a", "68.183.90.120", "2400:6180:100:d0::982:d001"),
),
7: derpRegion(7, "tok", "Tokyo",
derpNode("a", "167.179.89.145", "2401:c080:1000:467f:5400:2ff:feee:22aa"),
),
8: derpRegion(8, "lhr", "London",
derpNode("a", "167.71.139.179", "2a03:b0c0:1:e0::3cc:e001"),
),
9: derpRegion(9, "dfw", "Dallas",
derpNode("a", "207.148.3.137", "2001:19f0:6401:1d9c:5400:2ff:feef:bb82"),
),
10: derpRegion(10, "sea", "Seattle",
derpNode("a", "137.220.36.168", "2001:19f0:8001:2d9:5400:2ff:feef:bbb1"),
),
},
}
}

11
go.mod
View File

@@ -3,7 +3,6 @@ module tailscale.com
go 1.14
require (
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29
github.com/coreos/go-iptables v0.4.5
@@ -22,18 +21,18 @@ require (
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
github.com/peterbourgon/ff/v2 v2.0.0
github.com/tailscale/winipcfg-go v0.0.0-20200413171540-609dcf2df55f
github.com/tailscale/wireguard-go v0.0.0-20200902185615-1997cf6f9fe4
github.com/tailscale/wireguard-go v0.0.0-20200724155040-d554a2a5e7e1
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
go4.org/mem v0.0.0-20200706164138-185c595c3ecc
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425
honnef.co/go/tools v0.0.1-2020.1.4
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98
inet.af/netaddr v0.0.0-20200718043157-99321d6ad24c
rsc.io/goversion v1.2.0
)

23
go.sum
View File

@@ -9,8 +9,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4=
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 h1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=
@@ -88,10 +86,6 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tailscale/winipcfg-go v0.0.0-20200413171540-609dcf2df55f h1:uFj5bslHsMzxIM8UTjAhq4VXeo6GfNW91rpoh/WMJaY=
github.com/tailscale/winipcfg-go v0.0.0-20200413171540-609dcf2df55f/go.mod h1:x880GWw5fvrl2DVTQ04ttXQD4DuppTt1Yz6wLibbjNE=
github.com/tailscale/wireguard-go v0.0.0-20200806235025-91988cfbaa3a h1:dQEgNpoOJf+8MswlvXJicb8ZDQqZAGe8f/WfzbDMvtE=
github.com/tailscale/wireguard-go v0.0.0-20200806235025-91988cfbaa3a/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4=
github.com/tailscale/wireguard-go v0.0.0-20200902185615-1997cf6f9fe4 h1:UiTXdZChEWxxci7bx+jS9OyHQx2IA8zmMWQqp5wfP7c=
github.com/tailscale/wireguard-go v0.0.0-20200902185615-1997cf6f9fe4/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
@@ -106,8 +100,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 h1:TjszyFsQsyZNHwdVdZ5m7bjmreu0znc2kRYsEml9/Ww=
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -121,8 +115,8 @@ golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
@@ -142,11 +136,8 @@ golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d h1:QQrM/CCYEzTs91GZylDCQjGHudbPTxF/1fvXdVh5lMo=
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e h1:hq86ru83GdWTlfQFZGO4nZJTU4Bs2wfHl8oFHRaXsfc=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -176,7 +167,5 @@ honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
inet.af/netaddr v0.0.0-20200718043157-99321d6ad24c h1:si3Owrfem175Ry6gKqnh59eOXxDojyBTIHxUKuvK/Eo=
inet.af/netaddr v0.0.0-20200718043157-99321d6ad24c/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww=
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98 h1:bWyWDZP0l6VnQ1TDKf6yNwuiEDV6Q3q1Mv34m+lzT1I=
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww=
rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=
rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=

View File

@@ -18,23 +18,13 @@ import (
"reflect"
)
func Hash(v ...interface{}) string {
func Hash(v interface{}) string {
h := sha256.New()
Print(h, v)
return fmt.Sprintf("%x", h.Sum(nil))
}
// UpdateHash sets last to the hash of v and reports whether its value changed.
func UpdateHash(last *string, v ...interface{}) (changed bool) {
sig := Hash(v)
if *last != sig {
*last = sig
return true
}
return false
}
func Print(w io.Writer, v ...interface{}) {
func Print(w io.Writer, v interface{}) {
print(w, reflect.ValueOf(v), make(map[uintptr]bool))
}

View File

@@ -11,7 +11,6 @@ import (
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/router/dns"
)
func TestDeepPrint(t *testing.T) {
@@ -51,7 +50,7 @@ func getVal() []interface{} {
},
},
&router.Config{
DNS: dns.Config{
DNSConfig: router.DNSConfig{
Nameservers: []netaddr.IP{netaddr.IPv4(8, 8, 8, 8)},
Domains: []string{"tailscale.net"},
},

View File

@@ -62,7 +62,6 @@ type Notify struct {
Status *ipnstate.Status // full status
BrowseToURL *string // UI should open a browser right now
BackendLogID *string // public logtail id used by backend
PingResult *ipnstate.PingResult
// LocalTCPPort, if non-nil, informs the UI frontend which
// (non-zero) localhost TCP port it's listening on.
@@ -144,9 +143,6 @@ type Backend interface {
// WantRunning. This may cause the wireguard engine to
// reconfigure or stop.
SetPrefs(*Prefs)
// SetWantRunning is like SetPrefs but sets only the
// WantRunning field.
SetWantRunning(wantRunning bool)
// RequestEngineStatus polls for an update from the wireguard
// engine. Only needed if you want to display byte
// counts. Connection events are emitted automatically without
@@ -160,8 +156,4 @@ type Backend interface {
// make sure they react properly with keys that are going to
// expire.
FakeExpireAfter(x time.Duration)
// Ping attempts to start connecting to the given IP and sends a Notify
// with its PingResult. If the host is down, there might never
// be a PingResult sent. The cmd/tailscale CLI client adds a timeout.
Ping(ip string)
}

View File

@@ -79,10 +79,6 @@ func (b *FakeBackend) SetPrefs(new *Prefs) {
}
}
func (b *FakeBackend) SetWantRunning(v bool) {
b.SetPrefs(&Prefs{WantRunning: v})
}
func (b *FakeBackend) RequestEngineStatus() {
b.notify(Notify{Engine: &EngineStatus{}})
}
@@ -94,7 +90,3 @@ func (b *FakeBackend) RequestStatus() {
func (b *FakeBackend) FakeExpireAfter(x time.Duration) {
b.notify(Notify{NetMap: &controlclient.NetworkMap{}})
}
func (b *FakeBackend) Ping(ip string) {
b.notify(Notify{PingResult: &ipnstate.PingResult{}})
}

View File

@@ -69,6 +69,10 @@ type Options struct {
// DebugMux, if non-nil, specifies an HTTP ServeMux in which
// to register a debug handler.
DebugMux *http.ServeMux
// ErrorMessage, if not empty, signals that the server will exist
// only to relay the provided critical error message to the user.
ErrorMessage string
}
// server is an IPN backend and its set of 0 or more active connections
@@ -148,9 +152,7 @@ func (s *server) writeToClients(b []byte) {
}
}
// Run runs a Tailscale backend service.
// The getEngine func is called repeatedly, once per connection, until it returns an engine successfully.
func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (wgengine.Engine, error), opts Options) error {
func Run(ctx context.Context, logf logger.Logf, logid string, opts Options, e wgengine.Engine) error {
runDone := make(chan struct{})
defer close(runDone)
@@ -175,40 +177,27 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
}()
logf("Listening on %v", listen.Addr())
bo := backoff.NewBackoff("ipnserver", logf, 30*time.Second)
bo := backoff.NewBackoff("ipnserver", logf)
var unservedConn net.Conn // if non-nil, accepted, but hasn't served yet
eng, err := getEngine()
if err != nil {
logf("Initial getEngine call: %v", err)
if opts.ErrorMessage != "" {
for i := 1; ctx.Err() == nil; i++ {
c, err := listen.Accept()
s, err := listen.Accept()
if err != nil {
logf("%d: Accept: %v", i, err)
bo.BackOff(ctx, err)
continue
}
logf("%d: trying getEngine again...", i)
eng, err = getEngine()
if err == nil {
logf("%d: GetEngine worked; exiting failure loop", i)
unservedConn = c
break
serverToClient := func(b []byte) {
ipn.WriteMsg(s, b)
}
logf("%d: getEngine failed again: %v", i, err)
errMsg := err.Error()
go func() {
defer c.Close()
serverToClient := func(b []byte) { ipn.WriteMsg(c, b) }
defer s.Close()
bs := ipn.NewBackendServer(logf, nil, serverToClient)
bs.SendErrorMessage(errMsg)
time.Sleep(time.Second)
bs.SendErrorMessage(opts.ErrorMessage)
s.Read(make([]byte, 1))
}()
}
if err := ctx.Err(); err != nil {
return err
}
return ctx.Err()
}
var store ipn.StateStore
@@ -221,7 +210,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
store = &ipn.MemoryStore{}
}
b, err := ipn.NewLocalBackend(logf, logid, store, eng)
b, err := ipn.NewLocalBackend(logf, logid, store, e)
if err != nil {
return fmt.Errorf("NewLocalBackend: %v", err)
}
@@ -254,14 +243,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
}
for i := 1; ctx.Err() == nil; i++ {
var c net.Conn
var err error
if unservedConn != nil {
c = unservedConn
unservedConn = nil
} else {
c, err = listen.Accept()
}
c, err := listen.Accept()
if err != nil {
if ctx.Err() == nil {
logf("ipnserver: Accept: %v", err)
@@ -306,7 +288,7 @@ func BabysitProc(ctx context.Context, args []string, logf logger.Logf) {
proc.mu.Unlock()
}()
bo := backoff.NewBackoff("BabysitProc", logf, 30*time.Second)
bo := backoff.NewBackoff("BabysitProc", logf)
for {
startTime := time.Now()
@@ -389,8 +371,3 @@ func BabysitProc(ctx context.Context, args []string, logf logger.Logf) {
}
}
}
// FixedEngine returns a func that returns eng and a nil error.
func FixedEngine(eng wgengine.Engine) func() (wgengine.Engine, error) {
return func() (wgengine.Engine, error) { return eng, nil }
}

View File

@@ -72,6 +72,6 @@ func TestRunMultipleAccepts(t *testing.T) {
SocketPath: socketPath,
}
t.Logf("pre-Run")
err = ipnserver.Run(ctx, logTriggerTestf, "dummy_logid", ipnserver.FixedEngine(eng), opts)
err = ipnserver.Run(ctx, logTriggerTestf, "dummy_logid", opts, eng)
t.Logf("ipnserver.Run = %v", err)
}

View File

@@ -18,7 +18,6 @@ import (
"sync"
"time"
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)
@@ -26,11 +25,8 @@ import (
// Status represents the entire state of the IPN network.
type Status struct {
BackendState string
TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
Self *PeerStatus
Peer map[key.Public]*PeerStatus
User map[tailcfg.UserID]tailcfg.UserProfile
Peer map[key.Public]*PeerStatus
User map[tailcfg.UserID]tailcfg.UserProfile
}
func (s *Status) Peers() []key.Public {
@@ -45,7 +41,6 @@ func (s *Status) Peers() []key.Public {
type PeerStatus struct {
PublicKey key.Public
HostName string // HostInfo's Hostname (not a DNS name or necessarily unique)
DNSName string
OS string // HostInfo.OS
UserID tailcfg.UserID
@@ -91,12 +86,6 @@ type StatusBuilder struct {
st Status
}
func (sb *StatusBuilder) SetBackendState(v string) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.BackendState = v
}
func (sb *StatusBuilder) Status() *Status {
sb.mu.Lock()
defer sb.mu.Unlock()
@@ -104,13 +93,6 @@ func (sb *StatusBuilder) Status() *Status {
return &sb.st
}
// SetSelfStatus sets the status of the local machine.
func (sb *StatusBuilder) SetSelfStatus(ss *PeerStatus) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.Self = ss
}
// AddUser adds a user profile to the status.
func (sb *StatusBuilder) AddUser(id tailcfg.UserID, up tailcfg.UserProfile) {
sb.mu.Lock()
@@ -127,18 +109,6 @@ func (sb *StatusBuilder) AddUser(id tailcfg.UserID, up tailcfg.UserProfile) {
sb.st.User[id] = up
}
// AddIP adds a Tailscale IP address to the status.
func (sb *StatusBuilder) AddTailscaleIP(ip netaddr.IP) {
sb.mu.Lock()
defer sb.mu.Unlock()
if sb.locked {
log.Printf("[unexpected] ipnstate: AddIP after Locked")
return
}
sb.st.TailscaleIPs = append(sb.st.TailscaleIPs, ip)
}
// AddPeer adds a peer node to the status.
//
// Its PeerStatus is mixed with any previous status already added.
@@ -167,9 +137,6 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
if v := st.HostName; v != "" {
e.HostName = v
}
if v := st.DNSName; v != "" {
e.DNSName = v
}
if v := st.Relay; v != "" {
e.Relay = v
}
@@ -251,12 +218,6 @@ table tbody tr:nth-child(even) td { background-color: #f5f5f5; }
//f("<p><b>logid:</b> %s</p>\n", logid)
//f("<p><b>opts:</b> <code>%s</code></p>\n", html.EscapeString(fmt.Sprintf("%+v", opts)))
ips := make([]string, 0, len(st.TailscaleIPs))
for _, ip := range st.TailscaleIPs {
ips = append(ips, ip.String())
}
f("<p>Tailscale IP: %s", strings.Join(ips, ", "))
f("<table>\n<thead>\n")
f("<tr><th>Peer</th><th>Node</th><th>Owner</th><th>Rx</th><th>Tx</th><th>Activity</th><th>Endpoints</th></tr>\n")
f("</thead>\n<tbody>\n")
@@ -341,21 +302,3 @@ func osEmoji(os string) string {
}
return "👽"
}
// PingResult contains response information for the "tailscale ping" subcommand,
// saying how Tailscale can reach a Tailscale IP or subnet-routed IP.
type PingResult struct {
IP string // ping destination
NodeIP string // Tailscale IP of node handling IP (different for subnet routers)
NodeName string // DNS name base or (possibly not unique) hostname
Err string
LatencySeconds float64
Endpoint string // ip:port if direct UDP was used
DERPRegionID int // non-zero if DERP was used
DERPRegionCode string // three-letter airport/region code if DERP was used
// TODO(bradfitz): details like whether port mapping was used on either side? (Once supported)
}

View File

@@ -16,11 +16,8 @@ import (
"golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/internal/deepprint"
"tailscale.com/ipn/ipnstate"
"tailscale.com/ipn/policy"
"tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr"
"tailscale.com/portlist"
"tailscale.com/tailcfg"
"tailscale.com/types/empty"
@@ -30,7 +27,6 @@ import (
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/router/dns"
"tailscale.com/wgengine/tsdns"
)
@@ -55,10 +51,12 @@ type LocalBackend struct {
backendLogID string
portpoll *portlist.Poller // may be nil
portpollOnce sync.Once
serverURL string // tailcontrol URL
newDecompressor func() (controlclient.Decompressor, error)
filterHash string
// TODO: these fields are accessed unsafely by concurrent
// goroutines. They need to be protected.
serverURL string // tailcontrol URL
lastFilterPrint time.Time
// The mutex protects the following elements.
mu sync.Mutex
@@ -110,23 +108,11 @@ func NewLocalBackend(logf logger.Logf, logid string, store StateStore, e wgengin
state: NoState,
portpoll: portpoll,
}
e.SetLinkChangeCallback(b.linkChange)
b.statusChanged = sync.NewCond(&b.statusLock)
return b, nil
}
func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
// TODO(bradfitz): on a major link change, ask controlclient
// whether its host (e.g. login.tailscale.com) is reachable.
// If not, down the world and poll for a bit. Windows' WinHTTP
// service might be unable to resolve its WPAD PAC URL if we
// have DNS/routes configured. So we need to remove that DNS
// and those routes to let it figure out its proxy
// settings. Once it's back up and happy, then we can resume
// and our connection to the control server would work again.
}
// Shutdown halts the backend and all its sub-components. The backend
// can no longer be used after Shutdown returns.
func (b *LocalBackend) Shutdown() {
@@ -157,8 +143,6 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
b.mu.Lock()
defer b.mu.Unlock()
sb.SetBackendState(b.state.String())
// TODO: hostinfo, and its networkinfo
// TODO: EngineStatus copy (and deprecate it?)
if b.netMap != nil {
@@ -179,7 +163,6 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
UserID: p.User,
TailAddr: tailAddr,
HostName: p.Hostinfo.Hostname,
DNSName: p.Name,
OS: p.Hostinfo.OS,
KeepAlive: p.KeepAlive,
Created: p.Created,
@@ -203,56 +186,21 @@ func (b *LocalBackend) SetDecompressor(fn func() (controlclient.Decompressor, er
// setClientStatus is the callback invoked by the control client whenever it posts a new status.
// Among other things, this is where we update the netmap, packet filters, DNS and DERP maps.
func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// The following do not depend on any data for which we need to lock b.
if st.Err != "" {
// TODO(crawshaw): display in the UI.
b.logf("Received error: %v", st.Err)
return
}
if st.LoginFinished != nil {
// Auth completed, unblock the engine
b.blockEngineUpdates(false)
b.authReconfig()
b.send(Notify{LoginFinished: &empty.Message{}})
}
prefsChanged := false
// Lock b once and do only the things that require locking.
b.mu.Lock()
prefs := b.prefs
stateKey := b.stateKey
netMap := b.netMap
interact := b.interact
if st.Persist != nil {
if !b.prefs.Persist.Equals(st.Persist) {
prefsChanged = true
b.prefs.Persist = st.Persist.Clone()
}
}
if st.NetMap != nil {
b.netMap = st.NetMap
}
if st.URL != "" {
b.authURL = st.URL
}
if b.state == NeedsLogin {
if !b.prefs.WantRunning {
prefsChanged = true
}
b.prefs.WantRunning = true
}
// Prefs will be written out; this is not safe unless locked or cloned.
if prefsChanged {
prefs = b.prefs.Clone()
}
persist := *st.Persist // copy
b.mu.Unlock()
b.mu.Lock()
b.prefs.Persist = &persist
prefs := b.prefs.Clone()
stateKey := b.stateKey
b.mu.Unlock()
// Now complete the lock-free parts of what we started while locked.
if prefsChanged {
if stateKey != "" {
if err := b.store.WriteState(stateKey, prefs.ToBytes()); err != nil {
b.logf("Failed to save new controlclient state: %v", err)
@@ -261,41 +209,63 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
b.send(Notify{Prefs: prefs})
}
if st.NetMap != nil {
if netMap != nil {
diff := st.NetMap.ConciseDiffFrom(netMap)
// Netmap is unchanged only when the diff is empty.
changed := true
b.mu.Lock()
if b.netMap != nil {
diff := st.NetMap.ConciseDiffFrom(b.netMap)
if strings.TrimSpace(diff) == "" {
changed = false
b.logf("netmap diff: (none)")
} else {
b.logf("netmap diff:\n%v", diff)
}
}
disableDERP := b.prefs != nil && b.prefs.DisableDERP
b.netMap = st.NetMap
b.mu.Unlock()
b.updateFilter(st.NetMap, prefs)
b.e.SetNetworkMap(st.NetMap)
if !dnsMapsEqual(st.NetMap, netMap) {
b.send(Notify{NetMap: st.NetMap})
// There is nothing to update if the map hasn't changed.
if changed {
b.updateFilter(st.NetMap)
b.updateDNSMap(st.NetMap)
b.e.SetNetworkMap(st.NetMap)
}
disableDERP := prefs != nil && prefs.DisableDERP
if disableDERP {
b.e.SetDERPMap(nil)
} else {
b.e.SetDERPMap(st.NetMap.DERPMap)
}
b.send(Notify{NetMap: st.NetMap})
}
if st.URL != "" {
b.logf("Received auth URL: %.20v...", st.URL)
b.mu.Lock()
interact := b.interact
b.authURL = st.URL
b.mu.Unlock()
if interact > 0 {
b.popBrowserAuthNow()
}
}
if st.Err != "" {
// TODO(crawshaw): display in the UI.
b.logf("Received error: %v", st.Err)
return
}
if st.NetMap != nil {
b.mu.Lock()
if b.state == NeedsLogin {
b.prefs.WantRunning = true
}
prefs := b.prefs
b.mu.Unlock()
b.SetPrefs(prefs)
}
b.stateMachine()
// This is currently (2020-07-28) necessary; conditionally disabling it is fragile!
// This is where netmap information gets propagated to router and magicsock.
b.authReconfig()
}
// setWgengineStatus is the callback by the wireguard engine whenever it posts a new status.
@@ -390,7 +360,7 @@ func (b *LocalBackend) Start(opts Options) error {
persist := b.prefs.Persist
b.mu.Unlock()
b.updateFilter(nil, nil)
b.updateFilter(nil)
var discoPublic tailcfg.DiscoKey
if controlclient.Debug.Disco {
@@ -454,121 +424,65 @@ func (b *LocalBackend) Start(opts Options) error {
// updateFilter updates the packet filter in wgengine based on the
// given netMap and user preferences.
func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *Prefs) {
// NOTE(danderson): keep change detection as the first thing in
// this function. Don't try to optimize by returning early, more
// likely than not you'll just end up breaking the change
// detection and end up with the wrong filter installed. This is
// quite hard to debug, so save yourself the trouble.
var (
haveNetmap = netMap != nil
addrs []wgcfg.CIDR
packetFilter filter.Matches
advRoutes []wgcfg.CIDR
shieldsUp = prefs == nil || prefs.ShieldsUp // Be conservative when not ready
)
if haveNetmap {
addrs = netMap.Addresses
packetFilter = netMap.PacketFilter
}
if prefs != nil {
advRoutes = prefs.AdvertiseRoutes
}
changed := deepprint.UpdateHash(&b.filterHash, haveNetmap, addrs, packetFilter, advRoutes, shieldsUp)
if !changed {
return
}
if !haveNetmap {
func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap) {
if netMap == nil {
// Not configured yet, block everything
b.logf("netmap packet filter: (not ready yet)")
b.e.SetFilter(filter.NewAllowNone(b.logf))
return
}
b.mu.Lock()
advRoutes := b.prefs.AdvertiseRoutes
b.mu.Unlock()
localNets := wgCIDRsToFilter(netMap.Addresses, advRoutes)
if shieldsUp {
if b.shieldsAreUp() {
// Shields up, block everything
b.logf("netmap packet filter: (shields up)")
var prevFilter *filter.Filter // don't reuse old filter state
b.e.SetFilter(filter.New(filter.Matches{}, localNets, prevFilter, b.logf))
return
}
// TODO(apenwarr): don't replace filter at all if unchanged.
// TODO(apenwarr): print a diff instead of full filter.
now := time.Now()
if now.Sub(b.lastFilterPrint) > 1*time.Minute {
b.logf("netmap packet filter: %v", netMap.PacketFilter)
b.lastFilterPrint = now
} else {
b.logf("netmap packet filter: %v", packetFilter)
b.e.SetFilter(filter.New(packetFilter, localNets, b.e.GetFilter(), b.logf))
b.logf("netmap packet filter: (length %d)", len(netMap.PacketFilter))
}
}
// dnsCIDRsEqual determines whether two CIDR lists are equal
// for DNS map construction purposes (that is, only the first entry counts).
func dnsCIDRsEqual(newAddr, oldAddr []wgcfg.CIDR) bool {
if len(newAddr) != len(oldAddr) {
return false
}
if len(newAddr) == 0 || newAddr[0] == oldAddr[0] {
return true
}
return false
}
// dnsMapsEqual determines whether the new and the old network map
// induce the same DNS map. It does so without allocating memory,
// at the expense of giving false negatives if peers are reordered.
func dnsMapsEqual(new, old *controlclient.NetworkMap) bool {
if (old == nil) != (new == nil) {
return false
}
if old == nil && new == nil {
return true
}
if len(new.Peers) != len(old.Peers) {
return false
}
if new.Name != old.Name {
return false
}
if !dnsCIDRsEqual(new.Addresses, old.Addresses) {
return false
}
for i, newPeer := range new.Peers {
oldPeer := old.Peers[i]
if newPeer.Name != oldPeer.Name {
return false
}
if !dnsCIDRsEqual(newPeer.Addresses, oldPeer.Addresses) {
return false
}
}
return true
b.e.SetFilter(filter.New(netMap.PacketFilter, localNets, b.e.GetFilter(), b.logf))
}
// updateDNSMap updates the domain map in the DNS resolver in wgengine
// based on the given netMap and user preferences.
func (b *LocalBackend) updateDNSMap(netMap *controlclient.NetworkMap) {
if netMap == nil {
b.logf("dns map: (not ready)")
return
}
nameToIP := make(map[string]netaddr.IP)
set := func(name string, addrs []wgcfg.CIDR) {
domainToIP := make(map[string]netaddr.IP)
set := func(hostname string, addrs []wgcfg.CIDR) {
if len(addrs) == 0 {
return
}
nameToIP[name] = netaddr.IPFrom16(addrs[0].IP.Addr)
domain := hostname
// Like PeerStatus.SimpleHostName()
domain = strings.TrimSuffix(domain, ".local")
domain = strings.TrimSuffix(domain, ".localdomain")
domain = domain + ".b.tailscale.net"
domainToIP[domain] = netaddr.IPFrom16(addrs[0].IP.Addr)
}
for _, peer := range netMap.Peers {
set(peer.Name, peer.Addresses)
set(peer.Hostinfo.Hostname, peer.Addresses)
}
set(netMap.Name, netMap.Addresses)
set(netMap.Hostinfo.Hostname, netMap.Addresses)
dnsMap := tsdns.NewMap(nameToIP, domainsForProxying(netMap))
// map diff will be logged in tsdns.Resolver.SetMap.
b.e.SetDNSMap(dnsMap)
b.e.SetDNSMap(tsdns.NewMap(domainToIP))
}
// readPoller is a goroutine that receives service lists from
@@ -761,17 +675,6 @@ func (b *LocalBackend) FakeExpireAfter(x time.Duration) {
b.send(Notify{NetMap: b.netMap})
}
func (b *LocalBackend) Ping(ipStr string) {
ip, err := netaddr.ParseIP(ipStr)
if err != nil {
b.logf("ignoring Ping request to invalid IP %q", ipStr)
return
}
b.e.Ping(ip, func(pr *ipnstate.PingResult) {
b.send(Notify{PingResult: pr})
})
}
func (b *LocalBackend) parseWgStatus(s *wgengine.Status) (ret EngineStatus) {
var (
peerStats []string
@@ -792,9 +695,7 @@ func (b *LocalBackend) parseWgStatus(s *wgengine.Status) (ret EngineStatus) {
ret.WBytes += p.TxBytes
}
if len(peerStats) > 0 {
// [GRINDER STATS LINE] - please don't remove (used for log parsing)
b.keyLogf("peer keys: %s", strings.Join(peerKeys, " "))
// [GRINDER STATS LINE] - please don't remove (used for log parsing)
b.logf("v%v peers: %v", version.LONG, strings.Join(peerStats, " "))
}
return ret
@@ -812,18 +713,6 @@ func (b *LocalBackend) shieldsAreUp() bool {
return b.prefs.ShieldsUp
}
func (b *LocalBackend) SetWantRunning(wantRunning bool) {
b.mu.Lock()
new := b.prefs.Clone()
b.mu.Unlock()
if new.WantRunning == wantRunning {
return
}
new.WantRunning = wantRunning
b.logf("SetWantRunning: %v", wantRunning)
b.SetPrefs(new)
}
// SetPrefs saves new user preferences and propagates them throughout
// the system. Implements Backend.
func (b *LocalBackend) SetPrefs(new *Prefs) {
@@ -832,46 +721,37 @@ func (b *LocalBackend) SetPrefs(new *Prefs) {
}
b.mu.Lock()
netMap := b.netMap
stateKey := b.stateKey
old := b.prefs
new.Persist = old.Persist // caller isn't allowed to override this
b.prefs = new
// We do this to avoid holding the lock while doing everything else.
new = b.prefs.Clone()
if b.stateKey != "" {
if err := b.store.WriteState(b.stateKey, b.prefs.ToBytes()); err != nil {
b.logf("Failed to save new controlclient state: %v", err)
}
}
oldHi := b.hostinfo
newHi := oldHi.Clone()
newHi.RoutableIPs = append([]wgcfg.CIDR(nil), b.prefs.AdvertiseRoutes...)
applyPrefsToHostinfo(newHi, new)
b.hostinfo = newHi
hostInfoChanged := !oldHi.Equal(newHi)
b.mu.Unlock()
if stateKey != "" {
if err := b.store.WriteState(stateKey, new.ToBytes()); err != nil {
b.logf("Failed to save new controlclient state: %v", err)
}
}
// [GRINDER STATS LINE] - please don't remove (used for log parsing)
b.logf("SetPrefs: %v", new.Pretty())
if old.ShieldsUp != new.ShieldsUp || hostInfoChanged {
b.doSetHostinfoFilterServices(newHi)
}
b.updateFilter(netMap, new)
b.updateFilter(b.netMap)
// TODO(dmytro): when Prefs gain an EnableTailscaleDNS toggle, updateDNSMap here.
turnDERPOff := new.DisableDERP && !old.DisableDERP
turnDERPOn := !new.DisableDERP && old.DisableDERP
if turnDERPOff {
b.e.SetDERPMap(nil)
} else if turnDERPOn && netMap != nil {
b.e.SetDERPMap(netMap.DERPMap)
} else if turnDERPOn && b.netMap != nil {
b.e.SetDERPMap(b.netMap.DERPMap)
}
if old.WantRunning != new.WantRunning {
@@ -964,72 +844,28 @@ func (b *LocalBackend) authReconfig() {
flags |= controlclient.AllowSingleHosts
}
cfg, err := nm.WGCfg(b.logf, flags)
dns := nm.DNS
dom := nm.DNSDomains
if !uc.CorpDNS {
dns = []wgcfg.IP{}
dom = []string{}
}
cfg, err := nm.WGCfg(b.logf, flags, dns)
if err != nil {
b.logf("wgcfg: %v", err)
return
}
rcfg := routerConfig(cfg, uc)
// If CorpDNS is false, rcfg.DNS remains the zero value.
if uc.CorpDNS {
domains := nm.DNS.Domains
proxied := nm.DNS.Proxied
if proxied {
if len(nm.DNS.Nameservers) == 0 {
b.logf("[unexpected] dns proxied but no nameservers")
proxied = false
} else {
// Domains for proxying should come first to avoid leaking queries.
domains = append(domainsForProxying(nm), domains...)
}
}
rcfg.DNS = dns.Config{
Nameservers: nm.DNS.Nameservers,
Domains: domains,
PerDomain: nm.DNS.PerDomain,
Proxied: proxied,
}
}
err = b.e.Reconfig(cfg, rcfg)
err = b.e.Reconfig(cfg, routerConfig(cfg, uc, dom))
if err == wgengine.ErrNoChanges {
return
}
b.logf("authReconfig: ra=%v dns=%v 0x%02x: %v", uc.RouteAll, uc.CorpDNS, flags, err)
}
// domainsForProxying produces a list of search domains for proxied DNS.
func domainsForProxying(nm *controlclient.NetworkMap) []string {
var domains []string
if idx := strings.IndexByte(nm.Name, '.'); idx != -1 {
domains = append(domains, nm.Name[idx+1:])
}
for _, peer := range nm.Peers {
idx := strings.IndexByte(peer.Name, '.')
if idx == -1 {
continue
}
domain := peer.Name[idx+1:]
seen := false
// In theory this makes the function O(n^2) worst case,
// but in practice we expect domains to contain very few elements
// (only one until invitations are introduced).
for _, seenDomain := range domains {
if domain == seenDomain {
seen = true
}
}
if !seen {
domains = append(domains, domain)
}
}
return domains
}
// routerConfig produces a router.Config from a wireguard config and IPN prefs.
func routerConfig(cfg *wgcfg.Config, prefs *Prefs) *router.Config {
// routerConfig produces a router.Config from a wireguard config,
// IPN prefs, and the dnsDomains pulled from control's network map.
func routerConfig(cfg *wgcfg.Config, prefs *Prefs, dnsDomains []string) *router.Config {
var addrs []wgcfg.CIDR
for _, addr := range cfg.Addresses {
addrs = append(addrs, wgcfg.CIDR{
@@ -1043,14 +879,20 @@ func routerConfig(cfg *wgcfg.Config, prefs *Prefs) *router.Config {
SubnetRoutes: wgCIDRToNetaddr(prefs.AdvertiseRoutes),
SNATSubnetRoutes: !prefs.NoSNAT,
NetfilterMode: prefs.NetfilterMode,
DNSConfig: router.DNSConfig{
Nameservers: wgIPToNetaddr(cfg.DNS),
Domains: dnsDomains,
},
}
for _, peer := range cfg.Peers {
rs.Routes = append(rs.Routes, wgCIDRToNetaddr(peer.AllowedIPs)...)
}
// The Tailscale DNS IP.
// TODO(dmytro): make this configurable.
rs.Routes = append(rs.Routes, netaddr.IPPrefix{
IP: tsaddr.TailscaleServiceIP(),
IP: netaddr.IPv4(100, 100, 100, 100),
Bits: 32,
})
@@ -1074,6 +916,17 @@ func wgCIDRsToFilter(cidrLists ...[]wgcfg.CIDR) (ret []filter.Net) {
return ret
}
func wgIPToNetaddr(ips []wgcfg.IP) (ret []netaddr.IP) {
for _, ip := range ips {
nip, ok := netaddr.FromStdIP(ip.IP())
if !ok {
panic(fmt.Sprintf("conversion of %s from wgcfg to netaddr IP failed", ip))
}
ret = append(ret, nip.Unmap())
}
return ret
}
func wgCIDRToNetaddr(cidrs []wgcfg.CIDR) (ret []netaddr.IPPrefix) {
for _, cidr := range cidrs {
ncidr, ok := netaddr.FromStdIPNet(cidr.IPNet())
@@ -1111,7 +964,6 @@ func (b *LocalBackend) enterState(newState State) {
b.state = newState
prefs := b.prefs
notify := b.notify
bc := b.c
b.mu.Unlock()
if state == newState {
@@ -1123,10 +975,6 @@ func (b *LocalBackend) enterState(newState State) {
b.send(Notify{State: &newState})
}
if bc != nil {
bc.SetPaused(newState == Stopped)
}
switch newState {
case NeedsLogin:
b.blockEngineUpdates(true)

View File

@@ -1,107 +0,0 @@
// Copyright (c) 2020 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 ipn
import (
"testing"
"time"
"tailscale.com/control/controlclient"
"tailscale.com/logtail"
"tailscale.com/tailcfg"
"tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/wgengine"
)
// TestLocalLogLines tests to make sure that the log lines required for log parsing are
// being logged by the expected functions. Update these tests if moving log lines between
// functions.
func TestLocalLogLines(t *testing.T) {
logListen := tstest.ListenFor(t.Logf, []string{
"SetPrefs: %v",
"peer keys: %s",
"v%v peers: %v",
})
logid := func(hex byte) logtail.PublicID {
var ret logtail.PublicID
for i := 0; i < len(ret); i++ {
ret[i] = hex
}
return ret
}
idA := logid(0xaa)
// set up a LocalBackend, super bare bones. No functional data.
store := &MemoryStore{
cache: make(map[StateKey][]byte),
}
e, err := wgengine.NewFakeUserspaceEngine(logListen.Logf, 0)
if err != nil {
t.Fatal(err)
}
lb, err := NewLocalBackend(logListen.Logf, idA.String(), store, e)
if err != nil {
t.Fatal(err)
}
// custom adjustments for required non-nil fields
lb.prefs = NewPrefs()
lb.hostinfo = &tailcfg.Hostinfo{}
// hacky manual override of the usual log-on-change behaviour of keylogf
lb.keyLogf = logListen.Logf
// testing infrastructure
type linesTest struct {
name string
want []string
}
tests := []linesTest{
{
name: "after prefs",
want: []string{
"peer keys: %s",
"v%v peers: %v",
},
},
{
name: "after peers",
want: []string{},
},
}
testLogs := func(want linesTest) func(t *testing.T) {
return func(t *testing.T) {
if linesLeft := logListen.Check(); len(linesLeft) != len(want.want) {
t.Errorf("got %v, expected %v", linesLeft, want)
}
}
}
// log prefs line
persist := &controlclient.Persist{}
prefs := NewPrefs()
prefs.Persist = persist
lb.SetPrefs(prefs)
t.Run(tests[0].name, testLogs(tests[0]))
// log peers, peer keys
status := &wgengine.Status{
Peers: []wgengine.PeerStatus{wgengine.PeerStatus{
TxBytes: 10,
RxBytes: 10,
LastHandshake: time.Now(),
NodeKey: tailcfg.NodeKey(key.NewPrivate()),
}},
LocalAddrs: []string{"idk an address"},
}
lb.parseWgStatus(status)
t.Run(tests[1].name, testLogs(tests[1]))
}

View File

@@ -33,10 +33,6 @@ type FakeExpireAfterArgs struct {
Duration time.Duration
}
type PingArgs struct {
IP string
}
// Command is a command message that is JSON encoded and sent by a
// frontend to a backend.
type Command struct {
@@ -57,11 +53,9 @@ type Command struct {
Login *oauth2.Token
Logout *NoArgs
SetPrefs *SetPrefsArgs
SetWantRunning *bool
RequestEngineStatus *NoArgs
RequestStatus *NoArgs
FakeExpireAfter *FakeExpireAfterArgs
Ping *PingArgs
}
type BackendServer struct {
@@ -145,9 +139,6 @@ func (bs *BackendServer) GotCommand(cmd *Command) error {
} else if c := cmd.SetPrefs; c != nil {
bs.b.SetPrefs(c.New)
return nil
} else if c := cmd.SetWantRunning; c != nil {
bs.b.SetWantRunning(*c)
return nil
} else if c := cmd.RequestEngineStatus; c != nil {
bs.b.RequestEngineStatus()
return nil
@@ -157,9 +148,6 @@ func (bs *BackendServer) GotCommand(cmd *Command) error {
} else if c := cmd.FakeExpireAfter; c != nil {
bs.b.FakeExpireAfter(c.Duration)
return nil
} else if c := cmd.Ping; c != nil {
bs.b.Ping(c.IP)
return nil
} else {
return fmt.Errorf("BackendServer.Do: no command specified")
}
@@ -266,16 +254,8 @@ func (bc *BackendClient) FakeExpireAfter(x time.Duration) {
bc.send(Command{FakeExpireAfter: &FakeExpireAfterArgs{Duration: x}})
}
func (bc *BackendClient) Ping(ip string) {
bc.send(Command{Ping: &PingArgs{IP: ip}})
}
func (bc *BackendClient) SetWantRunning(v bool) {
bc.send(Command{SetWantRunning: &v})
}
// MaxMessageSize is the maximum message size, in bytes.
const MaxMessageSize = 10 << 20
const MaxMessageSize = 1 << 20
// TODO(apenwarr): incremental json decode?
// That would let us avoid storing the whole byte array uselessly in RAM.

View File

@@ -31,10 +31,7 @@ import (
"tailscale.com/logtail/filch"
"tailscale.com/net/netns"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy"
"tailscale.com/paths"
"tailscale.com/smallzstd"
"tailscale.com/types/logger"
"tailscale.com/version"
)
@@ -57,7 +54,7 @@ type Policy struct {
func (c *Config) ToBytes() []byte {
data, err := json.MarshalIndent(c, "", "\t")
if err != nil {
log.Fatalf("logpolicy.Config marshal: %v", err)
log.Fatalf("logpolicy.Config marshal: %v\n", err)
}
return data
}
@@ -104,36 +101,21 @@ func (l logWriter) Write(buf []byte) (int, error) {
// logsDir returns the directory to use for log configuration and
// buffer storage.
func logsDir(logf logger.Logf) string {
// STATE_DIRECTORY is set by systemd 240+ but we support older
// systems-d. For example, Ubuntu 18.04 (Bionic Beaver) is 237.
func logsDir() string {
systemdStateDir := os.Getenv("STATE_DIRECTORY")
if systemdStateDir != "" {
logf("logpolicy: using $STATE_DIRECTORY, %q", systemdStateDir)
return systemdStateDir
}
// Default to e.g. /var/lib/tailscale or /var/db/tailscale on Unix.
if d := paths.DefaultTailscaledStateFile(); d != "" {
d = filepath.Dir(d) // directory of e.g. "/var/lib/tailscale/tailscaled.state"
if err := os.MkdirAll(d, 0700); err == nil {
logf("logpolicy: using system state directory %q", d)
return d
}
}
cacheDir, err := os.UserCacheDir()
if err == nil {
d := filepath.Join(cacheDir, "Tailscale")
logf("logpolicy: using UserCacheDir, %q", d)
return d
return filepath.Join(cacheDir, "Tailscale")
}
// Use the current working directory, unless we're being run by a
// service manager that sets it to /.
wd, err := os.Getwd()
if err == nil && wd != "/" {
logf("logpolicy: using current directory, %q", wd)
return wd
}
@@ -144,7 +126,6 @@ func logsDir(logf logger.Logf) string {
if err != nil {
panic("no safe place found to store log state")
}
logf("logpolicy: using temp directory, %q", tmp)
return tmp
}
@@ -168,14 +149,6 @@ func runningUnderSystemd() bool {
// moved from whereever it does exist, into dir. Leftover logs state
// in / and $CACHE_DIRECTORY is deleted.
func tryFixLogStateLocation(dir, cmdname string) {
switch runtime.GOOS {
case "linux", "freebsd", "openbsd":
// These are the OSes where we might have written stuff into
// root. Others use different logic to find the logs storage
// dir.
default:
return
}
if cmdname == "" {
log.Printf("[unexpected] no cmdname given to tryFixLogStateLocation, please file a bug at https://github.com/tailscale/tailscale")
return
@@ -190,6 +163,14 @@ func tryFixLogStateLocation(dir, cmdname string) {
// Only root could have written log configs to weird places.
return
}
switch runtime.GOOS {
case "linux", "freebsd", "openbsd":
// These are the OSes where we might have written stuff into
// root. Others use different logic to find the logs storage
// dir.
default:
return
}
// We stored logs in 2 incorrect places: either /, or CACHE_DIR
// (aka /var/cache/tailscale). We want to move files into the
@@ -322,28 +303,23 @@ func New(collection string) *Policy {
}
console := log.New(stderrWriter{}, "", lflags)
var earlyErrBuf bytes.Buffer
earlyLogf := func(format string, a ...interface{}) {
fmt.Fprintf(&earlyErrBuf, format, a...)
earlyErrBuf.WriteByte('\n')
dir := logsDir()
if runtime.GOOS != "windows" { // version.CmdName call was blowing some Windows stack limit via goversion DLL loading
tryFixLogStateLocation(dir, version.CmdName())
}
dir := logsDir(earlyLogf)
cmdName := version.CmdName()
tryFixLogStateLocation(dir, cmdName)
cfgPath := filepath.Join(dir, fmt.Sprintf("%s.log.conf", cmdName))
cfgPath := filepath.Join(dir, fmt.Sprintf("%s.log.conf", version.CmdName()))
var oldc *Config
data, err := ioutil.ReadFile(cfgPath)
if err != nil {
earlyLogf("logpolicy.Read %v: %v", cfgPath, err)
log.Printf("logpolicy.Read %v: %v\n", cfgPath, err)
oldc = &Config{}
oldc.Collection = collection
} else {
oldc, err = ConfigFromBytes(data)
if err != nil {
earlyLogf("logpolicy.Config unmarshal: %v", err)
log.Printf("logpolicy.Config unmarshal: %v\n", err)
oldc = &Config{}
}
}
@@ -365,7 +341,7 @@ func New(collection string) *Policy {
newc.PublicID = newc.PrivateID.Public()
if newc != *oldc {
if err := newc.save(cfgPath); err != nil {
earlyLogf("logpolicy.Config.Save: %v", err)
log.Printf("logpolicy.Config.Save: %v\n", err)
}
}
@@ -383,7 +359,7 @@ func New(collection string) *Policy {
HTTPC: &http.Client{Transport: newLogtailTransport(logtail.DefaultHost)},
}
filchBuf, filchErr := filch.New(filepath.Join(dir, cmdName), filch.Options{})
filchBuf, filchErr := filch.New(filepath.Join(dir, version.CmdName()), filch.Options{})
if filchBuf != nil {
c.Buffer = filchBuf
}
@@ -391,17 +367,14 @@ func New(collection string) *Policy {
log.SetFlags(0) // other logflags are set on console, not here
log.SetOutput(lw)
log.Printf("Program starting: v%v, Go %v: %#v",
log.Printf("Program starting: v%v, Go %v: %#v\n",
version.LONG,
strings.TrimPrefix(runtime.Version(), "go"),
os.Args)
log.Printf("LogID: %v", newc.PublicID)
log.Printf("LogID: %v\n", newc.PublicID)
if filchErr != nil {
log.Printf("filch failed: %v", filchErr)
}
if earlyErrBuf.Len() != 0 {
log.Printf("%s", earlyErrBuf.Bytes())
}
return &Policy{
Logtail: lw,
@@ -420,7 +393,7 @@ func (p *Policy) Close() {
// log upload if it can be done before ctx is canceled.
func (p *Policy) Shutdown(ctx context.Context) error {
if p.Logtail != nil {
log.Printf("flushing log.")
log.Printf("flushing log.\n")
return p.Logtail.Shutdown(ctx)
}
return nil
@@ -432,9 +405,6 @@ func newLogtailTransport(host string) *http.Transport {
// Start with a copy of http.DefaultTransport and tweak it a bit.
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
// We do our own zstd compression on uploads, and responses never contain any payload,
// so don't send "Accept-Encoding: gzip" to save a few bytes on the wire, since there
// will never be any body to decompress:

View File

@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package backoff provides a back-off timer type.
package backoff
import (
@@ -13,70 +12,54 @@ import (
"tailscale.com/types/logger"
)
// Backoff tracks state the history of consecutive failures and sleeps
// an increasing amount of time, up to a provided limit.
type Backoff struct {
n int // number of consecutive failures
maxBackoff time.Duration
const MAX_BACKOFF_MSEC = 30000
type Backoff struct {
n int
// Name is the name of this backoff timer, for logging purposes.
name string
// logf is the function used for log messages when backing off.
logf logger.Logf
// NewTimer is the function that acts like time.NewTimer.
// It's for use in unit tests.
NewTimer func(time.Duration) *time.Timer
// NewTimer is the function that acts like time.NewTimer().
// You can override this in unit tests.
NewTimer func(d time.Duration) *time.Timer
// LogLongerThan sets the minimum time of a single backoff interval
// before we mention it in the log.
LogLongerThan time.Duration
}
// NewBackoff returns a new Backoff timer with the provided name (for logging), logger,
// and max backoff time. By default, all failures (calls to BackOff with a non-nil err)
// are logged unless the returned Backoff.LogLongerThan is adjusted.
func NewBackoff(name string, logf logger.Logf, maxBackoff time.Duration) *Backoff {
return &Backoff{
name: name,
logf: logf,
maxBackoff: maxBackoff,
NewTimer: time.NewTimer,
func NewBackoff(name string, logf logger.Logf) Backoff {
return Backoff{
name: name,
logf: logf,
NewTimer: time.NewTimer,
}
}
// Backoff sleeps an increasing amount of time if err is non-nil.
// and the context is not a
// It resets the backoff schedule once err is nil.
func (b *Backoff) BackOff(ctx context.Context, err error) {
if err == nil {
// No error. Reset number of consecutive failures.
if ctx.Err() == nil && err != nil {
b.n++
// n^2 backoff timer is a little smoother than the
// common choice of 2^n.
msec := b.n * b.n * 10
if msec > MAX_BACKOFF_MSEC {
msec = MAX_BACKOFF_MSEC
}
// Randomize the delay between 0.5-1.5 x msec, in order
// to prevent accidental "thundering herd" problems.
msec = rand.Intn(msec) + msec/2
dur := time.Duration(msec) * time.Millisecond
if dur >= b.LogLongerThan {
b.logf("%s: backoff: %d msec\n", b.name, msec)
}
t := b.NewTimer(dur)
select {
case <-ctx.Done():
t.Stop()
case <-t.C:
}
} else {
// not a regular error
b.n = 0
return
}
if ctx.Err() != nil {
// Fast path.
return
}
b.n++
// n^2 backoff timer is a little smoother than the
// common choice of 2^n.
d := time.Duration(b.n*b.n) * 10 * time.Millisecond
if d > b.maxBackoff {
d = b.maxBackoff
}
// Randomize the delay between 0.5-1.5 x msec, in order
// to prevent accidental "thundering herd" problems.
d = time.Duration(float64(d) * (rand.Float64() + 0.5))
if d >= b.LogLongerThan {
b.logf("%s: backoff: %d msec", b.name, d.Milliseconds())
}
t := b.NewTimer(d)
select {
case <-ctx.Done():
t.Stop()
case <-t.C:
}
}

View File

@@ -105,7 +105,7 @@ func Log(cfg Config, logf tslogger.Logf) Logger {
sentinel: make(chan int32, 16),
drainLogs: cfg.DrainLogs,
timeNow: cfg.TimeNow,
bo: backoff.NewBackoff("logtail", logf, 30*time.Second),
bo: backoff.NewBackoff("logtail", logf),
shutdownStart: make(chan struct{}),
shutdownDone: make(chan struct{}),
@@ -133,7 +133,7 @@ type logger struct {
drainLogs <-chan struct{} // if non-nil, external signal to attempt a drain
sentinel chan int32
timeNow func() time.Time
bo *backoff.Backoff
bo backoff.Backoff
zstdEncoder Encoder
uploadCancel func()
@@ -462,6 +462,5 @@ func (l *logger) Write(buf []byte) (int, error) {
}
}
b := l.encode(buf)
_, err := l.send(b)
return len(buf), err
return l.send(b)
}

View File

@@ -32,18 +32,3 @@ func TestLoggerEncodeTextAllocs(t *testing.T) {
t.Logf("allocs = %d; want 1", int(n))
}
}
func TestLoggerWriteLength(t *testing.T) {
lg := &logger{
timeNow: time.Now,
buffer: NewMemoryBuffer(1024),
}
inBuf := []byte("some text to encode")
n, err := lg.Write(inBuf)
if err != nil {
t.Error(err)
}
if n != len(inBuf) {
t.Errorf("logger.Write wrote %d bytes, expected %d", n, len(inBuf))
}
}

View File

@@ -8,19 +8,13 @@ package interfaces
import (
"fmt"
"net"
"net/http"
"reflect"
"strings"
"inet.af/netaddr"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tshttpproxy"
)
// LoginEndpointForProxyDetermination is the URL used for testing
// which HTTP proxy the system should use.
var LoginEndpointForProxyDetermination = "https://login.tailscale.com/"
// Tailscale returns the current machine's Tailscale interface, if any.
// If none is found, all zero values are returned.
// A non-nil error is only returned on a problem listing the system interfaces.
@@ -49,8 +43,7 @@ func Tailscale() (net.IP, *net.Interface, error) {
// maybeTailscaleInterfaceName reports whether s is an interface
// name that might be used by Tailscale.
func maybeTailscaleInterfaceName(s string) bool {
return s == "Tailscale" ||
strings.HasPrefix(s, "wg") ||
return strings.HasPrefix(s, "wg") ||
strings.HasPrefix(s, "ts") ||
strings.HasPrefix(s, "tailscale") ||
strings.HasPrefix(s, "utun")
@@ -170,13 +163,6 @@ type State struct {
// considered "expensive", which currently means LTE/etc
// instead of Wifi. This field is not populated by GetState.
IsExpensive bool
// DefaultRouteInterface is the interface name for the machine's default route.
// It is not yet populated on all OSes.
DefaultRouteInterface string
// HTTPProxy is the HTTP proxy to use.
HTTPProxy string
}
func (s *State) Equal(s2 *State) bool {
@@ -189,8 +175,7 @@ func (s *State) Equal(s2 *State) bool {
// /^tailscale/)
func (s *State) RemoveTailscaleInterfaces() {
for name := range s.InterfaceIPs {
if name == "Tailscale" || // as it is on Windows
strings.HasPrefix(name, "tailscale") { // TODO: use --tun flag value, etc; see TODO in method doc
if strings.HasPrefix(name, "tailscale") { // TODO: use --tun flag value, etc; see TODO in method doc
delete(s.InterfaceIPs, name)
delete(s.InterfaceUp, name)
}
@@ -213,16 +198,6 @@ func GetState() (*State, error) {
}); err != nil {
return nil, err
}
s.DefaultRouteInterface, _ = DefaultRouteInterface()
req, err := http.NewRequest("GET", LoginEndpointForProxyDetermination, nil)
if err != nil {
return nil, err
}
if u, err := tshttpproxy.ProxyFromEnvironment(req); err == nil && u != nil {
s.HTTPProxy = u.String()
}
return s, nil
}

View File

@@ -10,9 +10,12 @@ import (
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/util/lineread"
"tailscale.com/version"
)
func init() {
likelyHomeRouterIP = likelyHomeRouterIPDarwin
}
/*
Parse out 10.0.0.1 from:
@@ -28,13 +31,7 @@ default link#14 UCSI utun2
...
*/
func likelyHomeRouterIPDarwinExec() (ret netaddr.IP, ok bool) {
if version.IsMobile() {
// Don't try to do subprocesses on iOS. Ends up with log spam like:
// kernel: "Sandbox: IPNExtension(86580) deny(1) process-fork"
// This is why we have likelyHomeRouterIPDarwinSyscall.
return ret, false
}
func likelyHomeRouterIPDarwin() (ret netaddr.IP, ok bool) {
cmd := exec.Command("/usr/sbin/netstat", "-r", "-n", "-f", "inet")
stdout, err := cmd.StdoutPipe()
if err != nil {

View File

@@ -1,124 +0,0 @@
// Copyright (c) 2020 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.
// +build darwin,cgo
package interfaces
/*
#import "route.h"
#import <netinet/in.h>
#import <sys/sysctl.h>
#import <stdlib.h>
#import <stdio.h>
// privateGatewayIPFromRoute returns the private gateway ip address from rtm, if it exists.
// Otherwise, it returns 0.
int privateGatewayIPFromRoute(struct rt_msghdr2 *rtm)
{
// sockaddrs are after the message header
struct sockaddr* dst_sa = (struct sockaddr *)(rtm + 1);
if((rtm->rtm_addrs & (RTA_DST|RTA_GATEWAY)) != (RTA_DST|RTA_GATEWAY))
return 0; // missing dst or gateway addr
if (dst_sa->sa_family != AF_INET)
return 0; // dst not IPv4
if ((rtm->rtm_flags & RTF_GATEWAY) == 0)
return 0; // gateway flag not set
struct sockaddr_in* dst_si = (struct sockaddr_in *)dst_sa;
if (dst_si->sin_addr.s_addr != INADDR_ANY)
return 0; // not default route
#define ROUNDUP(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
struct sockaddr* gateway_sa = (struct sockaddr *)((char *)dst_sa + ROUNDUP(dst_sa->sa_len));
if (gateway_sa->sa_family != AF_INET)
return 0; // gateway not IPv4
struct sockaddr_in* gateway_si= (struct sockaddr_in *)gateway_sa;
int ip;
ip = gateway_si->sin_addr.s_addr;
unsigned char a, b;
a = (ip >> 0) & 0xff;
b = (ip >> 8) & 0xff;
// Check whether ip is private, that is, whether it is
// in one of 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16.
if (a == 10)
return ip; // matches 10.0.0.0/8
if (a == 172 && (b >> 4) == 1)
return ip; // matches 172.16.0.0/12
if (a == 192 && b == 168)
return ip; // matches 192.168.0.0/16
// Not a private IP.
return 0;
}
// privateGatewayIP returns the private gateway IP address, if it exists.
// If no private gateway IP address was found, it returns 0.
// On an error, it returns an error code in (0, 255].
// Any private gateway IP address is > 255.
int privateGatewayIP()
{
size_t needed;
int mib[6];
char *buf;
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = 0;
mib[4] = NET_RT_DUMP2;
mib[5] = 0;
if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
return 1; // route dump size estimation failed
if ((buf = malloc(needed)) == 0)
return 2; // malloc failed
if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
free(buf);
return 3; // route dump failed
}
// Loop over all routes.
char *next, *lim;
lim = buf + needed;
struct rt_msghdr2 *rtm;
for (next = buf; next < lim; next += rtm->rtm_msglen) {
rtm = (struct rt_msghdr2 *)next;
int ip;
ip = privateGatewayIPFromRoute(rtm);
if (ip) {
free(buf);
return ip;
}
}
free(buf);
return 0; // no gateway found
}
*/
import "C"
import (
"encoding/binary"
"inet.af/netaddr"
)
func init() {
likelyHomeRouterIP = likelyHomeRouterIPDarwinSyscall
}
func likelyHomeRouterIPDarwinSyscall() (ret netaddr.IP, ok bool) {
ip := C.privateGatewayIP()
if ip < 255 {
return netaddr.IP{}, false
}
var q [4]byte
binary.LittleEndian.PutUint32(q[:], uint32(ip))
return netaddr.IPv4(q[0], q[1], q[2], q[3]), true
}

View File

@@ -1,20 +0,0 @@
// Copyright (c) 2020 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.
// +build cgo,darwin
package interfaces
import "testing"
func TestLikelyHomeRouterIPSyscallExec(t *testing.T) {
syscallIP, syscallOK := likelyHomeRouterIPDarwinSyscall()
netstatIP, netstatOK := likelyHomeRouterIPDarwinExec()
if syscallOK != netstatOK || syscallIP != netstatIP {
t.Errorf("syscall() = %v, %v, netstat = %v, %v",
syscallIP, syscallOK,
netstatIP, netstatOK,
)
}
}

View File

@@ -1,11 +0,0 @@
// Copyright (c) 2020 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.
// +build darwin,!cgo
package interfaces
func init() {
likelyHomeRouterIP = likelyHomeRouterIPDarwinExec
}

View File

@@ -1,15 +0,0 @@
// Copyright (c) 2020 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.
// +build !linux
package interfaces
import "errors"
var errTODO = errors.New("TODO")
func DefaultRouteInterface() (string, error) {
return "TODO", errTODO
}

View File

@@ -5,19 +5,8 @@
package interfaces
import (
"bufio"
"bytes"
"errors"
"io"
"log"
"os"
"os/exec"
"runtime"
"strings"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/syncs"
"tailscale.com/util/lineread"
)
@@ -25,8 +14,6 @@ func init() {
likelyHomeRouterIP = likelyHomeRouterIPLinux
}
var procNetRouteErr syncs.AtomicBool
/*
Parse 10.0.0.1 out of:
@@ -36,17 +23,9 @@ ens18 00000000 0100000A 0003 0 0 0 00000000
ens18 0000000A 00000000 0001 0 0 0 0000FFFF 0 0 0
*/
func likelyHomeRouterIPLinux() (ret netaddr.IP, ok bool) {
if procNetRouteErr.Get() {
// If we failed to read /proc/net/route previously, don't keep trying.
// But if we're on Android, go into the Android path.
if runtime.GOOS == "android" {
return likelyHomeRouterIPAndroid()
}
return ret, false
}
lineNum := 0
var f []mem.RO
err := lineread.File("/proc/net/route", func(line []byte) error {
lineread.File("/proc/net/route", func(line []byte) error {
lineNum++
if lineNum == 1 {
// Skip header line.
@@ -76,135 +55,5 @@ func likelyHomeRouterIPLinux() (ret netaddr.IP, ok bool) {
}
return nil
})
if err != nil {
procNetRouteErr.Set(true)
if runtime.GOOS == "android" {
return likelyHomeRouterIPAndroid()
}
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
}
return ret, !ret.IsZero()
}
// Android apps don't have permission to read /proc/net/route, at
// least on Google devices and the Android emulator.
func likelyHomeRouterIPAndroid() (ret netaddr.IP, ok bool) {
cmd := exec.Command("/system/bin/ip", "route", "show", "table", "0")
out, err := cmd.StdoutPipe()
if err != nil {
return
}
if err := cmd.Start(); err != nil {
log.Printf("interfaces: running /system/bin/ip: %v", err)
return
}
// Search for line like "default via 10.0.2.2 dev radio0 table 1016 proto static mtu 1500 "
lineread.Reader(out, func(line []byte) error {
const pfx = "default via "
if !mem.HasPrefix(mem.B(line), mem.S(pfx)) {
return nil
}
line = line[len(pfx):]
sp := bytes.IndexByte(line, ' ')
if sp == -1 {
return nil
}
ipb := line[:sp]
if ip, err := netaddr.ParseIP(string(ipb)); err == nil && ip.Is4() {
ret = ip
log.Printf("interfaces: found Android default route %v", ip)
}
return nil
})
cmd.Process.Kill()
cmd.Wait()
return ret, !ret.IsZero()
}
// DefaultRouteInterface returns the name of the network interface that owns
// the default route, not including any tailscale interfaces.
func DefaultRouteInterface() (string, error) {
v, err := defaultRouteInterfaceProcNet()
if err == nil {
return v, nil
}
if runtime.GOOS == "android" {
return defaultRouteInterfaceAndroidIPRoute()
}
return v, err
}
var zeroRouteBytes = []byte("00000000")
func defaultRouteInterfaceProcNet() (string, error) {
f, err := os.Open("/proc/net/route")
if err != nil {
return "", err
}
defer f.Close()
br := bufio.NewReaderSize(f, 128)
for {
line, err := br.ReadSlice('\n')
if err == io.EOF {
break
}
if err != nil {
return "", err
}
if !bytes.Contains(line, zeroRouteBytes) {
continue
}
fields := strings.Fields(string(line))
ifc := fields[0]
ip := fields[1]
netmask := fields[7]
if strings.HasPrefix(ifc, "tailscale") ||
strings.HasPrefix(ifc, "wg") {
continue
}
if ip == "00000000" && netmask == "00000000" {
// default route
return ifc, nil // interface name
}
}
return "", errors.New("no default routes found")
}
// defaultRouteInterfaceAndroidIPRoute tries to find the machine's default route interface name
// by parsing the "ip route" command output. We use this on Android where /proc/net/route
// can be missing entries or have locked-down permissions.
// See also comments in https://github.com/tailscale/tailscale/pull/666.
func defaultRouteInterfaceAndroidIPRoute() (ifname string, err error) {
cmd := exec.Command("/system/bin/ip", "route", "show", "table", "0")
out, err := cmd.StdoutPipe()
if err != nil {
return "", err
}
if err := cmd.Start(); err != nil {
log.Printf("interfaces: running /system/bin/ip: %v", err)
return "", err
}
// Search for line like "default via 10.0.2.2 dev radio0 table 1016 proto static mtu 1500 "
lineread.Reader(out, func(line []byte) error {
const pfx = "default via "
if !mem.HasPrefix(mem.B(line), mem.S(pfx)) {
return nil
}
ff := strings.Fields(string(line))
for i, v := range ff {
if i > 0 && ff[i-1] == "dev" && ifname == "" {
ifname = v
}
}
return nil
})
cmd.Process.Kill()
cmd.Wait()
if ifname == "" {
return "", errors.New("no default routes found")
}
return ifname, nil
}

View File

@@ -1,16 +0,0 @@
// Copyright (c) 2020 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 interfaces
import "testing"
func BenchmarkDefaultRouteInterface(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := DefaultRouteInterface(); err != nil {
b.Fatal(err)
}
}
}

View File

@@ -1,257 +0,0 @@
/*
* Copyright (c) 2000-2017 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. The rights granted to you under the License
* may not be used to create, or enable the creation or redistribution of,
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
*
* Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
/*
* Copyright (c) 1980, 1986, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)route.h 8.3 (Berkeley) 4/19/94
* $FreeBSD: src/sys/net/route.h,v 1.36.2.1 2000/08/16 06:14:23 jayanth Exp $
*/
#ifndef _NET_ROUTE_H_
#define _NET_ROUTE_H_
#include <sys/appleapiopts.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
/*
* These numbers are used by reliable protocols for determining
* retransmission behavior and are included in the routing structure.
*/
struct rt_metrics {
u_int32_t rmx_locks; /* Kernel leaves these values alone */
u_int32_t rmx_mtu; /* MTU for this path */
u_int32_t rmx_hopcount; /* max hops expected */
int32_t rmx_expire; /* lifetime for route, e.g. redirect */
u_int32_t rmx_recvpipe; /* inbound delay-bandwidth product */
u_int32_t rmx_sendpipe; /* outbound delay-bandwidth product */
u_int32_t rmx_ssthresh; /* outbound gateway buffer limit */
u_int32_t rmx_rtt; /* estimated round trip time */
u_int32_t rmx_rttvar; /* estimated rtt variance */
u_int32_t rmx_pksent; /* packets sent using this route */
u_int32_t rmx_state; /* route state */
u_int32_t rmx_filler[3]; /* will be used for T/TCP later */
};
/*
* rmx_rtt and rmx_rttvar are stored as microseconds;
*/
#define RTM_RTTUNIT 1000000 /* units for rtt, rttvar, as units per sec */
#define RTF_UP 0x1 /* route usable */
#define RTF_GATEWAY 0x2 /* destination is a gateway */
#define RTF_HOST 0x4 /* host entry (net otherwise) */
#define RTF_REJECT 0x8 /* host or net unreachable */
#define RTF_DYNAMIC 0x10 /* created dynamically (by redirect) */
#define RTF_MODIFIED 0x20 /* modified dynamically (by redirect) */
#define RTF_DONE 0x40 /* message confirmed */
#define RTF_DELCLONE 0x80 /* delete cloned route */
#define RTF_CLONING 0x100 /* generate new routes on use */
#define RTF_XRESOLVE 0x200 /* external daemon resolves name */
#define RTF_LLINFO 0x400 /* DEPRECATED - exists ONLY for backward
* compatibility */
#define RTF_LLDATA 0x400 /* used by apps to add/del L2 entries */
#define RTF_STATIC 0x800 /* manually added */
#define RTF_BLACKHOLE 0x1000 /* just discard pkts (during updates) */
#define RTF_NOIFREF 0x2000 /* not eligible for RTF_IFREF */
#define RTF_PROTO2 0x4000 /* protocol specific routing flag */
#define RTF_PROTO1 0x8000 /* protocol specific routing flag */
#define RTF_PRCLONING 0x10000 /* protocol requires cloning */
#define RTF_WASCLONED 0x20000 /* route generated through cloning */
#define RTF_PROTO3 0x40000 /* protocol specific routing flag */
/* 0x80000 unused */
#define RTF_PINNED 0x100000 /* future use */
#define RTF_LOCAL 0x200000 /* route represents a local address */
#define RTF_BROADCAST 0x400000 /* route represents a bcast address */
#define RTF_MULTICAST 0x800000 /* route represents a mcast address */
#define RTF_IFSCOPE 0x1000000 /* has valid interface scope */
#define RTF_CONDEMNED 0x2000000 /* defunct; no longer modifiable */
#define RTF_IFREF 0x4000000 /* route holds a ref to interface */
#define RTF_PROXY 0x8000000 /* proxying, no interface scope */
#define RTF_ROUTER 0x10000000 /* host is a router */
#define RTF_DEAD 0x20000000 /* Route entry is being freed */
/* 0x40000000 and up unassigned */
#define RTPRF_OURS RTF_PROTO3 /* set on routes we manage */
#define RTF_BITS \
"\020\1UP\2GATEWAY\3HOST\4REJECT\5DYNAMIC\6MODIFIED\7DONE" \
"\10DELCLONE\11CLONING\12XRESOLVE\13LLINFO\14STATIC\15BLACKHOLE" \
"\16NOIFREF\17PROTO2\20PROTO1\21PRCLONING\22WASCLONED\23PROTO3" \
"\25PINNED\26LOCAL\27BROADCAST\30MULTICAST\31IFSCOPE\32CONDEMNED" \
"\33IFREF\34PROXY\35ROUTER"
#define IS_DIRECT_HOSTROUTE(rt) \
(((rt)->rt_flags & (RTF_HOST | RTF_GATEWAY)) == RTF_HOST)
/*
* Routing statistics.
*/
struct rtstat {
short rts_badredirect; /* bogus redirect calls */
short rts_dynamic; /* routes created by redirects */
short rts_newgateway; /* routes modified by redirects */
short rts_unreach; /* lookups which failed */
short rts_wildcard; /* lookups satisfied by a wildcard */
short rts_badrtgwroute; /* route to gateway is not direct */
};
/*
* Structures for routing messages.
*/
struct rt_msghdr {
u_short rtm_msglen; /* to skip over non-understood messages */
u_char rtm_version; /* future binary compatibility */
u_char rtm_type; /* message type */
u_short rtm_index; /* index for associated ifp */
int rtm_flags; /* flags, incl. kern & message, e.g. DONE */
int rtm_addrs; /* bitmask identifying sockaddrs in msg */
pid_t rtm_pid; /* identify sender */
int rtm_seq; /* for sender to identify action */
int rtm_errno; /* why failed */
int rtm_use; /* from rtentry */
u_int32_t rtm_inits; /* which metrics we are initializing */
struct rt_metrics rtm_rmx; /* metrics themselves */
};
struct rt_msghdr2 {
u_short rtm_msglen; /* to skip over non-understood messages */
u_char rtm_version; /* future binary compatibility */
u_char rtm_type; /* message type */
u_short rtm_index; /* index for associated ifp */
int rtm_flags; /* flags, incl. kern & message, e.g. DONE */
int rtm_addrs; /* bitmask identifying sockaddrs in msg */
int32_t rtm_refcnt; /* reference count */
int rtm_parentflags; /* flags of the parent route */
int rtm_reserved; /* reserved field set to 0 */
int rtm_use; /* from rtentry */
u_int32_t rtm_inits; /* which metrics we are initializing */
struct rt_metrics rtm_rmx; /* metrics themselves */
};
#define RTM_VERSION 5 /* Up the ante and ignore older versions */
/*
* Message types.
*/
#define RTM_ADD 0x1 /* Add Route */
#define RTM_DELETE 0x2 /* Delete Route */
#define RTM_CHANGE 0x3 /* Change Metrics or flags */
#define RTM_GET 0x4 /* Report Metrics */
#define RTM_LOSING 0x5 /* RTM_LOSING is no longer generated by xnu
* and is deprecated */
#define RTM_REDIRECT 0x6 /* Told to use different route */
#define RTM_MISS 0x7 /* Lookup failed on this address */
#define RTM_LOCK 0x8 /* fix specified metrics */
#define RTM_OLDADD 0x9 /* caused by SIOCADDRT */
#define RTM_OLDDEL 0xa /* caused by SIOCDELRT */
#define RTM_RESOLVE 0xb /* req to resolve dst to LL addr */
#define RTM_NEWADDR 0xc /* address being added to iface */
#define RTM_DELADDR 0xd /* address being removed from iface */
#define RTM_IFINFO 0xe /* iface going up/down etc. */
#define RTM_NEWMADDR 0xf /* mcast group membership being added to if */
#define RTM_DELMADDR 0x10 /* mcast group membership being deleted */
#define RTM_IFINFO2 0x12 /* */
#define RTM_NEWMADDR2 0x13 /* */
#define RTM_GET2 0x14 /* */
/*
* Bitmask values for rtm_inits and rmx_locks.
*/
#define RTV_MTU 0x1 /* init or lock _mtu */
#define RTV_HOPCOUNT 0x2 /* init or lock _hopcount */
#define RTV_EXPIRE 0x4 /* init or lock _expire */
#define RTV_RPIPE 0x8 /* init or lock _recvpipe */
#define RTV_SPIPE 0x10 /* init or lock _sendpipe */
#define RTV_SSTHRESH 0x20 /* init or lock _ssthresh */
#define RTV_RTT 0x40 /* init or lock _rtt */
#define RTV_RTTVAR 0x80 /* init or lock _rttvar */
/*
* Bitmask values for rtm_addrs.
*/
#define RTA_DST 0x1 /* destination sockaddr present */
#define RTA_GATEWAY 0x2 /* gateway sockaddr present */
#define RTA_NETMASK 0x4 /* netmask sockaddr present */
#define RTA_GENMASK 0x8 /* cloning mask sockaddr present */
#define RTA_IFP 0x10 /* interface name sockaddr present */
#define RTA_IFA 0x20 /* interface addr sockaddr present */
#define RTA_AUTHOR 0x40 /* sockaddr for author of redirect */
#define RTA_BRD 0x80 /* for NEWADDR, broadcast or p-p dest addr */
/*
* Index offsets for sockaddr array for alternate internal encoding.
*/
#define RTAX_DST 0 /* destination sockaddr present */
#define RTAX_GATEWAY 1 /* gateway sockaddr present */
#define RTAX_NETMASK 2 /* netmask sockaddr present */
#define RTAX_GENMASK 3 /* cloning mask sockaddr present */
#define RTAX_IFP 4 /* interface name sockaddr present */
#define RTAX_IFA 5 /* interface addr sockaddr present */
#define RTAX_AUTHOR 6 /* sockaddr for author of redirect */
#define RTAX_BRD 7 /* for NEWADDR, broadcast or p-p dest addr */
#define RTAX_MAX 8 /* size of array to allocate */
struct rt_addrinfo {
int rti_addrs;
struct sockaddr *rti_info[RTAX_MAX];
};
#endif /* _NET_ROUTE_H_ */

View File

@@ -18,9 +18,7 @@ import (
"log"
"net"
"net/http"
"os"
"sort"
"strconv"
"sync"
"time"
@@ -38,45 +36,6 @@ import (
"tailscale.com/types/opt"
)
// Debugging and experimentation tweakables.
var (
debugNetcheck, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_NETCHECK"))
)
// The various default timeouts for things.
const (
// overallProbeTimeout is the maximum amount of time netcheck will
// spend gathering a single report.
overallProbeTimeout = 5 * time.Second
// stunTimeout is the maximum amount of time netcheck will spend
// probing with STUN packets without getting a reply before
// switching to HTTP probing, on the assumption that outbound UDP
// is blocked.
stunProbeTimeout = 3 * time.Second
// hairpinCheckTimeout is the amount of time we wait for a
// hairpinned packet to come back.
hairpinCheckTimeout = 100 * time.Millisecond
// defaultActiveRetransmitTime is the retransmit interval we use
// for STUN probes when we're in steady state (not in start-up),
// but don't have previous latency information for a DERP
// node. This is a somewhat conservative guess because if we have
// no data, likely the DERP node is very far away and we have no
// data because we timed out the last time we probed it.
defaultActiveRetransmitTime = 200 * time.Millisecond
// defaultInitialRetransmitTime is the retransmit interval used
// when netcheck first runs. We have no past context to work with,
// and we want answers relatively quickly, so it's biased slightly
// more aggressive than defaultActiveRetransmitTime. A few extra
// packets at startup is fine.
defaultInitialRetransmitTime = 100 * time.Millisecond
// portMapServiceProbeTimeout is the time we wait for port mapping
// services (UPnP, NAT-PMP, PCP) to respond before we give up and
// decide that they're not there. Since these services are on the
// same LAN as this machine and a single L3 hop away, we don't
// give them much time to respond.
portMapServiceProbeTimeout = 100 * time.Millisecond
)
type Report struct {
UDP bool // UDP works
IPv6 bool // IPv6 works
@@ -171,15 +130,6 @@ type STUNConn interface {
ReadFrom([]byte) (int, net.Addr, error)
}
func (c *Client) enoughRegions() int {
if c.Verbose {
// Abuse verbose a bit here so netcheck can show all region latencies
// in verbose mode.
return 100
}
return 3
}
func (c *Client) logf(format string, a ...interface{}) {
if c.Logf != nil {
c.Logf(format, a...)
@@ -189,7 +139,7 @@ func (c *Client) logf(format string, a ...interface{}) {
}
func (c *Client) vlogf(format string, a ...interface{}) {
if c.Verbose || debugNetcheck {
if c.Verbose {
c.logf(format, a...)
}
}
@@ -220,8 +170,6 @@ func (c *Client) MakeNextReportFull() {
}
func (c *Client) ReceiveSTUNPacket(pkt []byte, src netaddr.IPPort) {
c.vlogf("received STUN packet from %s", src)
c.mu.Lock()
if c.handleHairSTUNLocked(pkt, src) {
c.mu.Unlock()
@@ -382,7 +330,7 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *interfaces.State, last *Report)
n := reg.Nodes[try%len(reg.Nodes)]
prevLatency := last.RegionLatency[reg.RegionID] * 120 / 100
if prevLatency == 0 {
prevLatency = defaultActiveRetransmitTime
prevLatency = 200 * time.Millisecond
}
delay := time.Duration(try) * prevLatency
if do4 {
@@ -405,12 +353,16 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *interfaces.State, last *Report)
func makeProbePlanInitial(dm *tailcfg.DERPMap, ifState *interfaces.State) (plan probePlan) {
plan = make(probePlan)
// initialSTUNTimeout is only 100ms because some extra retransmits
// when starting up is tolerable.
const initialSTUNTimeout = 100 * time.Millisecond
for _, reg := range dm.Regions {
var p4 []probe
var p6 []probe
for try := 0; try < 3; try++ {
n := reg.Nodes[try%len(reg.Nodes)]
delay := time.Duration(try) * defaultInitialRetransmitTime
delay := time.Duration(try) * initialSTUNTimeout
if ifState.HaveV4 && nodeMight4(n) {
p4 = append(p4, probe{delay: delay, node: n.Name, proto: probeIPv4})
}
@@ -566,7 +518,7 @@ func (rs *reportState) startHairCheckLocked(dst netaddr.IPPort) {
ua := dst.UDPAddr()
rs.pc4Hair.WriteTo(stun.Request(rs.hairTX), ua)
rs.c.vlogf("sent haircheck to %v", ua)
time.AfterFunc(hairpinCheckTimeout, func() { close(rs.hairTimeout) })
time.AfterFunc(500*time.Millisecond, func() { close(rs.hairTimeout) })
}
func (rs *reportState) waitHairCheck(ctx context.Context) {
@@ -587,7 +539,6 @@ func (rs *reportState) waitHairCheck(ctx context.Context) {
case <-rs.gotHairSTUN:
ret.HairPinning.Set(true)
case <-rs.hairTimeout:
rs.c.vlogf("hairCheck timeout")
ret.HairPinning.Set(false)
default:
select {
@@ -624,13 +575,12 @@ func (rs *reportState) addNodeLatency(node *tailcfg.DERPNode, ipp netaddr.IPPort
ret.UDP = true
updateLatency(ret.RegionLatency, node.RegionID, d)
// Once we've heard from enough regions (3), start a timer to
// give up on the other ones. The timer's duration is a
// function of whether this is our initial full probe or an
// incremental one. For incremental ones, wait for the
// duration of the slowest region. For initial ones, double
// that.
if len(ret.RegionLatency) == rs.c.enoughRegions() {
// Once we've heard from 3 regions, start a timer to give up
// on the other ones. The timer's duration is a function of
// whether this is our initial full probe or an incremental
// one. For incremental ones, wait for the duration of the
// slowest region. For initial ones, double that.
if len(ret.RegionLatency) == 3 {
timeout := maxDurationValue(ret.RegionLatency)
if !rs.incremental {
timeout *= 2
@@ -699,7 +649,7 @@ func (rs *reportState) probePortMapServices() {
}
defer uc.Close()
tempPort := uc.LocalAddr().(*net.UDPAddr).Port
uc.SetReadDeadline(time.Now().Add(portMapServiceProbeTimeout))
uc.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
// Send request packets for all three protocols.
uc.WriteTo(uPnPPacket, port1900)
@@ -777,10 +727,15 @@ func newReport() *Report {
//
// It may not be called concurrently with itself.
func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, error) {
// Wait for STUN for 3 seconds, but then give HTTP probing
// another 2 seconds if all UDP failed.
const overallTimeout = 5 * time.Second
const stunTimeout = 3 * time.Second
// Mask user context with ours that we guarantee to cancel so
// we can depend on it being closed in goroutines later.
// (User ctx might be context.Background, etc)
ctx, cancel := context.WithTimeout(ctx, overallProbeTimeout)
ctx, cancel := context.WithTimeout(ctx, overallTimeout)
defer cancel()
if dm == nil {
@@ -889,7 +844,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
}(probeSet)
}
stunTimer := time.NewTimer(stunProbeTimeout)
stunTimer := time.NewTimer(stunTimeout)
defer stunTimer.Stop()
select {
@@ -902,9 +857,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
}
rs.waitHairCheck(ctx)
c.vlogf("hairCheck done")
rs.waitPortMap.Wait()
c.vlogf("portMap done")
rs.stopTimers()
// Try HTTPS latency check if all STUN probes failed due to UDP presumably being blocked.
@@ -959,7 +912,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegion) (time.Duration, netaddr.IP, error) {
var result httpstat.Result
ctx, cancel := context.WithTimeout(httpstat.WithHTTPStat(ctx, &result), overallProbeTimeout)
ctx, cancel := context.WithTimeout(httpstat.WithHTTPStat(ctx, &result), 5*time.Second)
defer cancel()
var ip netaddr.IP

View File

@@ -5,15 +5,19 @@
package netns
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"golang.org/x/sys/unix"
"tailscale.com/net/interfaces"
)
// tailscaleBypassMark is the mark indicating that packets originating
@@ -39,6 +43,47 @@ func ipRuleAvailable() bool {
return ipRuleOnce.v
}
var zeroRouteBytes = []byte("00000000")
// defaultRouteInterface returns the name of the network interface that owns
// the default route, not including any tailscale interfaces. We only use
// this in SO_BINDTODEVICE mode.
func defaultRouteInterface() (string, error) {
f, err := os.Open("/proc/net/route")
if err != nil {
return "", err
}
defer f.Close()
br := bufio.NewReaderSize(f, 128)
for {
line, err := br.ReadSlice('\n')
if err == io.EOF {
break
}
if err != nil {
return "", err
}
if !bytes.Contains(line, zeroRouteBytes) {
continue
}
fields := strings.Fields(string(line))
ifc := fields[0]
ip := fields[1]
netmask := fields[7]
if strings.HasPrefix(ifc, "tailscale") ||
strings.HasPrefix(ifc, "wg") {
continue
}
if ip == "00000000" && netmask == "00000000" {
// default route
return ifc, nil // interface name
}
}
return "", errors.New("no default routes found")
}
// ignoreErrors returns true if we should ignore setsocketopt errors in
// this instance.
func ignoreErrors() bool {
@@ -88,7 +133,7 @@ func setBypassMark(fd uintptr) error {
}
func bindToDevice(fd uintptr) error {
ifc, err := interfaces.DefaultRouteInterface()
ifc, err := defaultRouteInterface()
if err != nil {
// Make sure we bind to *some* interface,
// or we could get a routing loop.

View File

@@ -49,3 +49,12 @@ func TestBypassMarkInSync(t *testing.T) {
}
t.Errorf("tailscaleBypassMark not found in router_linux.go")
}
func BenchmarkDefaultRouteInterface(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := defaultRouteInterface(); err != nil {
b.Fatal(err)
}
}
}

View File

@@ -137,15 +137,15 @@ func foreachAttr(b []byte, fn func(attrType uint16, a []byte) error) error {
}
attrType := binary.BigEndian.Uint16(b[:2])
attrLen := int(binary.BigEndian.Uint16(b[2:4]))
attrLenWithPad := (attrLen + 3) &^ 3
attrLenPad := attrLen % 4
b = b[4:]
if attrLenWithPad > len(b) {
if attrLen+attrLenPad > len(b) {
return ErrMalformedAttrs
}
if err := fn(attrType, b[:attrLen]); err != nil {
return err
}
b = b[attrLenWithPad:]
b = b[attrLen+attrLenPad:]
}
return nil
}

View File

@@ -140,41 +140,6 @@ var responseTests = []struct {
wantAddr: net.ParseIP("2602:d1:b4cf:c100:38b2:31ff:feef:96f6"),
wantPort: 37070,
},
// Testing STUN attribute padding rules using STUN software attribute
// with values of 1 & 3 length respectively before the XorMappedAddress attribute
{
name: "software-a",
data: []byte{
0x01, 0x01, 0x00, 0x14, 0x21, 0x12, 0xa4, 0x42,
0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
0x4f, 0x3e, 0x30, 0x8e, 0x80, 0x22, 0x00, 0x01,
0x61, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x08,
0x00, 0x01, 0xce, 0x66, 0x5e, 0x12, 0xa4, 0x43,
},
wantTID: []byte{
0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
0x4f, 0x3e, 0x30, 0x8e,
},
wantAddr: []byte{127, 0, 0, 1},
wantPort: 61300,
},
{
name: "software-abc",
data: []byte{
0x01, 0x01, 0x00, 0x14, 0x21, 0x12, 0xa4, 0x42,
0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
0x4f, 0x3e, 0x30, 0x8e, 0x80, 0x22, 0x00, 0x03,
0x61, 0x62, 0x63, 0x00, 0x00, 0x20, 0x00, 0x08,
0x00, 0x01, 0xce, 0x66, 0x5e, 0x12, 0xa4, 0x43,
},
wantTID: []byte{
0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
0x4f, 0x3e, 0x30, 0x8e,
},
wantAddr: []byte{127, 0, 0, 1},
wantPort: 61300,
},
}
func TestParseResponse(t *testing.T) {

View File

@@ -32,15 +32,6 @@ func CGNATRange() netaddr.IPPrefix {
var cgnatRange oncePrefix
// TailscaleServiceIP returns the listen address of services
// provided by Tailscale itself such as the Magic DNS proxy.
func TailscaleServiceIP() netaddr.IP {
serviceIP.Do(func() { mustIP(&serviceIP.v, "100.100.100.100") })
return serviceIP.v
}
var serviceIP onceIP
// IsTailscaleIP reports whether ip is an IP address in a range that
// Tailscale assigns from.
func IsTailscaleIP(ip netaddr.IP) bool {
@@ -59,16 +50,3 @@ type oncePrefix struct {
sync.Once
v netaddr.IPPrefix
}
func mustIP(v *netaddr.IP, ip string) {
var err error
*v, err = netaddr.ParseIP(ip)
if err != nil {
panic(err)
}
}
type onceIP struct {
sync.Once
v netaddr.IP
}

View File

@@ -1,60 +0,0 @@
// Copyright (c) 2020 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 tshttpproxy contains Tailscale additions to httpproxy not available
// in golang.org/x/net/http/httpproxy. Notably, it aims to support Windows better.
package tshttpproxy
import (
"net/http"
"net/url"
"os"
)
// sysProxyFromEnv, if non-nil, specifies a platform-specific ProxyFromEnvironment
// func to use if http.ProxyFromEnvironment doesn't return a proxy.
// For example, WPAD PAC files on Windows.
var sysProxyFromEnv func(*http.Request) (*url.URL, error)
func ProxyFromEnvironment(req *http.Request) (*url.URL, error) {
u, err := http.ProxyFromEnvironment(req)
if u != nil && err == nil {
return u, nil
}
if sysProxyFromEnv != nil {
u, err := sysProxyFromEnv(req)
if u != nil && err == nil {
return u, nil
}
}
return nil, err
}
var sysAuthHeader func(*url.URL) (string, error)
// GetAuthHeader returns the Authorization header value to send to proxy u.
func GetAuthHeader(u *url.URL) (string, error) {
if fake := os.Getenv("TS_DEBUG_FAKE_PROXY_AUTH"); fake != "" {
return fake, nil
}
if sysAuthHeader != nil {
return sysAuthHeader(u)
}
return "", nil
}
var condSetTransportGetProxyConnectHeader func(*http.Transport)
// SetTarnsportGetProxyConnectHeader sets the provided Transport's
// GetProxyConnectHeader field, if the current build of Go supports
// it.
//
// See https://github.com/golang/go/issues/41048.
func SetTransportGetProxyConnectHeader(tr *http.Transport) {
if f := condSetTransportGetProxyConnectHeader; f != nil {
f(tr)
}
}

View File

@@ -1,45 +0,0 @@
// Copyright (c) 2020 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.
// +build tailscale_go
// We want to use https://github.com/golang/go/issues/41048 but it's only in the
// Tailscale Go tree for now. Hence the build tag above.
package tshttpproxy
import (
"context"
"fmt"
"log"
"net/http"
"net/url"
)
const proxyAuthHeader = "Proxy-Authorization"
func init() {
condSetTransportGetProxyConnectHeader = func(tr *http.Transport) {
tr.GetProxyConnectHeader = func(ctx context.Context, proxyURL *url.URL, target string) (http.Header, error) {
v, err := GetAuthHeader(proxyURL)
if err != nil {
log.Printf("failed to get proxy Auth header for %v; ignoring: %v", proxyURL, err)
return nil, nil
}
if v == "" {
return nil, nil
}
return http.Header{proxyAuthHeader: []string{v}}, nil
}
tr.OnProxyConnectResponse = func(ctx context.Context, proxyURL *url.URL, connectReq *http.Request, res *http.Response) error {
auth := connectReq.Header.Get(proxyAuthHeader)
const truncLen = 20
if len(auth) > truncLen {
auth = fmt.Sprintf("%s...(%d total bytes)", auth[:truncLen], len(auth))
}
log.Printf("tshttpproxy: CONNECT response from %v for target %q (auth %q): %v", proxyURL, connectReq.Host, auth, res.Status)
return nil
}
}
}

View File

@@ -1,213 +0,0 @@
// Copyright (c) 2020 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 tshttpproxy
import (
"context"
"encoding/base64"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"sync"
"syscall"
"time"
"unsafe"
"github.com/alexbrainman/sspi/negotiate"
"golang.org/x/sys/windows"
)
var (
winHTTP = windows.NewLazySystemDLL("winhttp.dll")
httpOpenProc = winHTTP.NewProc("WinHttpOpen")
closeHandleProc = winHTTP.NewProc("WinHttpCloseHandle")
getProxyForUrlProc = winHTTP.NewProc("WinHttpGetProxyForUrl")
)
func init() {
sysProxyFromEnv = proxyFromWinHTTPOrCache
sysAuthHeader = sysAuthHeaderWindows
}
var cachedProxy struct {
sync.Mutex
val *url.URL
}
func proxyFromWinHTTPOrCache(req *http.Request) (*url.URL, error) {
if req.URL == nil {
return nil, nil
}
urlStr := req.URL.String()
ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second)
defer cancel()
type result struct {
proxy *url.URL
err error
}
resc := make(chan result, 1)
go func() {
proxy, err := proxyFromWinHTTP(ctx, urlStr)
resc <- result{proxy, err}
}()
select {
case res := <-resc:
err := res.err
if err == nil {
cachedProxy.Lock()
defer cachedProxy.Unlock()
if was, now := fmt.Sprint(cachedProxy.val), fmt.Sprint(res.proxy); was != now {
log.Printf("tshttpproxy: winhttp: updating cached proxy setting from %v to %v", was, now)
}
cachedProxy.val = res.proxy
return res.proxy, nil
}
// See https://docs.microsoft.com/en-us/windows/win32/winhttp/error-messages
const ERROR_WINHTTP_AUTODETECTION_FAILED = 12180
if err == syscall.Errno(ERROR_WINHTTP_AUTODETECTION_FAILED) {
return nil, nil
}
log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q): %v/%#v", urlStr, err, err)
return nil, err
case <-ctx.Done():
cachedProxy.Lock()
defer cachedProxy.Unlock()
log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q): timeout; using cached proxy %v", urlStr, cachedProxy.val)
return cachedProxy.val, nil
}
}
func proxyFromWinHTTP(ctx context.Context, urlStr string) (proxy *url.URL, err error) {
whi, err := winHTTPOpen()
if err != nil {
log.Printf("winhttp: Open: %v", err)
return nil, err
}
defer whi.Close()
t0 := time.Now()
v, err := whi.GetProxyForURL(urlStr)
td := time.Since(t0).Round(time.Millisecond)
if err := ctx.Err(); err != nil {
log.Printf("tshttpproxy: winhttp: context canceled, ignoring GetProxyForURL(%q) after %v", urlStr, td)
return nil, err
}
if err != nil {
return nil, err
}
if v == "" {
return nil, nil
}
// Discard all but first proxy value for now.
if i := strings.Index(v, ";"); i != -1 {
v = v[:i]
}
if !strings.HasPrefix(v, "https://") {
v = "http://" + v
}
return url.Parse(v)
}
var userAgent = windows.StringToUTF16Ptr("Tailscale")
const (
winHTTP_ACCESS_TYPE_AUTOMATIC_PROXY = 4
winHTTP_AUTOPROXY_ALLOW_AUTOCONFIG = 0x00000100
winHTTP_AUTOPROXY_AUTO_DETECT = 1
winHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001
winHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002
)
func winHTTPOpen() (winHTTPInternet, error) {
if err := httpOpenProc.Find(); err != nil {
return 0, err
}
r, _, err := httpOpenProc.Call(
uintptr(unsafe.Pointer(userAgent)),
winHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
0, /* WINHTTP_NO_PROXY_NAME */
0, /* WINHTTP_NO_PROXY_BYPASS */
0)
if r == 0 {
return 0, err
}
return winHTTPInternet(r), nil
}
type winHTTPInternet windows.Handle
func (hi winHTTPInternet) Close() error {
if err := closeHandleProc.Find(); err != nil {
return err
}
r, _, err := closeHandleProc.Call(uintptr(hi))
if r == 1 {
return nil
}
return err
}
// WINHTTP_AUTOPROXY_OPTIONS
// https://docs.microsoft.com/en-us/windows/win32/api/winhttp/ns-winhttp-winhttp_autoproxy_options
type autoProxyOptions struct {
DwFlags uint32
DwAutoDetectFlags uint32
AutoConfigUrl *uint16
_ uintptr
_ uint32
FAutoLogonIfChallenged bool
}
// WINHTTP_PROXY_INFO
// https://docs.microsoft.com/en-us/windows/win32/api/winhttp/ns-winhttp-winhttp_proxy_info
type winHTTPProxyInfo struct {
AccessType uint16
Proxy *uint16
ProxyBypass *uint16
}
var proxyForURLOpts = &autoProxyOptions{
DwFlags: winHTTP_AUTOPROXY_ALLOW_AUTOCONFIG | winHTTP_AUTOPROXY_AUTO_DETECT,
DwAutoDetectFlags: winHTTP_AUTO_DETECT_TYPE_DHCP, // | winHTTP_AUTO_DETECT_TYPE_DNS_A,
}
func (hi winHTTPInternet) GetProxyForURL(urlStr string) (string, error) {
if err := getProxyForUrlProc.Find(); err != nil {
return "", err
}
var out winHTTPProxyInfo
r, _, err := getProxyForUrlProc.Call(
uintptr(hi),
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(urlStr))),
uintptr(unsafe.Pointer(proxyForURLOpts)),
uintptr(unsafe.Pointer(&out)))
if r == 1 {
return windows.UTF16PtrToString(out.Proxy), nil
}
return "", err
}
func sysAuthHeaderWindows(u *url.URL) (string, error) {
spn := "HTTP/" + u.Hostname()
creds, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
return "", fmt.Errorf("negotiate.AcquireCurrentUserCredentials: %w", err)
}
defer creds.Release()
secCtx, token, err := negotiate.NewClientContext(creds, spn)
if err != nil {
return "", fmt.Errorf("negotiate.NewClientContext: %w", err)
}
defer secCtx.Release()
return "Negotiate " + base64.StdEncoding.EncodeToString(token), nil
}

View File

@@ -41,10 +41,6 @@ func parsePort(s string) int {
return int(port)
}
func isLoopbackAddr(s string) bool {
return strings.HasPrefix(s, "127.0.0.1:") || strings.HasPrefix(s, "127.0.0.1.")
}
type nothing struct{}
// Lowest common denominator parser for "netstat -na" format.
@@ -78,7 +74,7 @@ func parsePortsNetstat(output string) List {
// not interested in non-listener sockets
continue
}
if isLoopbackAddr(laddr) {
if strings.HasPrefix(laddr, "127.0.0.1:") || strings.HasPrefix(laddr, "127.0.0.1.") {
// not interested in loopback-bound listeners
continue
}
@@ -89,7 +85,7 @@ func parsePortsNetstat(output string) List {
proto = "udp"
laddr = cols[len(cols)-2]
raddr = cols[len(cols)-1]
if isLoopbackAddr(laddr) {
if strings.HasPrefix(laddr, "127.0.0.1:") || strings.HasPrefix(laddr, "127.0.0.1.") {
// not interested in loopback-bound listeners
continue
}

View File

@@ -10,15 +10,12 @@ import (
"io"
"io/ioutil"
"os"
"runtime"
"sort"
"strconv"
"strings"
"syscall"
"time"
"golang.org/x/sys/unix"
"tailscale.com/syncs"
)
// Reading the sockfiles on Linux is very fast, so we can do it often.
@@ -29,30 +26,13 @@ const pollInterval = 1 * time.Second
var sockfiles = []string{"/proc/net/tcp", "/proc/net/udp"}
var protos = []string{"tcp", "udp"}
var sawProcNetPermissionErr syncs.AtomicBool
func listPorts() (List, error) {
if sawProcNetPermissionErr.Get() {
return nil, nil
}
l := []Port{}
for pi, fname := range sockfiles {
proto := protos[pi]
// Android 10+ doesn't allow access to this anymore.
// https://developer.android.com/about/versions/10/privacy/changes#proc-net-filesystem
// Ignore it rather than have the system log about our violation.
if runtime.GOOS == "android" && syscall.Access(fname, unix.R_OK) != nil {
sawProcNetPermissionErr.Set(true)
return nil, nil
}
f, err := os.Open(fname)
if os.IsPermission(err) {
sawProcNetPermissionErr.Set(true)
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("%s: %s", fname, err)
}
@@ -116,18 +96,7 @@ func addProcesses(pl []Port) ([]Port, error) {
}
err := foreachPID(func(pid string) error {
fdPath := fmt.Sprintf("/proc/%s/fd", pid)
// Android logs a bunch of audit violations in logcat
// if we try to open things we don't have access
// to. So on Android only, ask if we have permission
// rather than just trying it to determine whether we
// have permission.
if runtime.GOOS == "android" && syscall.Access(fdPath, unix.R_OK) != nil {
return nil
}
fdDir, err := os.Open(fdPath)
fdDir, err := os.Open(fmt.Sprintf("/proc/%s/fd", pid))
if err != nil {
// Can't open fd list for this pid. Maybe
// don't have access. Ignore it.

View File

@@ -95,12 +95,9 @@ func addProcesses(pl []Port) ([]Port, error) {
if port > 0 {
pp := ProtoPort{proto, uint16(port)}
p := m[pp]
switch {
case p != nil:
if p != nil {
p.Process = cmd
case isLoopbackAddr(val):
// ignore
default:
} else {
fmt.Fprintf(os.Stderr, "weird: missing %v\n", pp)
}
}

View File

@@ -51,10 +51,6 @@ type DERPRegion struct {
// "fra", etc.
RegionCode string
// RegionName is a long English name for the region: "New York City",
// "San Francisco", "Singapore", "Frankfurt", etc.
RegionName string
// Nodes are the DERP nodes running in this region, in
// priority order for the current client. Client TLS
// connections should ideally only go to the first entry

View File

@@ -4,7 +4,7 @@
package tailcfg
//go:generate go run tailscale.com/cmd/cloner -type=User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig -output=tailcfg_clone.go
//go:generate go run tailscale.com/cmd/cloner -type=User,Node,Hostinfo,NetInfo -output=tailcfg_clone.go
import (
"bytes"
@@ -17,7 +17,6 @@ import (
"github.com/tailscale/wireguard-go/wgcfg"
"go4.org/mem"
"golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/types/key"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
@@ -261,7 +260,6 @@ type Hostinfo struct {
OSVersion string // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041")
DeviceModel string // mobile phone model ("Pixel 3a", "iPhone 11 Pro")
Hostname string // name of the host the client runs on
GoArch string // the host's GOARCH value (of the running binary)
RoutableIPs []wgcfg.CIDR `json:",omitempty"` // set of IP ranges this client can route
RequestTags []string `json:",omitempty"` // set of ACL tags this node wants to claim
Services []Service `json:",omitempty"` // services advertised by this machine
@@ -443,12 +441,11 @@ type RegisterResponse struct {
type MapRequest struct {
Version int // current version is 4
Compress string // "zstd" or "" (no compression)
KeepAlive bool // whether server should send keep-alives back to us
KeepAlive bool // server sends keep-alives
NodeKey NodeKey
DiscoKey DiscoKey
Endpoints []string // caller's endpoints (IPv4 or IPv6)
IncludeIPv6 bool // include IPv6 endpoints in returned Node Endpoints
DeltaPeers bool // whether the 2nd+ network map in response should be deltas, using PeersChanged, PeersRemoved
Stream bool // if true, multiple MapResponse objects are returned
Hostinfo *Hostinfo
@@ -494,53 +491,15 @@ var FilterAllowAll = []FilterRule{
},
}
// DNSConfig is the DNS configuration.
type DNSConfig struct {
// Nameservers are the IP addresses of the nameservers to use.
Nameservers []netaddr.IP `json:",omitempty"`
// Domains are the search domains to use.
Domains []string `json:",omitempty"`
// PerDomain indicates whether it is preferred to use Nameservers
// only for DNS queries for subdomains of Domains.
// Some OSes and OS configurations don't support per-domain DNS configuration,
// in which case Nameservers applies to all DNS requests regardless of PerDomain's value.
PerDomain bool
// Proxied indicates whether DNS requests are proxied through a tsdns.Resolver.
// This enables Magic DNS. It is togglable independently of PerDomain.
Proxied bool
}
type MapResponse struct {
KeepAlive bool `json:",omitempty"` // if set, all other fields are ignored
KeepAlive bool // if set, all other fields are ignored
// Networking
Node *Node
DERPMap *DERPMap `json:",omitempty"` // if non-empty, a change in the DERP map.
// Peers, if non-empty, is the complete list of peers.
// It will be set in the first MapResponse for a long-polled request/response.
// Subsequent responses will be delta-encoded if DeltaPeers was set in the request.
// If Peers is non-empty, PeersChanged and PeersRemoved should
// be ignored (and should be empty).
// Peers is always returned sorted by Node.ID.
Peers []*Node `json:",omitempty"`
// PeersChanged are the Nodes (identified by their ID) that
// have changed or been added since the past update on the
// HTTP response. It's only set if MapRequest.DeltaPeers was true.
// PeersChanged is always returned sorted by Node.ID.
PeersChanged []*Node `json:",omitempty"`
// PeersRemoved are the NodeIDs that are no longer in the peer list.
PeersRemoved []NodeID `json:",omitempty"`
// DNS is the same as DNSConfig.Nameservers.
//
// TODO(dmytro): should be sent in DNSConfig.Nameservers once clients have updated.
DNS []wgcfg.IP `json:",omitempty"`
// SearchPaths are the same as DNSConfig.Domains.
//
// TODO(dmytro): should be sent in DNSConfig.Domains once clients have updated.
SearchPaths []string `json:",omitempty"`
DNSConfig DNSConfig `json:",omitempty"`
Node *Node
Peers []*Node
DNS []wgcfg.IP
SearchPaths []string
DERPMap *DERPMap
// ACLs
Domain string
@@ -571,17 +530,6 @@ type Debug struct {
// always do its background STUN queries (see magicsock's
// periodicReSTUN), regardless of inactivity.
ForceBackgroundSTUN bool `json:",omitempty"`
// DERPRoute controls whether the DERP reverse path
// optimization (see Issue 150) should be enabled or
// disabled. The environment variable in magicsock is the
// highest priority (if set), then this (if set), then the
// binary default value.
DERPRoute opt.Bool `json:",omitempty"`
// TrimWGConfig controls whether Tailscale does lazy, on-demand
// wireguard configuration of peers.
TrimWGConfig opt.Bool `json:",omitempty"`
}
func (k MachineKey) String() string { return fmt.Sprintf("mkey:%x", k[:]) }
@@ -647,7 +595,6 @@ func (n *Node) Equal(n2 *Node) bool {
eqCIDRs(n.Addresses, n2.Addresses) &&
eqCIDRs(n.AllowedIPs, n2.AllowedIPs) &&
eqStrings(n.Endpoints, n2.Endpoints) &&
n.DERP == n2.DERP &&
n.Hostinfo.Equal(&n2.Hostinfo) &&
n.Created.Equal(n2.Created) &&
eqTimePtr(n.LastSeen, n2.LastSeen) &&

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig; DO NOT EDIT.
// Code generated by tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo; DO NOT EDIT.
package tailcfg
@@ -73,62 +73,3 @@ func (src *NetInfo) Clone() *NetInfo {
}
return dst
}
// Clone makes a deep copy of Group.
// The result aliases no memory with the original.
func (src *Group) Clone() *Group {
if src == nil {
return nil
}
dst := new(Group)
*dst = *src
dst.Members = append(src.Members[:0:0], src.Members...)
return dst
}
// Clone makes a deep copy of Role.
// The result aliases no memory with the original.
func (src *Role) Clone() *Role {
if src == nil {
return nil
}
dst := new(Role)
*dst = *src
dst.Capabilities = append(src.Capabilities[:0:0], src.Capabilities...)
return dst
}
// Clone makes a deep copy of Capability.
// The result aliases no memory with the original.
func (src *Capability) Clone() *Capability {
if src == nil {
return nil
}
dst := new(Capability)
*dst = *src
return dst
}
// Clone makes a deep copy of Login.
// The result aliases no memory with the original.
func (src *Login) Clone() *Login {
if src == nil {
return nil
}
dst := new(Login)
*dst = *src
return dst
}
// Clone makes a deep copy of DNSConfig.
// The result aliases no memory with the original.
func (src *DNSConfig) Clone() *DNSConfig {
if src == nil {
return nil
}
dst := new(DNSConfig)
*dst = *src
dst.Nameservers = append(src.Nameservers[:0:0], src.Nameservers...)
dst.Domains = append(src.Domains[:0:0], src.Domains...)
return dst
}

View File

@@ -24,7 +24,7 @@ func fieldsOf(t reflect.Type) (fields []string) {
func TestHostinfoEqual(t *testing.T) {
hiHandles := []string{
"IPNVersion", "FrontendLogID", "BackendLogID", "OS", "OSVersion",
"DeviceModel", "Hostname", "GoArch", "RoutableIPs", "RequestTags", "Services",
"DeviceModel", "Hostname", "RoutableIPs", "RequestTags", "Services",
"NetInfo",
}
if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) {
@@ -315,11 +315,6 @@ func TestNodeEqual(t *testing.T) {
&Node{LastSeen: &now},
true,
},
{
&Node{DERP: "foo"},
&Node{DERP: "bar"},
false,
},
}
for i, tt := range tests {
got := tt.a.Equal(tt.b)

View File

@@ -7,10 +7,7 @@ package tstest
import (
"log"
"os"
"sync"
"testing"
"tailscale.com/types/logger"
)
type testLogWriter struct {
@@ -44,48 +41,3 @@ func (panicLogWriter) Write(b []byte) (int, error) {
func PanicOnLog() {
log.SetOutput(panicLogWriter{})
}
// ListenFor produces a LogListener wrapping a given logf with the given logStrings
func ListenFor(logf logger.Logf, logStrings []string) *LogListener {
ret := LogListener{
logf: logf,
listenFor: logStrings,
seen: make(map[string]bool),
}
for _, line := range logStrings {
ret.seen[line] = false
}
return &ret
}
// LogListener takes a list of log lines to listen for
type LogListener struct {
logf logger.Logf
listenFor []string
mu sync.Mutex
seen map[string]bool
}
// Logf records and logs a given line
func (ll *LogListener) Logf(format string, args ...interface{}) {
ll.mu.Lock()
if _, ok := ll.seen[format]; ok {
ll.seen[format] = true
}
ll.mu.Unlock()
ll.logf(format, args)
}
// Check returns which lines haven't been logged yet
func (ll *LogListener) Check() []string {
ll.mu.Lock()
defer ll.mu.Unlock()
var notSeen []string
for _, line := range ll.listenFor {
if !ll.seen[line] {
notSeen = append(notSeen, line)
}
}
return notSeen
}

View File

@@ -1,46 +0,0 @@
// Copyright (c) 2020 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 tstest
import "testing"
func TestLogListener(t *testing.T) {
var (
l1 = "line 1: %s"
l2 = "line 2: %s"
l3 = "line 3: %s"
lineList = []string{l1, l2}
)
ll := ListenFor(t.Logf, lineList)
if len(ll.Check()) != len(lineList) {
t.Errorf("expected %v, got %v", lineList, ll.Check())
}
ll.Logf(l3, "hi")
if len(ll.Check()) != len(lineList) {
t.Errorf("expected %v, got %v", lineList, ll.Check())
}
ll.Logf(l1, "hi")
if len(ll.Check()) != len(lineList)-1 {
t.Errorf("expected %v, got %v", lineList, ll.Check())
}
ll.Logf(l1, "bye")
if len(ll.Check()) != len(lineList)-1 {
t.Errorf("expected %v, got %v", lineList, ll.Check())
}
ll.Logf(l2, "hi")
if ll.Check() != nil {
t.Errorf("expected empty list, got ll.Check()")
}
}

View File

@@ -93,10 +93,9 @@ func mustPrefix(s string) netaddr.IPPrefix {
// NewInternet returns a network that simulates the internet.
func NewInternet() *Network {
return &Network{
Name: "internet",
// easily recognizable internett-y addresses
Prefix4: mustPrefix("1.0.0.0/24"),
Prefix6: mustPrefix("1111::/64"),
Name: "internet",
Prefix4: mustPrefix("203.0.113.0/24"), // documentation netblock that looks Internet-y
Prefix6: mustPrefix("fc00:52::/64"),
}
}
@@ -185,17 +184,6 @@ func (n *Network) write(p *Packet) (num int, err error) {
defer n.mu.Unlock()
iface, ok := n.machine[p.Dst.IP]
if !ok {
// If the destination is within the network's authoritative
// range, no route to host.
if p.Dst.IP.Is4() && n.Prefix4.Contains(p.Dst.IP) {
p.Trace("no route to %v", p.Dst.IP)
return len(p.Payload), nil
}
if p.Dst.IP.Is6() && n.Prefix6.Contains(p.Dst.IP) {
p.Trace("no route to %v", p.Dst.IP)
return len(p.Payload), nil
}
if n.defaultGW == nil {
p.Trace("no route to %v", p.Dst.IP)
return len(p.Payload), nil

View File

@@ -39,7 +39,7 @@ func goroutineDump() (int, string) {
return p.Count(), b.String()
}
func (r *ResourceCheck) Assert(t testing.TB) {
func (r *ResourceCheck) Assert(t *testing.T) {
t.Helper()
want := r.startNumRoutines

View File

@@ -7,6 +7,7 @@ package tsweb
import (
"encoding/json"
"net/http"
"reflect"
)
type response struct {
@@ -15,59 +16,119 @@ type response struct {
Data interface{} `json:"data,omitempty"`
}
// TODO: Header
func responseSuccess(data interface{}) *response {
return &response{
Status: "success",
Data: data,
}
}
// JSONHandlerFunc only take *http.Request as argument to avoid any misuse of http.ResponseWriter.
// The function's results must be (status int, data interface{}, err error).
// Return a HTTPError to show an error message, otherwise JSONHandler will only show "internal server error".
type JSONHandlerFunc func(r *http.Request) (status int, data interface{}, err error)
func responseError(e string) *response {
return &response{
Status: "error",
Error: e,
}
}
// ServeHTTP calls the JSONHandlerFunc and automatically marshals http responses.
//
// Use the following code to unmarshal the request body
// body := new(DataType)
// if err := json.NewDecoder(r.Body).Decode(body); err != nil {
// return http.StatusBadRequest, nil, err
// }
//
// Check jsonhandler_text.go for examples
func (fn JSONHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func writeResponse(w http.ResponseWriter, s int, resp *response) {
b, _ := json.Marshal(resp)
w.Header().Set("Content-Type", "application/json")
var resp *response
status, data, err := fn(r)
if status == 0 {
status = http.StatusInternalServerError
resp = &response{
Status: "error",
Error: "internal server error",
}
} else if err == nil {
resp = &response{
Status: "success",
Data: data,
}
} else {
if werr, ok := err.(HTTPError); ok {
resp = &response{
Status: "error",
Error: werr.Msg,
Data: data,
}
} else {
resp = &response{
Status: "error",
Error: "internal server error",
}
}
}
b, err := json.Marshal(resp)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"status":"error","error":"json marshal error"}`))
return
}
w.WriteHeader(status)
w.WriteHeader(s)
w.Write(b)
}
func checkFn(t reflect.Type) {
h := reflect.TypeOf(http.HandlerFunc(nil))
switch t.NumIn() {
case 2, 3:
if !t.In(0).AssignableTo(h.In(0)) {
panic("first argument must be http.ResponseWriter")
}
if !t.In(1).AssignableTo(h.In(1)) {
panic("second argument must be *http.Request")
}
default:
panic("JSONHandler: number of input parameter should be 2 or 3")
}
switch t.NumOut() {
case 1:
if !t.Out(0).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
panic("return value must be error")
}
case 2:
if !t.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
panic("second return value must be error")
}
default:
panic("JSONHandler: number of return values should be 1 or 2")
}
}
// JSONHandler wraps an HTTP handler function with a version that automatically
// unmarshals and marshals requests and responses respectively into fn's arguments
// and results.
//
// The fn parameter is a function. It must take two or three input arguments.
// The first two arguments must be http.ResponseWriter and *http.Request.
// The optional third argument can be of any type representing the JSON input.
// The function's results can be either (error) or (T, error), where T is the
// JSON-marshalled result type.
//
// For example:
// fn := func(w http.ResponseWriter, r *http.Request, in *Req) (*Res, error) { ... }
func JSONHandler(fn interface{}) http.Handler {
v := reflect.ValueOf(fn)
t := v.Type()
checkFn(t)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
wv := reflect.ValueOf(w)
rv := reflect.ValueOf(r)
var vs []reflect.Value
switch t.NumIn() {
case 2:
vs = v.Call([]reflect.Value{wv, rv})
case 3:
dv := reflect.New(t.In(2))
err := json.NewDecoder(r.Body).Decode(dv.Interface())
if err != nil {
writeResponse(w, http.StatusBadRequest, responseError("bad json"))
return
}
vs = v.Call([]reflect.Value{wv, rv, dv.Elem()})
default:
panic("JSONHandler: number of input parameter should be 2 or 3")
}
var e reflect.Value
switch len(vs) {
case 1:
// todo support other error types
if vs[0].IsZero() {
writeResponse(w, http.StatusOK, responseSuccess(nil))
return
}
e = vs[0]
case 2:
if vs[1].IsZero() {
if !vs[0].IsZero() {
writeResponse(w, http.StatusOK, responseSuccess(vs[0].Interface()))
}
return
}
e = vs[1]
default:
panic("JSONHandler: number of return values should be 1 or 2")
}
if e.Type().AssignableTo(reflect.TypeOf(HTTPError{})) {
err := e.Interface().(HTTPError)
writeResponse(w, err.Code, responseError(err.Error()))
} else {
err := e.Interface().(error)
writeResponse(w, http.StatusBadRequest, responseError(err.Error()))
}
})
}

View File

@@ -5,8 +5,9 @@
package tsweb
import (
"bytes"
"encoding/json"
"fmt"
"errors"
"net/http"
"net/http/httptest"
"strings"
@@ -25,7 +26,7 @@ type Response struct {
}
func TestNewJSONHandler(t *testing.T) {
checkStatus := func(w *httptest.ResponseRecorder, status string, code int) *Response {
checkStatus := func(w *httptest.ResponseRecorder, status string) *Response {
d := &Response{
Data: &Data{},
}
@@ -43,10 +44,6 @@ func TestNewJSONHandler(t *testing.T) {
t.Fatalf("wrong status: %s %s", d.Status, status)
}
if w.Code != code {
t.Fatalf("wrong status code: %d %d", w.Code, code)
}
if w.Header().Get("Content-Type") != "application/json" {
t.Fatalf("wrong content type: %s", w.Header().Get("Content-Type"))
}
@@ -54,139 +51,163 @@ func TestNewJSONHandler(t *testing.T) {
return d
}
h21 := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
return http.StatusOK, nil, nil
// 2 1
h21 := JSONHandler(func(w http.ResponseWriter, r *http.Request) error {
return nil
})
t.Run("200 simple", func(t *testing.T) {
t.Run("2 1 simple", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
h21.ServeHTTP(w, r)
checkStatus(w, "success", http.StatusOK)
checkStatus(w, "success")
})
t.Run("403 HTTPError", func(t *testing.T) {
h := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
return http.StatusForbidden, nil, fmt.Errorf("forbidden")
t.Run("2 1 HTTPError", func(t *testing.T) {
h := JSONHandler(func(w http.ResponseWriter, r *http.Request) HTTPError {
return Error(http.StatusForbidden, "forbidden", nil)
})
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
h.ServeHTTP(w, r)
checkStatus(w, "error", http.StatusForbidden)
if w.Code != http.StatusForbidden {
t.Fatalf("wrong code: %d %d", w.Code, http.StatusForbidden)
}
})
h22 := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
return http.StatusOK, &Data{Name: "tailscale"}, nil
// 2 2
h22 := JSONHandler(func(w http.ResponseWriter, r *http.Request) (*Data, error) {
return &Data{Name: "tailscale"}, nil
})
t.Run("200 get data", func(t *testing.T) {
t.Run("2 2 get data", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
h22.ServeHTTP(w, r)
checkStatus(w, "success", http.StatusOK)
checkStatus(w, "success")
})
h31 := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
body := new(Data)
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
return http.StatusBadRequest, nil, err
// 3 1
h31 := JSONHandler(func(w http.ResponseWriter, r *http.Request, d *Data) error {
if d.Name == "" {
return errors.New("name is empty")
}
if body.Name == "" {
return http.StatusBadRequest, nil, Error(http.StatusBadGateway, "name is empty", nil)
}
return http.StatusOK, nil, nil
return nil
})
t.Run("200 post data", func(t *testing.T) {
t.Run("3 1 post data", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{"Name": "tailscale"}`))
h31.ServeHTTP(w, r)
checkStatus(w, "success", http.StatusOK)
checkStatus(w, "success")
})
t.Run("400 bad json", func(t *testing.T) {
t.Run("3 1 bad json", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{`))
h31.ServeHTTP(w, r)
checkStatus(w, "error", http.StatusBadRequest)
checkStatus(w, "error")
})
t.Run("400 post data error", func(t *testing.T) {
t.Run("3 1 post data error", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{}`))
h31.ServeHTTP(w, r)
resp := checkStatus(w, "error", http.StatusBadRequest)
resp := checkStatus(w, "error")
if resp.Error != "name is empty" {
t.Fatalf("wrong error")
}
})
h32 := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
body := new(Data)
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
return http.StatusBadRequest, nil, err
}
if body.Name == "root" {
return http.StatusInternalServerError, nil, fmt.Errorf("invalid name")
}
if body.Price == 0 {
return http.StatusBadRequest, nil, Error(http.StatusBadGateway, "price is empty", nil)
// 3 2
h32 := JSONHandler(func(w http.ResponseWriter, r *http.Request, d *Data) (*Data, error) {
if d.Price == 0 {
return nil, errors.New("price is empty")
}
return http.StatusOK, &Data{Price: body.Price * 2}, nil
return &Data{Price: d.Price * 2}, nil
})
t.Run("200 post data", func(t *testing.T) {
t.Run("3 2 post data", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{"Price": 10}`))
h32.ServeHTTP(w, r)
resp := checkStatus(w, "success", http.StatusOK)
resp := checkStatus(w, "success")
t.Log(resp.Data)
if resp.Data.Price != 20 {
t.Fatalf("wrong price: %d %d", resp.Data.Price, 10)
}
})
t.Run("400 post data error", func(t *testing.T) {
t.Run("3 2 post data error", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{}`))
h32.ServeHTTP(w, r)
resp := checkStatus(w, "error", http.StatusBadRequest)
resp := checkStatus(w, "error")
if resp.Error != "price is empty" {
t.Fatalf("wrong error")
}
})
t.Run("500 internal server error", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{"Name": "root"}`))
h32.ServeHTTP(w, r)
resp := checkStatus(w, "error", http.StatusInternalServerError)
if resp.Error != "internal server error" {
t.Fatalf("wrong error")
// fn check
shouldPanic := func() {
r := recover()
if r == nil {
t.Fatalf("should panic")
}
t.Log(r)
}
t.Run("2 0 panic", func(t *testing.T) {
defer shouldPanic()
JSONHandler(func(w http.ResponseWriter, r *http.Request) {})
})
t.Run("500 misuse", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", nil)
JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
return http.StatusOK, make(chan int), nil
}).ServeHTTP(w, r)
resp := checkStatus(w, "error", http.StatusInternalServerError)
if resp.Error != "json marshal error" {
t.Fatalf("wrong error")
}
t.Run("2 1 panic return value", func(t *testing.T) {
defer shouldPanic()
JSONHandler(func(w http.ResponseWriter, r *http.Request) string {
return ""
})
})
t.Run("500 empty status code", func(t *testing.T) {
t.Run("2 1 panic arguments", func(t *testing.T) {
defer shouldPanic()
JSONHandler(func(r *http.Request, w http.ResponseWriter) error {
return nil
})
})
t.Run("3 1 panic arguments", func(t *testing.T) {
defer shouldPanic()
JSONHandler(func(name string, r *http.Request, w http.ResponseWriter) error {
return nil
})
})
t.Run("3 2 panic return value", func(t *testing.T) {
defer shouldPanic()
//lint:ignore ST1008 intentional
JSONHandler(func(name string, r *http.Request, w http.ResponseWriter) (error, string) {
return nil, "panic"
})
})
t.Run("2 2 forbidden", func(t *testing.T) {
code := http.StatusForbidden
body := []byte("forbidden")
h := JSONHandler(func(w http.ResponseWriter, r *http.Request) (*Data, error) {
w.WriteHeader(code)
w.Write(body)
return nil, nil
})
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", nil)
JSONHandlerFunc(func(r *http.Request) (status int, data interface{}, err error) {
return
}).ServeHTTP(w, r)
checkStatus(w, "error", http.StatusInternalServerError)
r := httptest.NewRequest("GET", "/", nil)
h.ServeHTTP(w, r)
if w.Code != http.StatusForbidden {
t.Fatalf("wrong code: %d %d", w.Code, code)
}
if !bytes.Equal(w.Body.Bytes(), []byte("forbidden")) {
t.Fatalf("wrong body: %s %s", w.Body.Bytes(), body)
}
})
}

View File

@@ -28,8 +28,6 @@ func NewPrivate() Private {
if _, err := io.ReadFull(crand.Reader, p[:]); err != nil {
panic(err)
}
p[0] &= 248
p[31] = (p[31] & 127) | 64
return p
}

View File

@@ -6,8 +6,6 @@ package key
import (
"testing"
"github.com/tailscale/wireguard-go/wgcfg"
)
func TestTextUnmarshal(t *testing.T) {
@@ -24,31 +22,3 @@ func TestTextUnmarshal(t *testing.T) {
t.Fatalf("mismatch; got %x want %x", p2, p)
}
}
func TestClamping(t *testing.T) {
t.Run("NewPrivate", func(t *testing.T) { testClamping(t, NewPrivate) })
// Also test the wgcfg package, as their behavior should match.
t.Run("wgcfg", func(t *testing.T) {
testClamping(t, func() Private {
k, err := wgcfg.NewPrivateKey()
if err != nil {
t.Fatal(err)
}
return Private(k)
})
})
}
func testClamping(t *testing.T, newKey func() Private) {
for i := 0; i < 100; i++ {
k := newKey()
if k[0]&0b111 != 0 {
t.Fatalf("Bogus clamping in first byte: %#08b", k[0])
return
}
if k[31]>>6 != 1 {
t.Fatalf("Bogus clamping in last byte: %#08b", k[0])
}
}
}

View File

@@ -9,7 +9,6 @@ package version
import (
"os"
"path"
"path/filepath"
"strings"
"rsc.io/goversion/version"
@@ -23,27 +22,22 @@ func CmdName() string {
if err != nil {
return "cmd"
}
// fallbackName, the lowercase basename of the executable, is what we return if
// we can't find the Go module metadata embedded in the file.
fallbackName := filepath.Base(strings.TrimSuffix(strings.ToLower(e), ".exe"))
var ret string
v, err := version.ReadExe(e)
if err != nil {
return fallbackName
}
// v is like:
// "path\ttailscale.com/cmd/tailscale\nmod\ttailscale.com\t(devel)\t\ndep\tgithub.com/apenwarr/fixconsole\tv0.0.0-20191012055117-5a9f6489cc29\th1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=\ndep\tgithub....
for _, line := range strings.Split(v.ModuleInfo, "\n") {
if strings.HasPrefix(line, "path\t") {
goPkg := strings.TrimPrefix(line, "path\t") // like "tailscale.com/cmd/tailscale"
ret = path.Base(goPkg) // goPkg is always forward slashes; use path, not filepath
break
ret = strings.TrimSuffix(strings.ToLower(e), ".exe")
} else {
// v is like:
// "path\ttailscale.com/cmd/tailscale\nmod\ttailscale.com\t(devel)\t\ndep\tgithub.com/apenwarr/fixconsole\tv0.0.0-20191012055117-5a9f6489cc29\th1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=\ndep\tgithub....
for _, line := range strings.Split(v.ModuleInfo, "\n") {
if strings.HasPrefix(line, "path\t") {
ret = path.Base(strings.TrimPrefix(line, "path\t"))
break
}
}
}
if ret == "" {
return fallbackName
return "cmd"
}
return ret
}

View File

@@ -5,107 +5,89 @@ set -eu
mode=$1
describe=$2
# Git describe output overall looks like
# MAJOR.MINOR.PATCH-NUMCOMMITS-GITHASH. Depending on the tag being
# described and the state of the repo, ver can be missing PATCH,
# NUMCOMMITS, or both.
#
# Valid values look like: 1.2.3-1234-abcdef, 0.98-1234-abcdef,
# 1.0.0-abcdef, 0.99-abcdef.
ver="${describe#v}"
stem="${ver%%-*}" # Just the semver-ish bit e.g. 1.2.3, 0.98
suffix="${ver#$stem}" # The rest e.g. -23-abcdef, -abcdef
long() {
ver="${describe#v}"
stem="${ver%%-*}"
case "$stem" in
*.*.*)
# Full SemVer, nothing to do.
semver="${stem}"
;;
*.*)
# Old style major.minor, add a .0
semver="${stem}.0"
;;
*)
echo "Unparseable version $stem" >&2
exit 1
;;
esac
suffix="${ver#$stem}"
case "$suffix" in
-*-*)
# Has a change count in addition to the commit hash.
;;
-*)
# Missing change count, add one.
suffix="-0${suffix}"
;;
*)
echo "Unexpected version suffix" >&2
exit 1
esac
echo "${semver}${suffix}"
}
# Normalize the stem into a full major.minor.patch semver. We might
# not use all those pieces depending on what kind of version we're
# making, but it's good to have them all on hand.
case "$stem" in
*.*.*)
# Full SemVer, nothing to do
stem="$stem"
;;
*.*)
# Old style major.minor, add a .0
stem="${stem}.0"
;;
*)
echo "Unparseable version $stem" >&2
exit 1
;;
esac
major=$(echo "$stem" | cut -f1 -d.)
minor=$(echo "$stem" | cut -f2 -d.)
patch=$(echo "$stem" | cut -f3 -d.)
short() {
ver="$(long)"
case "$ver" in
*-*-*)
echo "${ver%-*}"
;;
*-*)
echo "$ver"
;;
*)
echo "Long version in invalid format" >&2
exit 1
;;
esac
}
# Extract the change count and git ID from the suffix.
case "$suffix" in
-*-*)
# Has both a change count and a commit hash.
changecount=$(echo "$suffix" | cut -f2 -d-)
githash=$(echo "$suffix" | cut -f3 -d-)
;;
-*)
# Git hash only, change count is zero.
changecount="0"
githash=$(echo "$suffix" | cut -f2 -d-)
;;
*)
echo "Unparseable version suffix $suffix" >&2
exit 1
;;
esac
xcode() {
ver=$(short | sed -e 's/-/./')
major=$(echo "$ver" | cut -f1 -d.)
minor=$(echo "$ver" | cut -f2 -d.)
patch=$(echo "$ver" | cut -f3 -d.)
changecount=$(echo "$ver" | cut -f4 -d.)
# Validate that the version data makes sense. Rules:
# - Odd number minors are unstable. Patch must be 0, and gets
# replaced by changecount.
# - Even number minors are stable. Changecount must be 0, and
# gets removed.
#
# After this section, we only use major/minor/patch, which have been
# tweaked as needed.
if expr "$minor" : "[0-9]*[13579]$" >/dev/null; then
# Unstable
if [ "$patch" != "0" ]; then
# This is a fatal error, because a non-zero patch number
# indicates that we created an unstable git tag in violation
# of our versioning policy, and we want to blow up loudly to
# get that fixed.
echo "Unstable release $describe has a non-zero patch number, which is not allowed" >&2
exit 1
fi
patch="$changecount"
else
# Stable
if [ "$changecount" != "0" ]; then
# This is a commit that's sitting between two stable
# releases. We never want to release such a commit to the
# pbulic, but it's useful to be able to build it for
# debugging. Just force the version to 0.0.0, so that we're
# forced to rely on the git commit hash.
major=0
minor=0
patch=0
fi
fi
# Apple version numbers must be major.minor.patch. We have 4 fields
# because we need major.minor.patch for go module compatibility, and
# changecount for automatic version numbering of unstable builds. To
# resolve this, for Apple builds, we combine changecount into patch:
patch=$((patch*10000 + changecount))
case "$1" in
# CFBundleShortVersionString: the "short name" used in the App Store.
# eg. 0.92.98
echo "VERSION_NAME = $major.$minor.$patch"
# CFBundleVersion: the build number. Needs to be 3 numeric sections
# that increment for each release according to SemVer rules.
#
# We start counting at 100 because we submitted using raw build
# numbers before, and Apple doesn't let you start over.
# e.g. 0.98.3-123 -> 100.98.3123
major=$((major + 100))
echo "VERSION_ID = $major.$minor.$patch"
}
case "$mode" in
long)
echo "${major}.${minor}.${patch}-${githash}"
;;
long
;;
short)
echo "${major}.${minor}.${patch}"
;;
short
;;
xcode)
# CFBundleShortVersionString: the "short name" used in the App
# Store. eg. 0.92.98
echo "VERSION_NAME = ${major}.${minor}.${patch}"
# CFBundleVersion: the build number. Needs to be 3 numeric
# sections that increment for each release according to SemVer
# rules.
#
# We start counting at 100 because we submitted using raw
# build numbers before, and Apple doesn't let you start over.
# e.g. 0.98.3 -> 100.98.3
echo "VERSION_ID = $((major + 100)).${minor}.${patch}"
;;
xcode
;;
esac

View File

@@ -15,60 +15,42 @@ func xcode(short, long string) string {
return fmt.Sprintf("VERSION_NAME = %s\nVERSION_ID = %s", short, long)
}
func mkversion(t *testing.T, mode, in string) (string, bool) {
func mkversion(t *testing.T, mode, in string) string {
t.Helper()
bs, err := exec.Command("./mkversion.sh", mode, in).CombinedOutput()
if err != nil {
t.Logf("mkversion.sh output: %s", string(bs))
return "", false
t.Fatalf("mkversion.sh %s %s: %v", mode, in, err)
}
return strings.TrimSpace(string(bs)), true
return strings.TrimSpace(string(bs))
}
func TestMkversion(t *testing.T) {
tests := []struct {
in string
ok bool
long string
short string
xcode string
}{
{"v0.98-abcdef", true, "0.98.0-abcdef", "0.98.0", xcode("0.98.0", "100.98.0")},
{"v0.98.1-abcdef", true, "0.98.1-abcdef", "0.98.1", xcode("0.98.1", "100.98.1")},
{"v1.1.0-37-abcdef", true, "1.1.37-abcdef", "1.1.37", xcode("1.1.37", "101.1.37")},
{"v1.2.9-abcdef", true, "1.2.9-abcdef", "1.2.9", xcode("1.2.9", "101.2.9")},
{"v1.2.9-0-abcdef", true, "1.2.9-abcdef", "1.2.9", xcode("1.2.9", "101.2.9")},
{"v1.15.0-129-abcdef", true, "1.15.129-abcdef", "1.15.129", xcode("1.15.129", "101.15.129")},
{"v0.98-123-abcdef", true, "0.0.0-abcdef", "0.0.0", xcode("0.0.0", "100.0.0")},
{"v1.0.0-37-abcdef", true, "0.0.0-abcdef", "0.0.0", xcode("0.0.0", "100.0.0")},
{"v0.99.5-0-abcdef", false, "", "", ""}, // unstable, patch not allowed
{"v0.99.5-123-abcdef", false, "", "", ""}, // unstable, patch not allowed
{"v1-abcdef", false, "", "", ""}, // bad semver
{"v1.0", false, "", "", ""}, // missing suffix
{"v0.98-abcdef", "0.98.0-0-abcdef", "0.98.0-0", xcode("0.98.0", "100.98.0")},
{"v0.98-123-abcdef", "0.98.0-123-abcdef", "0.98.0-123", xcode("0.98.123", "100.98.123")},
{"v0.99.5-123-abcdef", "0.99.5-123-abcdef", "0.99.5-123", xcode("0.99.50123", "100.99.50123")},
{"v0.99.5-123-abcdef", "0.99.5-123-abcdef", "0.99.5-123", xcode("0.99.50123", "100.99.50123")},
{"v2.3-0-abcdef", "2.3.0-0-abcdef", "2.3.0-0", xcode("2.3.0", "102.3.0")},
{"1.2.3-4-abcdef", "1.2.3-4-abcdef", "1.2.3-4", xcode("1.2.30004", "101.2.30004")},
}
for _, test := range tests {
gotlong, longOK := mkversion(t, "long", test.in)
if longOK != test.ok {
t.Errorf("mkversion.sh long %q ok=%v, want %v", test.in, longOK, test.ok)
}
gotshort, shortOK := mkversion(t, "short", test.in)
if shortOK != test.ok {
t.Errorf("mkversion.sh short %q ok=%v, want %v", test.in, shortOK, test.ok)
}
gotxcode, xcodeOK := mkversion(t, "xcode", test.in)
if xcodeOK != test.ok {
t.Errorf("mkversion.sh xcode %q ok=%v, want %v", test.in, xcodeOK, test.ok)
}
if longOK && gotlong != test.long {
gotlong := mkversion(t, "long", test.in)
gotshort := mkversion(t, "short", test.in)
gotxcode := mkversion(t, "xcode", test.in)
if gotlong != test.long {
t.Errorf("mkversion.sh long %q: got %q, want %q", test.in, gotlong, test.long)
}
if shortOK && gotshort != test.short {
if gotshort != test.short {
t.Errorf("mkversion.sh short %q: got %q, want %q", test.in, gotshort, test.short)
}
if xcodeOK && gotxcode != test.xcode {
if gotxcode != test.xcode {
t.Errorf("mkversion.sh xcode %q: got %q, want %q", test.in, gotxcode, test.xcode)
}
}

View File

@@ -7,5 +7,5 @@
// Package version provides the version that the binary was built at.
package version
const LONG = "date.20200820"
const LONG = "date.20200727"
const SHORT = LONG

View File

@@ -6,14 +6,11 @@
package filter
import (
"fmt"
"net"
"sync"
"time"
"github.com/golang/groupcache/lru"
"golang.org/x/time/rate"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/wgengine/packet"
)
@@ -131,79 +128,6 @@ func maybeHexdump(flag RunFlags, b []byte) string {
return packet.Hexdump(b) + "\n"
}
// MatchesFromFilterRules parse a number of wire-format FilterRule values into
// the Matches format.
// If an error is returned, the Matches result is still valid, containing the rules that
// were successfully converted.
func MatchesFromFilterRules(pf []tailcfg.FilterRule) (Matches, error) {
mm := make([]Match, 0, len(pf))
var erracc error
for _, r := range pf {
m := Match{}
for i, s := range r.SrcIPs {
bits := 32
if len(r.SrcBits) > i {
bits = r.SrcBits[i]
}
net, err := parseIP(s, bits)
if err != nil && erracc == nil {
erracc = err
continue
}
m.Srcs = append(m.Srcs, net)
}
for _, d := range r.DstPorts {
bits := 32
if d.Bits != nil {
bits = *d.Bits
}
net, err := parseIP(d.IP, bits)
if err != nil && erracc == nil {
erracc = err
continue
}
m.Dsts = append(m.Dsts, NetPortRange{
Net: net,
Ports: PortRange{
First: d.Ports.First,
Last: d.Ports.Last,
},
})
}
mm = append(mm, m)
}
return mm, erracc
}
func parseIP(host string, defaultBits int) (Net, error) {
ip := net.ParseIP(host)
if ip != nil && ip.IsUnspecified() {
// For clarity, reject 0.0.0.0 as an input
return NetNone, fmt.Errorf("ports=%#v: to allow all IP addresses, use *:port, not 0.0.0.0:port", host)
} else if ip == nil && host == "*" {
// User explicitly requested wildcard dst ip
return NetAny, nil
} else {
if ip != nil {
ip = ip.To4()
}
if ip == nil || len(ip) != 4 {
return NetNone, fmt.Errorf("ports=%#v: invalid IPv4 address", host)
}
if len(ip) == 4 && (defaultBits < 0 || defaultBits > 32) {
return NetNone, fmt.Errorf("invalid CIDR size %d for host %q", defaultBits, host)
}
return Net{
IP: NewIP(ip),
Mask: Netmask(defaultBits),
}, nil
}
}
// TODO(apenwarr): use a bigger bucket for specifically TCP SYN accept logging?
// Logging is a quick way to record every newly opened TCP connection, but
// we have to be cautious about flooding the logs vs letting people use
@@ -212,13 +136,9 @@ func parseIP(host string, defaultBits int) (Net, error) {
var acceptBucket = rate.NewLimiter(rate.Every(10*time.Second), 3)
var dropBucket = rate.NewLimiter(rate.Every(5*time.Second), 10)
func (f *Filter) logRateLimit(runflags RunFlags, q *packet.ParsedPacket, dir direction, r Response, why string) {
func (f *Filter) logRateLimit(runflags RunFlags, q *packet.ParsedPacket, r Response, why string) {
var verdict string
if r == Drop && omitDropLogging(q, dir) {
return
}
if r == Drop && (runflags&LogDrops) != 0 && dropBucket.Allow() {
verdict = "Drop"
runflags &= HexdumpDrops
@@ -237,28 +157,26 @@ func (f *Filter) logRateLimit(runflags RunFlags, q *packet.ParsedPacket, dir dir
// RunIn determines whether this node is allowed to receive q from a Tailscale peer.
func (f *Filter) RunIn(q *packet.ParsedPacket, rf RunFlags) Response {
dir := in
r := f.pre(q, rf, dir)
r := f.pre(q, rf)
if r == Accept || r == Drop {
// already logged
return r
}
r, why := f.runIn(q)
f.logRateLimit(rf, q, dir, r, why)
f.logRateLimit(rf, q, r, why)
return r
}
// RunOut determines whether this node is allowed to send q to a Tailscale peer.
func (f *Filter) RunOut(q *packet.ParsedPacket, rf RunFlags) Response {
dir := out
r := f.pre(q, rf, dir)
r := f.pre(q, rf)
if r == Drop || r == Accept {
// already logged
return r
}
r, why := f.runOut(q)
f.logRateLimit(rf, q, dir, r, why)
f.logRateLimit(rf, q, r, why)
return r
}
@@ -270,11 +188,6 @@ func (f *Filter) runIn(q *packet.ParsedPacket) (r Response, why string) {
return Drop, "destination not allowed"
}
if q.IPVersion == 6 {
// TODO: support IPv6.
return Drop, "no rules matched"
}
switch q.IPProto {
case packet.ICMP:
if q.IsEchoResponse() || q.IsError() {
@@ -334,106 +247,30 @@ func (f *Filter) runOut(q *packet.ParsedPacket) (r Response, why string) {
return Accept, "ok out"
}
// direction is whether a packet was flowing in to this machine, or flowing out.
type direction int
const (
in direction = iota
out
)
func (d direction) String() string {
switch d {
case in:
return "in"
case out:
return "out"
default:
return fmt.Sprintf("[??dir=%d]", int(d))
}
}
func (f *Filter) pre(q *packet.ParsedPacket, rf RunFlags, dir direction) Response {
func (f *Filter) pre(q *packet.ParsedPacket, rf RunFlags) Response {
if len(q.Buffer()) == 0 {
// wireguard keepalive packet, always permit.
return Accept
}
if len(q.Buffer()) < 20 {
f.logRateLimit(rf, q, dir, Drop, "too short")
f.logRateLimit(rf, q, Drop, "too short")
return Drop
}
if q.IPVersion == 6 {
f.logRateLimit(rf, q, dir, Drop, "ipv6")
return Drop
}
switch q.IPProto {
case packet.Unknown:
// Unknown packets are dangerous; always drop them.
f.logRateLimit(rf, q, dir, Drop, "unknown")
f.logRateLimit(rf, q, Drop, "unknown")
return Drop
case packet.IPv6:
f.logRateLimit(rf, q, Drop, "ipv6")
return Drop
case packet.Fragment:
// Fragments after the first always need to be passed through.
// Very small fragments are considered Junk by ParsedPacket.
f.logRateLimit(rf, q, dir, Accept, "fragment")
f.logRateLimit(rf, q, Accept, "fragment")
return Accept
}
return noVerdict
}
const (
// ipv6AllRoutersLinkLocal is ff02::2 (All link-local routers)
ipv6AllRoutersLinkLocal = "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
// ipv6AllMLDv2CapableRouters is ff02::16 (All MLDv2-capable routers)
ipv6AllMLDv2CapableRouters = "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16"
)
// omitDropLogging reports whether packet p, which has already been
// deemded a packet to Drop, should bypass the [rate-limited] logging.
// We don't want to log scary & spammy reject warnings for packets that
// are totally normal, like IPv6 route announcements.
func omitDropLogging(p *packet.ParsedPacket, dir direction) bool {
b := p.Buffer()
switch dir {
case out:
switch p.IPVersion {
case 4:
// ParsedPacket.Decode zeros out ParsedPacket.IPProtocol for protocols
// it doesn't know about, so parse it out ourselves if needed.
ipProto := p.IPProto
if ipProto == 0 && len(b) > 8 {
ipProto = packet.IPProto(b[9])
}
// Omit logging about outgoing IGMP.
if ipProto == packet.IGMP {
return true
}
case 6:
if len(b) < 40 {
return false
}
src, dst := b[8:8+16], b[24:24+16]
// Omit logging for outgoing IPv6 ICMP-v6 queries to ff02::2,
// as sent by the OS, looking for routers.
if p.IPProto == packet.ICMPv6 {
if isLinkLocalV6(src) && string(dst) == ipv6AllRoutersLinkLocal {
return true
}
}
if string(dst) == ipv6AllMLDv2CapableRouters {
return true
}
// Actually, just catch all multicast.
if dst[0] == 0xff {
return true
}
}
}
return false
}
// isLinkLocalV6 reports whether src is in fe80::/10.
func isLinkLocalV6(src []byte) bool {
return len(src) == 16 && src[0] == 0xfe && src[1]>>6 == 0x80>>6
}

View File

@@ -6,10 +6,7 @@ package filter
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"net"
"strings"
"testing"
"tailscale.com/types/logger"
@@ -163,35 +160,6 @@ func TestNoAllocs(t *testing.T) {
}
}
func TestParseIP(t *testing.T) {
tests := []struct {
host string
bits int
want Net
wantErr string
}{
{"8.8.8.8", 24, Net{IP: packet.NewIP(net.ParseIP("8.8.8.8")), Mask: packet.NewIP(net.ParseIP("255.255.255.0"))}, ""},
{"8.8.8.8", 33, Net{}, `invalid CIDR size 33 for host "8.8.8.8"`},
{"8.8.8.8", -1, Net{}, `invalid CIDR size -1 for host "8.8.8.8"`},
{"0.0.0.0", 24, Net{}, `ports="0.0.0.0": to allow all IP addresses, use *:port, not 0.0.0.0:port`},
{"*", 24, NetAny, ""},
{"fe80::1", 128, NetNone, `ports="fe80::1": invalid IPv4 address`},
}
for _, tt := range tests {
got, err := parseIP(tt.host, tt.bits)
if err != nil {
if err.Error() == tt.wantErr {
continue
}
t.Errorf("parseIP(%q, %v) error: %v; want error %q", tt.host, tt.bits, err, tt.wantErr)
}
if got != tt.want {
t.Errorf("parseIP(%q, %v) = %#v; want %#v", tt.host, tt.bits, got, tt.want)
continue
}
}
}
func BenchmarkFilter(b *testing.B) {
acl := newFilter(b.Logf)
@@ -251,7 +219,7 @@ func TestPreFilter(t *testing.T) {
for _, testPacket := range packets {
p := &ParsedPacket{}
p.Decode(testPacket.b)
got := f.pre(p, LogDrops|LogAccepts, in)
got := f.pre(p, LogDrops|LogAccepts)
if got != testPacket.want {
t.Errorf("%q got=%v want=%v packet:\n%s", testPacket.desc, got, testPacket.want, packet.Hexdump(testPacket.b))
}
@@ -330,63 +298,3 @@ func rawdefault(proto packet.IPProto, trimLength int) []byte {
port := uint16(53)
return rawpacket(proto, ip, ip, port, port, trimLength)
}
func parseHexPkt(t *testing.T, h string) *packet.ParsedPacket {
t.Helper()
b, err := hex.DecodeString(strings.ReplaceAll(h, " ", ""))
if err != nil {
t.Fatalf("failed to read hex %q: %v", h, err)
}
p := new(packet.ParsedPacket)
p.Decode(b)
return p
}
func TestOmitDropLogging(t *testing.T) {
tests := []struct {
name string
pkt *packet.ParsedPacket
dir direction
want bool
}{
{
name: "v4_tcp_out",
pkt: &packet.ParsedPacket{IPVersion: 4, IPProto: packet.TCP},
dir: out,
want: false,
},
{
name: "v6_icmp_out", // as seen on Linux
pkt: parseHexPkt(t, "60 00 00 00 00 00 3a 00 fe800000000000000000000000000000 ff020000000000000000000000000002"),
dir: out,
want: true,
},
{
name: "v6_to_MLDv2_capable_routers", // as seen on Windows
pkt: parseHexPkt(t, "60 00 00 00 00 24 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff 02 00 00 00 00 00 00 00 00 00 00 00 00 00 16 3a 00 05 02 00 00 01 00 8f 00 6e 80 00 00 00 01 04 00 00 00 ff 02 00 00 00 00 00 00 00 00 00 00 00 00 00 0c"),
dir: out,
want: true,
},
{
name: "v4_igmp_out", // on Windows, from https://github.com/tailscale/tailscale/issues/618
pkt: parseHexPkt(t, "46 00 00 30 37 3a 00 00 01 02 10 0e a9 fe 53 6b e0 00 00 16 94 04 00 00 22 00 14 05 00 00 00 02 04 00 00 00 e0 00 00 fb 04 00 00 00 e0 00 00 fc"),
dir: out,
want: true,
},
{
name: "v6_udp_multicast",
pkt: parseHexPkt(t, "60 00 00 00 00 00 11 00 fe800000000000007dc6bc04499262a3 ff120000000000000000000000008384"),
dir: out,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := omitDropLogging(tt.pkt, tt.dir)
if got != tt.want {
t.Errorf("got %v; want %v\npacket: %#v\n%s", got, tt.want, tt.pkt, packet.Hexdump(tt.pkt.Buffer()))
}
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,23 +6,19 @@ package magicsock
import (
"bytes"
"context"
crand "crypto/rand"
"crypto/tls"
"encoding/binary"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"os"
"strconv"
"strings"
"sync"
"testing"
"time"
"unsafe"
"github.com/google/go-cmp/cmp"
"github.com/tailscale/wireguard-go/device"
@@ -30,11 +26,9 @@ import (
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/crypto/nacl/box"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/derp/derpmap"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/stun/stuntest"
"tailscale.com/tailcfg"
"tailscale.com/tstest"
@@ -72,6 +66,11 @@ func runDERPAndStun(t *testing.T, logf logger.Logf, l nettype.PacketListener, st
t.Fatal(err)
}
d := derp.NewServer(serverPrivateKey, logf)
if l != (nettype.Std{}) {
// When using virtual networking, only allow DERP to forward
// discovery traffic, not actual packets.
d.OnlyDisco = true
}
httpsrv := httptest.NewUnstartedServer(derphttp.Handler(d))
httpsrv.Config.ErrorLog = logger.StdLogger(logf)
@@ -173,7 +172,7 @@ func newMagicStack(t *testing.T, logf logger.Logf, l nettype.PacketListener, der
// Wait for first endpoint update to be available
deadline := time.Now().Add(2 * time.Second)
for len(epCh) == 0 && time.Now().Before(deadline) {
time.Sleep(100 * time.Millisecond)
time.Sleep(10 * time.Millisecond)
}
return &magicStack{
@@ -186,136 +185,11 @@ func newMagicStack(t *testing.T, logf logger.Logf, l nettype.PacketListener, der
}
}
func (s *magicStack) String() string {
pub := s.Public()
return pub.ShortString()
}
func (s *magicStack) Close() {
s.dev.Close()
s.conn.Close()
}
func (s *magicStack) Public() key.Public {
return key.Public(s.privateKey.Public())
}
func (s *magicStack) Status() *ipnstate.Status {
var sb ipnstate.StatusBuilder
s.conn.UpdateStatus(&sb)
return sb.Status()
}
// IP returns the Tailscale IP address assigned to this magicStack.
//
// Something external needs to provide a NetworkMap and WireGuard
// configs to the magicStack in order for it to acquire an IP
// address. See meshStacks for one possible source of netmaps and IPs.
func (s *magicStack) IP(t *testing.T) netaddr.IP {
for deadline := time.Now().Add(5 * time.Second); time.Now().Before(deadline); time.Sleep(10 * time.Millisecond) {
st := s.Status()
if len(st.TailscaleIPs) > 0 {
return st.TailscaleIPs[0]
}
}
t.Fatal("timed out waiting for magicstack to get an IP assigned")
panic("unreachable") // compiler doesn't know t.Fatal panics
}
// meshStacks monitors epCh on all given ms, and plumbs network maps
// and WireGuard configs into everyone to form a full mesh that has up
// to date endpoint info. Think of it as an extremely stripped down
// and purpose-built Tailscale control plane.
//
// meshStacks only supports disco connections, not legacy logic.
func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) {
ctx, cancel := context.WithCancel(context.Background())
// Serialize all reconfigurations globally, just to keep things
// simpler.
var (
mu sync.Mutex
eps = make([][]string, len(ms))
)
buildNetmapLocked := func(myIdx int) *controlclient.NetworkMap {
me := ms[myIdx]
nm := &controlclient.NetworkMap{
PrivateKey: me.privateKey,
NodeKey: tailcfg.NodeKey(me.privateKey.Public()),
Addresses: []wgcfg.CIDR{{IP: wgcfg.IPv4(1, 0, 0, byte(myIdx+1)), Mask: 32}},
}
for i, peer := range ms {
if i == myIdx {
continue
}
addrs := []wgcfg.CIDR{{IP: wgcfg.IPv4(1, 0, 0, byte(i+1)), Mask: 32}}
peer := &tailcfg.Node{
ID: tailcfg.NodeID(i + 1),
Name: fmt.Sprintf("node%d", i+1),
Key: tailcfg.NodeKey(peer.privateKey.Public()),
DiscoKey: peer.conn.DiscoPublicKey(),
Addresses: addrs,
AllowedIPs: addrs,
Endpoints: eps[i],
DERP: "127.3.3.40:1",
}
nm.Peers = append(nm.Peers, peer)
}
return nm
}
updateEps := func(idx int, newEps []string) {
mu.Lock()
defer mu.Unlock()
eps[idx] = newEps
for i, m := range ms {
netmap := buildNetmapLocked(i)
m.conn.SetNetworkMap(netmap)
peerSet := make(map[key.Public]struct{}, len(netmap.Peers))
for _, peer := range netmap.Peers {
peerSet[key.Public(peer.Key)] = struct{}{}
}
m.conn.UpdatePeers(peerSet)
wg, err := netmap.WGCfg(logf, controlclient.AllowSingleHosts)
if err != nil {
// We're too far from the *testing.T to be graceful,
// blow up. Shouldn't happen anyway.
panic(fmt.Sprintf("failed to construct wgcfg from netmap: %v", err))
}
if err := m.dev.Reconfig(wg); err != nil {
panic(fmt.Sprintf("device reconfig failed: %v", err))
}
}
}
var wg sync.WaitGroup
wg.Add(len(ms))
for i := range ms {
go func(myIdx int) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case eps := <-ms[myIdx].epCh:
logf("conn%d endpoints update", myIdx+1)
updateEps(myIdx, eps)
}
}
}(i)
}
return func() {
cancel()
wg.Wait()
}
}
func TestNewConn(t *testing.T) {
tstest.PanicOnLog()
rc := tstest.NewResourceCheck()
@@ -559,136 +433,65 @@ func makeNestable(t *testing.T) (logf logger.Logf, setT func(t *testing.T)) {
}
func TestTwoDevicePing(t *testing.T) {
l, ip := nettype.Std{}, netaddr.IPv4(127, 0, 0, 1)
n := &devices{
m1: l,
m1IP: ip,
m2: l,
m2IP: ip,
stun: l,
stunIP: ip,
}
testTwoDevicePing(t, n)
}
func TestActiveDiscovery(t *testing.T) {
t.Run("simple_internet", func(t *testing.T) {
t.Parallel()
mstun := &natlab.Machine{Name: "stun"}
m1 := &natlab.Machine{Name: "m1"}
m2 := &natlab.Machine{Name: "m2"}
inet := natlab.NewInternet()
sif := mstun.Attach("eth0", inet)
m1if := m1.Attach("eth0", inet)
m2if := m2.Attach("eth0", inet)
t.Run("real", func(t *testing.T) {
l, ip := nettype.Std{}, netaddr.IPv4(127, 0, 0, 1)
n := &devices{
m1: m1,
m1IP: m1if.V4(),
m2: m2,
m2IP: m2if.V4(),
stun: mstun,
stunIP: sif.V4(),
m1: l,
m1IP: ip,
m2: l,
m2IP: ip,
stun: l,
stunIP: ip,
}
testActiveDiscovery(t, n)
testTwoDevicePing(t, n)
})
t.Run("natlab", func(t *testing.T) {
t.Run("simple internet", func(t *testing.T) {
mstun := &natlab.Machine{Name: "stun"}
m1 := &natlab.Machine{Name: "m1"}
m2 := &natlab.Machine{Name: "m2"}
inet := natlab.NewInternet()
sif := mstun.Attach("eth0", inet)
m1if := m1.Attach("eth0", inet)
m2if := m2.Attach("eth0", inet)
t.Run("facing_easy_firewalls", func(t *testing.T) {
mstun := &natlab.Machine{Name: "stun"}
m1 := &natlab.Machine{
Name: "m1",
PacketHandler: &natlab.Firewall{},
}
m2 := &natlab.Machine{
Name: "m2",
PacketHandler: &natlab.Firewall{},
}
inet := natlab.NewInternet()
sif := mstun.Attach("eth0", inet)
m1if := m1.Attach("eth0", inet)
m2if := m2.Attach("eth0", inet)
n := &devices{
m1: m1,
m1IP: m1if.V4(),
m2: m2,
m2IP: m2if.V4(),
stun: mstun,
stunIP: sif.V4(),
}
testTwoDevicePing(t, n)
})
n := &devices{
m1: m1,
m1IP: m1if.V4(),
m2: m2,
m2IP: m2if.V4(),
stun: mstun,
stunIP: sif.V4(),
}
testActiveDiscovery(t, n)
t.Run("facing firewalls", func(t *testing.T) {
mstun := &natlab.Machine{Name: "stun"}
m1 := &natlab.Machine{
Name: "m1",
PacketHandler: &natlab.Firewall{},
}
m2 := &natlab.Machine{
Name: "m2",
PacketHandler: &natlab.Firewall{},
}
inet := natlab.NewInternet()
sif := mstun.Attach("eth0", inet)
m1if := m1.Attach("eth0", inet)
m2if := m2.Attach("eth0", inet)
n := &devices{
m1: m1,
m1IP: m1if.V4(),
m2: m2,
m2IP: m2if.V4(),
stun: mstun,
stunIP: sif.V4(),
}
testTwoDevicePing(t, n)
})
})
t.Run("facing_nats", func(t *testing.T) {
mstun := &natlab.Machine{Name: "stun"}
m1 := &natlab.Machine{
Name: "m1",
PacketHandler: &natlab.Firewall{},
}
nat1 := &natlab.Machine{
Name: "nat1",
}
m2 := &natlab.Machine{
Name: "m2",
PacketHandler: &natlab.Firewall{},
}
nat2 := &natlab.Machine{
Name: "nat2",
}
inet := natlab.NewInternet()
lan1 := &natlab.Network{
Name: "lan1",
Prefix4: mustPrefix("192.168.0.0/24"),
}
lan2 := &natlab.Network{
Name: "lan2",
Prefix4: mustPrefix("192.168.1.0/24"),
}
sif := mstun.Attach("eth0", inet)
nat1WAN := nat1.Attach("wan", inet)
nat1LAN := nat1.Attach("lan1", lan1)
nat2WAN := nat2.Attach("wan", inet)
nat2LAN := nat2.Attach("lan2", lan2)
m1if := m1.Attach("eth0", lan1)
m2if := m2.Attach("eth0", lan2)
lan1.SetDefaultGateway(nat1LAN)
lan2.SetDefaultGateway(nat2LAN)
nat1.PacketHandler = &natlab.SNAT44{
Machine: nat1,
ExternalInterface: nat1WAN,
Firewall: &natlab.Firewall{
TrustedInterface: nat1LAN,
},
}
nat2.PacketHandler = &natlab.SNAT44{
Machine: nat2,
ExternalInterface: nat2WAN,
Firewall: &natlab.Firewall{
TrustedInterface: nat2LAN,
},
}
n := &devices{
m1: m1,
m1IP: m1if.V4(),
m2: m2,
m2IP: m2if.V4(),
stun: mstun,
stunIP: sif.V4(),
}
testActiveDiscovery(t, n)
})
}
func mustPrefix(s string) netaddr.IPPrefix {
pfx, err := netaddr.ParseIPPrefix(s)
if err != nil {
panic(err)
}
return pfx
}
type devices struct {
@@ -702,135 +505,6 @@ type devices struct {
stunIP netaddr.IP
}
// newPinger starts continuously sending test packets from srcM to
// dstM, until cleanup is invoked to stop it. Each ping has 1 second
// to transit the network. It is a test failure to lose a ping.
func newPinger(t *testing.T, logf logger.Logf, src, dst *magicStack) (cleanup func()) {
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
one := func() bool {
// TODO(danderson): requiring exactly zero packet loss
// will probably be too strict for some tests we'd like to
// run (e.g. discovery switching to a new path on
// failure). Figure out what kind of thing would be
// acceptable to test instead of "every ping must
// transit".
pkt := tuntest.Ping(dst.IP(t).IPAddr().IP, src.IP(t).IPAddr().IP)
select {
case src.tun.Outbound <- pkt:
case <-ctx.Done():
return false
}
select {
case <-dst.tun.Inbound:
return true
case <-time.After(10 * time.Second):
// Very generous timeout here because depending on
// magicsock setup races, the first handshake might get
// eaten by the receiving end (if wireguard-go hasn't been
// configured quite yet), so we have to wait for at least
// the first retransmit from wireguard before we declare
// failure.
t.Errorf("timed out waiting for ping to transit")
return true
case <-ctx.Done():
// Try a little bit longer to consume the packet we're
// waiting for. This is to deal with shutdown races, where
// natlab may still be delivering a packet to us from a
// goroutine.
select {
case <-dst.tun.Inbound:
case <-time.After(time.Second):
}
return false
}
}
cleanup = func() {
cancel()
<-done
}
// Synchronously transit one ping to get things started. This is
// nice because it means that newPinger returning means we've
// worked through initial connectivity.
if !one() {
cleanup()
return
}
go func() {
logf("sending ping stream from %s (%s) to %s (%s)", src, src.IP(t), dst, dst.IP(t))
defer close(done)
for one() {
}
}()
return cleanup
}
// testActiveDiscovery verifies that two magicStacks tied to the given
// devices can establish a direct p2p connection with each other. See
// TestActiveDiscovery for the various configurations of devices that
// get exercised.
func testActiveDiscovery(t *testing.T, d *devices) {
tstest.PanicOnLog()
rc := tstest.NewResourceCheck()
defer rc.Assert(t)
tlogf, setT := makeNestable(t)
setT(t)
start := time.Now()
logf := func(msg string, args ...interface{}) {
msg = fmt.Sprintf("%s: %s", time.Since(start), msg)
tlogf(msg, args...)
}
derpMap, cleanup := runDERPAndStun(t, logf, d.stun, d.stunIP)
defer cleanup()
m1 := newMagicStack(t, logger.WithPrefix(logf, "conn1: "), d.m1, derpMap)
defer m1.Close()
m2 := newMagicStack(t, logger.WithPrefix(logf, "conn2: "), d.m2, derpMap)
defer m2.Close()
cleanup = meshStacks(logf, []*magicStack{m1, m2})
defer cleanup()
m1IP := m1.IP(t)
m2IP := m2.IP(t)
logf("IPs: %s %s", m1IP, m2IP)
cleanup = newPinger(t, logf, m1, m2)
defer cleanup()
// Everything is now up and running, active discovery should find
// a direct path between our peers. Wait for it to switch away
// from DERP.
mustDirect := func(m1, m2 *magicStack) {
lastLog := time.Now().Add(-time.Minute)
for deadline := time.Now().Add(5 * time.Second); time.Now().Before(deadline); time.Sleep(10 * time.Millisecond) {
pst := m1.Status().Peer[m2.Public()]
if pst.CurAddr != "" {
logf("direct link %s->%s found with addr %s", m1, m2, pst.CurAddr)
return
}
if now := time.Now(); now.Sub(lastLog) > time.Second {
logf("no direct path %s->%s yet, addrs %v", m1, m2, pst.Addrs)
lastLog = now
}
}
t.Errorf("magicsock did not find a direct path from %s to %s", m1, m2)
}
mustDirect(m1, m2)
mustDirect(m2, m1)
logf("starting cleanup")
}
func testTwoDevicePing(t *testing.T, d *devices) {
tstest.PanicOnLog()
rc := tstest.NewResourceCheck()
@@ -930,7 +604,7 @@ func testTwoDevicePing(t *testing.T, d *devices) {
})
// TODO: Remove this once the following tests are reliable.
if run, _ := strconv.ParseBool(os.Getenv("RUN_CURSED_TESTS")); !run {
if os.Getenv("RUN_CURSED_TESTS") == "" {
t.Skip("skipping following tests because RUN_CURSED_TESTS is not set.")
}
@@ -1028,8 +702,10 @@ func testTwoDevicePing(t *testing.T, d *devices) {
defer setT(outerT)
defer func() {
if t.Failed() || true {
logf("cfg0: %v", stringifyConfig(cfgs[0]))
logf("cfg1: %v", stringifyConfig(cfgs[1]))
uapi1, _ := cfgs[0].ToUAPI()
logf("cfg0: %v", uapi1)
uapi2, _ := cfgs[1].ToUAPI()
logf("cfg1: %v", uapi2)
}
}()
pingSeq(t, 20, 0, true)
@@ -1267,7 +943,6 @@ func initAddrSet(as *AddrSet) {
func TestDiscoMessage(t *testing.T) {
c := newConn()
c.logf = t.Logf
c.privateKey = key.NewPrivate()
peer1Pub := c.DiscoPublicKey()
peer1Priv := c.discoPrivate
@@ -1315,25 +990,3 @@ func TestDiscoStringLogRace(t *testing.T) {
}()
wg.Wait()
}
func stringifyConfig(cfg wgcfg.Config) string {
j, err := json.Marshal(cfg)
if err != nil {
panic(err)
}
return string(j)
}
func TestDiscoEndpointAlignment(t *testing.T) {
var de discoEndpoint
off := unsafe.Offsetof(de.lastRecvUnixAtomic)
if off%8 != 0 {
t.Fatalf("lastRecvUnixAtomic is not 8-byte aligned")
}
if !de.isFirstRecvActivityInAwhile() { // verify this doesn't panic on 32-bit
t.Error("expected true")
}
if de.isFirstRecvActivityInAwhile() {
t.Error("expected false on second call")
}
}

View File

@@ -110,7 +110,7 @@ func (m *Mon) pump() {
default:
}
// Keep retrying while we're not closed.
m.logf("error from link monitor: %v", err)
m.logf("error receiving from connection: %v", err)
time.Sleep(time.Second)
continue
}

View File

@@ -30,6 +30,9 @@ func newOSMon(logf logger.Logf) (osMon, error) {
if err != nil {
return nil, fmt.Errorf("devd dial error: %v", err)
}
if err != nil {
return nil, fmt.Errorf("dialing devd socket: %v", err)
}
return &devdConn{conn}, nil
}

View File

@@ -79,7 +79,7 @@ func (c *nlConn) Receive() (message, error) {
case unix.RTM_NEWADDR, unix.RTM_DELADDR:
var rmsg rtnetlink.AddressMessage
if err := rmsg.UnmarshalBinary(msg.Data); err != nil {
c.logf("failed to parse type %v: %v", msg.Header.Type, err)
c.logf("failed to parse type 0x%x: %v", msg.Header.Type, err)
return unspecifiedMessage{}, nil
}
return &newAddrMessage{
@@ -87,41 +87,20 @@ func (c *nlConn) Receive() (message, error) {
Addr: netaddrIP(rmsg.Attributes.Local),
Delete: msg.Header.Type == unix.RTM_DELADDR,
}, nil
case unix.RTM_NEWROUTE, unix.RTM_DELROUTE:
typeStr := "RTM_NEWROUTE"
if msg.Header.Type == unix.RTM_DELROUTE {
typeStr = "RTM_DELROUTE"
}
case unix.RTM_NEWROUTE:
var rmsg rtnetlink.RouteMessage
if err := rmsg.UnmarshalBinary(msg.Data); err != nil {
c.logf("%s: failed to parse: %v", typeStr, err)
return unspecifiedMessage{}, nil
}
src := netaddrIPPrefix(rmsg.Attributes.Src, rmsg.SrcLength)
dst := netaddrIPPrefix(rmsg.Attributes.Dst, rmsg.DstLength)
gw := netaddrIP(rmsg.Attributes.Gateway)
if msg.Header.Type == unix.RTM_NEWROUTE && rmsg.Table == tsTable && rmsg.DstLength == 32 {
// Don't log. Spammy and normal to see a bunch of these on start-up,
// which we make ourselves.
} else {
c.logf("%s: src=%v, dst=%v, gw=%v, outif=%v, table=%v", typeStr,
condNetAddrPrefix(src), condNetAddrPrefix(dst), condNetAddrIP(gw),
rmsg.Attributes.OutIface, rmsg.Attributes.Table)
}
if msg.Header.Type == unix.RTM_DELROUTE {
// Just logging it for now.
// (Debugging https://github.com/tailscale/tailscale/issues/643)
c.logf("RTM_NEWROUTE: failed to parse: %v", err)
return unspecifiedMessage{}, nil
}
return &newRouteMessage{
Table: rmsg.Table,
Src: src,
Dst: dst,
Gateway: gw,
Src: netaddrIP(rmsg.Attributes.Src),
Dst: netaddrIP(rmsg.Attributes.Dst),
Gateway: netaddrIP(rmsg.Attributes.Gateway),
}, nil
default:
c.logf("unhandled netlink msg type %+v, %q", msg.Header, msg.Data)
c.logf("unhandled netlink msg type 0x%x: %+v, %q", msg.Header.Type, msg.Header, msg.Data)
return unspecifiedMessage{}, nil
}
}
@@ -131,36 +110,14 @@ func netaddrIP(std net.IP) netaddr.IP {
return ip
}
func netaddrIPPrefix(std net.IP, bits uint8) netaddr.IPPrefix {
ip, _ := netaddr.FromStdIP(std)
return netaddr.IPPrefix{IP: ip, Bits: bits}
}
func condNetAddrPrefix(ipp netaddr.IPPrefix) string {
if ipp.IP.IsZero() {
return ""
}
return ipp.String()
}
func condNetAddrIP(ip netaddr.IP) string {
if ip.IsZero() {
return ""
}
return ip.String()
}
// newRouteMessage is a message for a new route being added.
type newRouteMessage struct {
Src, Dst netaddr.IPPrefix
Gateway netaddr.IP
Table uint8
Src, Dst, Gateway netaddr.IP
Table uint8
}
const tsTable = 52
func (m *newRouteMessage) ignore() bool {
return m.Table == tsTable || tsaddr.IsTailscaleIP(m.Dst.IP)
return m.Table == 88 || tsaddr.IsTailscaleIP(m.Dst)
}
// newAddrMessage is a message for a new address being added.

Some files were not shown because too many files have changed in this diff Show More