summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNaman Sood <mail@nsood.in>2021-03-29 14:28:08 -0400
committerNaman Sood <mail@nsood.in>2021-03-29 14:28:08 -0400
commitc0a88a0129ebf0f9886b93b1f4e4f04a7c3bb86f (patch)
tree57d5aef2985e3424e5bb6f4c810628aa3ccbf5d0
parent47bd3c4cf5543fd7ecb049302c37c1001fa9f2d6 (diff)
parenta4c679e64691a3f0ba41ad9078312ca67e5e67fd (diff)
downloadtailscale-naman/netstack-subnet-routing.tar.xz
tailscale-naman/netstack-subnet-routing.zip
Signed-off-by: Naman Sood <mail@nsood.in>
-rw-r--r--client/tailscale/tailscale.go33
-rw-r--r--cmd/tailscale/cli/cli.go84
-rw-r--r--cmd/tailscale/cli/down.go24
-rw-r--r--cmd/tailscale/cli/ip.go69
-rw-r--r--cmd/tailscale/cli/ping.go35
-rw-r--r--cmd/tailscale/cli/status.go47
-rw-r--r--cmd/tailscale/cli/up.go9
-rw-r--r--cmd/tailscale/cli/version.go31
-rw-r--r--cmd/tailscale/depaware.txt12
-rw-r--r--cmd/tailscaled/depaware.txt21
-rw-r--r--cmd/tailscaled/tailscaled.go42
-rw-r--r--cmd/tailscaled/tailscaled_windows.go16
-rw-r--r--control/controlclient/auto.go11
-rw-r--r--control/controlclient/direct.go23
-rw-r--r--control/controlclient/sign.go31
-rw-r--r--control/controlclient/sign_supported.go160
-rw-r--r--control/controlclient/sign_unsupported.go17
-rw-r--r--go.mod7
-rw-r--r--go.sum328
-rw-r--r--health/health.go12
-rw-r--r--internal/deepprint/deepprint_test.go2
-rw-r--r--ipn/backend.go11
-rw-r--r--ipn/fake_test.go10
-rw-r--r--ipn/handle.go8
-rw-r--r--ipn/ipnlocal/local.go202
-rw-r--r--ipn/ipnlocal/local_test.go32
-rw-r--r--ipn/ipnlocal/peerapi.go167
-rw-r--r--ipn/ipnlocal/peerapi_macios_ext.go54
-rw-r--r--ipn/ipnstate/ipnstate.go59
-rw-r--r--ipn/localapi/localapi.go32
-rw-r--r--ipn/message.go21
-rw-r--r--ipn/message_test.go4
-rw-r--r--net/dns/config.go (renamed from wgengine/router/dns/config.go)4
-rw-r--r--net/dns/direct.go (renamed from wgengine/router/dns/direct.go)0
-rw-r--r--net/dns/flush_windows.go19
-rw-r--r--net/dns/forwarder.go (renamed from wgengine/tsdns/forwarder.go)4
-rw-r--r--net/dns/manager.go (renamed from wgengine/router/dns/manager.go)0
-rw-r--r--net/dns/manager_default.go (renamed from wgengine/router/dns/manager_default.go)0
-rw-r--r--net/dns/manager_freebsd.go (renamed from wgengine/router/dns/manager_freebsd.go)0
-rw-r--r--net/dns/manager_linux.go (renamed from wgengine/router/dns/manager_linux.go)0
-rw-r--r--net/dns/manager_openbsd.go (renamed from wgengine/router/dns/manager_openbsd.go)0
-rw-r--r--net/dns/manager_windows.go (renamed from wgengine/router/dns/manager_windows.go)0
-rw-r--r--net/dns/map.go (renamed from wgengine/tsdns/map.go)2
-rw-r--r--net/dns/map_test.go (renamed from wgengine/tsdns/map_test.go)2
-rw-r--r--net/dns/neterr_darwin.go (renamed from wgengine/tsdns/neterr_darwin.go)2
-rw-r--r--net/dns/neterr_other.go (renamed from wgengine/tsdns/neterr_other.go)2
-rw-r--r--net/dns/neterr_windows.go (renamed from wgengine/tsdns/neterr_windows.go)2
-rw-r--r--net/dns/nm.go (renamed from wgengine/router/dns/nm.go)0
-rw-r--r--net/dns/noop.go (renamed from wgengine/router/dns/noop.go)0
-rw-r--r--net/dns/registry_windows.go (renamed from wgengine/router/dns/registry_windows.go)0
-rw-r--r--net/dns/resolvconf.go (renamed from wgengine/router/dns/resolvconf.go)0
-rw-r--r--net/dns/resolved.go (renamed from wgengine/router/dns/resolved.go)0
-rw-r--r--net/dns/tsdns.go (renamed from wgengine/tsdns/tsdns.go)6
-rw-r--r--net/dns/tsdns_server_test.go (renamed from wgengine/tsdns/tsdns_server_test.go)2
-rw-r--r--net/dns/tsdns_test.go (renamed from wgengine/tsdns/tsdns_test.go)2
-rw-r--r--net/flowtrack/flowtrack.go10
-rw-r--r--net/interfaces/interfaces.go117
-rw-r--r--net/interfaces/interfaces_test.go12
-rw-r--r--net/interfaces/interfaces_windows.go108
-rw-r--r--net/packet/header.go1
-rw-r--r--net/packet/icmp4.go8
-rw-r--r--net/packet/ip4.go3
-rw-r--r--net/packet/ip6.go3
-rw-r--r--net/packet/packet.go104
-rw-r--r--net/packet/packet_test.go48
-rw-r--r--net/packet/tsmp.go72
-rw-r--r--net/packet/udp4.go8
-rw-r--r--net/packet/udp6.go8
-rw-r--r--net/tsaddr/tsaddr.go2
-rw-r--r--net/tstun/fake.go (renamed from wgengine/tstun/faketun.go)6
-rw-r--r--net/tstun/ifstatus_noop.go (renamed from wgengine/ifstatus_noop.go)2
-rw-r--r--net/tstun/ifstatus_windows.go (renamed from wgengine/ifstatus_windows.go)2
-rw-r--r--net/tstun/tun.go129
-rw-r--r--net/tstun/tun_windows.go (renamed from wgengine/tstun/tun_windows.go)0
-rw-r--r--net/tstun/wrap.go (renamed from wgengine/tstun/tun.go)109
-rw-r--r--net/tstun/wrap_test.go (renamed from wgengine/tstun/tun_test.go)27
-rw-r--r--syncs/syncs.go47
-rw-r--r--syncs/syncs_test.go27
-rw-r--r--syncs/watchdog_test.go9
-rw-r--r--tailcfg/tailcfg.go110
-rw-r--r--tstest/natlab/natlab.go4
-rw-r--r--types/ipproto/ipproto.go (renamed from net/packet/ip.go)36
-rw-r--r--wgengine/filter/filter.go57
-rw-r--r--wgengine/filter/filter_test.go257
-rw-r--r--wgengine/filter/match.go21
-rw-r--r--wgengine/filter/match_clone.go7
-rw-r--r--wgengine/filter/tailcfg.go19
-rw-r--r--wgengine/magicsock/magicsock.go194
-rw-r--r--wgengine/magicsock/magicsock_test.go141
-rw-r--r--wgengine/monitor/monitor.go142
-rw-r--r--wgengine/monitor/monitor_polling.go3
-rw-r--r--wgengine/netstack/netstack.go10
-rw-r--r--wgengine/pendopen.go27
-rw-r--r--wgengine/router/router.go7
-rw-r--r--wgengine/router/router_darwin.go5
-rw-r--r--wgengine/router/router_default.go5
-rw-r--r--wgengine/router/router_fake.go10
-rw-r--r--wgengine/router/router_freebsd.go5
-rw-r--r--wgengine/router/router_linux.go5
-rw-r--r--wgengine/router/router_linux_test.go2
-rw-r--r--wgengine/router/router_openbsd.go5
-rw-r--r--wgengine/router/router_userspace_bsd.go5
-rw-r--r--wgengine/router/router_windows.go14
-rw-r--r--wgengine/userspace.go462
-rw-r--r--wgengine/userspace_test.go51
-rw-r--r--wgengine/watchdog.go8
-rw-r--r--wgengine/watchdog_test.go12
-rw-r--r--wgengine/wgcfg/device_test.go8
-rw-r--r--wgengine/wgengine.go6
109 files changed, 2821 insertions, 1340 deletions
diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go
index a6d304332..3947f4ff5 100644
--- a/client/tailscale/tailscale.go
+++ b/client/tailscale/tailscale.go
@@ -15,6 +15,7 @@ import (
"net/url"
"strconv"
+ "tailscale.com/ipn/ipnstate"
"tailscale.com/safesocket"
"tailscale.com/tailcfg"
)
@@ -79,6 +80,7 @@ func WhoIs(ctx context.Context, remoteAddr string) (*tailcfg.WhoIsResponse, erro
return r, nil
}
+// Goroutines returns a dump of the Tailscale daemon's current goroutines.
func Goroutines(ctx context.Context) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/goroutines", nil)
if err != nil {
@@ -98,3 +100,34 @@ func Goroutines(ctx context.Context) ([]byte, error) {
}
return body, nil
}
+
+// Status returns the Tailscale daemon's status.
+func Status(ctx context.Context) (*ipnstate.Status, error) {
+ return status(ctx, "")
+}
+
+// StatusWithPeers returns the Tailscale daemon's status, without the peer info.
+func StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) {
+ return status(ctx, "?peers=false")
+}
+
+func status(ctx context.Context, queryString string) (*ipnstate.Status, error) {
+ req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/status"+queryString, nil)
+ if err != nil {
+ return nil, err
+ }
+ res, err := DoLocalRequest(req)
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+ if res.StatusCode != 200 {
+ body, _ := ioutil.ReadAll(res.Body)
+ return nil, fmt.Errorf("HTTP %s: %s", res.Status, body)
+ }
+ st := new(ipnstate.Status)
+ if err := json.NewDecoder(res.Body).Decode(st); err != nil {
+ return nil, err
+ }
+ return st, nil
+}
diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go
index 254210ab6..1d7f95766 100644
--- a/cmd/tailscale/cli/cli.go
+++ b/cmd/tailscale/cli/cli.go
@@ -9,6 +9,7 @@ package cli
import (
"context"
"flag"
+ "fmt"
"log"
"net"
"os"
@@ -16,6 +17,7 @@ import (
"runtime"
"strings"
"syscall"
+ "text/tabwriter"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/ipn"
@@ -53,9 +55,7 @@ func Run(args []string) error {
ShortUsage: "tailscale [flags] <subcommand> [command flags]",
ShortHelp: "The easiest, most secure way to use WireGuard.",
LongHelp: strings.TrimSpace(`
-For help on subcommands, add -help after: "tailscale status -help".
-
-All flags can use single or double hyphen prefixes (-help or --help).
+For help on subcommands, add --help after: "tailscale status --help".
This CLI is still under active development. Commands and flags will
change in the future.
@@ -64,12 +64,17 @@ change in the future.
upCmd,
downCmd,
netcheckCmd,
+ ipCmd,
statusCmd,
pingCmd,
versionCmd,
},
- FlagSet: rootfs,
- Exec: func(context.Context, []string) error { return flag.ErrHelp },
+ FlagSet: rootfs,
+ Exec: func(context.Context, []string) error { return flag.ErrHelp },
+ UsageFunc: usageFunc,
+ }
+ for _, c := range rootCmd.Subcommands {
+ c.UsageFunc = usageFunc
}
// Don't advertise the debug command, but it exists.
@@ -147,3 +152,72 @@ func strSliceContains(ss []string, s string) bool {
}
return false
}
+
+func usageFunc(c *ffcli.Command) string {
+ var b strings.Builder
+
+ fmt.Fprintf(&b, "USAGE\n")
+ if c.ShortUsage != "" {
+ fmt.Fprintf(&b, " %s\n", c.ShortUsage)
+ } else {
+ fmt.Fprintf(&b, " %s\n", c.Name)
+ }
+ fmt.Fprintf(&b, "\n")
+
+ if c.LongHelp != "" {
+ fmt.Fprintf(&b, "%s\n\n", c.LongHelp)
+ }
+
+ if len(c.Subcommands) > 0 {
+ fmt.Fprintf(&b, "SUBCOMMANDS\n")
+ tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0)
+ for _, subcommand := range c.Subcommands {
+ fmt.Fprintf(tw, " %s\t%s\n", subcommand.Name, subcommand.ShortHelp)
+ }
+ tw.Flush()
+ fmt.Fprintf(&b, "\n")
+ }
+
+ if countFlags(c.FlagSet) > 0 {
+ fmt.Fprintf(&b, "FLAGS\n")
+ tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0)
+ c.FlagSet.VisitAll(func(f *flag.Flag) {
+ var s string
+ name, usage := flag.UnquoteUsage(f)
+ if isBoolFlag(f) {
+ s = fmt.Sprintf(" --%s, --%s=false", f.Name, f.Name)
+ } else {
+ s = fmt.Sprintf(" --%s", f.Name) // Two spaces before --; see next two comments.
+ if len(name) > 0 {
+ s += " " + name
+ }
+ }
+ // Four spaces before the tab triggers good alignment
+ // for both 4- and 8-space tab stops.
+ s += "\n \t"
+ s += strings.ReplaceAll(usage, "\n", "\n \t")
+
+ if f.DefValue != "" {
+ s += fmt.Sprintf(" (default %s)", f.DefValue)
+ }
+
+ fmt.Fprintln(&b, s)
+ })
+ tw.Flush()
+ fmt.Fprintf(&b, "\n")
+ }
+
+ return strings.TrimSpace(b.String())
+}
+
+func isBoolFlag(f *flag.Flag) bool {
+ bf, ok := f.Value.(interface {
+ IsBoolFlag() bool
+ })
+ return ok && bf.IsBoolFlag()
+}
+
+func countFlags(fs *flag.FlagSet) (n int) {
+ fs.VisitAll(func(*flag.Flag) { n++ })
+ return n
+}
diff --git a/cmd/tailscale/cli/down.go b/cmd/tailscale/cli/down.go
index 7e53c0cf2..dd1e8491f 100644
--- a/cmd/tailscale/cli/down.go
+++ b/cmd/tailscale/cli/down.go
@@ -6,10 +6,12 @@ package cli
import (
"context"
+ "fmt"
"log"
"time"
"github.com/peterbourgon/ff/v2/ffcli"
+ "tailscale.com/client/tailscale"
"tailscale.com/ipn"
)
@@ -26,6 +28,16 @@ func runDown(ctx context.Context, args []string) error {
log.Fatalf("too many non-flag arguments: %q", args)
}
+ st, err := tailscale.Status(ctx)
+ if err != nil {
+ return fmt.Errorf("error fetching current status: %w", err)
+ }
+ if st.BackendState == "Stopped" {
+ log.Printf("already stopped")
+ return nil
+ }
+ log.Printf("was in state %q", st.BackendState)
+
c, bc, ctx, cancel := connect(ctx)
defer cancel()
@@ -38,17 +50,6 @@ func runDown(ctx context.Context, args []string) error {
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 {
@@ -58,7 +59,6 @@ func runDown(ctx context.Context, args []string) error {
}
})
- bc.RequestStatus()
bc.SetWantRunning(false)
pump(ctx, bc, c)
diff --git a/cmd/tailscale/cli/ip.go b/cmd/tailscale/cli/ip.go
new file mode 100644
index 000000000..053ea165e
--- /dev/null
+++ b/cmd/tailscale/cli/ip.go
@@ -0,0 +1,69 @@
+// 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"
+
+ "github.com/peterbourgon/ff/v2/ffcli"
+ "tailscale.com/client/tailscale"
+)
+
+var ipCmd = &ffcli.Command{
+ Name: "ip",
+ ShortUsage: "ip [-4] [-6]",
+ ShortHelp: "Show this machine's current Tailscale IP address(es)",
+ Exec: runIP,
+ FlagSet: (func() *flag.FlagSet {
+ fs := flag.NewFlagSet("ip", flag.ExitOnError)
+ fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address")
+ fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address")
+ return fs
+ })(),
+}
+
+var ipArgs struct {
+ want4 bool
+ want6 bool
+}
+
+func runIP(ctx context.Context, args []string) error {
+ if len(args) > 0 {
+ return errors.New("unknown arguments")
+ }
+ v4, v6 := ipArgs.want4, ipArgs.want6
+ if v4 && v6 {
+ return errors.New("tailscale up -4 and -6 are mutually exclusive")
+ }
+ if !v4 && !v6 {
+ v4, v6 = true, true
+ }
+ st, err := tailscale.Status(ctx)
+ if err != nil {
+ return err
+ }
+ if len(st.TailscaleIPs) == 0 {
+ return fmt.Errorf("no current Tailscale IPs; state: %v", st.BackendState)
+ }
+ match := false
+ for _, ip := range st.TailscaleIPs {
+ if ip.Is4() && v4 || ip.Is6() && v6 {
+ match = true
+ fmt.Println(ip)
+ }
+ }
+ if !match {
+ if ipArgs.want4 {
+ return errors.New("no Tailscale IPv4 address")
+ }
+ if ipArgs.want6 {
+ return errors.New("no Tailscale IPv6 address")
+ }
+ }
+ return nil
+}
diff --git a/cmd/tailscale/cli/ping.go b/cmd/tailscale/cli/ping.go
index f98a08d4f..c82eb7902 100644
--- a/cmd/tailscale/cli/ping.go
+++ b/cmd/tailscale/cli/ping.go
@@ -15,6 +15,7 @@ import (
"time"
"github.com/peterbourgon/ff/v2/ffcli"
+ "tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
)
@@ -47,6 +48,7 @@ relay node.
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.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through IP + wireguard, but not involving host OS stack)")
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
@@ -57,6 +59,7 @@ var pingArgs struct {
num int
untilDirect bool
verbose bool
+ tsmp bool
timeout time.Duration
}
@@ -69,7 +72,6 @@ func runPing(ctx context.Context, args []string) error {
}
var ip string
prc := make(chan *ipnstate.PingResult, 1)
- stc := make(chan *ipnstate.Status, 1)
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
@@ -77,9 +79,6 @@ func runPing(ctx context.Context, args []string) error {
if pr := n.PingResult; pr != nil && pr.IP == ip {
prc <- pr
}
- if n.Status != nil {
- stc <- n.Status
- }
})
go pump(ctx, bc, c)
@@ -92,17 +91,15 @@ func runPing(ctx context.Context, args []string) error {
// Otherwise, try to resolve it first from the network peer list.
if ip == "" {
- bc.RequestStatus()
- select {
- case st := <-stc:
- for _, ps := range st.Peer {
- if hostOrIP == dnsOrQuoteHostname(st, ps) || hostOrIP == ps.DNSName {
- ip = ps.TailAddr
- break
- }
+ st, err := tailscale.Status(ctx)
+ if err != nil {
+ return err
+ }
+ for _, ps := range st.Peer {
+ if hostOrIP == dnsOrQuoteHostname(st, ps) || hostOrIP == ps.DNSName {
+ ip = ps.TailAddr
+ break
}
- case <-ctx.Done():
- return ctx.Err()
}
}
@@ -125,7 +122,7 @@ func runPing(ctx context.Context, args []string) error {
anyPong := false
for {
n++
- bc.Ping(ip)
+ bc.Ping(ip, pingArgs.tsmp)
timer := time.NewTimer(pingArgs.timeout)
select {
case <-timer.C:
@@ -140,8 +137,16 @@ func runPing(ctx context.Context, args []string) error {
if pr.DERPRegionID != 0 {
via = fmt.Sprintf("DERP(%s)", pr.DERPRegionCode)
}
+ if pingArgs.tsmp {
+ // TODO(bradfitz): populate the rest of ipnstate.PingResult for TSMP queries?
+ // For now just say it came via TSMP.
+ via = "TSMP"
+ }
anyPong = true
fmt.Printf("pong from %s (%s) via %v in %v\n", pr.NodeName, pr.NodeIP, via, latency)
+ if pingArgs.tsmp {
+ return nil
+ }
if pr.Endpoint != "" && pingArgs.untilDirect {
return nil
}
diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go
index 851b0c2bd..72b843f2d 100644
--- a/cmd/tailscale/cli/status.go
+++ b/cmd/tailscale/cli/status.go
@@ -10,7 +10,6 @@ import (
"encoding/json"
"flag"
"fmt"
- "log"
"net"
"net/http"
"os"
@@ -19,6 +18,7 @@ import (
"github.com/peterbourgon/ff/v2/ffcli"
"github.com/toqueteos/webbrowser"
+ "tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/interfaces"
@@ -27,7 +27,7 @@ import (
var statusCmd = &ffcli.Command{
Name: "status",
- ShortUsage: "status [-active] [-web] [-json]",
+ ShortUsage: "status [--active] [--web] [--json]",
ShortHelp: "Show state of tailscaled and its connections",
Exec: runStatus,
FlagSet: (func() *flag.FlagSet {
@@ -53,47 +53,8 @@ var statusArgs struct {
peers bool // in CLI mode, show status of peer machines
}
-func getStatusFromServer(ctx context.Context, c net.Conn, bc *ipn.BackendClient) func() (*ipnstate.Status, error) {
- ch := make(chan *ipnstate.Status, 1)
- bc.SetNotifyCallback(func(n ipn.Notify) {
- if n.ErrMessage != nil {
- log.Fatal(*n.ErrMessage)
- }
- if n.Status != nil {
- select {
- case ch <- n.Status:
- default:
- // A status update from somebody else's request.
- // Ignoring this matters mostly for "tailscale status -web"
- // mode, otherwise the channel send would block forever
- // and pump would stop reading from tailscaled, which
- // previously caused tailscaled to block (while holding
- // a mutex), backing up unrelated clients.
- // See https://github.com/tailscale/tailscale/issues/1234
- }
- }
- })
- go pump(ctx, bc, c)
-
- return func() (*ipnstate.Status, error) {
- bc.RequestStatus()
- select {
- case st := <-ch:
- return st, nil
- case <-ctx.Done():
- return nil, ctx.Err()
- }
- }
-}
-
func runStatus(ctx context.Context, args []string) error {
- c, bc, ctx, cancel := connect(ctx)
- defer cancel()
-
- bc.AllowVersionSkew = true
-
- getStatus := getStatusFromServer(ctx, c, bc)
- st, err := getStatus()
+ st, err := tailscale.Status(ctx)
if err != nil {
return err
}
@@ -131,7 +92,7 @@ func runStatus(ctx context.Context, args []string) error {
http.NotFound(w, r)
return
}
- st, err := getStatus()
+ st, err := tailscale.Status(ctx)
if err != nil {
http.Error(w, err.Error(), 500)
return
diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go
index 75acae665..0a66eb139 100644
--- a/cmd/tailscale/cli/up.go
+++ b/cmd/tailscale/cli/up.go
@@ -21,6 +21,7 @@ import (
"github.com/peterbourgon/ff/v2/ffcli"
"inet.af/netaddr"
+ "tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/tailcfg"
"tailscale.com/types/preftype"
@@ -48,11 +49,11 @@ specify any flags, options are reset to their default.
upf.StringVar(&upArgs.exitNodeIP, "exit-node", "", "Tailscale IP of the exit node for internet traffic")
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.advertiseTags, "advertise-tags", "", "ACL tags to request (comma-separated, e.g. \"tag:eng,tag:montreal,tag: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")
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)")
+ 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\")")
upf.BoolVar(&upArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet")
}
if runtime.GOOS == "linux" {
@@ -253,7 +254,7 @@ func runUp(ctx context.Context, args []string) error {
defer cancel()
if !prefs.ExitNodeIP.IsZero() {
- st, err := getStatusFromServer(ctx, c, bc)()
+ st, err := tailscale.Status(ctx)
if err != nil {
fatalf("can't fetch status from tailscaled: %v", err)
}
@@ -315,7 +316,7 @@ func runUp(ctx context.Context, args []string) error {
// supports server mode, though, the transition to StateStore
// is only half complete. Only server mode uses it, and the
// Windows service (~tailscaled) is the one that computes the
- // StateKey based on the connection idenity. So for now, just
+ // StateKey based on the connection identity. So for now, just
// do as the Windows GUI's always done:
if runtime.GOOS == "windows" {
// The Windows service will set this as needed based
diff --git a/cmd/tailscale/cli/version.go b/cmd/tailscale/cli/version.go
index a6bbb6912..2c6f97a3b 100644
--- a/cmd/tailscale/cli/version.go
+++ b/cmd/tailscale/cli/version.go
@@ -11,7 +11,7 @@ import (
"log"
"github.com/peterbourgon/ff/v2/ffcli"
- "tailscale.com/ipn"
+ "tailscale.com/client/tailscale"
"tailscale.com/version"
)
@@ -42,29 +42,10 @@ func runVersion(ctx context.Context, args []string) error {
fmt.Printf("Client: %s\n", version.String())
- c, bc, ctx, cancel := connect(ctx)
- defer cancel()
-
- bc.AllowVersionSkew = true
-
- done := make(chan struct{})
-
- bc.SetNotifyCallback(func(n ipn.Notify) {
- if n.ErrMessage != nil {
- log.Fatal(*n.ErrMessage)
- }
- if n.Status != nil {
- fmt.Printf("Daemon: %s\n", n.Version)
- close(done)
- }
- })
- go pump(ctx, bc, c)
-
- bc.RequestStatus()
- select {
- case <-done:
- return nil
- case <-ctx.Done():
- return ctx.Err()
+ st, err := tailscale.StatusWithoutPeers(ctx)
+ if err != nil {
+ return err
}
+ fmt.Printf("Daemon: %s\n", st.Version)
+ return nil
}
diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt
index 2e722db5c..74ae1d54e 100644
--- a/cmd/tailscale/depaware.txt
+++ b/cmd/tailscale/depaware.txt
@@ -39,6 +39,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/types/empty from tailscale.com/ipn
+ tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
tailscale.com/types/key from tailscale.com/derp+
tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+
tailscale.com/types/netmap from tailscale.com/ipn
@@ -50,7 +51,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/types/wgkey from tailscale.com/types/netmap+
tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+
W tailscale.com/util/endian from tailscale.com/net/netns
- LW tailscale.com/util/lineread from tailscale.com/net/interfaces
+ L tailscale.com/util/lineread from tailscale.com/net/interfaces
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli
tailscale.com/wgengine/filter from tailscale.com/types/netmap
@@ -65,7 +66,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
- golang.org/x/net/context/ctxhttp from golang.org/x/oauth2/internal
golang.org/x/net/dns/dnsmessage from net
golang.org/x/net/http/httpguts from net/http
golang.org/x/net/http/httpproxy from net/http
@@ -73,8 +73,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
golang.org/x/net/proxy from tailscale.com/net/netns
D golang.org/x/net/route from net+
- golang.org/x/oauth2 from tailscale.com/ipn+
- golang.org/x/oauth2/internal from golang.org/x/oauth2
golang.org/x/sync/errgroup from tailscale.com/derp
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
@@ -135,13 +133,13 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
html from tailscale.com/ipn/ipnstate
io from bufio+
io/fs from crypto/rand+
- io/ioutil from golang.org/x/oauth2/internal+
+ io/ioutil from golang.org/x/sys/cpu+
log from expvar+
math from compress/flate+
math/big from crypto/dsa+
math/bits from compress/flate+
math/rand from math/big+
- mime from golang.org/x/oauth2/internal+
+ mime from mime/multipart+
mime/multipart from net/http
mime/quotedprintable from mime/multipart
net from crypto/tls+
@@ -165,7 +163,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
sync from compress/flate+
sync/atomic from context+
syscall from crypto/rand+
- text/tabwriter from github.com/peterbourgon/ff/v2/ffcli
+ text/tabwriter from github.com/peterbourgon/ff/v2/ffcli+
time from compress/gzip+
unicode from bytes+
unicode/utf16 from encoding/asn1+
diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt
index c9cd378e5..d91a960c4 100644
--- a/cmd/tailscaled/depaware.txt
+++ b/cmd/tailscaled/depaware.txt
@@ -3,10 +3,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router
+ W 💣 github.com/github/certstore from tailscale.com/control/controlclient
github.com/go-multierror/multierror from tailscale.com/wgengine/router+
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
- L 💣 github.com/godbus/dbus/v5 from tailscale.com/wgengine/router/dns
+ L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
github.com/google/btree from inet.af/netstack/tcpip/header+
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
@@ -19,6 +20,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
+ W github.com/pkg/errors from github.com/github/certstore
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine+
💣 github.com/tailscale/wireguard-go/ipc from github.com/tailscale/wireguard-go/device
@@ -88,6 +90,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/logtail/backoff from tailscale.com/control/controlclient+
tailscale.com/logtail/filch from tailscale.com/logpolicy
tailscale.com/metrics from tailscale.com/derp
+ tailscale.com/net/dns from tailscale.com/ipn/ipnlocal+
tailscale.com/net/dnscache from tailscale.com/control/controlclient+
tailscale.com/net/dnsfallback from tailscale.com/control/controlclient
tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+
@@ -102,6 +105,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
+ tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
tailscale.com/paths from tailscale.com/cmd/tailscaled+
tailscale.com/portlist from tailscale.com/ipn/ipnlocal
tailscale.com/safesocket from tailscale.com/ipn/ipnserver
@@ -113,6 +117,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/tstime from tailscale.com/wgengine/magicsock
tailscale.com/types/empty from tailscale.com/control/controlclient+
tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled
+ tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
tailscale.com/types/key from tailscale.com/derp+
tailscale.com/types/logger from tailscale.com/cmd/tailscaled+
tailscale.com/types/netmap from tailscale.com/control/controlclient+
@@ -124,13 +129,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/strbuilder from tailscale.com/net/packet
tailscale.com/types/structs from tailscale.com/control/controlclient+
tailscale.com/types/wgkey from tailscale.com/control/controlclient+
- tailscale.com/util/dnsname from tailscale.com/wgengine/tsdns+
+ tailscale.com/util/dnsname from tailscale.com/ipn/ipnstate+
LW tailscale.com/util/endian from tailscale.com/net/netns+
- LW tailscale.com/util/lineread from tailscale.com/control/controlclient+
+ L tailscale.com/util/lineread from tailscale.com/control/controlclient+
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
tailscale.com/util/racebuild from tailscale.com/logpolicy
tailscale.com/util/systemd from tailscale.com/control/controlclient+
- tailscale.com/util/winutil from tailscale.com/logpolicy
+ tailscale.com/util/winutil from tailscale.com/logpolicy+
tailscale.com/version from tailscale.com/cmd/tailscaled+
tailscale.com/version/distro from tailscale.com/control/controlclient+
tailscale.com/wgengine from tailscale.com/cmd/tailscaled+
@@ -139,9 +144,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/monitor from tailscale.com/wgengine+
tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
- tailscale.com/wgengine/router/dns from tailscale.com/ipn/ipnlocal+
- tailscale.com/wgengine/tsdns from tailscale.com/ipn/ipnlocal+
- tailscale.com/wgengine/tstun from tailscale.com/wgengine+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
tailscale.com/wgengine/wglog from tailscale.com/wgengine
@@ -159,7 +161,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/net/bpf from github.com/mdlayher/netlink+
- golang.org/x/net/context/ctxhttp from golang.org/x/oauth2/internal
golang.org/x/net/dns/dnsmessage from net+
golang.org/x/net/http/httpguts from net/http
golang.org/x/net/http/httpproxy from net/http
@@ -169,8 +170,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/net/ipv6 from github.com/tailscale/wireguard-go/device+
golang.org/x/net/proxy from tailscale.com/net/netns
D golang.org/x/net/route from net+
- golang.org/x/oauth2 from tailscale.com/control/controlclient+
- golang.org/x/oauth2/internal from golang.org/x/oauth2
golang.org/x/sync/errgroup from tailscale.com/derp
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
@@ -241,7 +240,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
math/big from crypto/dsa+
math/bits from compress/flate+
math/rand from github.com/mdlayher/netlink+
- mime from golang.org/x/oauth2/internal+
+ mime from mime/multipart+
mime/multipart from net/http
mime/quotedprintable from mime/multipart
net from crypto/tls+
diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go
index a760db708..931072b55 100644
--- a/cmd/tailscaled/tailscaled.go
+++ b/cmd/tailscaled/tailscaled.go
@@ -32,6 +32,7 @@ import (
"tailscale.com/ipn/ipnserver"
"tailscale.com/logpolicy"
"tailscale.com/net/socks5"
+ "tailscale.com/net/tstun"
"tailscale.com/paths"
"tailscale.com/types/flagtype"
"tailscale.com/types/logger"
@@ -43,7 +44,6 @@ import (
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/netstack"
"tailscale.com/wgengine/router"
- "tailscale.com/wgengine/tstun"
)
// globalStateKey is the ipn.StateKey that tailscaled loads on
@@ -316,18 +316,7 @@ func createEngine(logf logger.Logf, linkMon *monitor.Mon) (e wgengine.Engine, is
var errs []error
for _, name := range strings.Split(args.tunname, ",") {
logf("wgengine.NewUserspaceEngine(tun %q) ...", name)
- conf := wgengine.Config{
- ListenPort: args.port,
- LinkMonitor: linkMon,
- }
- isUserspace = name == "userspace-networking"
- if isUserspace {
- conf.TUN = tstun.NewFakeTUN()
- conf.RouterGen = router.NewFake
- } else {
- conf.TUNName = name
- }
- e, err := wgengine.NewUserspaceEngine(logf, conf)
+ e, isUserspace, err = tryEngine(logf, linkMon, name)
if err == nil {
return e, isUserspace, nil
}
@@ -337,6 +326,33 @@ func createEngine(logf logger.Logf, linkMon *monitor.Mon) (e wgengine.Engine, is
return nil, false, multierror.New(errs)
}
+func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.Engine, isUserspace bool, err error) {
+ conf := wgengine.Config{
+ ListenPort: args.port,
+ LinkMonitor: linkMon,
+ }
+ isUserspace = name == "userspace-networking"
+ if !isUserspace {
+ dev, err := tstun.New(logf, name)
+ if err != nil {
+ tstun.Diagnose(logf, name)
+ return nil, false, err
+ }
+ conf.Tun = dev
+ r, err := router.New(logf, dev)
+ if err != nil {
+ dev.Close()
+ return nil, false, err
+ }
+ conf.Router = r
+ }
+ e, err = wgengine.NewUserspaceEngine(logf, conf)
+ if err != nil {
+ return nil, isUserspace, err
+ }
+ return e, isUserspace, nil
+}
+
func newDebugMux() *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/debug/pprof/", pprof.Index)
diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go
index cf97bef4a..b57190b7e 100644
--- a/cmd/tailscaled/tailscaled_windows.go
+++ b/cmd/tailscaled/tailscaled_windows.go
@@ -30,10 +30,12 @@ import (
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/ipn/ipnserver"
"tailscale.com/logpolicy"
+ "tailscale.com/net/tstun"
"tailscale.com/tempfork/wireguard-windows/firewall"
"tailscale.com/types/logger"
"tailscale.com/version"
"tailscale.com/wgengine"
+ "tailscale.com/wgengine/router"
)
const serviceName = "Tailscale"
@@ -159,11 +161,23 @@ func startIPNServer(ctx context.Context, logid string) error {
var err error
getEngine := func() (wgengine.Engine, error) {
+ dev, err := tstun.New(logf, "Tailscale")
+ if err != nil {
+ return nil, err
+ }
+ r, err := router.New(logf, dev)
+ if err != nil {
+ dev.Close()
+ return nil, err
+ }
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
- TUNName: "Tailscale",
+ Tun: dev,
+ Router: r,
ListenPort: 41641,
})
if err != nil {
+ r.Close()
+ dev.Close()
return nil, err
}
return wgengine.NewWatchdog(eng), nil
diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go
index 2549bd9af..c731666e1 100644
--- a/control/controlclient/auto.go
+++ b/control/controlclient/auto.go
@@ -17,7 +17,6 @@ import (
"sync"
"time"
- "golang.org/x/oauth2"
"tailscale.com/health"
"tailscale.com/logtail/backoff"
"tailscale.com/tailcfg"
@@ -102,10 +101,10 @@ func (s Status) String() string {
type LoginGoal struct {
_ structs.Incomparable
- wantLoggedIn bool // true if we *want* to be logged in
- token *oauth2.Token // oauth token to use when logging in
- flags LoginFlags // flags to use when logging in
- url string // auth url that needs to be visited
+ wantLoggedIn bool // true if we *want* to be logged in
+ token *tailcfg.Oauth2Token // oauth token to use when logging in
+ flags LoginFlags // flags to use when logging in
+ url string // auth url that needs to be visited
}
// Client connects to a tailcontrol server for a node.
@@ -668,7 +667,7 @@ func (c *Client) sendStatus(who string, err error, url string, nm *netmap.Networ
c.mu.Unlock()
}
-func (c *Client) Login(t *oauth2.Token, flags LoginFlags) {
+func (c *Client) Login(t *tailcfg.Oauth2Token, flags LoginFlags) {
c.logf("client.Login(%v, %v)", t != nil, flags)
c.mu.Lock()
diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go
index 2977ece67..57631b72e 100644
--- a/control/controlclient/direct.go
+++ b/control/controlclient/direct.go
@@ -31,7 +31,6 @@ import (
"time"
"golang.org/x/crypto/nacl/box"
- "golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/health"
"tailscale.com/log/logheap"
@@ -266,7 +265,7 @@ func (c *Direct) TryLogout(ctx context.Context) error {
return nil
}
-func (c *Direct) TryLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags) (url string, err error) {
+func (c *Direct) TryLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags) (url string, err error) {
c.logf("direct.TryLogin(token=%v, flags=%v)", t != nil, flags)
return c.doLoginOrRegen(ctx, t, flags, false, "")
}
@@ -276,7 +275,7 @@ func (c *Direct) WaitLoginURL(ctx context.Context, url string) (newUrl string, e
return c.doLoginOrRegen(ctx, nil, LoginDefault, false, url)
}
-func (c *Direct) doLoginOrRegen(ctx context.Context, t *oauth2.Token, flags LoginFlags, regen bool, url string) (newUrl string, err error) {
+func (c *Direct) doLoginOrRegen(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags, regen bool, url string) (newUrl string, err error) {
mustregen, url, err := c.doLogin(ctx, t, flags, regen, url)
if err != nil {
return url, err
@@ -288,7 +287,7 @@ func (c *Direct) doLoginOrRegen(ctx context.Context, t *oauth2.Token, flags Logi
return url, err
}
-func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags, regen bool, url string) (mustregen bool, newurl string, err error) {
+func (c *Direct) doLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags, regen bool, url string) (mustregen bool, newurl string, err error) {
c.mu.Lock()
persist := c.persist
tryingNewKey := c.tryingNewKey
@@ -352,12 +351,14 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
err = errors.New("hostinfo: BackendLogID missing")
return regen, url, err
}
+ now := time.Now().Round(time.Second)
request := tailcfg.RegisterRequest{
Version: 1,
OldNodeKey: tailcfg.NodeKey(oldNodeKey),
NodeKey: tailcfg.NodeKey(tryingNewKey.Public()),
Hostinfo: hostinfo,
Followup: url,
+ Timestamp: &now,
}
c.logf("RegisterReq: onode=%v node=%v fup=%v",
request.OldNodeKey.ShortString(),
@@ -366,6 +367,20 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
request.Auth.Provider = persist.Provider
request.Auth.LoginName = persist.LoginName
request.Auth.AuthKey = authKey
+ err = signRegisterRequest(&request, c.serverURL, c.serverKey, c.machinePrivKey.Public())
+ if err != nil {
+ // If signing failed, clear all related fields
+ request.SignatureType = tailcfg.SignatureNone
+ request.Timestamp = nil
+ request.DeviceCert = nil
+ request.Signature = nil
+
+ // Don't log the common error types. Signatures are not usually enabled,
+ // so these are expected.
+ if err != errCertificateNotConfigured && err != errNoCertStore {
+ c.logf("RegisterReq sign error: %v", err)
+ }
+ }
bodyData, err := encode(request, &serverKey, &c.machinePrivKey)
if err != nil {
return regen, url, err
diff --git a/control/controlclient/sign.go b/control/controlclient/sign.go
new file mode 100644
index 000000000..83b35f6f7
--- /dev/null
+++ b/control/controlclient/sign.go
@@ -0,0 +1,31 @@
+// 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 (
+ "crypto"
+ "errors"
+ "fmt"
+ "time"
+
+ "tailscale.com/types/wgkey"
+)
+
+var (
+ errNoCertStore = errors.New("no certificate store")
+ errCertificateNotConfigured = errors.New("no certificate subject configured")
+)
+
+// HashRegisterRequest generates the hash required sign or verify a
+// tailcfg.RegisterRequest with tailcfg.SignatureV1.
+func HashRegisterRequest(ts time.Time, serverURL string, deviceCert []byte, serverPubKey, machinePubKey wgkey.Key) []byte {
+ h := crypto.SHA256.New()
+
+ // hash.Hash.Write never returns an error, so we don't check for one here.
+ fmt.Fprintf(h, "%s%s%s%s%s",
+ ts.UTC().Format(time.RFC3339), serverURL, deviceCert, serverPubKey, machinePubKey)
+
+ return h.Sum(nil)
+}
diff --git a/control/controlclient/sign_supported.go b/control/controlclient/sign_supported.go
new file mode 100644
index 000000000..9eadcafd0
--- /dev/null
+++ b/control/controlclient/sign_supported.go
@@ -0,0 +1,160 @@
+// 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 windows,cgo
+
+// darwin,cgo is also supported by certstore but machineCertificateSubject will
+// need to be loaded by a different mechanism, so this is not currently enabled
+// on darwin.
+
+package controlclient
+
+import (
+ "crypto"
+ "crypto/rsa"
+ "crypto/x509"
+ "errors"
+ "fmt"
+ "sync"
+
+ "github.com/github/certstore"
+ "tailscale.com/tailcfg"
+ "tailscale.com/types/wgkey"
+ "tailscale.com/util/winutil"
+)
+
+var getMachineCertificateSubjectOnce struct {
+ sync.Once
+ v string // Subject of machine certificate to search for
+}
+
+// getMachineCertificateSubject returns the exact name of a Subject that needs
+// to be present in an identity's certificate chain to sign a RegisterRequest,
+// formatted as per pkix.Name.String(). The Subject may be that of the identity
+// itself, an intermediate CA or the root CA.
+//
+// If getMachineCertificateSubject() returns "" then no lookup will occur and
+// each RegisterRequest will be unsigned.
+//
+// Example: "CN=Tailscale Inc Test Root CA,OU=Tailscale Inc Test Certificate Authority,O=Tailscale Inc,ST=ON,C=CA"
+func getMachineCertificateSubject() string {
+ getMachineCertificateSubjectOnce.Do(func() {
+ getMachineCertificateSubjectOnce.v = winutil.GetRegString("MachineCertificateSubject", "")
+ })
+
+ return getMachineCertificateSubjectOnce.v
+}
+
+var (
+ errNoMatch = errors.New("no matching certificate")
+ errBadRequest = errors.New("malformed request")
+)
+
+// findIdentity locates an identity from the Windows or Darwin certificate
+// store. It returns the first certificate with a matching Subject anywhere in
+// its certificate chain, so it is possible to search for the leaf certificate,
+// intermediate CA or root CA. If err is nil then the returned identity will
+// never be nil (if no identity is found, the error errNoMatch will be
+// returned). If an identity is returned then its certificate chain is also
+// returned.
+func findIdentity(subject string, st certstore.Store) (certstore.Identity, []*x509.Certificate, error) {
+ ids, err := st.Identities()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var selected certstore.Identity
+ var chain []*x509.Certificate
+
+ for _, id := range ids {
+ chain, err = id.CertificateChain()
+ if err != nil {
+ continue
+ }
+
+ if chain[0].PublicKeyAlgorithm != x509.RSA {
+ continue
+ }
+
+ for _, c := range chain {
+ if c.Subject.String() == subject {
+ selected = id
+ break
+ }
+ }
+ }
+
+ for _, id := range ids {
+ if id != selected {
+ id.Close()
+ }
+ }
+
+ if selected == nil {
+ return nil, nil, errNoMatch
+ }
+
+ return selected, chain, nil
+}
+
+// signRegisterRequest looks for a suitable machine identity from the local
+// system certificate store, and if one is found, signs the RegisterRequest
+// using that identity's public key. In addition to the signature, the full
+// certificate chain is included so that the control server can validate the
+// certificate from a copy of the root CA's certificate.
+func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey wgkey.Key) (err error) {
+ defer func() {
+ if err != nil {
+ err = fmt.Errorf("signRegisterRequest: %w", err)
+ }
+ }()
+
+ if req.Timestamp == nil {
+ return errBadRequest
+ }
+
+ machineCertificateSubject := getMachineCertificateSubject()
+ if machineCertificateSubject == "" {
+ return errCertificateNotConfigured
+ }
+
+ st, err := certstore.Open(certstore.System)
+ if err != nil {
+ return fmt.Errorf("open cert store: %w", err)
+ }
+ defer st.Close()
+
+ id, chain, err := findIdentity(machineCertificateSubject, st)
+ if err != nil {
+ return fmt.Errorf("find identity: %w", err)
+ }
+ defer id.Close()
+
+ signer, err := id.Signer()
+ if err != nil {
+ return fmt.Errorf("create signer: %w", err)
+ }
+
+ cl := 0
+ for _, c := range chain {
+ cl += len(c.Raw)
+ }
+ req.DeviceCert = make([]byte, 0, cl)
+ for _, c := range chain {
+ req.DeviceCert = append(req.DeviceCert, c.Raw...)
+ }
+
+ h := HashRegisterRequest(req.Timestamp.UTC(), serverURL, req.DeviceCert, serverPubKey, machinePubKey)
+
+ req.Signature, err = signer.Sign(nil, h, &rsa.PSSOptions{
+ SaltLength: rsa.PSSSaltLengthEqualsHash,
+ Hash: crypto.SHA256,
+ })
+ if err != nil {
+ return fmt.Errorf("sign: %w", err)
+ }
+ req.SignatureType = tailcfg.SignatureV1
+
+ return nil
+}
diff --git a/control/controlclient/sign_unsupported.go b/control/controlclient/sign_unsupported.go
new file mode 100644
index 000000000..e20ced316
--- /dev/null
+++ b/control/controlclient/sign_unsupported.go
@@ -0,0 +1,17 @@
+// 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 !windows !cgo
+
+package controlclient
+
+import (
+ "tailscale.com/tailcfg"
+ "tailscale.com/types/wgkey"
+)
+
+// signRegisterRequest on non-supported platforms always returns errNoCertStore.
+func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey wgkey.Key) error {
+ return errNoCertStore
+}
diff --git a/go.mod b/go.mod
index 4d4fa34c4..3cfff12dd 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@ require (
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
github.com/coreos/go-iptables v0.4.5
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
+ github.com/github/certstore v0.1.0
github.com/gliderlabs/ssh v0.2.2
github.com/go-multierror/multierror v1.0.2
github.com/go-ole/go-ole v1.2.4
@@ -23,20 +24,18 @@ require (
github.com/peterbourgon/ff/v2 v2.0.0
github.com/pkg/errors v0.9.1 // indirect
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
- github.com/tailscale/wireguard-go v0.0.0-20210210202228-3cc76ed5f222
+ github.com/tailscale/wireguard-go v0.0.0-20210327173134-f6a42a1646a0
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
- golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8
- google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
honnef.co/go/tools v0.1.0
inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44
@@ -44,3 +43,5 @@ require (
inet.af/peercred v0.0.0-20210302202138-56e694897155
rsc.io/goversion v1.2.0
)
+
+replace github.com/github/certstore => github.com/cyolosecurity/certstore v0.0.0-20200922073901-ece7f1d353c2
diff --git a/go.sum b/go.sum
index f954a508b..ce753994b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,39 +1,5 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14=
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
@@ -47,95 +13,42 @@ github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/cyolosecurity/certstore v0.0.0-20200922073901-ece7f1d353c2 h1:TGPWAij+nY2FB7TlyUTqTmYvXJon/AZAfRMYc/76K80=
+github.com/cyolosecurity/certstore v0.0.0-20200922073901-ece7f1d353c2/go.mod h1:Sgb3YVYOB2iCO06NJ6We5gjXe7uxxM3zPYoEXjuTKno=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dvyukov/go-fuzz v0.0.0-20201127111758-49e582c6c23d/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
+github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-multierror/multierror v1.0.2 h1:AwsKbEXkmf49ajdFJgcFXqSG0aLo0HEyAE9zk9JguJo=
github.com/go-multierror/multierror v1.0.2/go.mod h1:U7SZR/D9jHgt2nkSj8XcbCWdmVM2igraCHQ3HC1HiKY=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0 h1:BW6OvS3kpT5UEPbCZ+KyX/OB4Ks9/MNMhWjqPPkZxsE=
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/goreleaser/nfpm v1.1.10 h1:0nwzKUJTcygNxTzVKq2Dh9wpVP1W2biUH6SNKmoxR3w=
github.com/goreleaser/nfpm v1.1.10/go.mod h1:oOcoGRVwvKIODz57NUfiRwFWGfn00NXdgnn6MrYtO5k=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
@@ -148,8 +61,6 @@ github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
@@ -196,7 +107,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b h1:+gCnWOZV8Z/8jehJ2CdqB47Z3S+SREmQcuXkRFLNsiI=
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
@@ -209,6 +119,10 @@ github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBW
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/tailscale/wireguard-go v0.0.0-20210210202228-3cc76ed5f222 h1:VzTS7LIwCH8jlxwrZguU0TsCLV/MDOunoNIDJdFajyM=
github.com/tailscale/wireguard-go v0.0.0-20210210202228-3cc76ed5f222/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924=
+github.com/tailscale/wireguard-go v0.0.0-20210324165952-2963b66bc23a h1:tQ7Y0ALSe5109GMFB7TVtfNBsVcAuM422hVSJrXWMTE=
+github.com/tailscale/wireguard-go v0.0.0-20210324165952-2963b66bc23a/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924=
+github.com/tailscale/wireguard-go v0.0.0-20210327173134-f6a42a1646a0 h1:7KFBvUmm3TW/K+bAN22D7M6xSSoY/39s+PajaNBGrLw=
+github.com/tailscale/wireguard-go v0.0.0-20210327173134-f6a42a1646a0/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924=
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=
@@ -217,15 +131,8 @@ github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
-github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 h1:vSug/WNOi2+4jrKdivxayTN/zd8EA1UrStjpWvvo1jk=
@@ -234,77 +141,29 @@ go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -314,59 +173,26 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84 h1:duBc5zuJsmJXYOVVE/6PxejI+N3AaCqKjtsoLn1Je5Q=
-golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -376,6 +202,7 @@ golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e h1:XNp2Flc/1eWQGk5BLzqTAN7fQIwIbfyVTuVxXxZh73M=
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -383,60 +210,16 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
-golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 h1:1Bs6RVeBFtLZ8Yi1Hk07DiOqzvwLD/4hln4iahvFlag=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -446,84 +229,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9 h1:qowcZ56hhpeoESmWzI4Exhx4Y78TpCyXUJur4/c0CoE=
golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9/go.mod h1:LMeNfjlcPZTrBC1juwgbQyA4Zy2XVcsrdO/fIJxwyuA=
+golang.zx2c4.com/wireguard v0.0.20201118/go.mod h1:Dz+cq5bnrai9EpgYj4GDof/+qaGzbRWbeaAOs1bUYa0=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8 h1:nlXPqGA98n+qcq1pwZ28KjM5EsFQvamKS00A+VUeVjs=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8/go.mod h1:psva4yDnAHLuh7lUzOK7J7bLYxNFfo0iKWz+mi9gzkA=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
-google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b h1:jEdfCm+8YTWSYgU4L7Nq0jjU+q9RxIhi0cXLTY+Ih3A=
-google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
@@ -534,13 +242,6 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.1.0 h1:AWNL1W1i7f0wNZ8VwOKNJ0sliKvOF/adn0EHenfUh+c=
honnef.co/go/tools v0.1.0/go.mod h1:XtegFAyX/PfluP4921rXU5IkjkqBCDnUq4W8VCIoKvM=
inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44 h1:p7fX77zWzZMuNdJUhniBsmN1OvFOrW9SOtvgnzqUZX4=
@@ -549,8 +250,5 @@ inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22 h1:DNtszwGa6w76qlIr+PbPEnlBJ
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22/go.mod h1:GVx+5OZtbG4TVOW5ilmyRZAZXr1cNwfqUEkTOtWK0PM=
inet.af/peercred v0.0.0-20210302202138-56e694897155 h1:KojYNEYqDkZ2O3LdyTstR1l13L3ePKTIEM2h7ONkfkE=
inet.af/peercred v0.0.0-20210302202138-56e694897155/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=
rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/health/health.go b/health/health.go
index 81d346d15..3da92b8a4 100644
--- a/health/health.go
+++ b/health/health.go
@@ -35,6 +35,7 @@ var (
lastMapRequestHeard time.Time // time we got a 200 from control for a MapRequest
ipnState string
ipnWantRunning bool
+ anyInterfaceUp = true // until told otherwise
)
// Subsystem is the name of a subsystem whose health can be monitored.
@@ -195,6 +196,14 @@ func SetIPNState(state string, wantRunning bool) {
selfCheckLocked()
}
+// SetAnyInterfaceUp sets whether any network interface is up.
+func SetAnyInterfaceUp(up bool) {
+ mu.Lock()
+ defer mu.Unlock()
+ anyInterfaceUp = up
+ selfCheckLocked()
+}
+
func timerSelfCheck() {
mu.Lock()
defer mu.Unlock()
@@ -213,6 +222,9 @@ func selfCheckLocked() {
}
func overallErrorLocked() error {
+ if !anyInterfaceUp {
+ return errors.New("network down")
+ }
if ipnState != "Running" || !ipnWantRunning {
return fmt.Errorf("state=%v, wantRunning=%v", ipnState, ipnWantRunning)
}
diff --git a/internal/deepprint/deepprint_test.go b/internal/deepprint/deepprint_test.go
index e5b2b0924..c7e2031f2 100644
--- a/internal/deepprint/deepprint_test.go
+++ b/internal/deepprint/deepprint_test.go
@@ -9,8 +9,8 @@ import (
"testing"
"inet.af/netaddr"
+ "tailscale.com/net/dns"
"tailscale.com/wgengine/router"
- "tailscale.com/wgengine/router/dns"
"tailscale.com/wgengine/wgcfg"
)
diff --git a/ipn/backend.go b/ipn/backend.go
index 9352853b1..dee548c54 100644
--- a/ipn/backend.go
+++ b/ipn/backend.go
@@ -8,7 +8,6 @@ import (
"net/http"
"time"
- "golang.org/x/oauth2"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
"tailscale.com/types/empty"
@@ -28,7 +27,7 @@ const (
Running
)
-// GoogleIDToken Type is the oauth2.Token.TokenType for the Google
+// GoogleIDToken Type is the tailcfg.Oauth2Token.TokenType for the Google
// ID tokens used by the Android client.
const GoogleIDTokenType = "ts_android_google_login"
@@ -65,7 +64,6 @@ type Notify struct {
Prefs *Prefs // preferences were changed
NetMap *netmap.NetworkMap // new netmap received
Engine *EngineStatus // wireguard engine stats
- 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
@@ -143,7 +141,7 @@ type Backend interface {
// eventually.
StartLoginInteractive()
// Login logs in with an OAuth2 token.
- Login(token *oauth2.Token)
+ Login(token *tailcfg.Oauth2Token)
// Logout terminates the current login session and stops the
// wireguard engine.
Logout()
@@ -159,9 +157,6 @@ type Backend interface {
// counts. Connection events are emitted automatically without
// polling.
RequestEngineStatus()
- // RequestStatus requests that a full Status update
- // notification is sent.
- RequestStatus()
// FakeExpireAfter pretends that the current key is going to
// expire after duration x. This is useful for testing GUIs to
// make sure they react properly with keys that are going to
@@ -170,5 +165,5 @@ type Backend interface {
// 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)
+ Ping(ip string, useTSMP bool)
}
diff --git a/ipn/fake_test.go b/ipn/fake_test.go
index e918f77f0..eef580f57 100644
--- a/ipn/fake_test.go
+++ b/ipn/fake_test.go
@@ -8,8 +8,8 @@ import (
"log"
"time"
- "golang.org/x/oauth2"
"tailscale.com/ipn/ipnstate"
+ "tailscale.com/tailcfg"
"tailscale.com/types/netmap"
)
@@ -46,7 +46,7 @@ func (b *FakeBackend) StartLoginInteractive() {
b.login()
}
-func (b *FakeBackend) Login(token *oauth2.Token) {
+func (b *FakeBackend) Login(token *tailcfg.Oauth2Token) {
b.login()
}
@@ -87,14 +87,10 @@ func (b *FakeBackend) RequestEngineStatus() {
b.notify(Notify{Engine: &EngineStatus{}})
}
-func (b *FakeBackend) RequestStatus() {
- b.notify(Notify{Status: &ipnstate.Status{}})
-}
-
func (b *FakeBackend) FakeExpireAfter(x time.Duration) {
b.notify(Notify{NetMap: &netmap.NetworkMap{}})
}
-func (b *FakeBackend) Ping(ip string) {
+func (b *FakeBackend) Ping(ip string, useTSMP bool) {
b.notify(Notify{PingResult: &ipnstate.PingResult{}})
}
diff --git a/ipn/handle.go b/ipn/handle.go
index 91b757f56..54a61140e 100644
--- a/ipn/handle.go
+++ b/ipn/handle.go
@@ -8,8 +8,8 @@ import (
"sync"
"time"
- "golang.org/x/oauth2"
"inet.af/netaddr"
+ "tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
)
@@ -155,7 +155,7 @@ func (h *Handle) StartLoginInteractive() {
h.b.StartLoginInteractive()
}
-func (h *Handle) Login(token *oauth2.Token) {
+func (h *Handle) Login(token *tailcfg.Oauth2Token) {
h.b.Login(token)
}
@@ -167,10 +167,6 @@ func (h *Handle) RequestEngineStatus() {
h.b.RequestEngineStatus()
}
-func (h *Handle) RequestStatus() {
- h.b.RequestStatus()
-}
-
func (h *Handle) FakeExpireAfter(x time.Duration) {
h.b.FakeExpireAfter(x)
}
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index 38c9323d7..f6173a763 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -9,13 +9,14 @@ import (
"context"
"errors"
"fmt"
+ "net"
"os"
"runtime"
+ "strconv"
"strings"
"sync"
"time"
- "golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/health"
@@ -23,6 +24,7 @@ import (
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/ipn/policy"
+ "tailscale.com/net/dns"
"tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr"
"tailscale.com/portlist"
@@ -38,8 +40,6 @@ import (
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/router"
- "tailscale.com/wgengine/router/dns"
- "tailscale.com/wgengine/tsdns"
"tailscale.com/wgengine/wgcfg"
"tailscale.com/wgengine/wgcfg/nmcfg"
)
@@ -96,15 +96,16 @@ type LocalBackend struct {
// hostinfo is mutated in-place while mu is held.
hostinfo *tailcfg.Hostinfo
// netMap is not mutated in-place once set.
- netMap *netmap.NetworkMap
- nodeByAddr map[netaddr.IP]*tailcfg.Node
- activeLogin string // last logged LoginName from netMap
- engineStatus ipn.EngineStatus
- endpoints []string
- blocked bool
- authURL string
- interact bool
- prevIfState *interfaces.State
+ netMap *netmap.NetworkMap
+ nodeByAddr map[netaddr.IP]*tailcfg.Node
+ activeLogin string // last logged LoginName from netMap
+ engineStatus ipn.EngineStatus
+ endpoints []string
+ blocked bool
+ authURL string
+ interact bool
+ prevIfState *interfaces.State
+ peerAPIListeners []*peerAPIListener
// statusLock must be held before calling statusChanged.Wait() or
// statusChanged.Broadcast().
@@ -144,6 +145,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wge
b.statusChanged = sync.NewCond(&b.statusLock)
linkMon := e.GetLinkMonitor()
+ b.prevIfState = linkMon.InterfaceState()
// Call our linkChange code once with the current state, and
// then also whenever it changes:
b.linkChange(false, linkMon.InterfaceState())
@@ -218,52 +220,82 @@ func (b *LocalBackend) Status() *ipnstate.Status {
return sb.Status()
}
+// StatusWithoutPeers is like Status but omits any details
+// of peers.
+func (b *LocalBackend) StatusWithoutPeers() *ipnstate.Status {
+ sb := new(ipnstate.StatusBuilder)
+ b.updateStatus(sb, nil)
+ return sb.Status()
+}
+
// UpdateStatus implements ipnstate.StatusUpdater.
func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
b.e.UpdateStatus(sb)
+ b.updateStatus(sb, b.populatePeerStatusLocked)
+}
+// updateStatus populates sb with status.
+//
+// extraLocked, if non-nil, is called while b.mu is still held.
+func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func(*ipnstate.StatusBuilder)) {
b.mu.Lock()
defer b.mu.Unlock()
-
- sb.SetBackendState(b.state.String())
- sb.SetAuthURL(b.authURL)
-
+ sb.MutateStatus(func(s *ipnstate.Status) {
+ s.Version = version.Long
+ s.BackendState = b.state.String()
+ s.AuthURL = b.authURL
+ if b.netMap != nil {
+ s.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
+ }
+ })
+ sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
+ for _, pln := range b.peerAPIListeners {
+ ss.PeerAPIURL = append(ss.PeerAPIURL, pln.urlStr)
+ }
+ })
// TODO: hostinfo, and its networkinfo
// TODO: EngineStatus copy (and deprecate it?)
- if b.netMap != nil {
- sb.SetMagicDNSSuffix(b.netMap.MagicDNSSuffix())
- for id, up := range b.netMap.UserProfiles {
- sb.AddUser(id, up)
+
+ if extraLocked != nil {
+ extraLocked(sb)
+ }
+}
+
+func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
+ if b.netMap == nil {
+ return
+ }
+ for id, up := range b.netMap.UserProfiles {
+ sb.AddUser(id, up)
+ }
+ for _, p := range b.netMap.Peers {
+ var lastSeen time.Time
+ if p.LastSeen != nil {
+ lastSeen = *p.LastSeen
}
- for _, p := range b.netMap.Peers {
- var lastSeen time.Time
- if p.LastSeen != nil {
- lastSeen = *p.LastSeen
- }
- var tailAddr string
- for _, addr := range p.Addresses {
- // The peer struct currently only allows a single
- // Tailscale IP address. For compatibility with the
- // old display, make sure it's the IPv4 address.
- if addr.IP.Is4() && addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP) {
- tailAddr = addr.IP.String()
- break
- }
+ var tailAddr string
+ for _, addr := range p.Addresses {
+ // The peer struct currently only allows a single
+ // Tailscale IP address. For compatibility with the
+ // old display, make sure it's the IPv4 address.
+ if addr.IP.Is4() && addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP) {
+ tailAddr = addr.IP.String()
+ break
}
- sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{
- InNetworkMap: true,
- UserID: p.User,
- TailAddr: tailAddr,
- HostName: p.Hostinfo.Hostname,
- DNSName: p.Name,
- OS: p.Hostinfo.OS,
- KeepAlive: p.KeepAlive,
- Created: p.Created,
- LastSeen: lastSeen,
- ShareeNode: p.Hostinfo.ShareeNode,
- ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID,
- })
}
+ sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{
+ InNetworkMap: true,
+ UserID: p.User,
+ TailAddr: tailAddr,
+ HostName: p.Hostinfo.Hostname,
+ DNSName: p.Name,
+ OS: p.Hostinfo.OS,
+ KeepAlive: p.KeepAlive,
+ Created: p.Created,
+ LastSeen: lastSeen,
+ ShareeNode: p.Hostinfo.ShareeNode,
+ ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID,
+ })
}
}
@@ -697,10 +729,16 @@ var removeFromDefaultRoute = []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("192.168.0.0/16"),
netaddr.MustParseIPPrefix("172.16.0.0/12"),
netaddr.MustParseIPPrefix("10.0.0.0/8"),
+ // IPv4 link-local
+ netaddr.MustParseIPPrefix("169.254.0.0/16"),
+ // IPv4 multicast
+ netaddr.MustParseIPPrefix("224.0.0.0/4"),
// Tailscale IPv4 range
tsaddr.CGNATRange(),
// IPv6 Link-local addresses
netaddr.MustParseIPPrefix("fe80::/10"),
+ // IPv6 multicast
+ netaddr.MustParseIPPrefix("ff00::/8"),
// Tailscale IPv6 range
tsaddr.TailscaleULARange(),
}
@@ -710,6 +748,7 @@ var removeFromDefaultRoute = []netaddr.IPPrefix{
func shrinkDefaultRoute(route netaddr.IPPrefix) (*netaddr.IPSet, error) {
var b netaddr.IPSetBuilder
b.AddPrefix(route)
+ var hostIPs []netaddr.IP
err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netaddr.IPPrefix) {
if tsaddr.IsTailscaleIP(pfx.IP) {
return
@@ -717,11 +756,25 @@ func shrinkDefaultRoute(route netaddr.IPPrefix) (*netaddr.IPSet, error) {
if pfx.IsSingleIP() {
return
}
+ hostIPs = append(hostIPs, pfx.IP)
b.RemovePrefix(pfx)
})
if err != nil {
return nil, err
}
+
+ // Having removed all the LAN subnets, re-add the hosts's own
+ // IPs. It's fine for clients to connect to an exit node's public
+ // IP address, just not the attached subnet.
+ //
+ // Truly forbidden subnets (in removeFromDefaultRoute) will still
+ // be stripped back out by the next step.
+ for _, ip := range hostIPs {
+ if route.Contains(ip) {
+ b.Add(ip)
+ }
+ }
+
for _, pfx := range removeFromDefaultRoute {
b.RemovePrefix(pfx)
}
@@ -796,8 +849,8 @@ func (b *LocalBackend) updateDNSMap(netMap *netmap.NetworkMap) {
}
set(netMap.Name, netMap.Addresses)
- dnsMap := tsdns.NewMap(nameToIP, magicDNSRootDomains(netMap))
- // map diff will be logged in tsdns.Resolver.SetMap.
+ dnsMap := dns.NewMap(nameToIP, magicDNSRootDomains(netMap))
+ // map diff will be logged in dns.Resolver.SetMap.
b.e.SetDNSMap(dnsMap)
}
@@ -1078,7 +1131,7 @@ func (b *LocalBackend) getEngineStatus() ipn.EngineStatus {
}
// Login implements Backend.
-func (b *LocalBackend) Login(token *oauth2.Token) {
+func (b *LocalBackend) Login(token *tailcfg.Oauth2Token) {
b.mu.Lock()
b.assertClientLocked()
c := b.c
@@ -1129,13 +1182,13 @@ func (b *LocalBackend) FakeExpireAfter(x time.Duration) {
b.send(ipn.Notify{NetMap: b.netMap})
}
-func (b *LocalBackend) Ping(ipStr string) {
+func (b *LocalBackend) Ping(ipStr string, useTSMP bool) {
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.e.Ping(ip, useTSMP, func(pr *ipnstate.PingResult) {
b.send(ipn.Notify{PingResult: pr})
})
}
@@ -1382,6 +1435,45 @@ func (b *LocalBackend) authReconfig() {
return
}
b.logf("[v1] authReconfig: ra=%v dns=%v 0x%02x: %v", uc.RouteAll, uc.CorpDNS, flags, err)
+
+ b.initPeerAPIListener()
+}
+
+func (b *LocalBackend) initPeerAPIListener() {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+
+ for _, pln := range b.peerAPIListeners {
+ pln.ln.Close()
+ }
+ b.peerAPIListeners = nil
+
+ if len(b.netMap.Addresses) == 0 || b.netMap.SelfNode == nil {
+ return
+ }
+
+ var tunName string
+ if ge, ok := b.e.(wgengine.InternalsGetter); ok {
+ tunDev, _ := ge.GetInternals()
+ tunName, _ = tunDev.Name()
+ }
+
+ for _, a := range b.netMap.Addresses {
+ ln, err := peerAPIListen(a.IP, b.prevIfState, tunName)
+ if err != nil {
+ b.logf("[unexpected] peerAPI listen(%q) error: %v", a.IP, err)
+ continue
+ }
+ pln := &peerAPIListener{
+ ln: ln,
+ lb: b,
+ selfNode: b.netMap.SelfNode,
+ }
+ pln.urlStr = "http://" + net.JoinHostPort(a.IP.String(), strconv.Itoa(pln.Port()))
+
+ go pln.serve()
+ b.peerAPIListeners = append(b.peerAPIListeners, pln)
+ }
}
// magicDNSRootDomains returns the subset of nm.DNS.Domains that are the search domains for MagicDNS.
@@ -1617,12 +1709,6 @@ func (b *LocalBackend) RequestEngineStatus() {
b.e.RequestStatus()
}
-// RequestStatus implements Backend.
-func (b *LocalBackend) RequestStatus() {
- st := b.Status()
- b.send(ipn.Notify{Status: st})
-}
-
// stateMachine updates the state machine state based on other things
// that have happened. It is invoked from the various callbacks that
// feed events into LocalBackend.
diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go
index 667cc2287..2a6fc9315 100644
--- a/ipn/ipnlocal/local_test.go
+++ b/ipn/ipnlocal/local_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"inet.af/netaddr"
+ "tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/netmap"
@@ -122,11 +123,21 @@ func TestNetworkMapCompare(t *testing.T) {
}
}
+func inRemove(ip netaddr.IP) bool {
+ for _, pfx := range removeFromDefaultRoute {
+ if pfx.Contains(ip) {
+ return true
+ }
+ }
+ return false
+}
+
func TestShrinkDefaultRoute(t *testing.T) {
tests := []struct {
- route string
- in []string
- out []string
+ route string
+ in []string
+ out []string
+ localIPFn func(netaddr.IP) bool // true if this machine's local IP address should be "in" after shrinking.
}{
{
route: "0.0.0.0/0",
@@ -139,19 +150,24 @@ func TestShrinkDefaultRoute(t *testing.T) {
"172.16.0.1",
"172.31.255.255",
"100.101.102.103",
+ "224.0.0.1",
+ "169.254.169.254",
// Some random IPv6 stuff that shouldn't be in a v4
// default route.
"fe80::",
"2601::1",
},
+ localIPFn: func(ip netaddr.IP) bool { return !inRemove(ip) && ip.Is4() },
},
{
route: "::/0",
in: []string{"::1", "2601::1"},
out: []string{
"fe80::1",
+ "ff00::1",
tsaddr.TailscaleULARange().IP.String(),
},
+ localIPFn: func(ip netaddr.IP) bool { return !inRemove(ip) && ip.Is6() },
},
}
@@ -171,6 +187,16 @@ func TestShrinkDefaultRoute(t *testing.T) {
t.Errorf("shrink(%q).Contains(%v) = true, want false", test.route, ip)
}
}
+ ips, _, err := interfaces.LocalAddresses()
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, ip := range ips {
+ want := test.localIPFn(ip)
+ if gotContains := got.Contains(ip); gotContains != want {
+ t.Errorf("shrink(%q).Contains(%v) = %v, want %v", test.route, ip, gotContains, want)
+ }
+ }
}
}
diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go
new file mode 100644
index 000000000..390b45efc
--- /dev/null
+++ b/ipn/ipnlocal/peerapi.go
@@ -0,0 +1,167 @@
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ipnlocal
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "hash/crc32"
+ "html"
+ "io"
+ "net"
+ "net/http"
+ "runtime"
+ "strconv"
+
+ "inet.af/netaddr"
+ "tailscale.com/net/interfaces"
+ "tailscale.com/tailcfg"
+)
+
+var initListenConfig func(*net.ListenConfig, netaddr.IP, *interfaces.State, string) error
+
+func peerAPIListen(ip netaddr.IP, ifState *interfaces.State, tunIfName string) (ln net.Listener, err error) {
+ ipStr := ip.String()
+
+ var lc net.ListenConfig
+ if initListenConfig != nil {
+ // On iOS/macOS, this sets the lc.Control hook to
+ // setsockopt the interface index to bind to, to get
+ // out of the network sandbox.
+ if err := initListenConfig(&lc, ip, ifState, tunIfName); err != nil {
+ return nil, err
+ }
+ if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
+ ipStr = ""
+ }
+ }
+
+ tcp4or6 := "tcp4"
+ if ip.Is6() {
+ tcp4or6 = "tcp6"
+ }
+
+ // Make a best effort to pick a deterministic port number for
+ // the ip The lower three bytes are the same for IPv4 and IPv6
+ // Tailscale addresses (at least currently), so we'll usually
+ // get the same port number on both address families for
+ // dev/debugging purposes, which is nice. But it's not so
+ // deterministic that people will bake this into clients.
+ // We try a few times just in case something's already
+ // listening on that port (on all interfaces, probably).
+ for try := uint8(0); try < 5; try++ {
+ a16 := ip.As16()
+ hashData := a16[len(a16)-3:]
+ hashData[0] += try
+ tryPort := (32 << 10) | uint16(crc32.ChecksumIEEE(hashData))
+ ln, err = lc.Listen(context.Background(), tcp4or6, net.JoinHostPort(ipStr, strconv.Itoa(int(tryPort))))
+ if err == nil {
+ return ln, nil
+ }
+ }
+ // Fall back to random ephemeral port.
+ return lc.Listen(context.Background(), tcp4or6, net.JoinHostPort(ipStr, "0"))
+}
+
+type peerAPIListener struct {
+ ln net.Listener
+ lb *LocalBackend
+ urlStr string
+ selfNode *tailcfg.Node
+}
+
+func (pln *peerAPIListener) Port() int {
+ ta, ok := pln.ln.Addr().(*net.TCPAddr)
+ if !ok {
+ return 0
+ }
+ return ta.Port
+}
+
+func (pln *peerAPIListener) serve() {
+ defer pln.ln.Close()
+ logf := pln.lb.logf
+ for {
+ c, err := pln.ln.Accept()
+ if errors.Is(err, net.ErrClosed) {
+ return
+ }
+ if err != nil {
+ logf("peerapi.Accept: %v", err)
+ return
+ }
+ ta, ok := c.RemoteAddr().(*net.TCPAddr)
+ if !ok {
+ c.Close()
+ logf("peerapi: unexpected RemoteAddr %#v", c.RemoteAddr())
+ continue
+ }
+ ipp, ok := netaddr.FromStdAddr(ta.IP, ta.Port, "")
+ if !ok {
+ logf("peerapi: bogus TCPAddr %#v", ta)
+ c.Close()
+ continue
+ }
+ peerNode, peerUser, ok := pln.lb.WhoIs(ipp)
+ if !ok {
+ logf("peerapi: unknown peer %v", ipp)
+ c.Close()
+ continue
+ }
+ h := &peerAPIHandler{
+ isSelf: pln.selfNode.User == peerNode.User,
+ remoteAddr: ipp,
+ peerNode: peerNode,
+ peerUser: peerUser,
+ lb: pln.lb,
+ }
+ httpServer := &http.Server{
+ Handler: h,
+ }
+ go httpServer.Serve(&oneConnListener{Listener: pln.ln, conn: c})
+ }
+}
+
+type oneConnListener struct {
+ net.Listener
+ conn net.Conn
+}
+
+func (l *oneConnListener) Accept() (c net.Conn, err error) {
+ c = l.conn
+ if c == nil {
+ err = io.EOF
+ return
+ }
+ err = nil
+ l.conn = nil
+ return
+}
+
+func (l *oneConnListener) Close() error { return nil }
+
+// peerAPIHandler serves the Peer API for a source specific client.
+type peerAPIHandler struct {
+ remoteAddr netaddr.IPPort
+ isSelf bool // whether peerNode is owned by same user as this node
+ peerNode *tailcfg.Node // peerNode is who's making the request
+ peerUser tailcfg.UserProfile // profile of peerNode
+ lb *LocalBackend
+}
+
+func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ who := h.peerUser.DisplayName
+ fmt.Fprintf(w, `<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<body>
+<h1>Hello, %s (%v)</h1>
+This is my Tailscale device. Your device is %v.
+`, html.EscapeString(who), h.remoteAddr.IP, html.EscapeString(h.peerNode.ComputedName))
+
+ if h.isSelf {
+ fmt.Fprintf(w, "<p>You are the owner of this node.\n")
+ }
+}
diff --git a/ipn/ipnlocal/peerapi_macios_ext.go b/ipn/ipnlocal/peerapi_macios_ext.go
new file mode 100644
index 000000000..a75e18eed
--- /dev/null
+++ b/ipn/ipnlocal/peerapi_macios_ext.go
@@ -0,0 +1,54 @@
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin,redo ios,redo
+
+package ipnlocal
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "strings"
+ "syscall"
+
+ "golang.org/x/sys/unix"
+ "inet.af/netaddr"
+ "tailscale.com/net/interfaces"
+)
+
+func init() {
+ initListenConfig = initListenConfigNetworkExtension
+}
+
+// initListenConfigNetworkExtension configures nc for listening on IP
+// through the iOS/macOS Network/System Extension (Packet Tunnel
+// Provider) sandbox.
+func initListenConfigNetworkExtension(nc *net.ListenConfig, ip netaddr.IP, st *interfaces.State, tunIfName string) error {
+ tunIf, ok := st.Interface[tunIfName]
+ if !ok {
+ return fmt.Errorf("no interface with name %q", tunIfName)
+ }
+ nc.Control = func(network, address string, c syscall.RawConn) error {
+ var sockErr error
+ err := c.Control(func(fd uintptr) {
+
+ v6 := strings.Contains(address, "]:") || strings.HasSuffix(network, "6") // hacky test for v6
+ proto := unix.IPPROTO_IP
+ opt := unix.IP_BOUND_IF
+ if v6 {
+ proto = unix.IPPROTO_IPV6
+ opt = unix.IPV6_BOUND_IF
+ }
+
+ sockErr = unix.SetsockoptInt(int(fd), proto, opt, tunIf.Index)
+ log.Printf("peerapi: bind(%q, %q) on index %v = %v", network, address, tunIf.Index, sockErr)
+ })
+ if err != nil {
+ return err
+ }
+ return sockErr
+ }
+ return nil
+}
diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go
index b89f7bb08..de9d208f7 100644
--- a/ipn/ipnstate/ipnstate.go
+++ b/ipn/ipnstate/ipnstate.go
@@ -26,7 +26,14 @@ import (
// Status represents the entire state of the IPN network.
type Status struct {
+ // Version is the daemon's long version (see version.Long).
+ Version string
+
+ // BackendState is an ipn.State string value:
+ // "NoState", "NeedsLogin", "NeedsMachineAuth", "Stopped",
+ // "Starting", "Running".
BackendState string
+
AuthURL string // current URL provided by control to authorize client
TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
Self *PeerStatus
@@ -80,6 +87,8 @@ type PeerStatus struct {
KeepAlive bool
ExitNode bool // true if this is the currently selected exit node.
+ PeerAPIURL []string
+
// ShareeNode indicates this node exists in the netmap because
// it's owned by a shared-to user and that node might connect
// to us. These nodes should be hidden by "tailscale status"
@@ -105,22 +114,16 @@ 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) SetAuthURL(v string) {
- sb.mu.Lock()
- defer sb.mu.Unlock()
- sb.st.AuthURL = v
-}
-
-func (sb *StatusBuilder) SetMagicDNSSuffix(v string) {
+// MutateStatus calls f with the status to mutate.
+//
+// It may not assume other fields of status are already populated, and
+// may not retain or write to the Status after f returns.
+//
+// MutateStatus acquires a lock so f must not call back into sb.
+func (sb *StatusBuilder) MutateStatus(f func(*Status)) {
sb.mu.Lock()
defer sb.mu.Unlock()
- sb.st.MagicDNSSuffix = v
+ f(&sb.st)
}
func (sb *StatusBuilder) Status() *Status {
@@ -130,11 +133,19 @@ func (sb *StatusBuilder) Status() *Status {
return &sb.st
}
-// SetSelfStatus sets the status of the local machine.
-func (sb *StatusBuilder) SetSelfStatus(ss *PeerStatus) {
+// MutateSelfStatus calls f with the PeerStatus of our own node to mutate.
+//
+// It may not assume other fields of status are already populated, and
+// may not retain or write to the Status after f returns.
+//
+// MutateStatus acquires a lock so f must not call back into sb.
+func (sb *StatusBuilder) MutateSelfStatus(f func(*PeerStatus)) {
sb.mu.Lock()
defer sb.mu.Unlock()
- sb.st.Self = ss
+ if sb.st.Self == nil {
+ sb.st.Self = new(PeerStatus)
+ }
+ f(sb.st.Self)
}
// AddUser adds a user profile to the status.
@@ -394,10 +405,18 @@ type PingResult struct {
Err string
LatencySeconds float64
- Endpoint string // ip:port if direct UDP was used
+ // Endpoint is the ip:port if direct UDP was used.
+ // It is not currently set for TSMP pings.
+ Endpoint string
+
+ // DERPRegionID is non-zero DERP region ID if DERP was used.
+ // It is not currently set for TSMP pings.
+ DERPRegionID int
- DERPRegionID int // non-zero if DERP was used
- DERPRegionCode string // three-letter airport/region code if DERP was used
+ // DERPRegionCode is the three-letter region code
+ // corresponding to DERPRegionID.
+ // It is not currently set for TSMP pings.
+ DERPRegionCode string
// TODO(bradfitz): details like whether port mapping was used on either side? (Once supported)
}
diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go
index 4e0dba3da..169f18ba4 100644
--- a/ipn/localapi/localapi.go
+++ b/ipn/localapi/localapi.go
@@ -10,9 +10,11 @@ import (
"io"
"net/http"
"runtime"
+ "strconv"
"inet.af/netaddr"
"tailscale.com/ipn/ipnlocal"
+ "tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
)
@@ -56,6 +58,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.serveWhoIs(w, r)
case "/localapi/v0/goroutines":
h.serveGoroutines(w, r)
+ case "/localapi/v0/status":
+ h.serveStatus(w, r)
default:
io.WriteString(w, "tailscaled\n")
}
@@ -109,3 +113,31 @@ func (h *Handler) serveGoroutines(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write(buf)
}
+
+func (h *Handler) serveStatus(w http.ResponseWriter, r *http.Request) {
+ if !h.PermitRead {
+ http.Error(w, "status access denied", http.StatusForbidden)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ var st *ipnstate.Status
+ if defBool(r.FormValue("peers"), true) {
+ st = h.b.Status()
+ } else {
+ st = h.b.StatusWithoutPeers()
+ }
+ e := json.NewEncoder(w)
+ e.SetIndent("", "\t")
+ e.Encode(st)
+}
+
+func defBool(a string, def bool) bool {
+ if a == "" {
+ return def
+ }
+ v, err := strconv.ParseBool(a)
+ if err != nil {
+ return def
+ }
+ return v
+}
diff --git a/ipn/message.go b/ipn/message.go
index 9b7783f98..905664c93 100644
--- a/ipn/message.go
+++ b/ipn/message.go
@@ -15,7 +15,7 @@ import (
"log"
"time"
- "golang.org/x/oauth2"
+ "tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/structs"
"tailscale.com/version"
@@ -56,7 +56,8 @@ type FakeExpireAfterArgs struct {
}
type PingArgs struct {
- IP string
+ IP string
+ UseTSMP bool
}
// Command is a command message that is JSON encoded and sent by a
@@ -76,7 +77,7 @@ type Command struct {
Quit *NoArgs
Start *StartArgs
StartLoginInteractive *NoArgs
- Login *oauth2.Token
+ Login *tailcfg.Oauth2Token
Logout *NoArgs
SetPrefs *SetPrefsArgs
SetWantRunning *bool
@@ -173,11 +174,8 @@ func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error {
if c := cmd.RequestEngineStatus; c != nil {
bs.b.RequestEngineStatus()
return nil
- } else if c := cmd.RequestStatus; c != nil {
- bs.b.RequestStatus()
- return nil
} else if c := cmd.Ping; c != nil {
- bs.b.Ping(c.IP)
+ bs.b.Ping(c.IP, c.UseTSMP)
return nil
}
@@ -299,7 +297,7 @@ func (bc *BackendClient) StartLoginInteractive() {
bc.send(Command{StartLoginInteractive: &NoArgs{}})
}
-func (bc *BackendClient) Login(token *oauth2.Token) {
+func (bc *BackendClient) Login(token *tailcfg.Oauth2Token) {
bc.send(Command{Login: token})
}
@@ -323,8 +321,11 @@ 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) Ping(ip string, useTSMP bool) {
+ bc.send(Command{Ping: &PingArgs{
+ IP: ip,
+ UseTSMP: useTSMP,
+ }})
}
func (bc *BackendClient) SetWantRunning(v bool) {
diff --git a/ipn/message_test.go b/ipn/message_test.go
index 4422a64ca..59a9eef25 100644
--- a/ipn/message_test.go
+++ b/ipn/message_test.go
@@ -10,7 +10,7 @@ import (
"testing"
"time"
- "golang.org/x/oauth2"
+ "tailscale.com/tailcfg"
"tailscale.com/tstest"
)
@@ -176,7 +176,7 @@ func TestClientServer(t *testing.T) {
h.Logout()
flushUntil(NeedsLogin)
- h.Login(&oauth2.Token{
+ h.Login(&tailcfg.Oauth2Token{
AccessToken: "google_id_token",
TokenType: GoogleIDTokenType,
})
diff --git a/wgengine/router/dns/config.go b/net/dns/config.go
index 2b6ff615a..db08df7aa 100644
--- a/wgengine/router/dns/config.go
+++ b/net/dns/config.go
@@ -22,8 +22,8 @@ type Config struct {
// Note that Nameservers may still be applied to all queries
// if the manager does not support per-domain settings.
PerDomain bool
- // Proxied indicates whether DNS requests are proxied through a tsdns.Resolver.
- // This enables Magic DNS.
+ // Proxied indicates whether DNS requests are proxied through a dns.Resolver.
+ // This enables MagicDNS.
Proxied bool
}
diff --git a/wgengine/router/dns/direct.go b/net/dns/direct.go
index bd1c03b9d..bd1c03b9d 100644
--- a/wgengine/router/dns/direct.go
+++ b/net/dns/direct.go
diff --git a/net/dns/flush_windows.go b/net/dns/flush_windows.go
new file mode 100644
index 000000000..3c7e7d645
--- /dev/null
+++ b/net/dns/flush_windows.go
@@ -0,0 +1,19 @@
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package dns
+
+import (
+ "fmt"
+ "os/exec"
+)
+
+// Flush clears the local resolver cache.
+func Flush() error {
+ out, err := exec.Command("ipconfig", "/flushdns").CombinedOutput()
+ if err != nil {
+ return fmt.Errorf("%v (output: %s)", err, out)
+ }
+ return nil
+}
diff --git a/wgengine/tsdns/forwarder.go b/net/dns/forwarder.go
index 470748c4a..519c00027 100644
--- a/wgengine/tsdns/forwarder.go
+++ b/net/dns/forwarder.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package tsdns
+package dns
import (
"bytes"
@@ -316,7 +316,7 @@ func (c *fwdConn) send(packet []byte, dst net.Addr) {
var b *backoff.Backoff // lazily initialized, since it is not needed in the common case
backOff := func(err error) {
if b == nil {
- b = backoff.NewBackoff("tsdns-fwdConn-send", c.logf, 30*time.Second)
+ b = backoff.NewBackoff("dns-fwdConn-send", c.logf, 30*time.Second)
}
b.BackOff(context.Background(), err)
}
diff --git a/wgengine/router/dns/manager.go b/net/dns/manager.go
index 8e2fc9d71..8e2fc9d71 100644
--- a/wgengine/router/dns/manager.go
+++ b/net/dns/manager.go
diff --git a/wgengine/router/dns/manager_default.go b/net/dns/manager_default.go
index 04c8bb811..04c8bb811 100644
--- a/wgengine/router/dns/manager_default.go
+++ b/net/dns/manager_default.go
diff --git a/wgengine/router/dns/manager_freebsd.go b/net/dns/manager_freebsd.go
index 232635f7e..232635f7e 100644
--- a/wgengine/router/dns/manager_freebsd.go
+++ b/net/dns/manager_freebsd.go
diff --git a/wgengine/router/dns/manager_linux.go b/net/dns/manager_linux.go
index f53aed7d3..f53aed7d3 100644
--- a/wgengine/router/dns/manager_linux.go
+++ b/net/dns/manager_linux.go
diff --git a/wgengine/router/dns/manager_openbsd.go b/net/dns/manager_openbsd.go
index 228e3cca5..228e3cca5 100644
--- a/wgengine/router/dns/manager_openbsd.go
+++ b/net/dns/manager_openbsd.go
diff --git a/wgengine/router/dns/manager_windows.go b/net/dns/manager_windows.go
index 5940404e7..5940404e7 100644
--- a/wgengine/router/dns/manager_windows.go
+++ b/net/dns/manager_windows.go
diff --git a/wgengine/tsdns/map.go b/net/dns/map.go
index c51dbf59b..119b6cc0a 100644
--- a/wgengine/tsdns/map.go
+++ b/net/dns/map.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package tsdns
+package dns
import (
"sort"
diff --git a/wgengine/tsdns/map_test.go b/net/dns/map_test.go
index dba9bb586..c438f95a0 100644
--- a/wgengine/tsdns/map_test.go
+++ b/net/dns/map_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package tsdns
+package dns
import (
"fmt"
diff --git a/wgengine/tsdns/neterr_darwin.go b/net/dns/neterr_darwin.go
index 62bab6488..7fd621fc7 100644
--- a/wgengine/tsdns/neterr_darwin.go
+++ b/net/dns/neterr_darwin.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package tsdns
+package dns
import (
"errors"
diff --git a/wgengine/tsdns/neterr_other.go b/net/dns/neterr_other.go
index d245d0c38..b652f6e8b 100644
--- a/wgengine/tsdns/neterr_other.go
+++ b/net/dns/neterr_other.go
@@ -4,7 +4,7 @@
// +build !darwin,!windows
-package tsdns
+package dns
func networkIsDown(err error) bool { return false }
func networkIsUnreachable(err error) bool { return false }
diff --git a/wgengine/tsdns/neterr_windows.go b/net/dns/neterr_windows.go
index 90f0db2ab..2b197ee2b 100644
--- a/wgengine/tsdns/neterr_windows.go
+++ b/net/dns/neterr_windows.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package tsdns
+package dns
import (
"net"
diff --git a/wgengine/router/dns/nm.go b/net/dns/nm.go
index a597fa60d..a597fa60d 100644
--- a/wgengine/router/dns/nm.go
+++ b/net/dns/nm.go
diff --git a/wgengine/router/dns/noop.go b/net/dns/noop.go
index 35c07a232..35c07a232 100644
--- a/wgengine/router/dns/noop.go
+++ b/net/dns/noop.go
diff --git a/wgengine/router/dns/registry_windows.go b/net/dns/registry_windows.go
index f8e1f514a..f8e1f514a 100644
--- a/wgengine/router/dns/registry_windows.go
+++ b/net/dns/registry_windows.go
diff --git a/wgengine/router/dns/resolvconf.go b/net/dns/resolvconf.go
index 8bf97ee88..8bf97ee88 100644
--- a/wgengine/router/dns/resolvconf.go
+++ b/net/dns/resolvconf.go
diff --git a/wgengine/router/dns/resolved.go b/net/dns/resolved.go
index 9d8c40d90..9d8c40d90 100644
--- a/wgengine/router/dns/resolved.go
+++ b/net/dns/resolved.go
diff --git a/wgengine/tsdns/tsdns.go b/net/dns/tsdns.go
index b68b8c04e..2b530b81e 100644
--- a/wgengine/tsdns/tsdns.go
+++ b/net/dns/tsdns.go
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Package tsdns provides a Resolver capable of resolving
+// Package dns provides a Resolver capable of resolving
// domains on a Tailscale network.
-package tsdns
+package dns
import (
"encoding/hex"
@@ -100,7 +100,7 @@ type ResolverConfig struct {
// The root domain must be in canonical form (with a trailing period).
func NewResolver(config ResolverConfig) *Resolver {
r := &Resolver{
- logf: logger.WithPrefix(config.Logf, "tsdns: "),
+ logf: logger.WithPrefix(config.Logf, "dns: "),
linkMon: config.LinkMonitor,
queue: make(chan Packet, queueSize),
responses: make(chan Packet),
diff --git a/wgengine/tsdns/tsdns_server_test.go b/net/dns/tsdns_server_test.go
index df9047fc6..95544ba18 100644
--- a/wgengine/tsdns/tsdns_server_test.go
+++ b/net/dns/tsdns_server_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package tsdns
+package dns
import (
"log"
diff --git a/wgengine/tsdns/tsdns_test.go b/net/dns/tsdns_test.go
index 66a62d107..59bcd8ec1 100644
--- a/wgengine/tsdns/tsdns_test.go
+++ b/net/dns/tsdns_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package tsdns
+package dns
import (
"bytes"
diff --git a/net/flowtrack/flowtrack.go b/net/flowtrack/flowtrack.go
index 5fcd4ab20..8387145d3 100644
--- a/net/flowtrack/flowtrack.go
+++ b/net/flowtrack/flowtrack.go
@@ -15,16 +15,18 @@ import (
"fmt"
"inet.af/netaddr"
+ "tailscale.com/types/ipproto"
)
-// Tuple is a 4-tuple of source and destination IP and port.
+// Tuple is a 5-tuple of proto, source and destination IP and port.
type Tuple struct {
- Src netaddr.IPPort
- Dst netaddr.IPPort
+ Proto ipproto.Proto
+ Src netaddr.IPPort
+ Dst netaddr.IPPort
}
func (t Tuple) String() string {
- return fmt.Sprintf("(%v => %v)", t.Src, t.Dst)
+ return fmt.Sprintf("(%v %v => %v)", t.Proto, t.Src, t.Dst)
}
// Cache is an LRU cache keyed by Tuple.
diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go
index 3a0ffeb0b..f2988af34 100644
--- a/net/interfaces/interfaces.go
+++ b/net/interfaces/interfaces.go
@@ -6,10 +6,10 @@
package interfaces
import (
+ "bytes"
"fmt"
"net"
"net/http"
- "reflect"
"runtime"
"sort"
"strings"
@@ -190,6 +190,9 @@ func ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
}
}
}
+ sort.Slice(pfxs, func(i, j int) bool {
+ return pfxs[i].IP.Less(pfxs[j].IP)
+ })
fn(Interface{iface}, pfxs)
}
return nil
@@ -204,7 +207,7 @@ type State struct {
// IPPrefix, where the IP is the interface IP address and Bits is
// the subnet mask.
InterfaceIPs map[string][]netaddr.IPPrefix
- InterfaceUp map[string]bool
+ Interface map[string]Interface
// HaveV6Global is whether this machine has an IPv6 global address
// on some non-Tailscale interface that's up.
@@ -235,14 +238,14 @@ type State struct {
func (s *State) String() string {
var sb strings.Builder
fmt.Fprintf(&sb, "interfaces.State{defaultRoute=%v ifs={", s.DefaultRouteInterface)
- ifs := make([]string, 0, len(s.InterfaceUp))
- for k := range s.InterfaceUp {
+ ifs := make([]string, 0, len(s.Interface))
+ for k := range s.Interface {
if anyInterestingIP(s.InterfaceIPs[k]) {
ifs = append(ifs, k)
}
}
sort.Slice(ifs, func(i, j int) bool {
- upi, upj := s.InterfaceUp[ifs[i]], s.InterfaceUp[ifs[j]]
+ upi, upj := s.Interface[ifs[i]].IsUp(), s.Interface[ifs[j]].IsUp()
if upi != upj {
// Up sorts before down.
return upi
@@ -253,7 +256,7 @@ func (s *State) String() string {
if i > 0 {
sb.WriteString(" ")
}
- if s.InterfaceUp[ifName] {
+ if s.Interface[ifName].IsUp() {
fmt.Fprintf(&sb, "%s:[", ifName)
needSpace := false
for _, pfx := range s.InterfaceIPs[ifName] {
@@ -286,50 +289,76 @@ func (s *State) String() string {
return sb.String()
}
-func (s *State) Equal(s2 *State) bool {
- return reflect.DeepEqual(s, s2)
-}
-
-func (s *State) HasPAC() bool { return s != nil && s.PAC != "" }
-
-// AnyInterfaceUp reports whether any interface seems like it has Internet access.
-func (s *State) AnyInterfaceUp() bool {
- return s != nil && (s.HaveV4 || s.HaveV6Global)
-}
-
-// RemoveUninterestingInterfacesAndAddresses removes uninteresting IPs
-// from InterfaceIPs, also removing from both the InterfaceIPs and
-// InterfaceUp map any interfaces that don't have any interesting IPs.
-func (s *State) RemoveUninterestingInterfacesAndAddresses() {
- for ifName := range s.InterfaceUp {
- ips := s.InterfaceIPs[ifName]
- keep := ips[:0]
- for _, pfx := range ips {
- if isInterestingIP(pfx.IP) {
- keep = append(keep, pfx)
- }
- }
- if len(keep) == 0 {
- delete(s.InterfaceUp, ifName)
- delete(s.InterfaceIPs, ifName)
+// EqualFiltered reports whether s and s2 are equal,
+// considering only interfaces in s for which filter returns true.
+func (s *State) EqualFiltered(s2 *State, filter func(i Interface, ips []netaddr.IPPrefix) bool) bool {
+ if s == nil && s2 == nil {
+ return true
+ }
+ if s == nil || s2 == nil {
+ return false
+ }
+ if s.HaveV6Global != s2.HaveV6Global ||
+ s.HaveV4 != s2.HaveV4 ||
+ s.IsExpensive != s2.IsExpensive ||
+ s.DefaultRouteInterface != s2.DefaultRouteInterface ||
+ s.HTTPProxy != s2.HTTPProxy ||
+ s.PAC != s2.PAC {
+ return false
+ }
+ for iname, i := range s.Interface {
+ ips := s.InterfaceIPs[iname]
+ if !filter(i, ips) {
continue
}
- if len(keep) < len(ips) {
- s.InterfaceIPs[ifName] = keep
+ i2, ok := s2.Interface[iname]
+ if !ok {
+ return false
+ }
+ ips2, ok := s2.InterfaceIPs[iname]
+ if !ok {
+ return false
+ }
+ if !interfacesEqual(i, i2) || !prefixesEqual(ips, ips2) {
+ return false
}
}
+ return true
+}
+
+func interfacesEqual(a, b Interface) bool {
+ return a.Index == b.Index &&
+ a.MTU == b.MTU &&
+ a.Name == b.Name &&
+ a.Flags == b.Flags &&
+ bytes.Equal([]byte(a.HardwareAddr), []byte(b.HardwareAddr))
}
-// RemoveTailscaleInterfaces modifes s to remove any interfaces that
-// are owned by this process. (TODO: make this true; currently it
-// uses some heuristics)
-func (s *State) RemoveTailscaleInterfaces() {
- for name, pfxs := range s.InterfaceIPs {
- if isTailscaleInterface(name, pfxs) {
- delete(s.InterfaceIPs, name)
- delete(s.InterfaceUp, name)
+func prefixesEqual(a, b []netaddr.IPPrefix) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i, v := range a {
+ if b[i] != v {
+ return false
}
}
+ return true
+}
+
+// FilterInteresting reports whether i is an interesting non-Tailscale interface.
+func FilterInteresting(i Interface, ips []netaddr.IPPrefix) bool {
+ return !isTailscaleInterface(i.Name, ips) && anyInterestingIP(ips)
+}
+
+// FilterAll always returns true, to use EqualFiltered against all interfaces.
+func FilterAll(i Interface, ips []netaddr.IPPrefix) bool { return true }
+
+func (s *State) HasPAC() bool { return s != nil && s.PAC != "" }
+
+// AnyInterfaceUp reports whether any interface seems like it has Internet access.
+func (s *State) AnyInterfaceUp() bool {
+ return s != nil && (s.HaveV4 || s.HaveV6Global)
}
func hasTailscaleIP(pfxs []netaddr.IPPrefix) bool {
@@ -364,11 +393,11 @@ var getPAC func() string
func GetState() (*State, error) {
s := &State{
InterfaceIPs: make(map[string][]netaddr.IPPrefix),
- InterfaceUp: make(map[string]bool),
+ Interface: make(map[string]Interface),
}
if err := ForeachInterface(func(ni Interface, pfxs []netaddr.IPPrefix) {
ifUp := ni.IsUp()
- s.InterfaceUp[ni.Name] = ifUp
+ s.Interface[ni.Name] = ni
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...)
if !ifUp || isTailscaleInterface(ni.Name, pfxs) {
return
diff --git a/net/interfaces/interfaces_test.go b/net/interfaces/interfaces_test.go
index 88948b579..ab0ee3734 100644
--- a/net/interfaces/interfaces_test.go
+++ b/net/interfaces/interfaces_test.go
@@ -5,6 +5,7 @@
package interfaces
import (
+ "encoding/json"
"testing"
)
@@ -13,7 +14,11 @@ func TestGetState(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- t.Logf("Got: %#v", st)
+ j, err := json.MarshalIndent(st, "", "\t")
+ if err != nil {
+ t.Errorf("JSON: %v", err)
+ }
+ t.Logf("Got: %s", j)
t.Logf("As string: %s", st)
st2, err := GetState()
@@ -21,14 +26,13 @@ func TestGetState(t *testing.T) {
t.Fatal(err)
}
- if !st.Equal(st2) {
+ if !st.EqualFiltered(st2, FilterAll) {
// let's assume nobody was changing the system network interfaces between
// the two GetState calls.
t.Fatal("two States back-to-back were not equal")
}
- st.RemoveTailscaleInterfaces()
- t.Logf("As string without Tailscale:\n\t%s", st)
+ t.Logf("As string:\n\t%s", st)
}
func TestLikelyHomeRouterIP(t *testing.T) {
diff --git a/net/interfaces/interfaces_windows.go b/net/interfaces/interfaces_windows.go
index 19e9b48b4..91f679c97 100644
--- a/net/interfaces/interfaces_windows.go
+++ b/net/interfaces/interfaces_windows.go
@@ -7,17 +7,19 @@ package interfaces
import (
"fmt"
"log"
+ "net"
"net/url"
- "os/exec"
"syscall"
"unsafe"
- "go4.org/mem"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"inet.af/netaddr"
"tailscale.com/tsconst"
- "tailscale.com/util/lineread"
+)
+
+const (
+ fallbackInterfaceMetric = uint32(0) // Used if we cannot get the actual interface metric
)
func init() {
@@ -25,58 +27,76 @@ func init() {
getPAC = getPACWindows
}
-/*
-Parse out 10.0.0.1 from:
-
-Z:\>route print -4
-===========================================================================
-Interface List
- 15...aa 15 48 ff 1c 72 ......Red Hat VirtIO Ethernet Adapter
- 5...........................Tailscale Tunnel
- 1...........................Software Loopback Interface 1
-===========================================================================
-
-IPv4 Route Table
-===========================================================================
-Active Routes:
-Network Destination Netmask Gateway Interface Metric
- 0.0.0.0 0.0.0.0 10.0.0.1 10.0.28.63 5
- 10.0.0.0 255.255.0.0 On-link 10.0.28.63 261
- 10.0.28.63 255.255.255.255 On-link 10.0.28.63 261
- 10.0.42.0 255.255.255.0 100.103.42.106 100.103.42.106 5
- 10.0.255.255 255.255.255.255 On-link 10.0.28.63 261
- 34.193.248.174 255.255.255.255 100.103.42.106 100.103.42.106 5
-
-*/
func likelyHomeRouterIPWindows() (ret netaddr.IP, ok bool) {
- cmd := exec.Command("route", "print", "-4")
- cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
- stdout, err := cmd.StdoutPipe()
+ rs, err := winipcfg.GetIPForwardTable2(windows.AF_INET)
if err != nil {
+ log.Printf("routerIP/GetIPForwardTable2 error: %v", err)
return
}
- if err := cmd.Start(); err != nil {
+
+ var ifaceMetricCache map[winipcfg.LUID]uint32
+
+ getIfaceMetric := func(luid winipcfg.LUID) (metric uint32) {
+ if ifaceMetricCache == nil {
+ ifaceMetricCache = make(map[winipcfg.LUID]uint32)
+ } else if m, ok := ifaceMetricCache[luid]; ok {
+ return m
+ }
+
+ if iface, err := luid.IPInterface(windows.AF_INET); err == nil {
+ metric = iface.Metric
+ } else {
+ log.Printf("routerIP/luid.IPInterface error: %v", err)
+ metric = fallbackInterfaceMetric
+ }
+
+ ifaceMetricCache[luid] = metric
return
}
- defer cmd.Wait()
- var f []mem.RO
- lineread.Reader(stdout, func(lineb []byte) error {
- line := mem.B(lineb)
- if !mem.Contains(line, mem.S("0.0.0.0")) {
- return nil
+ unspec := net.IPv4(0, 0, 0, 0)
+ var best *winipcfg.MibIPforwardRow2 // best (lowest metric) found so far, or nil
+
+ for i := range rs {
+ r := &rs[i]
+ if r.Loopback || r.DestinationPrefix.PrefixLength != 0 || !r.DestinationPrefix.Prefix.IP().Equal(unspec) {
+ // Not a default route, so skip
+ continue
}
- f = mem.AppendFields(f[:0], line)
- if len(f) < 3 || !f[0].EqualString("0.0.0.0") || !f[1].EqualString("0.0.0.0") {
- return nil
+
+ ip, ok := netaddr.FromStdIP(r.NextHop.IP())
+ if !ok {
+ // Not a valid gateway, so skip (won't happen though)
+ continue
}
- ipm := f[2]
- ip, err := netaddr.ParseIP(string(mem.Append(nil, ipm)))
- if err == nil && isPrivateIP(ip) {
+
+ if best == nil {
+ best = r
ret = ip
+ continue
}
- return nil
- })
+
+ // We can get here only if there are multiple default gateways defined (rare case),
+ // in which case we need to calculate the effective metric.
+ // Effective metric is sum of interface metric and route metric offset
+ if ifaceMetricCache == nil {
+ // If we're here it means that previous route still isn't updated, so update it
+ best.Metric += getIfaceMetric(best.InterfaceLUID)
+ }
+ r.Metric += getIfaceMetric(r.InterfaceLUID)
+
+ if best.Metric > r.Metric || best.Metric == r.Metric && ret.Compare(ip) > 0 {
+ // Pick the route with lower metric, or lower IP if metrics are equal
+ best = r
+ ret = ip
+ }
+ }
+
+ if !ret.IsZero() && !isPrivateIP(ret) {
+ // Default route has a non-private gateway
+ return netaddr.IP{}, false
+ }
+
return ret, !ret.IsZero()
}
diff --git a/net/packet/header.go b/net/packet/header.go
index 86680a5a7..5cf4ef650 100644
--- a/net/packet/header.go
+++ b/net/packet/header.go
@@ -10,6 +10,7 @@ import (
)
const tcpHeaderLength = 20
+const sctpHeaderLength = 12
// maxPacketLength is the largest length that all headers support.
// IPv4 headers using uint16 for this forces an upper bound of 64KB.
diff --git a/net/packet/icmp4.go b/net/packet/icmp4.go
index 8a1568114..da4774887 100644
--- a/net/packet/icmp4.go
+++ b/net/packet/icmp4.go
@@ -4,7 +4,11 @@
package packet
-import "encoding/binary"
+import (
+ "encoding/binary"
+
+ "tailscale.com/types/ipproto"
+)
// icmp4HeaderLength is the size of the ICMPv4 packet header, not
// including the outer IP layer or the variable "response data"
@@ -66,7 +70,7 @@ func (h ICMP4Header) Marshal(buf []byte) error {
return errLargePacket
}
// The caller does not need to set this.
- h.IPProto = ICMPv4
+ h.IPProto = ipproto.ICMPv4
buf[20] = uint8(h.Type)
buf[21] = uint8(h.Code)
diff --git a/net/packet/ip4.go b/net/packet/ip4.go
index 0240abaa1..2c090d9f1 100644
--- a/net/packet/ip4.go
+++ b/net/packet/ip4.go
@@ -9,6 +9,7 @@ import (
"errors"
"inet.af/netaddr"
+ "tailscale.com/types/ipproto"
)
// ip4HeaderLength is the length of an IPv4 header with no IP options.
@@ -16,7 +17,7 @@ const ip4HeaderLength = 20
// IP4Header represents an IPv4 packet header.
type IP4Header struct {
- IPProto IPProto
+ IPProto ipproto.Proto
IPID uint16
Src netaddr.IP
Dst netaddr.IP
diff --git a/net/packet/ip6.go b/net/packet/ip6.go
index 59f605b32..e181f1dde 100644
--- a/net/packet/ip6.go
+++ b/net/packet/ip6.go
@@ -8,6 +8,7 @@ import (
"encoding/binary"
"inet.af/netaddr"
+ "tailscale.com/types/ipproto"
)
// ip6HeaderLength is the length of an IPv6 header with no IP options.
@@ -15,7 +16,7 @@ const ip6HeaderLength = 40
// IP6Header represents an IPv6 packet header.
type IP6Header struct {
- IPProto IPProto
+ IPProto ipproto.Proto
IPID uint32 // only lower 20 bits used
Src netaddr.IP
Dst netaddr.IP
diff --git a/net/packet/packet.go b/net/packet/packet.go
index a88a1af7a..05c4a382f 100644
--- a/net/packet/packet.go
+++ b/net/packet/packet.go
@@ -11,9 +11,12 @@ import (
"strings"
"inet.af/netaddr"
+ "tailscale.com/types/ipproto"
"tailscale.com/types/strbuilder"
)
+const unknown = ipproto.Unknown
+
// RFC1858: prevent overlapping fragment attacks.
const minFrag = 60 + 20 // max IPv4 header + basic TCP header
@@ -44,7 +47,7 @@ type Parsed struct {
// 6), or 0 if the packet doesn't look like IPv4 or IPv6.
IPVersion uint8
// IPProto is the IP subprotocol (UDP, TCP, etc.). Valid iff IPVersion != 0.
- IPProto IPProto
+ IPProto ipproto.Proto
// SrcIP4 is the source address. Family matches IPVersion. Port is
// valid iff IPProto == TCP || IPProto == UDP.
Src netaddr.IPPort
@@ -100,7 +103,7 @@ func (q *Parsed) Decode(b []byte) {
if len(b) < 1 {
q.IPVersion = 0
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
@@ -112,7 +115,7 @@ func (q *Parsed) Decode(b []byte) {
q.decode6(b)
default:
q.IPVersion = 0
- q.IPProto = Unknown
+ q.IPProto = unknown
}
}
@@ -125,16 +128,16 @@ func (q *Parsed) StuffForTesting(len int) {
func (q *Parsed) decode4(b []byte) {
if len(b) < ip4HeaderLength {
q.IPVersion = 0
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
// Check that it's IPv4.
- q.IPProto = IPProto(b[9])
+ q.IPProto = ipproto.Proto(b[9])
q.length = int(binary.BigEndian.Uint16(b[2:4]))
if len(b) < q.length {
// Packet was cut off before full IPv4 length.
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
@@ -145,7 +148,7 @@ func (q *Parsed) decode4(b []byte) {
q.subofs = int((b[0] & 0x0F) << 2)
if q.subofs > q.length {
// next-proto starts beyond end of packet.
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
sub := b[q.subofs:]
@@ -170,29 +173,29 @@ func (q *Parsed) decode4(b []byte) {
// This is the first fragment
if moreFrags && len(sub) < minFrag {
// Suspiciously short first fragment, dump it.
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
// otherwise, this is either non-fragmented (the usual case)
// or a big enough initial fragment that we can read the
// whole subprotocol header.
switch q.IPProto {
- case ICMPv4:
+ case ipproto.ICMPv4:
if len(sub) < icmp4HeaderLength {
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
q.Src.Port = 0
q.Dst.Port = 0
q.dataofs = q.subofs + icmp4HeaderLength
return
- case IGMP:
+ case ipproto.IGMP:
// Keep IPProto, but don't parse anything else
// out.
return
- case TCP:
+ case ipproto.TCP:
if len(sub) < tcpHeaderLength {
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
@@ -201,21 +204,29 @@ func (q *Parsed) decode4(b []byte) {
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
- case UDP:
+ case ipproto.UDP:
if len(sub) < udpHeaderLength {
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.dataofs = q.subofs + udpHeaderLength
return
- case TSMP:
+ case ipproto.SCTP:
+ if len(sub) < sctpHeaderLength {
+ q.IPProto = unknown
+ return
+ }
+ q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
+ q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
+ return
+ case ipproto.TSMP:
// Inter-tailscale messages.
q.dataofs = q.subofs
return
default:
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
} else {
@@ -223,7 +234,7 @@ func (q *Parsed) decode4(b []byte) {
if fragOfs < minFrag {
// First frag was suspiciously short, so we can't
// trust the followup either.
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
// otherwise, we have to permit the fragment to slide through.
@@ -232,7 +243,7 @@ func (q *Parsed) decode4(b []byte) {
// but that would require statefulness. Anyway, receivers'
// kernels know to drop fragments where the initial fragment
// doesn't arrive.
- q.IPProto = Fragment
+ q.IPProto = ipproto.Fragment
return
}
}
@@ -240,15 +251,15 @@ func (q *Parsed) decode4(b []byte) {
func (q *Parsed) decode6(b []byte) {
if len(b) < ip6HeaderLength {
q.IPVersion = 0
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
- q.IPProto = IPProto(b[6])
+ q.IPProto = ipproto.Proto(b[6])
q.length = int(binary.BigEndian.Uint16(b[4:6])) + ip6HeaderLength
if len(b) < q.length {
// Packet was cut off before the full IPv6 length.
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
@@ -274,17 +285,17 @@ func (q *Parsed) decode6(b []byte) {
sub = sub[:len(sub):len(sub)] // help the compiler do bounds check elimination
switch q.IPProto {
- case ICMPv6:
+ case ipproto.ICMPv6:
if len(sub) < icmp6HeaderLength {
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
q.Src.Port = 0
q.Dst.Port = 0
q.dataofs = q.subofs + icmp6HeaderLength
- case TCP:
+ case ipproto.TCP:
if len(sub) < tcpHeaderLength {
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
@@ -293,20 +304,28 @@ func (q *Parsed) decode6(b []byte) {
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
- case UDP:
+ case ipproto.UDP:
if len(sub) < udpHeaderLength {
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.dataofs = q.subofs + udpHeaderLength
- case TSMP:
+ case ipproto.SCTP:
+ if len(sub) < sctpHeaderLength {
+ q.IPProto = unknown
+ return
+ }
+ q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
+ q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
+ return
+ case ipproto.TSMP:
// Inter-tailscale messages.
q.dataofs = q.subofs
return
default:
- q.IPProto = Unknown
+ q.IPProto = unknown
return
}
}
@@ -324,6 +343,19 @@ func (q *Parsed) IP4Header() IP4Header {
}
}
+func (q *Parsed) IP6Header() IP6Header {
+ if q.IPVersion != 6 {
+ panic("IP6Header called on non-IPv6 Parsed")
+ }
+ ipid := (binary.BigEndian.Uint32(q.b[:4]) << 12) >> 12
+ return IP6Header{
+ IPID: ipid,
+ IPProto: q.IPProto,
+ Src: q.Src.IP,
+ Dst: q.Dst.IP,
+ }
+}
+
func (q *Parsed) ICMP4Header() ICMP4Header {
if q.IPVersion != 4 {
panic("IP4Header called on non-IPv4 Parsed")
@@ -367,13 +399,13 @@ func (q *Parsed) IsTCPSyn() bool {
// IsError reports whether q is an ICMP "Error" packet.
func (q *Parsed) IsError() bool {
switch q.IPProto {
- case ICMPv4:
+ case ipproto.ICMPv4:
if len(q.b) < q.subofs+8 {
return false
}
t := ICMP4Type(q.b[q.subofs])
return t == ICMP4Unreachable || t == ICMP4TimeExceeded
- case ICMPv6:
+ case ipproto.ICMPv6:
if len(q.b) < q.subofs+8 {
return false
}
@@ -387,9 +419,9 @@ func (q *Parsed) IsError() bool {
// IsEchoRequest reports whether q is an ICMP Echo Request.
func (q *Parsed) IsEchoRequest() bool {
switch q.IPProto {
- case ICMPv4:
+ case ipproto.ICMPv4:
return len(q.b) >= q.subofs+8 && ICMP4Type(q.b[q.subofs]) == ICMP4EchoRequest && ICMP4Code(q.b[q.subofs+1]) == ICMP4NoCode
- case ICMPv6:
+ case ipproto.ICMPv6:
return len(q.b) >= q.subofs+8 && ICMP6Type(q.b[q.subofs]) == ICMP6EchoRequest && ICMP6Code(q.b[q.subofs+1]) == ICMP6NoCode
default:
return false
@@ -399,9 +431,9 @@ func (q *Parsed) IsEchoRequest() bool {
// IsEchoRequest reports whether q is an IPv4 ICMP Echo Response.
func (q *Parsed) IsEchoResponse() bool {
switch q.IPProto {
- case ICMPv4:
+ case ipproto.ICMPv4:
return len(q.b) >= q.subofs+8 && ICMP4Type(q.b[q.subofs]) == ICMP4EchoReply && ICMP4Code(q.b[q.subofs+1]) == ICMP4NoCode
- case ICMPv6:
+ case ipproto.ICMPv6:
return len(q.b) >= q.subofs+8 && ICMP6Type(q.b[q.subofs]) == ICMP6EchoReply && ICMP6Code(q.b[q.subofs+1]) == ICMP6NoCode
default:
return false
diff --git a/net/packet/packet_test.go b/net/packet/packet_test.go
index 8bac5db4a..ac4fa33f3 100644
--- a/net/packet/packet_test.go
+++ b/net/packet/packet_test.go
@@ -10,6 +10,19 @@ import (
"testing"
"inet.af/netaddr"
+ "tailscale.com/types/ipproto"
+)
+
+const (
+ Unknown = ipproto.Unknown
+ TCP = ipproto.TCP
+ UDP = ipproto.UDP
+ SCTP = ipproto.SCTP
+ IGMP = ipproto.IGMP
+ ICMPv4 = ipproto.ICMPv4
+ ICMPv6 = ipproto.ICMPv6
+ TSMP = ipproto.TSMP
+ Fragment = ipproto.Fragment
)
func mustIPPort(s string) netaddr.IPPort {
@@ -305,6 +318,39 @@ var ipv4TSMPDecode = Parsed{
Dst: mustIPPort("100.74.70.3:0"),
}
+// IPv4 SCTP
+var sctpBuffer = []byte{
+ // IPv4 header:
+ 0x45, 0x00,
+ 0x00, 0x20, // 20 + 12 bytes total
+ 0x00, 0x00, // ID
+ 0x00, 0x00, // Fragment
+ 0x40, // TTL
+ byte(SCTP),
+ // Checksum, unchecked:
+ 1, 2,
+ // source IP:
+ 0x64, 0x5e, 0x0c, 0x0e,
+ // dest IP:
+ 0x64, 0x4a, 0x46, 0x03,
+ // Src Port, Dest Port:
+ 0x00, 0x7b, 0x01, 0xc8,
+ // Verification tag:
+ 1, 2, 3, 4,
+ // Checksum: (unchecked)
+ 5, 6, 7, 8,
+}
+
+var sctpDecode = Parsed{
+ b: sctpBuffer,
+ subofs: 20,
+ length: 20 + 12,
+ IPVersion: 4,
+ IPProto: SCTP,
+ Src: mustIPPort("100.94.12.14:123"),
+ Dst: mustIPPort("100.74.70.3:456"),
+}
+
func TestParsedString(t *testing.T) {
tests := []struct {
name string
@@ -320,6 +366,7 @@ func TestParsedString(t *testing.T) {
{"igmp", igmpPacketDecode, "IGMP{192.168.1.82:0 > 224.0.0.251:0}"},
{"unknown", unknownPacketDecode, "Unknown{???}"},
{"ipv4_tsmp", ipv4TSMPDecode, "TSMP{100.94.12.14:0 > 100.74.70.3:0}"},
+ {"sctp", sctpDecode, "SCTP{100.94.12.14:123 > 100.74.70.3:456}"},
}
for _, tt := range tests {
@@ -357,6 +404,7 @@ func TestDecode(t *testing.T) {
{"unknown", unknownPacketBuffer, unknownPacketDecode},
{"invalid4", invalid4RequestBuffer, invalid4RequestDecode},
{"ipv4_tsmp", ipv4TSMPBuffer, ipv4TSMPDecode},
+ {"ipv4_sctp", sctpBuffer, sctpDecode},
}
for _, tt := range tests {
diff --git a/net/packet/tsmp.go b/net/packet/tsmp.go
index 2346c9419..fb257556c 100644
--- a/net/packet/tsmp.go
+++ b/net/packet/tsmp.go
@@ -17,6 +17,7 @@ import (
"inet.af/netaddr"
"tailscale.com/net/flowtrack"
+ "tailscale.com/types/ipproto"
)
// TailscaleRejectedHeader is a TSMP message that says that one
@@ -39,7 +40,7 @@ type TailscaleRejectedHeader struct {
IPDst netaddr.IP // IPv4 or IPv6 header's dst IP
Src netaddr.IPPort // rejected flow's src
Dst netaddr.IPPort // rejected flow's dst
- Proto IPProto // proto that was rejected (TCP or UDP)
+ Proto ipproto.Proto // proto that was rejected (TCP or UDP)
Reason TailscaleRejectReason // why the connection was rejected
// MaybeBroken is whether the rejection is non-terminal (the
@@ -57,7 +58,7 @@ type TailscaleRejectedHeader struct {
const rejectFlagBitMaybeBroken = 0x1
func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple {
- return flowtrack.Tuple{Src: rh.Src, Dst: rh.Dst}
+ return flowtrack.Tuple{Proto: rh.Proto, Src: rh.Src, Dst: rh.Dst}
}
func (rh TailscaleRejectedHeader) String() string {
@@ -69,6 +70,12 @@ type TSMPType uint8
const (
// TSMPTypeRejectedConn is the type byte for a TailscaleRejectedHeader.
TSMPTypeRejectedConn TSMPType = '!'
+
+ // TSMPTypePing is the type byte for a TailscalePingRequest.
+ TSMPTypePing TSMPType = 'p'
+
+ // TSMPTypePong is the type byte for a TailscalePongResponse.
+ TSMPTypePong TSMPType = 'o'
)
type TailscaleRejectReason byte
@@ -138,7 +145,7 @@ func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
}
if h.Src.IP.Is4() {
iph := IP4Header{
- IPProto: TSMP,
+ IPProto: ipproto.TSMP,
Src: h.IPSrc,
Dst: h.IPDst,
}
@@ -146,7 +153,7 @@ func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
buf = buf[ip4HeaderLength:]
} else if h.Src.IP.Is6() {
iph := IP6Header{
- IPProto: TSMP,
+ IPProto: ipproto.TSMP,
Src: h.IPSrc,
Dst: h.IPDst,
}
@@ -181,7 +188,7 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo
return
}
h = TailscaleRejectedHeader{
- Proto: IPProto(p[1]),
+ Proto: ipproto.Proto(p[1]),
Reason: TailscaleRejectReason(p[2]),
IPSrc: pp.Src.IP,
IPDst: pp.Dst.IP,
@@ -194,3 +201,58 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo
}
return h, true
}
+
+// TSMPPingRequest is a TSMP message that's like an ICMP ping request.
+//
+// On the wire, after the IP header, it's currently 9 bytes:
+// * 'p' (TSMPTypePing)
+// * 8 opaque ping bytes to copy back in the response
+type TSMPPingRequest struct {
+ Data [8]byte
+}
+
+func (pp *Parsed) AsTSMPPing() (h TSMPPingRequest, ok bool) {
+ if pp.IPProto != ipproto.TSMP {
+ return
+ }
+ p := pp.Payload()
+ if len(p) < 9 || p[0] != byte(TSMPTypePing) {
+ return
+ }
+ copy(h.Data[:], p[1:])
+ return h, true
+}
+
+type TSMPPongReply struct {
+ IPHeader Header
+ Data [8]byte
+}
+
+func (pp *Parsed) AsTSMPPong() (data [8]byte, ok bool) {
+ if pp.IPProto != ipproto.TSMP {
+ return
+ }
+ p := pp.Payload()
+ if len(p) < 9 || p[0] != byte(TSMPTypePong) {
+ return
+ }
+ copy(data[:], p[1:])
+ return data, true
+}
+
+func (h TSMPPongReply) Len() int {
+ return h.IPHeader.Len() + 9
+}
+
+func (h TSMPPongReply) Marshal(buf []byte) error {
+ if len(buf) < h.Len() {
+ return errSmallBuffer
+ }
+ if err := h.IPHeader.Marshal(buf); err != nil {
+ return err
+ }
+ buf = buf[h.IPHeader.Len():]
+ buf[0] = byte(TSMPTypePong)
+ copy(buf[1:], h.Data[:])
+ return nil
+}
diff --git a/net/packet/udp4.go b/net/packet/udp4.go
index 82aa30179..ce179f89d 100644
--- a/net/packet/udp4.go
+++ b/net/packet/udp4.go
@@ -4,7 +4,11 @@
package packet
-import "encoding/binary"
+import (
+ "encoding/binary"
+
+ "tailscale.com/types/ipproto"
+)
// udpHeaderLength is the size of the UDP packet header, not including
// the outer IP header.
@@ -31,7 +35,7 @@ func (h UDP4Header) Marshal(buf []byte) error {
return errLargePacket
}
// The caller does not need to set this.
- h.IPProto = UDP
+ h.IPProto = ipproto.UDP
length := len(buf) - h.IP4Header.Len()
binary.BigEndian.PutUint16(buf[20:22], h.SrcPort)
diff --git a/net/packet/udp6.go b/net/packet/udp6.go
index 0450eae9e..18213c1fb 100644
--- a/net/packet/udp6.go
+++ b/net/packet/udp6.go
@@ -4,7 +4,11 @@
package packet
-import "encoding/binary"
+import (
+ "encoding/binary"
+
+ "tailscale.com/types/ipproto"
+)
// UDP6Header is an IPv6+UDP header.
type UDP6Header struct {
@@ -27,7 +31,7 @@ func (h UDP6Header) Marshal(buf []byte) error {
return errLargePacket
}
// The caller does not need to set this.
- h.IPProto = UDP
+ h.IPProto = ipproto.UDP
length := len(buf) - h.IP6Header.Len()
binary.BigEndian.PutUint16(buf[40:42], h.SrcPort)
diff --git a/net/tsaddr/tsaddr.go b/net/tsaddr/tsaddr.go
index 44cf5cf23..9bf81326e 100644
--- a/net/tsaddr/tsaddr.go
+++ b/net/tsaddr/tsaddr.go
@@ -37,7 +37,7 @@ var (
)
// TailscaleServiceIP returns the listen address of services
-// provided by Tailscale itself such as the Magic DNS proxy.
+// provided by Tailscale itself such as the MagicDNS proxy.
func TailscaleServiceIP() netaddr.IP {
serviceIP.Do(func() { mustIP(&serviceIP.v, "100.100.100.100") })
return serviceIP.v
diff --git a/wgengine/tstun/faketun.go b/net/tstun/fake.go
index 50880131a..f9c3a9d6f 100644
--- a/wgengine/tstun/faketun.go
+++ b/net/tstun/fake.go
@@ -16,10 +16,8 @@ type fakeTUN struct {
closechan chan struct{}
}
-// NewFakeTUN returns a fake TUN device that does not depend on the
-// operating system or any special permissions.
-// It primarily exists for testing.
-func NewFakeTUN() tun.Device {
+// NewFake returns a tun.Device that does nothing.
+func NewFake() tun.Device {
return &fakeTUN{
evchan: make(chan tun.Event),
closechan: make(chan struct{}),
diff --git a/wgengine/ifstatus_noop.go b/net/tstun/ifstatus_noop.go
index 7564d67ec..223be7949 100644
--- a/wgengine/ifstatus_noop.go
+++ b/net/tstun/ifstatus_noop.go
@@ -4,7 +4,7 @@
// +build !windows
-package wgengine
+package tstun
import (
"time"
diff --git a/wgengine/ifstatus_windows.go b/net/tstun/ifstatus_windows.go
index 840b6cf39..840e50f4d 100644
--- a/wgengine/ifstatus_windows.go
+++ b/net/tstun/ifstatus_windows.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package wgengine
+package tstun
import (
"fmt"
diff --git a/net/tstun/tun.go b/net/tstun/tun.go
new file mode 100644
index 000000000..d480d3244
--- /dev/null
+++ b/net/tstun/tun.go
@@ -0,0 +1,129 @@
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package tun creates a tuntap device, working around OS-specific
+// quirks if necessary.
+package tstun
+
+import (
+ "bytes"
+ "os"
+ "os/exec"
+ "runtime"
+ "time"
+
+ "github.com/tailscale/wireguard-go/tun"
+ "tailscale.com/types/logger"
+ "tailscale.com/version/distro"
+)
+
+// minimalMTU is the MTU we set on tailscale's TUN
+// interface. wireguard-go defaults to 1420 bytes, which only works if
+// the "outer" MTU is 1500 bytes. This breaks on DSL connections
+// (typically 1492 MTU) and on GCE (1460 MTU?!).
+//
+// 1280 is the smallest MTU allowed for IPv6, which is a sensible
+// "probably works everywhere" setting until we develop proper PMTU
+// discovery.
+const minimalMTU = 1280
+
+// New returns a tun.Device for the requested device name.
+func New(logf logger.Logf, tunName string) (tun.Device, error) {
+ dev, err := tun.CreateTUN(tunName, minimalMTU)
+ if err != nil {
+ return nil, err
+ }
+ if err := waitInterfaceUp(dev, 90*time.Second, logf); err != nil {
+ return nil, err
+ }
+ return dev, nil
+}
+
+// Diagnose tries to explain a tuntap device creation failure.
+// It pokes around the system and logs some diagnostic info that might
+// help debug why tun creation failed. Because device creation has
+// already failed and the program's about to end, log a lot.
+func Diagnose(logf logger.Logf, tunName string) {
+ switch runtime.GOOS {
+ case "linux":
+ diagnoseLinuxTUNFailure(tunName, logf)
+ case "darwin":
+ diagnoseDarwinTUNFailure(tunName, logf)
+ default:
+ logf("no TUN failure diagnostics for OS %q", runtime.GOOS)
+ }
+}
+
+func diagnoseDarwinTUNFailure(tunName string, logf logger.Logf) {
+ if os.Getuid() != 0 {
+ logf("failed to create TUN device as non-root user; use 'sudo tailscaled', or run under launchd with 'sudo tailscaled install-system-daemon'")
+ }
+ if tunName != "utun" {
+ logf("failed to create TUN device %q; try using tun device \"utun\" instead for automatic selection", tunName)
+ }
+}
+
+func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf) {
+ kernel, err := exec.Command("uname", "-r").Output()
+ kernel = bytes.TrimSpace(kernel)
+ if err != nil {
+ logf("no TUN, and failed to look up kernel version: %v", err)
+ return
+ }
+ logf("Linux kernel version: %s", kernel)
+
+ modprobeOut, err := exec.Command("/sbin/modprobe", "tun").CombinedOutput()
+ if err == nil {
+ logf("'modprobe tun' successful")
+ // Either tun is currently loaded, or it's statically
+ // compiled into the kernel (which modprobe checks
+ // with /lib/modules/$(uname -r)/modules.builtin)
+ //
+ // So if there's a problem at this point, it's
+ // probably because /dev/net/tun doesn't exist.
+ const dev = "/dev/net/tun"
+ if fi, err := os.Stat(dev); err != nil {
+ logf("tun module loaded in kernel, but %s does not exist", dev)
+ } else {
+ logf("%s: %v", dev, fi.Mode())
+ }
+
+ // We failed to find why it failed. Just let our
+ // caller report the error it got from wireguard-go.
+ return
+ }
+ logf("is CONFIG_TUN enabled in your kernel? `modprobe tun` failed with: %s", modprobeOut)
+
+ switch distro.Get() {
+ case distro.Debian:
+ dpkgOut, err := exec.Command("dpkg", "-S", "kernel/drivers/net/tun.ko").CombinedOutput()
+ if len(bytes.TrimSpace(dpkgOut)) == 0 || err != nil {
+ logf("tun module not loaded nor found on disk")
+ return
+ }
+ if !bytes.Contains(dpkgOut, kernel) {
+ logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", dpkgOut)
+ }
+ case distro.Arch:
+ findOut, err := exec.Command("find", "/lib/modules/", "-path", "*/net/tun.ko*").CombinedOutput()
+ if len(bytes.TrimSpace(findOut)) == 0 || err != nil {
+ logf("tun module not loaded nor found on disk")
+ return
+ }
+ if !bytes.Contains(findOut, kernel) {
+ logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", findOut)
+ }
+ case distro.OpenWrt:
+ out, err := exec.Command("opkg", "list-installed").CombinedOutput()
+ if err != nil {
+ logf("error querying OpenWrt installed packages: %s", out)
+ return
+ }
+ for _, pkg := range []string{"kmod-tun", "ca-bundle"} {
+ if !bytes.Contains(out, []byte(pkg+" - ")) {
+ logf("Missing required package %s; run: opkg install %s", pkg, pkg)
+ }
+ }
+ }
+}
diff --git a/wgengine/tstun/tun_windows.go b/net/tstun/tun_windows.go
index dc5fc2d79..dc5fc2d79 100644
--- a/wgengine/tstun/tun_windows.go
+++ b/net/tstun/tun_windows.go
diff --git a/wgengine/tstun/tun.go b/net/tstun/wrap.go
index 92af1b8b0..70225c52e 100644
--- a/wgengine/tstun/tun.go
+++ b/net/tstun/wrap.go
@@ -18,6 +18,7 @@ import (
"github.com/tailscale/wireguard-go/tun"
"inet.af/netaddr"
"tailscale.com/net/packet"
+ "tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/wgengine/filter"
)
@@ -30,11 +31,11 @@ const maxBufferSize = device.MaxMessageSize
const PacketStartOffset = device.MessageTransportHeaderSize
// MaxPacketSize is the maximum size (in bytes)
-// of a packet that can be injected into a tstun.TUN.
+// of a packet that can be injected into a tstun.Wrapper.
const MaxPacketSize = device.MaxContentSize
var (
- // ErrClosed is returned when attempting an operation on a closed TUN.
+ // ErrClosed is returned when attempting an operation on a closed Wrapper.
ErrClosed = errors.New("device closed")
// ErrFiltered is returned when the acted-on packet is rejected by a filter.
ErrFiltered = errors.New("packet dropped by filter")
@@ -51,17 +52,14 @@ var (
// do not escape through {Pre,Post}Filter{In,Out}.
var parsedPacketPool = sync.Pool{New: func() interface{} { return new(packet.Parsed) }}
-// FilterFunc is a packet-filtering function with access to the TUN device.
+// FilterFunc is a packet-filtering function with access to the Wrapper device.
// It must not hold onto the packet struct, as its backing storage will be reused.
-type FilterFunc func(*packet.Parsed, *TUN) filter.Response
+type FilterFunc func(*packet.Parsed, *Wrapper) filter.Response
-// TUN wraps a tun.Device from wireguard-go,
-// augmenting it with filtering and packet injection.
-// All the added work happens in Read and Write:
-// the other methods delegate to the underlying tdev.
-type TUN struct {
+// Wrapper augments a tun.Device with packet filtering and injection.
+type Wrapper struct {
logf logger.Logf
- // tdev is the underlying TUN device.
+ // tdev is the underlying Wrapper device.
tdev tun.Device
closeOnce sync.Once
@@ -108,12 +106,15 @@ type TUN struct {
// PostFilterOut is the outbound filter function that runs after the main filter.
PostFilterOut FilterFunc
+ // OnTSMPPongReceived, if non-nil, is called whenever a TSMP pong arrives.
+ OnTSMPPongReceived func(data [8]byte)
+
// disableFilter disables all filtering when set. This should only be used in tests.
disableFilter bool
}
-func WrapTUN(logf logger.Logf, tdev tun.Device) *TUN {
- tun := &TUN{
+func Wrap(logf logger.Logf, tdev tun.Device) *Wrapper {
+ tun := &Wrapper{
logf: logger.WithPrefix(logf, "tstun: "),
tdev: tdev,
// bufferConsumed is conceptually a condition variable:
@@ -136,12 +137,12 @@ func WrapTUN(logf logger.Logf, tdev tun.Device) *TUN {
// SetDestIPActivityFuncs sets a map of funcs to run per packet
// destination (the map keys).
//
-// The map ownership passes to the TUN. It must be non-nil.
-func (t *TUN) SetDestIPActivityFuncs(m map[netaddr.IP]func()) {
+// The map ownership passes to the Wrapper. It must be non-nil.
+func (t *Wrapper) SetDestIPActivityFuncs(m map[netaddr.IP]func()) {
t.destIPActivity.Store(m)
}
-func (t *TUN) Close() error {
+func (t *Wrapper) Close() error {
var err error
t.closeOnce.Do(func() {
// Other channels need not be closed: poll will exit gracefully after this.
@@ -152,30 +153,30 @@ func (t *TUN) Close() error {
return err
}
-func (t *TUN) Events() chan tun.Event {
+func (t *Wrapper) Events() chan tun.Event {
return t.tdev.Events()
}
-func (t *TUN) File() *os.File {
+func (t *Wrapper) File() *os.File {
return t.tdev.File()
}
-func (t *TUN) Flush() error {
+func (t *Wrapper) Flush() error {
return t.tdev.Flush()
}
-func (t *TUN) MTU() (int, error) {
+func (t *Wrapper) MTU() (int, error) {
return t.tdev.MTU()
}
-func (t *TUN) Name() (string, error) {
+func (t *Wrapper) Name() (string, error) {
return t.tdev.Name()
}
// poll polls t.tdev.Read, placing the oldest unconsumed packet into t.buffer.
// This is needed because t.tdev.Read in general may block (it does on Windows),
// so packets may be stuck in t.outbound if t.Read called t.tdev.Read directly.
-func (t *TUN) poll() {
+func (t *Wrapper) poll() {
for {
select {
case <-t.closed:
@@ -185,7 +186,7 @@ func (t *TUN) poll() {
}
// Read may use memory in t.buffer before PacketStartOffset for mandatory headers.
- // This is the rationale behind the tun.TUN.{Read,Write} interfaces
+ // This is the rationale behind the tun.Wrapper.{Read,Write} interfaces
// and the reason t.buffer has size MaxMessageSize and not MaxContentSize.
n, err := t.tdev.Read(t.buffer[:], PacketStartOffset)
if err != nil {
@@ -217,7 +218,7 @@ func (t *TUN) poll() {
var magicDNSIPPort = netaddr.MustParseIPPort("100.100.100.100:0")
-func (t *TUN) filterOut(p *packet.Parsed) filter.Response {
+func (t *Wrapper) filterOut(p *packet.Parsed) filter.Response {
// Fake ICMP echo responses to MagicDNS (100.100.100.100).
if p.IsEchoRequest() && p.Dst == magicDNSIPPort {
header := p.ICMP4Header()
@@ -253,7 +254,7 @@ func (t *TUN) filterOut(p *packet.Parsed) filter.Response {
}
// noteActivity records that there was a read or write at the current time.
-func (t *TUN) noteActivity() {
+func (t *Wrapper) noteActivity() {
atomic.StoreInt64(&t.lastActivityAtomic, time.Now().Unix())
}
@@ -261,12 +262,12 @@ func (t *TUN) noteActivity() {
//
// Its value is only accurate to roughly second granularity.
// If there's never been activity, the duration is since 1970.
-func (t *TUN) IdleDuration() time.Duration {
+func (t *Wrapper) IdleDuration() time.Duration {
sec := atomic.LoadInt64(&t.lastActivityAtomic)
return time.Since(time.Unix(sec, 0))
}
-func (t *TUN) Read(buf []byte, offset int) (int, error) {
+func (t *Wrapper) Read(buf []byte, offset int) (int, error) {
var n int
wasInjectedPacket := false
@@ -317,11 +318,23 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) {
return n, nil
}
-func (t *TUN) filterIn(buf []byte) filter.Response {
+func (t *Wrapper) filterIn(buf []byte) filter.Response {
p := parsedPacketPool.Get().(*packet.Parsed)
defer parsedPacketPool.Put(p)
p.Decode(buf)
+ if p.IPProto == ipproto.TSMP {
+ if pingReq, ok := p.AsTSMPPing(); ok {
+ t.noteActivity()
+ t.injectOutboundPong(p, pingReq)
+ return filter.DropSilently
+ } else if data, ok := p.AsTSMPPong(); ok {
+ if f := t.OnTSMPPongReceived; f != nil {
+ f(data)
+ }
+ }
+ }
+
if t.PreFilterIn != nil {
if res := t.PreFilterIn(p, t); res.IsDrop() {
return res
@@ -340,7 +353,7 @@ func (t *TUN) filterIn(buf []byte) filter.Response {
// Their host networking stack can translate this into ICMP
// or whatnot as required. But notably, their GUI or tailscale CLI
// can show them a rejection history with reasons.
- if p.IPVersion == 4 && p.IPProto == packet.TCP && p.TCPFlags&packet.TCPSyn != 0 {
+ if p.IPVersion == 4 && p.IPProto == ipproto.TCP && p.TCPFlags&packet.TCPSyn != 0 {
rj := packet.TailscaleRejectedHeader{
IPSrc: p.Dst.IP,
IPDst: p.Src.IP,
@@ -372,7 +385,7 @@ func (t *TUN) filterIn(buf []byte) filter.Response {
// Write accepts an incoming packet. The packet begins at buf[offset:],
// like wireguard-go/tun.Device.Write.
-func (t *TUN) Write(buf []byte, offset int) (int, error) {
+func (t *Wrapper) Write(buf []byte, offset int) (int, error) {
if !t.disableFilter {
res := t.filterIn(buf[offset:])
if res == filter.DropSilently {
@@ -387,16 +400,16 @@ func (t *TUN) Write(buf []byte, offset int) (int, error) {
return t.tdev.Write(buf, offset)
}
-func (t *TUN) GetFilter() *filter.Filter {
+func (t *Wrapper) GetFilter() *filter.Filter {
filt, _ := t.filter.Load().(*filter.Filter)
return filt
}
-func (t *TUN) SetFilter(filt *filter.Filter) {
+func (t *Wrapper) SetFilter(filt *filter.Filter) {
t.filter.Store(filt)
}
-// InjectInboundDirect makes the TUN device behave as if a packet
+// InjectInboundDirect makes the Wrapper device behave as if a packet
// with the given contents was received from the network.
// It blocks and does not take ownership of the packet.
// The injected packet will not pass through inbound filters.
@@ -404,7 +417,7 @@ func (t *TUN) SetFilter(filt *filter.Filter) {
// The packet contents are to start at &buf[offset].
// offset must be greater or equal to PacketStartOffset.
// The space before &buf[offset] will be used by Wireguard.
-func (t *TUN) InjectInboundDirect(buf []byte, offset int) error {
+func (t *Wrapper) InjectInboundDirect(buf []byte, offset int) error {
if len(buf) > MaxPacketSize {
return errPacketTooBig
}
@@ -423,7 +436,7 @@ func (t *TUN) InjectInboundDirect(buf []byte, offset int) error {
// InjectInboundCopy takes a packet without leading space,
// reallocates it to conform to the InjectInboundDirect interface
// and calls InjectInboundDirect on it. Injecting a nil packet is a no-op.
-func (t *TUN) InjectInboundCopy(packet []byte) error {
+func (t *Wrapper) InjectInboundCopy(packet []byte) error {
// We duplicate this check from InjectInboundDirect here
// to avoid wasting an allocation on an oversized packet.
if len(packet) > MaxPacketSize {
@@ -439,12 +452,32 @@ func (t *TUN) InjectInboundCopy(packet []byte) error {
return t.InjectInboundDirect(buf, PacketStartOffset)
}
-// InjectOutbound makes the TUN device behave as if a packet
+func (t *Wrapper) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingRequest) {
+ pong := packet.TSMPPongReply{
+ Data: req.Data,
+ }
+ switch pp.IPVersion {
+ case 4:
+ h4 := pp.IP4Header()
+ h4.ToResponse()
+ pong.IPHeader = h4
+ case 6:
+ h6 := pp.IP6Header()
+ h6.ToResponse()
+ pong.IPHeader = h6
+ default:
+ return
+ }
+
+ t.InjectOutbound(packet.Generate(pong, nil))
+}
+
+// InjectOutbound makes the Wrapper device behave as if a packet
// with the given contents was sent to the network.
// It does not block, but takes ownership of the packet.
// The injected packet will not pass through outbound filters.
// Injecting an empty packet is a no-op.
-func (t *TUN) InjectOutbound(packet []byte) error {
+func (t *Wrapper) InjectOutbound(packet []byte) error {
if len(packet) > MaxPacketSize {
return errPacketTooBig
}
@@ -459,7 +492,7 @@ func (t *TUN) InjectOutbound(packet []byte) error {
}
}
-// Unwrap returns the underlying TUN device.
-func (t *TUN) Unwrap() tun.Device {
+// Unwrap returns the underlying tun.Device.
+func (t *Wrapper) Unwrap() tun.Device {
return t.tdev
}
diff --git a/wgengine/tstun/tun_test.go b/net/tstun/wrap_test.go
index 365d56b4d..4032b168b 100644
--- a/wgengine/tstun/tun_test.go
+++ b/net/tstun/wrap_test.go
@@ -16,6 +16,7 @@ import (
"github.com/tailscale/wireguard-go/tun/tuntest"
"inet.af/netaddr"
"tailscale.com/net/packet"
+ "tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/wgengine/filter"
)
@@ -105,19 +106,23 @@ func netports(netPorts ...string) (ret []filter.NetPortRange) {
return ret
}
-func setfilter(logf logger.Logf, tun *TUN) {
+func setfilter(logf logger.Logf, tun *Wrapper) {
+ protos := []ipproto.Proto{
+ ipproto.TCP,
+ ipproto.UDP,
+ }
matches := []filter.Match{
- {Srcs: nets("5.6.7.8"), Dsts: netports("1.2.3.4:89-90")},
- {Srcs: nets("1.2.3.4"), Dsts: netports("5.6.7.8:98")},
+ {IPProto: protos, Srcs: nets("5.6.7.8"), Dsts: netports("1.2.3.4:89-90")},
+ {IPProto: protos, Srcs: nets("1.2.3.4"), Dsts: netports("5.6.7.8:98")},
}
var sb netaddr.IPSetBuilder
sb.AddPrefix(netaddr.MustParseIPPrefix("1.2.0.0/16"))
tun.SetFilter(filter.New(matches, sb.IPSet(), sb.IPSet(), nil, logf))
}
-func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *TUN) {
+func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *Wrapper) {
chtun := tuntest.NewChannelTUN()
- tun := WrapTUN(logf, chtun.TUN())
+ tun := Wrap(logf, chtun.TUN())
if secure {
setfilter(logf, tun)
} else {
@@ -126,9 +131,9 @@ func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *TUN) {
return chtun, tun
}
-func newFakeTUN(logf logger.Logf, secure bool) (*fakeTUN, *TUN) {
- ftun := NewFakeTUN()
- tun := WrapTUN(logf, ftun)
+func newFakeTUN(logf logger.Logf, secure bool) (*fakeTUN, *Wrapper) {
+ ftun := NewFake()
+ tun := Wrap(logf, ftun)
if secure {
setfilter(logf, tun)
} else {
@@ -269,7 +274,7 @@ func TestFilter(t *testing.T) {
{"good_packet_out", out, false, udp4("1.2.3.4", "5.6.7.8", 98, 98)},
}
- // A reader on the other end of the TUN.
+ // A reader on the other end of the tun.
go func() {
var recvbuf []byte
for {
@@ -372,11 +377,11 @@ func BenchmarkWrite(b *testing.B) {
}
func TestAtomic64Alignment(t *testing.T) {
- off := unsafe.Offsetof(TUN{}.lastActivityAtomic)
+ off := unsafe.Offsetof(Wrapper{}.lastActivityAtomic)
if off%8 != 0 {
t.Errorf("offset %v not 8-byte aligned", off)
}
- c := new(TUN)
+ c := new(Wrapper)
atomic.StoreInt64(&c.lastActivityAtomic, 123)
}
diff --git a/syncs/syncs.go b/syncs/syncs.go
index 0139ad925..f319f6489 100644
--- a/syncs/syncs.go
+++ b/syncs/syncs.go
@@ -5,7 +5,10 @@
// Package syncs contains additional sync types and functionality.
package syncs
-import "sync/atomic"
+import (
+ "context"
+ "sync/atomic"
+)
// ClosedChan returns a channel that's already closed.
func ClosedChan() <-chan struct{} { return closedChan }
@@ -79,3 +82,45 @@ func (b *AtomicBool) Set(v bool) {
func (b *AtomicBool) Get() bool {
return atomic.LoadInt32((*int32)(b)) != 0
}
+
+// Semaphore is a counting semaphore.
+//
+// Use NewSemaphore to create one.
+type Semaphore struct {
+ c chan struct{}
+}
+
+// NewSemaphore returns a semaphore with resource count n.
+func NewSemaphore(n int) Semaphore {
+ return Semaphore{c: make(chan struct{}, n)}
+}
+
+// Acquire blocks until a resource is acquired.
+func (s Semaphore) Acquire() {
+ s.c <- struct{}{}
+}
+
+// AcquireContext reports whether the resource was acquired before the ctx was done.
+func (s Semaphore) AcquireContext(ctx context.Context) bool {
+ select {
+ case s.c <- struct{}{}:
+ return true
+ case <-ctx.Done():
+ return false
+ }
+}
+
+// TryAcquire reports, without blocking, whether the resource was acquired.
+func (s Semaphore) TryAcquire() bool {
+ select {
+ case s.c <- struct{}{}:
+ return true
+ default:
+ return false
+ }
+}
+
+// Release releases a resource.
+func (s Semaphore) Release() {
+ <-s.c
+}
diff --git a/syncs/syncs_test.go b/syncs/syncs_test.go
index 9de72e22f..a6768e90b 100644
--- a/syncs/syncs_test.go
+++ b/syncs/syncs_test.go
@@ -4,7 +4,10 @@
package syncs
-import "testing"
+import (
+ "context"
+ "testing"
+)
func TestWaitGroupChan(t *testing.T) {
wg := NewWaitGroupChan()
@@ -48,3 +51,25 @@ func TestClosedChan(t *testing.T) {
}
}
}
+
+func TestSemaphore(t *testing.T) {
+ s := NewSemaphore(2)
+ s.Acquire()
+ if !s.TryAcquire() {
+ t.Fatal("want true")
+ }
+ if s.TryAcquire() {
+ t.Fatal("want false")
+ }
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+ if s.AcquireContext(ctx) {
+ t.Fatal("want false")
+ }
+ s.Release()
+ if !s.AcquireContext(context.Background()) {
+ t.Fatal("want true")
+ }
+ s.Release()
+ s.Release()
+}
diff --git a/syncs/watchdog_test.go b/syncs/watchdog_test.go
index b5cc3452e..116d00625 100644
--- a/syncs/watchdog_test.go
+++ b/syncs/watchdog_test.go
@@ -6,9 +6,12 @@ package syncs
import (
"context"
+ "runtime"
"sync"
"testing"
"time"
+
+ "tailscale.com/util/cibuild"
)
// Time-based tests are fundamentally flaky.
@@ -46,6 +49,12 @@ func TestWatchContended(t *testing.T) {
}
func TestWatchMultipleValues(t *testing.T) {
+ if cibuild.On() && runtime.GOOS == "windows" {
+ // On the CI machine, it sometimes takes 500ms to start a new goroutine.
+ // When this happens, we don't get enough events quickly enough.
+ // Nothing's wrong, and it's not worth working around. Just skip the test.
+ t.Skip("flaky on Windows CI")
+ }
mu := new(sync.Mutex)
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // not necessary, but keep vet happy
diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go
index f4992124e..30119b671 100644
--- a/tailcfg/tailcfg.go
+++ b/tailcfg/tailcfg.go
@@ -15,7 +15,6 @@ import (
"time"
"go4.org/mem"
- "golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/types/key"
"tailscale.com/types/opt"
@@ -36,7 +35,8 @@ import (
// 10: 2021-01-17: client understands MapResponse.PeerSeenChange
// 11: 2021-03-03: client understands IPv6, multiple default routes, and goroutine dumping
// 12: 2021-03-04: client understands PingRequest
-const CurrentMapRequestVersion = 12
+// 13: 2021-03-19: client understands FilterRule.IPProto
+const CurrentMapRequestVersion = 13
type StableID string
@@ -539,6 +539,61 @@ func (h *Hostinfo) Equal(h2 *Hostinfo) bool {
return reflect.DeepEqual(h, h2)
}
+// SignatureType specifies a scheme for signing RegisterRequest messages. It
+// specifies the crypto algorithms to use, the contents of what is signed, and
+// any other relevant details. Historically, requests were unsigned so the zero
+// value is SignatureNone.
+type SignatureType int
+
+const (
+ // SignatureNone indicates that there is no signature, no Timestamp is
+ // required (but may be specified if desired), and both DeviceCert and
+ // Signature should be empty.
+ SignatureNone = SignatureType(iota)
+ // SignatureUnknown represents an unknown signature scheme, which should
+ // be considered an error if seen.
+ SignatureUnknown
+ // SignatureV1 is computed as RSA-PSS-Sign(privateKeyForDeviceCert,
+ // SHA256(Timestamp || ServerIdentity || DeviceCert || ServerPubKey ||
+ // MachinePubKey)). The PSS salt length is equal to hash length
+ // (rsa.PSSSaltLengthEqualsHash). Device cert is required.
+ SignatureV1
+)
+
+func (st SignatureType) MarshalText() ([]byte, error) {
+ return []byte(st.String()), nil
+}
+
+func (st *SignatureType) UnmarshalText(b []byte) error {
+ switch string(b) {
+ case "signature-none":
+ *st = SignatureNone
+ case "signature-v1":
+ *st = SignatureV1
+ default:
+ var val int
+ if _, err := fmt.Sscanf(string(b), "signature-unknown(%d)", &val); err != nil {
+ *st = SignatureType(val)
+ } else {
+ *st = SignatureUnknown
+ }
+ }
+ return nil
+}
+
+func (st SignatureType) String() string {
+ switch st {
+ case SignatureNone:
+ return "signature-none"
+ case SignatureUnknown:
+ return "signature-unknown"
+ case SignatureV1:
+ return "signature-v1"
+ default:
+ return fmt.Sprintf("signature-unknown(%d)", int(st))
+ }
+}
+
// RegisterRequest is sent by a client to register the key for a node.
// It is encoded to JSON, encrypted with golang.org/x/crypto/nacl/box,
// using the local machine key, and sent to:
@@ -552,12 +607,19 @@ type RegisterRequest struct {
_ structs.Incomparable
// One of Provider/LoginName, Oauth2Token, or AuthKey is set.
Provider, LoginName string
- Oauth2Token *oauth2.Token
+ Oauth2Token *Oauth2Token
AuthKey string
}
Expiry time.Time // requested key expiry, server policy may override
Followup string // response waits until AuthURL is visited
Hostinfo *Hostinfo
+
+ // The following fields are not used for SignatureNone and are required for
+ // SignatureV1:
+ SignatureType SignatureType `json:",omitempty"`
+ Timestamp *time.Time `json:",omitempty"` // creation time of request to prevent replay
+ DeviceCert []byte `json:",omitempty"` // X.509 certificate for client device
+ Signature []byte `json:",omitempty"` // as described by SignatureType
}
// Clone makes a deep copy of RegisterRequest.
@@ -574,6 +636,8 @@ func (req *RegisterRequest) Clone() *RegisterRequest {
tok := *res.Auth.Oauth2Token
res.Auth.Oauth2Token = &tok
}
+ res.DeviceCert = append(res.DeviceCert[:0:0], res.DeviceCert...)
+ res.Signature = append(res.Signature[:0:0], res.Signature...)
return res
}
@@ -694,6 +758,17 @@ type FilterRule struct {
// DstPorts are the port ranges to allow once a source IP
// matches (is in the CIDR described by SrcIPs & SrcBits).
DstPorts []NetPortRange
+
+ // IPProto are the IP protocol numbers to match.
+ //
+ // As a special case, nil or empty means TCP, UDP, and ICMP.
+ //
+ // Numbers outside the uint8 range (below 0 or above 255) are
+ // reserved for Tailscale's use. Unknown ones are ignored.
+ //
+ // Depending on the IPProto values, DstPorts may or may not be
+ // used.
+ IPProto []int `json:",omitempty"`
}
var FilterAllowAll = []FilterRule{
@@ -719,8 +794,8 @@ type DNSConfig struct {
// 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 indicates whether DNS requests are proxied through a dns.Resolver.
+ // This enables MagicDNS. It is togglable independently of PerDomain.
Proxied bool
}
@@ -975,3 +1050,28 @@ type WhoIsResponse struct {
Node *Node
UserProfile *UserProfile
}
+
+// Oauth2Token is a copy of golang.org/x/oauth2.Token, to avoid the
+// go.mod dependency on App Engine and grpc, which was causing problems.
+// All we actually needed was this struct on the client side.
+type Oauth2Token struct {
+ // AccessToken is the token that authorizes and authenticates
+ // the requests.
+ AccessToken string `json:"access_token"`
+
+ // TokenType is the type of token.
+ // The Type method returns either this or "Bearer", the default.
+ TokenType string `json:"token_type,omitempty"`
+
+ // RefreshToken is a token that's used by the application
+ // (as opposed to the user) to refresh the access token
+ // if it expires.
+ RefreshToken string `json:"refresh_token,omitempty"`
+
+ // Expiry is the optional expiration time of the access token.
+ //
+ // If zero, TokenSource implementations will reuse the same
+ // token forever and RefreshToken or equivalent
+ // mechanisms for that TokenSource will not be used.
+ Expiry time.Time `json:"expiry,omitempty"`
+}
diff --git a/tstest/natlab/natlab.go b/tstest/natlab/natlab.go
index df2611be4..2d3f3ae70 100644
--- a/tstest/natlab/natlab.go
+++ b/tstest/natlab/natlab.go
@@ -26,7 +26,6 @@ import (
"sync"
"time"
- wgconn "github.com/tailscale/wireguard-go/conn"
"inet.af/netaddr"
)
@@ -759,8 +758,7 @@ func (c *conn) canRead() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.closed {
- // TODO: when we switch to Go 1.16, replace this with net.ErrClosed
- return wgconn.NetErrClosed
+ return net.ErrClosed
}
if !c.readDeadline.IsZero() && c.readDeadline.Before(time.Now()) {
return errors.New("read deadline exceeded")
diff --git a/net/packet/ip.go b/types/ipproto/ipproto.go
index 34194f344..5c9d101f6 100644
--- a/net/packet/ip.go
+++ b/types/ipproto/ipproto.go
@@ -1,28 +1,32 @@
-// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
+// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package packet
+// Package ipproto contains IP Protocol constants.
+package ipproto
-// IPProto is an IP subprotocol as defined by the IANA protocol
+import "fmt"
+
+// Proto is an IP subprotocol as defined by the IANA protocol
// numbers list
// (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml),
// or the special values Unknown or Fragment.
-type IPProto uint8
+type Proto uint8
const (
// Unknown represents an unknown or unsupported protocol; it's
// deliberately the zero value. Strictly speaking the zero
// value is IPv6 hop-by-hop extensions, but we don't support
// those, so this is still technically correct.
- Unknown IPProto = 0x00
+ Unknown Proto = 0x00
// Values from the IANA registry.
- ICMPv4 IPProto = 0x01
- IGMP IPProto = 0x02
- ICMPv6 IPProto = 0x3a
- TCP IPProto = 0x06
- UDP IPProto = 0x11
+ ICMPv4 Proto = 0x01
+ IGMP Proto = 0x02
+ ICMPv6 Proto = 0x3a
+ TCP Proto = 0x06
+ UDP Proto = 0x11
+ SCTP Proto = 0x84
// TSMP is the Tailscale Message Protocol (our ICMP-ish
// thing), an IP protocol used only between Tailscale nodes
@@ -33,7 +37,7 @@ const (
// scheme". We never accept these from the host OS stack nor
// send them to the host network stack. It's only used between
// nodes.
- TSMP IPProto = 99
+ TSMP Proto = 99
// Fragment represents any non-first IP fragment, for which we
// don't have the sub-protocol header (and therefore can't
@@ -41,11 +45,13 @@ const (
//
// 0xFF is reserved in the IANA registry, so we steal it for
// internal use.
- Fragment IPProto = 0xFF
+ Fragment Proto = 0xFF
)
-func (p IPProto) String() string {
+func (p Proto) String() string {
switch p {
+ case Unknown:
+ return "Unknown"
case Fragment:
return "Frag"
case ICMPv4:
@@ -58,9 +64,11 @@ func (p IPProto) String() string {
return "UDP"
case TCP:
return "TCP"
+ case SCTP:
+ return "SCTP"
case TSMP:
return "TSMP"
default:
- return "Unknown"
+ return fmt.Sprintf("IPProto-%d", int(p))
}
}
diff --git a/wgengine/filter/filter.go b/wgengine/filter/filter.go
index cbb985114..3c4964c34 100644
--- a/wgengine/filter/filter.go
+++ b/wgengine/filter/filter.go
@@ -14,6 +14,7 @@ import (
"inet.af/netaddr"
"tailscale.com/net/flowtrack"
"tailscale.com/net/packet"
+ "tailscale.com/types/ipproto"
"tailscale.com/types/logger"
)
@@ -182,6 +183,7 @@ func matchesFamily(ms matches, keep func(netaddr.IP) bool) matches {
var ret matches
for _, m := range ms {
var retm Match
+ retm.IPProto = m.IPProto
for _, src := range m.Srcs {
if keep(src.IP) {
retm.Srcs = append(retm.Srcs, src)
@@ -266,7 +268,7 @@ func (f *Filter) CheckTCP(srcIP, dstIP netaddr.IP, dstPort uint16) Response {
}
pkt.Src.IP = srcIP
pkt.Dst.IP = dstIP
- pkt.IPProto = packet.TCP
+ pkt.IPProto = ipproto.TCP
pkt.TCPFlags = packet.TCPSyn
pkt.Src.Port = 0
pkt.Dst.Port = dstPort
@@ -324,7 +326,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
}
switch q.IPProto {
- case packet.ICMPv4:
+ case ipproto.ICMPv4:
if q.IsEchoResponse() || q.IsError() {
// ICMP responses are allowed.
// TODO(apenwarr): consider using conntrack state.
@@ -336,7 +338,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
// If any port is open to an IP, allow ICMP to it.
return Accept, "icmp ok"
}
- case packet.TCP:
+ case ipproto.TCP:
// For TCP, we want to allow *outgoing* connections,
// which means we want to allow return packets on those
// connections. To make this restriction work, we need to
@@ -351,20 +353,20 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
if f.matches4.match(q) {
return Accept, "tcp ok"
}
- case packet.UDP:
- t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst}
+ case ipproto.UDP, ipproto.SCTP:
+ t := flowtrack.Tuple{Proto: q.IPProto, Src: q.Src, Dst: q.Dst}
f.state.mu.Lock()
_, ok := f.state.lru.Get(t)
f.state.mu.Unlock()
if ok {
- return Accept, "udp cached"
+ return Accept, "cached"
}
if f.matches4.match(q) {
- return Accept, "udp ok"
+ return Accept, "ok"
}
- case packet.TSMP:
+ case ipproto.TSMP:
return Accept, "tsmp ok"
default:
return Drop, "Unknown proto"
@@ -381,7 +383,7 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
}
switch q.IPProto {
- case packet.ICMPv6:
+ case ipproto.ICMPv6:
if q.IsEchoResponse() || q.IsError() {
// ICMP responses are allowed.
// TODO(apenwarr): consider using conntrack state.
@@ -393,7 +395,7 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
// If any port is open to an IP, allow ICMP to it.
return Accept, "icmp ok"
}
- case packet.TCP:
+ case ipproto.TCP:
// For TCP, we want to allow *outgoing* connections,
// which means we want to allow return packets on those
// connections. To make this restriction work, we need to
@@ -402,25 +404,27 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
// can't be initiated without first sending a SYN.
// It happens to also be much faster.
// TODO(apenwarr): Skip the rest of decoding in this path?
- if q.IPProto == packet.TCP && !q.IsTCPSyn() {
+ if q.IPProto == ipproto.TCP && !q.IsTCPSyn() {
return Accept, "tcp non-syn"
}
if f.matches6.match(q) {
return Accept, "tcp ok"
}
- case packet.UDP:
- t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst}
+ case ipproto.UDP, ipproto.SCTP:
+ t := flowtrack.Tuple{Proto: q.IPProto, Src: q.Src, Dst: q.Dst}
f.state.mu.Lock()
_, ok := f.state.lru.Get(t)
f.state.mu.Unlock()
if ok {
- return Accept, "udp cached"
+ return Accept, "cached"
}
if f.matches6.match(q) {
- return Accept, "udp ok"
+ return Accept, "ok"
}
+ case ipproto.TSMP:
+ return Accept, "tsmp ok"
default:
return Drop, "Unknown proto"
}
@@ -429,15 +433,16 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
// runIn runs the output-specific part of the filter logic.
func (f *Filter) runOut(q *packet.Parsed) (r Response, why string) {
- if q.IPProto != packet.UDP {
- return Accept, "ok out"
+ switch q.IPProto {
+ case ipproto.UDP, ipproto.SCTP:
+ tuple := flowtrack.Tuple{
+ Proto: q.IPProto,
+ Src: q.Dst, Dst: q.Src, // src/dst reversed
+ }
+ f.state.mu.Lock()
+ f.state.lru.Add(tuple, nil)
+ f.state.mu.Unlock()
}
-
- tuple := flowtrack.Tuple{Src: q.Dst, Dst: q.Src} // src/dst reversed
-
- f.state.mu.Lock()
- f.state.lru.Add(tuple, nil)
- f.state.mu.Unlock()
return Accept, "ok out"
}
@@ -485,11 +490,11 @@ func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) Response {
}
switch q.IPProto {
- case packet.Unknown:
+ case ipproto.Unknown:
// Unknown packets are dangerous; always drop them.
f.logRateLimit(rf, q, dir, Drop, "unknown")
return Drop
- case packet.Fragment:
+ case ipproto.Fragment:
// Fragments after the first always need to be passed through.
// Very small fragments are considered Junk by Parsed.
f.logRateLimit(rf, q, dir, Accept, "fragment")
@@ -513,5 +518,5 @@ func omitDropLogging(p *packet.Parsed, dir direction) bool {
return false
}
- return p.Dst.IP.IsMulticast() || (p.Dst.IP.IsLinkLocalUnicast() && p.Dst.IP != gcpDNSAddr) || p.IPProto == packet.IGMP
+ return p.Dst.IP.IsMulticast() || (p.Dst.IP.IsLinkLocalUnicast() && p.Dst.IP != gcpDNSAddr) || p.IPProto == ipproto.IGMP
}
diff --git a/wgengine/filter/filter_test.go b/wgengine/filter/filter_test.go
index 9f98761f6..ca807a5fd 100644
--- a/wgengine/filter/filter_test.go
+++ b/wgengine/filter/filter_test.go
@@ -7,6 +7,7 @@ package filter
import (
"encoding/hex"
"fmt"
+ "reflect"
"strconv"
"strings"
"testing"
@@ -16,19 +17,32 @@ import (
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
+ "tailscale.com/tailcfg"
+ "tailscale.com/types/ipproto"
"tailscale.com/types/logger"
)
func newFilter(logf logger.Logf) *Filter {
+ m := func(srcs []netaddr.IPPrefix, dsts []NetPortRange, protos ...ipproto.Proto) Match {
+ if protos == nil {
+ protos = defaultProtos
+ }
+ return Match{
+ IPProto: protos,
+ Srcs: srcs,
+ Dsts: dsts,
+ }
+ }
matches := []Match{
- {Srcs: nets("8.1.1.1", "8.2.2.2"), Dsts: netports("1.2.3.4:22", "5.6.7.8:23-24")},
- {Srcs: nets("8.1.1.1", "8.2.2.2"), Dsts: netports("5.6.7.8:27-28")},
- {Srcs: nets("2.2.2.2"), Dsts: netports("8.1.1.1:22")},
- {Srcs: nets("0.0.0.0/0"), Dsts: netports("100.122.98.50:*")},
- {Srcs: nets("0.0.0.0/0"), Dsts: netports("0.0.0.0/0:443")},
- {Srcs: nets("153.1.1.1", "153.1.1.2", "153.3.3.3"), Dsts: netports("1.2.3.4:999")},
- {Srcs: nets("::1", "::2"), Dsts: netports("2001::1:22", "2001::2:22")},
- {Srcs: nets("::/0"), Dsts: netports("::/0:443")},
+ m(nets("8.1.1.1", "8.2.2.2"), netports("1.2.3.4:22", "5.6.7.8:23-24")),
+ m(nets("9.1.1.1", "9.2.2.2"), netports("1.2.3.4:22", "5.6.7.8:23-24"), ipproto.SCTP),
+ m(nets("8.1.1.1", "8.2.2.2"), netports("5.6.7.8:27-28")),
+ m(nets("2.2.2.2"), netports("8.1.1.1:22")),
+ m(nets("0.0.0.0/0"), netports("100.122.98.50:*")),
+ m(nets("0.0.0.0/0"), netports("0.0.0.0/0:443")),
+ m(nets("153.1.1.1", "153.1.1.2", "153.3.3.3"), netports("1.2.3.4:999")),
+ m(nets("::1", "::2"), netports("2001::1:22", "2001::2:22")),
+ m(nets("::/0"), netports("::/0:443")),
}
// Expects traffic to 100.122.98.50, 1.2.3.4, 5.6.7.8,
@@ -52,43 +66,48 @@ func TestFilter(t *testing.T) {
}
tests := []InOut{
// allow 8.1.1.1 => 1.2.3.4:22
- {Accept, parsed(packet.TCP, "8.1.1.1", "1.2.3.4", 999, 22)},
- {Accept, parsed(packet.ICMPv4, "8.1.1.1", "1.2.3.4", 0, 0)},
- {Drop, parsed(packet.TCP, "8.1.1.1", "1.2.3.4", 0, 0)},
- {Accept, parsed(packet.TCP, "8.1.1.1", "1.2.3.4", 0, 22)},
- {Drop, parsed(packet.TCP, "8.1.1.1", "1.2.3.4", 0, 21)},
+ {Accept, parsed(ipproto.TCP, "8.1.1.1", "1.2.3.4", 999, 22)},
+ {Accept, parsed(ipproto.ICMPv4, "8.1.1.1", "1.2.3.4", 0, 0)},
+ {Drop, parsed(ipproto.TCP, "8.1.1.1", "1.2.3.4", 0, 0)},
+ {Accept, parsed(ipproto.TCP, "8.1.1.1", "1.2.3.4", 0, 22)},
+ {Drop, parsed(ipproto.TCP, "8.1.1.1", "1.2.3.4", 0, 21)},
// allow 8.2.2.2. => 1.2.3.4:22
- {Accept, parsed(packet.TCP, "8.2.2.2", "1.2.3.4", 0, 22)},
- {Drop, parsed(packet.TCP, "8.2.2.2", "1.2.3.4", 0, 23)},
- {Drop, parsed(packet.TCP, "8.3.3.3", "1.2.3.4", 0, 22)},
+ {Accept, parsed(ipproto.TCP, "8.2.2.2", "1.2.3.4", 0, 22)},
+ {Drop, parsed(ipproto.TCP, "8.2.2.2", "1.2.3.4", 0, 23)},
+ {Drop, parsed(ipproto.TCP, "8.3.3.3", "1.2.3.4", 0, 22)},
// allow 8.1.1.1 => 5.6.7.8:23-24
- {Accept, parsed(packet.TCP, "8.1.1.1", "5.6.7.8", 0, 23)},
- {Accept, parsed(packet.TCP, "8.1.1.1", "5.6.7.8", 0, 24)},
- {Drop, parsed(packet.TCP, "8.1.1.3", "5.6.7.8", 0, 24)},
- {Drop, parsed(packet.TCP, "8.1.1.1", "5.6.7.8", 0, 22)},
+ {Accept, parsed(ipproto.TCP, "8.1.1.1", "5.6.7.8", 0, 23)},
+ {Accept, parsed(ipproto.TCP, "8.1.1.1", "5.6.7.8", 0, 24)},
+ {Drop, parsed(ipproto.TCP, "8.1.1.3", "5.6.7.8", 0, 24)},
+ {Drop, parsed(ipproto.TCP, "8.1.1.1", "5.6.7.8", 0, 22)},
// allow * => *:443
- {Accept, parsed(packet.TCP, "17.34.51.68", "8.1.34.51", 0, 443)},
- {Drop, parsed(packet.TCP, "17.34.51.68", "8.1.34.51", 0, 444)},
+ {Accept, parsed(ipproto.TCP, "17.34.51.68", "8.1.34.51", 0, 443)},
+ {Drop, parsed(ipproto.TCP, "17.34.51.68", "8.1.34.51", 0, 444)},
// allow * => 100.122.98.50:*
- {Accept, parsed(packet.TCP, "17.34.51.68", "100.122.98.50", 0, 999)},
- {Accept, parsed(packet.TCP, "17.34.51.68", "100.122.98.50", 0, 0)},
+ {Accept, parsed(ipproto.TCP, "17.34.51.68", "100.122.98.50", 0, 999)},
+ {Accept, parsed(ipproto.TCP, "17.34.51.68", "100.122.98.50", 0, 0)},
// allow ::1, ::2 => [2001::1]:22
- {Accept, parsed(packet.TCP, "::1", "2001::1", 0, 22)},
- {Accept, parsed(packet.ICMPv6, "::1", "2001::1", 0, 0)},
- {Accept, parsed(packet.TCP, "::2", "2001::1", 0, 22)},
- {Accept, parsed(packet.TCP, "::2", "2001::2", 0, 22)},
- {Drop, parsed(packet.TCP, "::1", "2001::1", 0, 23)},
- {Drop, parsed(packet.TCP, "::1", "2001::3", 0, 22)},
- {Drop, parsed(packet.TCP, "::3", "2001::1", 0, 22)},
+ {Accept, parsed(ipproto.TCP, "::1", "2001::1", 0, 22)},
+ {Accept, parsed(ipproto.ICMPv6, "::1", "2001::1", 0, 0)},
+ {Accept, parsed(ipproto.TCP, "::2", "2001::1", 0, 22)},
+ {Accept, parsed(ipproto.TCP, "::2", "2001::2", 0, 22)},
+ {Drop, parsed(ipproto.TCP, "::1", "2001::1", 0, 23)},
+ {Drop, parsed(ipproto.TCP, "::1", "2001::3", 0, 22)},
+ {Drop, parsed(ipproto.TCP, "::3", "2001::1", 0, 22)},
// allow * => *:443
- {Accept, parsed(packet.TCP, "::1", "2001::1", 0, 443)},
- {Drop, parsed(packet.TCP, "::1", "2001::1", 0, 444)},
+ {Accept, parsed(ipproto.TCP, "::1", "2001::1", 0, 443)},
+ {Drop, parsed(ipproto.TCP, "::1", "2001::1", 0, 444)},
// localNets prefilter - accepted by policy filter, but
// unexpected dst IP.
- {Drop, parsed(packet.TCP, "8.1.1.1", "16.32.48.64", 0, 443)},
- {Drop, parsed(packet.TCP, "1::", "2602::1", 0, 443)},
+ {Drop, parsed(ipproto.TCP, "8.1.1.1", "16.32.48.64", 0, 443)},
+ {Drop, parsed(ipproto.TCP, "1::", "2602::1", 0, 443)},
+
+ // Don't allow protocols not specified by filter
+ {Drop, parsed(ipproto.SCTP, "8.1.1.1", "1.2.3.4", 999, 22)},
+ // But SCTP is allowed for 9.1.1.1
+ {Accept, parsed(ipproto.SCTP, "9.1.1.1", "1.2.3.4", 999, 22)},
}
for i, test := range tests {
aclFunc := acl.runIn4
@@ -98,7 +117,7 @@ func TestFilter(t *testing.T) {
if got, why := aclFunc(&test.p); test.want != got {
t.Errorf("#%d runIn got=%v want=%v why=%q packet:%v", i, got, test.want, why, test.p)
}
- if test.p.IPProto == packet.TCP {
+ if test.p.IPProto == ipproto.TCP {
var got Response
if test.p.IPVersion == 4 {
got = acl.CheckTCP(test.p.Src.IP, test.p.Dst.IP, test.p.Dst.Port)
@@ -109,7 +128,7 @@ func TestFilter(t *testing.T) {
t.Errorf("#%d CheckTCP got=%v want=%v packet:%v", i, got, test.want, test.p)
}
// TCP and UDP are treated equivalently in the filter - verify that.
- test.p.IPProto = packet.UDP
+ test.p.IPProto = ipproto.UDP
if got, why := aclFunc(&test.p); test.want != got {
t.Errorf("#%d runIn (UDP) got=%v want=%v why=%q packet:%v", i, got, test.want, why, test.p)
}
@@ -123,8 +142,8 @@ func TestUDPState(t *testing.T) {
acl := newFilter(t.Logf)
flags := LogDrops | LogAccepts
- a4 := parsed(packet.UDP, "119.119.119.119", "102.102.102.102", 4242, 4343)
- b4 := parsed(packet.UDP, "102.102.102.102", "119.119.119.119", 4343, 4242)
+ a4 := parsed(ipproto.UDP, "119.119.119.119", "102.102.102.102", 4242, 4343)
+ b4 := parsed(ipproto.UDP, "102.102.102.102", "119.119.119.119", 4343, 4242)
// Unsollicited UDP traffic gets dropped
if got := acl.RunIn(&a4, flags); got != Drop {
@@ -139,8 +158,8 @@ func TestUDPState(t *testing.T) {
t.Fatalf("incoming response packet not accepted, got=%v: %v", got, a4)
}
- a6 := parsed(packet.UDP, "2001::2", "2001::1", 4242, 4343)
- b6 := parsed(packet.UDP, "2001::1", "2001::2", 4343, 4242)
+ a6 := parsed(ipproto.UDP, "2001::2", "2001::1", 4242, 4343)
+ b6 := parsed(ipproto.UDP, "2001::1", "2001::2", 4343, 4242)
// Unsollicited UDP traffic gets dropped
if got := acl.RunIn(&a6, flags); got != Drop {
@@ -159,10 +178,10 @@ func TestUDPState(t *testing.T) {
func TestNoAllocs(t *testing.T) {
acl := newFilter(t.Logf)
- tcp4Packet := raw4(packet.TCP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
- udp4Packet := raw4(packet.UDP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
- tcp6Packet := raw6(packet.TCP, "2001::1", "2001::2", 999, 22, 0)
- udp6Packet := raw6(packet.UDP, "2001::1", "2001::2", 999, 22, 0)
+ tcp4Packet := raw4(ipproto.TCP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
+ udp4Packet := raw4(ipproto.UDP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
+ tcp6Packet := raw6(ipproto.TCP, "2001::1", "2001::2", 999, 22, 0)
+ udp6Packet := raw6(ipproto.UDP, "2001::1", "2001::2", 999, 22, 0)
tests := []struct {
name string
@@ -243,13 +262,13 @@ func TestParseIPSet(t *testing.T) {
}
func BenchmarkFilter(b *testing.B) {
- tcp4Packet := raw4(packet.TCP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
- udp4Packet := raw4(packet.UDP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
- icmp4Packet := raw4(packet.ICMPv4, "8.1.1.1", "1.2.3.4", 0, 0, 0)
+ tcp4Packet := raw4(ipproto.TCP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
+ udp4Packet := raw4(ipproto.UDP, "8.1.1.1", "1.2.3.4", 999, 22, 0)
+ icmp4Packet := raw4(ipproto.ICMPv4, "8.1.1.1", "1.2.3.4", 0, 0, 0)
- tcp6Packet := raw6(packet.TCP, "::1", "2001::1", 999, 22, 0)
- udp6Packet := raw6(packet.UDP, "::1", "2001::1", 999, 22, 0)
- icmp6Packet := raw6(packet.ICMPv6, "::1", "2001::1", 0, 0, 0)
+ tcp6Packet := raw6(ipproto.TCP, "::1", "2001::1", 999, 22, 0)
+ udp6Packet := raw6(ipproto.UDP, "::1", "2001::1", 999, 22, 0)
+ icmp6Packet := raw6(ipproto.ICMPv6, "::1", "2001::1", 0, 0, 0)
benches := []struct {
name string
@@ -296,11 +315,11 @@ func TestPreFilter(t *testing.T) {
}{
{"empty", Accept, []byte{}},
{"short", Drop, []byte("short")},
- {"junk", Drop, raw4default(packet.Unknown, 10)},
- {"fragment", Accept, raw4default(packet.Fragment, 40)},
- {"tcp", noVerdict, raw4default(packet.TCP, 0)},
- {"udp", noVerdict, raw4default(packet.UDP, 0)},
- {"icmp", noVerdict, raw4default(packet.ICMPv4, 0)},
+ {"junk", Drop, raw4default(ipproto.Unknown, 10)},
+ {"fragment", Accept, raw4default(ipproto.Fragment, 40)},
+ {"tcp", noVerdict, raw4default(ipproto.TCP, 0)},
+ {"udp", noVerdict, raw4default(ipproto.UDP, 0)},
+ {"icmp", noVerdict, raw4default(ipproto.ICMPv4, 0)},
}
f := NewAllowNone(t.Logf, &netaddr.IPSet{})
for _, testPacket := range packets {
@@ -322,7 +341,7 @@ func TestOmitDropLogging(t *testing.T) {
}{
{
name: "v4_tcp_out",
- pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP},
+ pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP},
dir: out,
want: false,
},
@@ -420,73 +439,73 @@ func TestLoggingPrivacy(t *testing.T) {
}{
{
name: "ts_to_ts_v4_out",
- pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: ts4, Dst: ts4},
+ pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: ts4, Dst: ts4},
dir: out,
logged: true,
},
{
name: "ts_to_internet_v4_out",
- pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: ts4, Dst: internet4},
+ pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: ts4, Dst: internet4},
dir: out,
logged: false,
},
{
name: "internet_to_ts_v4_out",
- pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: internet4, Dst: ts4},
+ pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: internet4, Dst: ts4},
dir: out,
logged: false,
},
{
name: "ts_to_ts_v4_in",
- pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: ts4, Dst: ts4},
+ pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: ts4, Dst: ts4},
dir: in,
logged: true,
},
{
name: "ts_to_internet_v4_in",
- pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: ts4, Dst: internet4},
+ pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: ts4, Dst: internet4},
dir: in,
logged: false,
},
{
name: "internet_to_ts_v4_in",
- pkt: &packet.Parsed{IPVersion: 4, IPProto: packet.TCP, Src: internet4, Dst: ts4},
+ pkt: &packet.Parsed{IPVersion: 4, IPProto: ipproto.TCP, Src: internet4, Dst: ts4},
dir: in,
logged: false,
},
{
name: "ts_to_ts_v6_out",
- pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: ts6, Dst: ts6},
+ pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: ts6, Dst: ts6},
dir: out,
logged: true,
},
{
name: "ts_to_internet_v6_out",
- pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: ts6, Dst: internet6},
+ pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: ts6, Dst: internet6},
dir: out,
logged: false,
},
{
name: "internet_to_ts_v6_out",
- pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: internet6, Dst: ts6},
+ pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: internet6, Dst: ts6},
dir: out,
logged: false,
},
{
name: "ts_to_ts_v6_in",
- pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: ts6, Dst: ts6},
+ pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: ts6, Dst: ts6},
dir: in,
logged: true,
},
{
name: "ts_to_internet_v6_in",
- pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: ts6, Dst: internet6},
+ pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: ts6, Dst: internet6},
dir: in,
logged: false,
},
{
name: "internet_to_ts_v6_in",
- pkt: &packet.Parsed{IPVersion: 6, IPProto: packet.TCP, Src: internet6, Dst: ts6},
+ pkt: &packet.Parsed{IPVersion: 6, IPProto: ipproto.TCP, Src: internet6, Dst: ts6},
dir: in,
logged: false,
},
@@ -520,7 +539,7 @@ func mustIP(s string) netaddr.IP {
return ip
}
-func parsed(proto packet.IPProto, src, dst string, sport, dport uint16) packet.Parsed {
+func parsed(proto ipproto.Proto, src, dst string, sport, dport uint16) packet.Parsed {
sip, dip := mustIP(src), mustIP(dst)
var ret packet.Parsed
@@ -541,7 +560,7 @@ func parsed(proto packet.IPProto, src, dst string, sport, dport uint16) packet.P
return ret
}
-func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen int) []byte {
+func raw6(proto ipproto.Proto, src, dst string, sport, dport uint16, trimLen int) []byte {
u := packet.UDP6Header{
IP6Header: packet.IP6Header{
Src: mustIP(src),
@@ -570,7 +589,7 @@ func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen in
}
}
-func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength int) []byte {
+func raw4(proto ipproto.Proto, src, dst string, sport, dport uint16, trimLength int) []byte {
u := packet.UDP4Header{
IP4Header: packet.IP4Header{
Src: mustIP(src),
@@ -588,7 +607,7 @@ func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength
// UDP marshaling clobbers IPProto, so override it here.
switch proto {
- case packet.Unknown, packet.Fragment:
+ case ipproto.Unknown, ipproto.Fragment:
default:
u.IP4Header.IPProto = proto
}
@@ -596,7 +615,7 @@ func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength
panic(err)
}
- if proto == packet.Fragment {
+ if proto == ipproto.Fragment {
// Set some fragment offset. This makes the IP
// checksum wrong, but we don't validate the checksum
// when parsing.
@@ -610,7 +629,7 @@ func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength
}
}
-func raw4default(proto packet.IPProto, trimLength int) []byte {
+func raw4default(proto ipproto.Proto, trimLength int) []byte {
return raw4(proto, "8.8.8.8", "8.8.8.8", 53, 53, trimLength)
}
@@ -707,3 +726,91 @@ func netports(netPorts ...string) (ret []NetPortRange) {
}
return ret
}
+
+func TestMatchesFromFilterRules(t *testing.T) {
+ tests := []struct {
+ name string
+ in []tailcfg.FilterRule
+ want []Match
+ }{
+ {
+ name: "empty",
+ want: []Match{},
+ },
+ {
+ name: "implicit_protos",
+ in: []tailcfg.FilterRule{
+ {
+ SrcIPs: []string{"100.64.1.1"},
+ DstPorts: []tailcfg.NetPortRange{{
+ IP: "*",
+ Ports: tailcfg.PortRange{First: 22, Last: 22},
+ }},
+ },
+ },
+ want: []Match{
+ {
+ IPProto: []ipproto.Proto{
+ ipproto.TCP,
+ ipproto.UDP,
+ ipproto.ICMPv4,
+ ipproto.ICMPv6,
+ },
+ Dsts: []NetPortRange{
+ {
+ Net: netaddr.MustParseIPPrefix("0.0.0.0/0"),
+ Ports: PortRange{22, 22},
+ },
+ {
+ Net: netaddr.MustParseIPPrefix("::0/0"),
+ Ports: PortRange{22, 22},
+ },
+ },
+ Srcs: []netaddr.IPPrefix{
+ netaddr.MustParseIPPrefix("100.64.1.1/32"),
+ },
+ },
+ },
+ },
+ {
+ name: "explicit_protos",
+ in: []tailcfg.FilterRule{
+ {
+ IPProto: []int{int(ipproto.TCP)},
+ SrcIPs: []string{"100.64.1.1"},
+ DstPorts: []tailcfg.NetPortRange{{
+ IP: "1.2.0.0/16",
+ Ports: tailcfg.PortRange{First: 22, Last: 22},
+ }},
+ },
+ },
+ want: []Match{
+ {
+ IPProto: []ipproto.Proto{
+ ipproto.TCP,
+ },
+ Dsts: []NetPortRange{
+ {
+ Net: netaddr.MustParseIPPrefix("1.2.0.0/16"),
+ Ports: PortRange{22, 22},
+ },
+ },
+ Srcs: []netaddr.IPPrefix{
+ netaddr.MustParseIPPrefix("100.64.1.1/32"),
+ },
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := MatchesFromFilterRules(tt.in)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("wrong\n got: %v\nwant: %v\n", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/wgengine/filter/match.go b/wgengine/filter/match.go
index c30c37552..a1b356113 100644
--- a/wgengine/filter/match.go
+++ b/wgengine/filter/match.go
@@ -10,6 +10,7 @@ import (
"inet.af/netaddr"
"tailscale.com/net/packet"
+ "tailscale.com/types/ipproto"
)
//go:generate go run tailscale.com/cmd/cloner --type=Match --output=match_clone.go
@@ -47,11 +48,13 @@ func (npr NetPortRange) String() string {
// Match matches packets from any IP address in Srcs to any ip:port in
// Dsts.
type Match struct {
- Dsts []NetPortRange
- Srcs []netaddr.IPPrefix
+ IPProto []ipproto.Proto // required set (no default value at this layer)
+ Dsts []NetPortRange
+ Srcs []netaddr.IPPrefix
}
func (m Match) String() string {
+ // TODO(bradfitz): use strings.Builder, add String tests
srcs := []string{}
for _, src := range m.Srcs {
srcs = append(srcs, src.String())
@@ -72,13 +75,16 @@ func (m Match) String() string {
} else {
ds = "[" + strings.Join(dsts, ",") + "]"
}
- return fmt.Sprintf("%v=>%v", ss, ds)
+ return fmt.Sprintf("%v%v=>%v", m.IPProto, ss, ds)
}
type matches []Match
func (ms matches) match(q *packet.Parsed) bool {
for _, m := range ms {
+ if !protoInList(q.IPProto, m.IPProto) {
+ continue
+ }
if !ipInList(q.Src.IP, m.Srcs) {
continue
}
@@ -117,3 +123,12 @@ func ipInList(ip netaddr.IP, netlist []netaddr.IPPrefix) bool {
}
return false
}
+
+func protoInList(proto ipproto.Proto, valid []ipproto.Proto) bool {
+ for _, v := range valid {
+ if proto == v {
+ return true
+ }
+ }
+ return false
+}
diff --git a/wgengine/filter/match_clone.go b/wgengine/filter/match_clone.go
index 571664bd5..04874ddec 100644
--- a/wgengine/filter/match_clone.go
+++ b/wgengine/filter/match_clone.go
@@ -8,6 +8,7 @@ package filter
import (
"inet.af/netaddr"
+ "tailscale.com/types/ipproto"
)
// Clone makes a deep copy of Match.
@@ -18,6 +19,7 @@ func (src *Match) Clone() *Match {
}
dst := new(Match)
*dst = *src
+ dst.IPProto = append(src.IPProto[:0:0], src.IPProto...)
dst.Dsts = append(src.Dsts[:0:0], src.Dsts...)
dst.Srcs = append(src.Srcs[:0:0], src.Srcs...)
return dst
@@ -26,6 +28,7 @@ func (src *Match) Clone() *Match {
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type Match
var _MatchNeedsRegeneration = Match(struct {
- Dsts []NetPortRange
- Srcs []netaddr.IPPrefix
+ IPProto []ipproto.Proto
+ Dsts []NetPortRange
+ Srcs []netaddr.IPPrefix
}{})
diff --git a/wgengine/filter/tailcfg.go b/wgengine/filter/tailcfg.go
index 2f20cdb61..1338a75b4 100644
--- a/wgengine/filter/tailcfg.go
+++ b/wgengine/filter/tailcfg.go
@@ -10,8 +10,16 @@ import (
"inet.af/netaddr"
"tailscale.com/tailcfg"
+ "tailscale.com/types/ipproto"
)
+var defaultProtos = []ipproto.Proto{
+ ipproto.TCP,
+ ipproto.UDP,
+ ipproto.ICMPv4,
+ ipproto.ICMPv6,
+}
+
// MatchesFromFilterRules converts tailcfg FilterRules into Matches.
// If an error is returned, the Matches result is still valid,
// containing the rules that were successfully converted.
@@ -22,6 +30,17 @@ func MatchesFromFilterRules(pf []tailcfg.FilterRule) ([]Match, error) {
for _, r := range pf {
m := Match{}
+ if len(r.IPProto) == 0 {
+ m.IPProto = append([]ipproto.Proto(nil), defaultProtos...)
+ } else {
+ m.IPProto = make([]ipproto.Proto, 0, len(r.IPProto))
+ for _, n := range r.IPProto {
+ if n >= 0 && n <= 0xff {
+ m.IPProto = append(m.IPProto, ipproto.Proto(n))
+ }
+ }
+ }
+
for i, s := range r.SrcIPs {
var bits *int
if len(r.SrcBits) > i {
diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go
index 917e55828..7a437f9e8 100644
--- a/wgengine/magicsock/magicsock.go
+++ b/wgengine/magicsock/magicsock.go
@@ -630,7 +630,7 @@ func (c *Conn) setEndpoints(endpoints []string, reasons map[string]string) (chan
delete(c.onEndpointRefreshed, de)
}
- if stringsEqual(endpoints, c.lastEndpoints) {
+ if stringSetsEqual(endpoints, c.lastEndpoints) {
return false
}
c.lastEndpoints = endpoints
@@ -814,46 +814,6 @@ func (c *Conn) SetNetInfoCallback(fn func(*tailcfg.NetInfo)) {
}
}
-// peerForIP returns the Node in nm that's responsible for
-// handling the given IP address.
-func peerForIP(nm *netmap.NetworkMap, ip netaddr.IP) (n *tailcfg.Node, ok bool) {
- if nm == nil {
- return nil, false
- }
- // Check for exact matches before looking for subnet matches.
- for _, p := range nm.Peers {
- for _, a := range p.Addresses {
- if a.IP == ip {
- return p, true
- }
- }
- }
-
- // TODO(bradfitz): this is O(n peers). Add ART to netaddr?
- var best netaddr.IPPrefix
- for _, p := range nm.Peers {
- for _, cidr := range p.AllowedIPs {
- if cidr.Contains(ip) {
- if best.IsZero() || cidr.Bits > best.Bits {
- n = p
- best = cidr
- }
- }
- }
- }
- return n, n != nil
-}
-
-// PeerForIP returns the node that ip should route to.
-func (c *Conn) PeerForIP(ip netaddr.IP) (n *tailcfg.Node, ok bool) {
- c.mu.Lock()
- defer c.mu.Unlock()
- if c.netMap == nil {
- return
- }
- return peerForIP(c.netMap, ip)
-}
-
// LastRecvActivityOfDisco returns the time we last got traffic from
// this endpoint (updated every ~10 seconds).
func (c *Conn) LastRecvActivityOfDisco(dk tailcfg.DiscoKey) time.Time {
@@ -871,21 +831,14 @@ func (c *Conn) LastRecvActivityOfDisco(dk tailcfg.DiscoKey) time.Time {
}
// Ping handles a "tailscale ping" CLI query.
-func (c *Conn) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) {
+func (c *Conn) Ping(peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
c.mu.Lock()
defer c.mu.Unlock()
- res := &ipnstate.PingResult{IP: ip.String()}
if c.privateKey.IsZero() {
res.Err = "local tailscaled stopped"
cb(res)
return
}
- peer, ok := peerForIP(c.netMap, ip)
- if !ok {
- res.Err = "no matching peer"
- cb(res)
- return
- }
if len(peer.Addresses) > 0 {
res.NodeIP = peer.Addresses[0].IP.String()
}
@@ -1111,12 +1064,32 @@ func (c *Conn) determineEndpoints(ctx context.Context) (ipPorts []string, reason
return eps, already, nil
}
-func stringsEqual(x, y []string) bool {
- if len(x) != len(y) {
- return false
+// stringSetsEqual reports whether x and y represent the same set of
+// strings. The order doesn't matter.
+//
+// It does not mutate the slices.
+func stringSetsEqual(x, y []string) bool {
+ if len(x) == len(y) {
+ orderMatches := true
+ for i := range x {
+ if x[i] != y[i] {
+ orderMatches = false
+ break
+ }
+ }
+ if orderMatches {
+ return true
+ }
}
- for i := range x {
- if x[i] != y[i] {
+ m := map[string]int{}
+ for _, v := range x {
+ m[v] |= 1
+ }
+ for _, v := range y {
+ m[v] |= 2
+ }
+ for _, n := range m {
+ if n != 3 {
return false
}
}
@@ -2034,7 +2007,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) (isDiscoMsg bo
return
}
if de != nil {
- c.logf("magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints",
+ c.logf("[v1] magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints",
c.discoShort, de.discoShort,
de.publicKey.ShortString(), derpStr(src.String()),
len(dm.MyNumber))
@@ -3012,25 +2985,7 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
c.mu.Lock()
defer c.mu.Unlock()
- ss := &ipnstate.PeerStatus{
- PublicKey: c.privateKey.Public(),
- Addrs: c.lastEndpoints,
- OS: version.OS(),
- }
- if c.netMap != nil {
- ss.HostName = c.netMap.Hostinfo.Hostname
- ss.DNSName = c.netMap.Name
- ss.UserID = c.netMap.User
- } else {
- ss.HostName, _ = os.Hostname()
- }
- if c.derpMap != nil {
- derpRegion, ok := c.derpMap.Regions[c.myDerp]
- if ok {
- ss.Relay = derpRegion.RegionCode
- }
- }
-
+ var tailAddr string
if c.netMap != nil {
for _, addr := range c.netMap.Addresses {
if !addr.IsSingleIP() {
@@ -3041,11 +2996,30 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
// readability of `tailscale status`, make it the IPv4
// address.
if addr.IP.Is4() {
- ss.TailAddr = addr.IP.String()
+ tailAddr = addr.IP.String()
}
}
}
- sb.SetSelfStatus(ss)
+
+ sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
+ ss.PublicKey = c.privateKey.Public()
+ ss.Addrs = c.lastEndpoints
+ ss.OS = version.OS()
+ if c.netMap != nil {
+ ss.HostName = c.netMap.Hostinfo.Hostname
+ ss.DNSName = c.netMap.Name
+ ss.UserID = c.netMap.User
+ } else {
+ ss.HostName, _ = os.Hostname()
+ }
+ if c.derpMap != nil {
+ derpRegion, ok := c.derpMap.Regions[c.myDerp]
+ if ok {
+ ss.Relay = derpRegion.RegionCode
+ }
+ }
+ ss.TailAddr = tailAddr
+ })
for dk, n := range c.nodeOfDisco {
ps := &ipnstate.PeerStatus{InMagicSock: true}
@@ -3106,10 +3080,9 @@ type discoEndpoint struct {
lastFullPing time.Time // last time we pinged all endpoints
derpAddr netaddr.IPPort // fallback/bootstrap path, if non-zero (non-zero for well-behaved clients)
- bestAddr netaddr.IPPort // best non-DERP path; zero if none
- bestAddrLatency time.Duration
- bestAddrAt time.Time // time best address re-confirmed
- trustBestAddrUntil time.Time // time when bestAddr expires
+ bestAddr addrLatency // best non-DERP path; zero if none
+ bestAddrAt time.Time // time best address re-confirmed
+ trustBestAddrUntil time.Time // time when bestAddr expires
sentPing map[stun.TxID]sentPing
endpointState map[netaddr.IPPort]*endpointState
isCallMeMaybeEP map[netaddr.IPPort]bool
@@ -3214,8 +3187,8 @@ func (st *endpointState) shouldDeleteLocked() bool {
func (de *discoEndpoint) deleteEndpointLocked(ep netaddr.IPPort) {
delete(de.endpointState, ep)
- if de.bestAddr == ep {
- de.bestAddr = netaddr.IPPort{}
+ if de.bestAddr.IPPort == ep {
+ de.bestAddr = addrLatency{}
}
}
@@ -3283,7 +3256,7 @@ func (de *discoEndpoint) DstToBytes() []byte { return packIPPort(de.fakeWGAddr)
//
// de.mu must be held.
func (de *discoEndpoint) addrForSendLocked(now time.Time) (udpAddr, derpAddr netaddr.IPPort) {
- udpAddr = de.bestAddr
+ udpAddr = de.bestAddr.IPPort
if udpAddr.IsZero() || now.After(de.trustBestAddrUntil) {
// We had a bestAddr but it expired so send both to it
// and DERP.
@@ -3336,7 +3309,7 @@ func (de *discoEndpoint) wantFullPingLocked(now time.Time) bool {
if now.After(de.trustBestAddrUntil) {
return true
}
- if de.bestAddrLatency <= goodEnoughLatency {
+ if de.bestAddr.latency <= goodEnoughLatency {
return false
}
if now.Sub(de.lastFullPing) >= upgradeInterval {
@@ -3589,7 +3562,7 @@ func (de *discoEndpoint) addCandidateEndpoint(ep netaddr.IPPort) {
}
// Newly discovered endpoint. Exciting!
- de.c.logf("magicsock: disco: adding %v as candidate endpoint for %v (%s)", ep, de.discoShort, de.publicKey.ShortString())
+ de.c.logf("[v1] magicsock: disco: adding %v as candidate endpoint for %v (%s)", ep, de.discoShort, de.publicKey.ShortString())
de.endpointState[ep] = &endpointState{
lastGotPing: time.Now(),
}
@@ -3602,7 +3575,7 @@ func (de *discoEndpoint) addCandidateEndpoint(ep netaddr.IPPort) {
}
}
size2 := len(de.endpointState)
- de.c.logf("magicsock: disco: addCandidateEndpoint pruned %v candidate set from %v to %v entries", size, size2)
+ de.c.logf("[v1] magicsock: disco: addCandidateEndpoint pruned %v candidate set from %v to %v entries", size, size2)
}
}
@@ -3668,20 +3641,50 @@ func (de *discoEndpoint) handlePongConnLocked(m *disco.Pong, src netaddr.IPPort)
// Promote this pong response to our current best address if it's lower latency.
// TODO(bradfitz): decide how latency vs. preference order affects decision
if !isDerp {
- if de.bestAddr.IsZero() || latency < de.bestAddrLatency {
- if de.bestAddr != sp.to {
- de.c.logf("magicsock: disco: node %v %v now using %v", de.publicKey.ShortString(), de.discoShort, sp.to)
- de.bestAddr = sp.to
- }
+ thisPong := addrLatency{sp.to, latency}
+ if betterAddr(thisPong, de.bestAddr) {
+ de.c.logf("magicsock: disco: node %v %v now using %v", de.publicKey.ShortString(), de.discoShort, sp.to)
+ de.bestAddr = thisPong
}
- if de.bestAddr == sp.to {
- de.bestAddrLatency = latency
+ if de.bestAddr.IPPort == thisPong.IPPort {
+ de.bestAddr.latency = latency
de.bestAddrAt = now
de.trustBestAddrUntil = now.Add(trustUDPAddrDuration)
}
}
}
+// addrLatency is an IPPort with an associated latency.
+type addrLatency struct {
+ netaddr.IPPort
+ latency time.Duration
+}
+
+// betterAddr reports whether a is a better addr to use than b.
+func betterAddr(a, b addrLatency) bool {
+ if a.IPPort == b.IPPort {
+ return false
+ }
+ if b.IsZero() {
+ return true
+ }
+ if a.IsZero() {
+ return false
+ }
+ if a.IP.Is6() && b.IP.Is4() {
+ // Prefer IPv6 for being a bit more robust, as long as
+ // the latencies are roughly equivalent.
+ if a.latency/10*9 < b.latency {
+ return true
+ }
+ } else if a.IP.Is4() && b.IP.Is6() {
+ if betterAddr(b, a) {
+ return false
+ }
+ }
+ return a.latency < b.latency
+}
+
// discoEndpoint.mu must be held.
func (st *endpointState) addPongReplyLocked(r pongReply) {
if n := len(st.recentPongs); n < pongHistoryCount {
@@ -3729,7 +3732,7 @@ func (de *discoEndpoint) handleCallMeMaybe(m *disco.CallMeMaybe) {
}
}
if len(newEPs) > 0 {
- de.c.logf("magicsock: disco: call-me-maybe from %v %v added new endpoints: %v",
+ de.c.logf("[v1] magicsock: disco: call-me-maybe from %v %v added new endpoints: %v",
de.publicKey.ShortString(), de.discoShort,
logger.ArgWriter(func(w *bufio.Writer) {
for i, ep := range newEPs {
@@ -3788,8 +3791,7 @@ func (de *discoEndpoint) stopAndReset() {
// state isn't a mix of before & after two sessions.
de.lastSend = time.Time{}
de.lastFullPing = time.Time{}
- de.bestAddr = netaddr.IPPort{}
- de.bestAddrLatency = 0
+ de.bestAddr = addrLatency{}
de.bestAddrAt = time.Time{}
de.trustBestAddrUntil = time.Time{}
for _, es := range de.endpointState {
diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go
index 8e64a2696..2e7e29635 100644
--- a/wgengine/magicsock/magicsock_test.go
+++ b/wgengine/magicsock/magicsock_test.go
@@ -37,6 +37,7 @@ import (
"tailscale.com/derp/derpmap"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/stun/stuntest"
+ "tailscale.com/net/tstun"
"tailscale.com/tailcfg"
"tailscale.com/tstest"
"tailscale.com/tstest/natlab"
@@ -47,7 +48,6 @@ import (
"tailscale.com/types/wgkey"
"tailscale.com/util/cibuild"
"tailscale.com/wgengine/filter"
- "tailscale.com/wgengine/tstun"
"tailscale.com/wgengine/wgcfg"
"tailscale.com/wgengine/wgcfg/nmcfg"
"tailscale.com/wgengine/wglog"
@@ -130,7 +130,7 @@ type magicStack struct {
epCh chan []string // endpoint updates produced by this peer
conn *Conn // the magicsock itself
tun *tuntest.ChannelTUN // TUN device to send/receive packets
- tsTun *tstun.TUN // wrapped tun that implements filtering and wgengine hooks
+ tsTun *tstun.Wrapper // wrapped tun that implements filtering and wgengine hooks
dev *device.Device // the wireguard-go Device that connects the previous things
wgLogger *wglog.Logger // wireguard-go log wrapper
}
@@ -166,16 +166,16 @@ func newMagicStack(t testing.TB, logf logger.Logf, l nettype.PacketListener, der
}
tun := tuntest.NewChannelTUN()
- tsTun := tstun.WrapTUN(logf, tun.TUN())
+ tsTun := tstun.Wrap(logf, tun.TUN())
tsTun.SetFilter(filter.NewAllowAllForTest(logf))
wgLogger := wglog.NewLogger(logf)
- dev := device.NewDevice(tsTun, &device.DeviceOptions{
- Logger: wgLogger.DeviceLogger,
+ opts := &device.DeviceOptions{
CreateEndpoint: conn.CreateEndpoint,
CreateBind: conn.CreateBind,
SkipBindUpdate: true,
- })
+ }
+ dev := device.NewDevice(tsTun, wgLogger.DeviceLogger, opts)
dev.Up()
// Wait for magicsock to connect up to DERP.
@@ -522,12 +522,13 @@ func TestDeviceStartStop(t *testing.T) {
defer conn.Close()
tun := tuntest.NewChannelTUN()
- dev := device.NewDevice(tun.TUN(), &device.DeviceOptions{
- Logger: wglog.NewLogger(t.Logf).DeviceLogger,
+ wgLogger := wglog.NewLogger(t.Logf)
+ opts := &device.DeviceOptions{
CreateEndpoint: conn.CreateEndpoint,
CreateBind: conn.CreateBind,
SkipBindUpdate: true,
- })
+ }
+ dev := device.NewDevice(tun.TUN(), wgLogger.DeviceLogger, opts)
dev.Up()
dev.Close()
}
@@ -1431,7 +1432,7 @@ func TestDerpReceiveFromIPv4(t *testing.T) {
t.Fatal(err)
}
defer sendConn.Close()
- nodeKey, _ := addTestEndpoint(conn, sendConn)
+ nodeKey, _ := addTestEndpoint(t, conn, sendConn)
var sends int = 250e3 // takes about a second
if testing.Short() {
@@ -1509,7 +1510,7 @@ func TestDerpReceiveFromIPv4(t *testing.T) {
// addTestEndpoint sets conn's network map to a single peer expected
// to receive packets from sendConn (or DERP), and returns that peer's
// nodekey and discokey.
-func addTestEndpoint(conn *Conn, sendConn net.PacketConn) (tailcfg.NodeKey, tailcfg.DiscoKey) {
+func addTestEndpoint(tb testing.TB, conn *Conn, sendConn net.PacketConn) (tailcfg.NodeKey, tailcfg.DiscoKey) {
// Give conn just enough state that it'll recognize sendConn as a
// valid peer and not fall through to the legacy magicsock
// codepath.
@@ -1525,7 +1526,10 @@ func addTestEndpoint(conn *Conn, sendConn net.PacketConn) (tailcfg.NodeKey, tail
},
})
conn.SetPrivateKey(wgkey.Private{0: 1})
- conn.CreateEndpoint([32]byte(nodeKey), "0000000000000000000000000000000000000000000000000000000000000001.disco.tailscale:12345")
+ _, err := conn.CreateEndpoint([32]byte(nodeKey), "0000000000000000000000000000000000000000000000000000000000000001.disco.tailscale:12345")
+ if err != nil {
+ tb.Fatal(err)
+ }
conn.addValidDiscoPathForTest(discoKey, netaddr.MustParseIPPort(sendConn.LocalAddr().String()))
return nodeKey, discoKey
}
@@ -1541,7 +1545,7 @@ func setUpReceiveFrom(tb testing.TB) (roundTrip func()) {
}
tb.Cleanup(func() { sendConn.Close() })
- addTestEndpoint(conn, sendConn)
+ addTestEndpoint(tb, conn, sendConn)
var dstAddr net.Addr = conn.pconn4.LocalAddr()
sendBuf := make([]byte, 1<<10)
@@ -1793,3 +1797,114 @@ func TestRebindStress(t *testing.T) {
t.Fatalf("Got ReceiveIPv4 error: %v (is closed = %v). Log:\n%s", err, errors.Is(err, net.ErrClosed), logBuf.Bytes())
}
}
+
+func TestStringSetsEqual(t *testing.T) {
+ s := func(nn ...int) (ret []string) {
+ for _, n := range nn {
+ ret = append(ret, strconv.Itoa(n))
+ }
+ return
+ }
+ tests := []struct {
+ a, b []string
+ want bool
+ }{
+ {
+ want: true,
+ },
+ {
+ a: s(1, 2, 3),
+ b: s(1, 2, 3),
+ want: true,
+ },
+ {
+ a: s(1, 2),
+ b: s(2, 1),
+ want: true,
+ },
+ {
+ a: s(1, 2),
+ b: s(2, 1, 1),
+ want: true,
+ },
+ {
+ a: s(1, 2, 2),
+ b: s(2, 1),
+ want: true,
+ },
+ {
+ a: s(1, 2, 2),
+ b: s(2, 1, 1),
+ want: true,
+ },
+ {
+ a: s(1, 2, 2, 3),
+ b: s(2, 1, 1),
+ want: false,
+ },
+ {
+ a: s(1, 2, 2),
+ b: s(2, 1, 1, 3),
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ if got := stringSetsEqual(tt.a, tt.b); got != tt.want {
+ t.Errorf("%q vs %q = %v; want %v", tt.a, tt.b, got, tt.want)
+ }
+ }
+
+}
+
+func TestBetterAddr(t *testing.T) {
+ const ms = time.Millisecond
+ al := func(ipps string, d time.Duration) addrLatency {
+ return addrLatency{netaddr.MustParseIPPort(ipps), d}
+ }
+ zero := addrLatency{}
+ tests := []struct {
+ a, b addrLatency
+ want bool
+ }{
+ {a: zero, b: zero, want: false},
+ {a: al("10.0.0.2:123", 5*ms), b: zero, want: true},
+ {a: zero, b: al("10.0.0.2:123", 5*ms), want: false},
+ {a: al("10.0.0.2:123", 5*ms), b: al("1.2.3.4:555", 6*ms), want: true},
+ {a: al("10.0.0.2:123", 5*ms), b: al("10.0.0.2:123", 10*ms), want: false}, // same IPPort
+
+ // Prefer IPv6 if roughly equivalent:
+ {
+ a: al("[2001::5]:123", 100*ms),
+ b: al("1.2.3.4:555", 91*ms),
+ want: true,
+ },
+ {
+ a: al("1.2.3.4:555", 91*ms),
+ b: al("[2001::5]:123", 100*ms),
+ want: false,
+ },
+ // But not if IPv4 is much faster:
+ {
+ a: al("[2001::5]:123", 100*ms),
+ b: al("1.2.3.4:555", 30*ms),
+ want: false,
+ },
+ {
+ a: al("1.2.3.4:555", 30*ms),
+ b: al("[2001::5]:123", 100*ms),
+ want: true,
+ },
+ }
+ for _, tt := range tests {
+ got := betterAddr(tt.a, tt.b)
+ if got != tt.want {
+ t.Errorf("betterAddr(%+v, %+v) = %v; want %v", tt.a, tt.b, got, tt.want)
+ continue
+ }
+ gotBack := betterAddr(tt.b, tt.a)
+ if got && gotBack {
+ t.Errorf("betterAddr(%+v, %+v) and betterAddr(%+v, %+v) both unexpectedly true", tt.a, tt.b, tt.b, tt.a)
+ }
+ }
+
+}
diff --git a/wgengine/monitor/monitor.go b/wgengine/monitor/monitor.go
index 254df7cb6..8ee7087ce 100644
--- a/wgengine/monitor/monitor.go
+++ b/wgengine/monitor/monitor.go
@@ -10,6 +10,7 @@ package monitor
import (
"encoding/json"
"errors"
+ "runtime"
"sync"
"time"
@@ -18,6 +19,13 @@ import (
"tailscale.com/types/logger"
)
+// pollWallTimeInterval is how often we check the time to check
+// for big jumps in wall (non-monotonic) time as a backup mechanism
+// to get notified of a sleeping device waking back up.
+// Usually there are also minor network change events on wake that let
+// us check the wall time sooner than this.
+const pollWallTimeInterval = 15 * time.Second
+
// message represents a message returned from an osMon.
type message interface {
// Ignore is whether we should ignore this message.
@@ -50,18 +58,20 @@ type Mon struct {
logf logger.Logf
om osMon // nil means not supported on this platform
change chan struct{}
- stop chan struct{}
-
- mu sync.Mutex // guards cbs
- cbs map[*callbackHandle]ChangeFunc
- ifState *interfaces.State
- gwValid bool // whether gw and gwSelfIP are valid (cached)x
- gw netaddr.IP
- gwSelfIP netaddr.IP
+ stop chan struct{} // closed on Stop
- onceStart sync.Once
+ mu sync.Mutex // guards all following fields
+ cbs map[*callbackHandle]ChangeFunc
+ ifState *interfaces.State
+ gwValid bool // whether gw and gwSelfIP are valid
+ gw netaddr.IP // our gateway's IP
+ gwSelfIP netaddr.IP // our own IP address (that corresponds to gw)
started bool
+ closed bool
goroutines sync.WaitGroup
+ wallTimer *time.Timer // nil until Started; re-armed AfterFunc per tick
+ lastWall time.Time
+ timeJumped bool // whether we need to send a changed=true after a big time jump
}
// New instantiates and starts a monitoring instance.
@@ -70,10 +80,11 @@ type Mon struct {
func New(logf logger.Logf) (*Mon, error) {
logf = logger.WithPrefix(logf, "monitor: ")
m := &Mon{
- logf: logf,
- cbs: map[*callbackHandle]ChangeFunc{},
- change: make(chan struct{}, 1),
- stop: make(chan struct{}),
+ logf: logf,
+ cbs: map[*callbackHandle]ChangeFunc{},
+ change: make(chan struct{}, 1),
+ stop: make(chan struct{}),
+ lastWall: wallTime(),
}
st, err := m.interfaceStateUncached()
if err != nil {
@@ -101,12 +112,7 @@ func (m *Mon) InterfaceState() *interfaces.State {
}
func (m *Mon) interfaceStateUncached() (*interfaces.State, error) {
- s, err := interfaces.GetState()
- if s != nil {
- s.RemoveTailscaleInterfaces()
- s.RemoveUninterestingInterfacesAndAddresses()
- }
- return s, err
+ return interfaces.GetState()
}
// GatewayAndSelfIP returns the current network's default gateway, and
@@ -145,28 +151,54 @@ func (m *Mon) RegisterChangeCallback(callback ChangeFunc) (unregister func()) {
// Start starts the monitor.
// A monitor can only be started & closed once.
func (m *Mon) Start() {
- m.onceStart.Do(func() {
- if m.om == nil {
- return
- }
- m.started = true
- m.goroutines.Add(2)
- go m.pump()
- go m.debounce()
- })
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ if m.started || m.closed {
+ return
+ }
+ m.started = true
+
+ switch runtime.GOOS {
+ case "ios", "android":
+ // For battery reasons, and because these platforms
+ // don't really sleep in the same way, don't poll
+ // for the wall time to detect for wake-for-sleep
+ // walltime jumps.
+ default:
+ m.wallTimer = time.AfterFunc(pollWallTimeInterval, m.pollWallTime)
+ }
+
+ if m.om == nil {
+ return
+ }
+ m.goroutines.Add(2)
+ go m.pump()
+ go m.debounce()
}
// Close closes the monitor.
-// It may only be called once.
func (m *Mon) Close() error {
+ m.mu.Lock()
+ if m.closed {
+ m.mu.Unlock()
+ return nil
+ }
+ m.closed = true
close(m.stop)
+
+ if m.wallTimer != nil {
+ m.wallTimer.Stop()
+ }
+
var err error
if m.om != nil {
err = m.om.Close()
}
- // If it was previously started, wait for those goroutines to finish.
- m.onceStart.Do(func() {})
- if m.started {
+
+ started := m.started
+ m.mu.Unlock()
+
+ if started {
m.goroutines.Wait()
}
return err
@@ -232,9 +264,17 @@ func (m *Mon) debounce() {
m.logf("interfaces.State: %v", err)
} else {
m.mu.Lock()
+
+ // See if we have a queued or new time jump signal.
+ m.checkWallTimeAdvanceLocked()
+ timeJumped := m.timeJumped
+ if timeJumped {
+ m.logf("time jumped (probably wake from sleep); synthesizing major change event")
+ }
+
oldState := m.ifState
- changed := !curState.Equal(oldState)
- if changed {
+ ifChanged := !curState.EqualFiltered(oldState, interfaces.FilterInteresting)
+ if ifChanged {
m.gwValid = false
m.ifState = curState
@@ -243,6 +283,10 @@ func (m *Mon) debounce() {
jsonSummary(oldState), jsonSummary(curState))
}
}
+ changed := ifChanged || timeJumped
+ if changed {
+ m.timeJumped = false
+ }
for _, cb := range m.cbs {
go cb(changed, m.ifState)
}
@@ -264,3 +308,33 @@ func jsonSummary(x interface{}) interface{} {
}
return j
}
+
+func wallTime() time.Time {
+ // From time package's docs: "The canonical way to strip a
+ // monotonic clock reading is to use t = t.Round(0)."
+ return time.Now().Round(0)
+}
+
+func (m *Mon) pollWallTime() {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ if m.closed {
+ return
+ }
+ m.checkWallTimeAdvanceLocked()
+ if m.timeJumped {
+ m.InjectEvent()
+ }
+ m.wallTimer.Reset(pollWallTimeInterval)
+}
+
+// checkWallTimeAdvanceLocked updates m.timeJumped, if wall time jumped
+// more than 150% of pollWallTimeInterval, indicating we probably just
+// came out of sleep.
+func (m *Mon) checkWallTimeAdvanceLocked() {
+ now := wallTime()
+ if now.Sub(m.lastWall) > pollWallTimeInterval*3/2 {
+ m.timeJumped = true
+ }
+ m.lastWall = now
+}
diff --git a/wgengine/monitor/monitor_polling.go b/wgengine/monitor/monitor_polling.go
index 079c956bb..cdc995ca4 100644
--- a/wgengine/monitor/monitor_polling.go
+++ b/wgengine/monitor/monitor_polling.go
@@ -12,6 +12,7 @@ import (
"sync"
"time"
+ "tailscale.com/net/interfaces"
"tailscale.com/types/logger"
)
@@ -53,7 +54,7 @@ func (pm *pollingMon) Receive() (message, error) {
defer ticker.Stop()
base := pm.m.InterfaceState()
for {
- if cur, err := pm.m.interfaceStateUncached(); err == nil && !cur.Equal(base) {
+ if cur, err := pm.m.interfaceStateUncached(); err == nil && !cur.EqualFiltered(base, interfaces.FilterInteresting) {
return unspecifiedMessage{}, nil
}
select {
diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go
index ad647fa6f..92881decd 100644
--- a/wgengine/netstack/netstack.go
+++ b/wgengine/netstack/netstack.go
@@ -32,13 +32,13 @@ import (
"inet.af/netstack/waiter"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
+ "tailscale.com/net/tstun"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/util/dnsname"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
- "tailscale.com/wgengine/tstun"
)
const debugNetstack = false
@@ -49,7 +49,7 @@ const debugNetstack = false
type Impl struct {
ipstack *stack.Stack
linkEP *channel.Endpoint
- tundev *tstun.TUN
+ tundev *tstun.Wrapper
e wgengine.Engine
mc *magicsock.Conn
logf logger.Logf
@@ -67,7 +67,7 @@ const nicID = 1
const mtu = 1500
// Create creates and populates a new Impl.
-func Create(logf logger.Logf, tundev *tstun.TUN, e wgengine.Engine, mc *magicsock.Conn) (*Impl, error) {
+func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magicsock.Conn) (*Impl, error) {
if mc == nil {
return nil, errors.New("nil magicsock.Conn")
}
@@ -300,7 +300,7 @@ func (m DNSMap) Resolve(ctx context.Context, addr string) (netaddr.IPPort, error
return netaddr.IPPort{IP: ip, Port: uint16(port16)}, nil
}
- // No Magic DNS name so try real DNS.
+ // No MagicDNS name so try real DNS.
var r net.Resolver
ips, err := r.LookupIP(ctx, "ip", host)
if err != nil {
@@ -363,7 +363,7 @@ func (ns *Impl) injectOutbound() {
}
}
-func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.TUN) filter.Response {
+func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
var pn tcpip.NetworkProtocolNumber
switch p.IPVersion {
case 4:
diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go
index be1fa1468..2951a0c7e 100644
--- a/wgengine/pendopen.go
+++ b/wgengine/pendopen.go
@@ -14,8 +14,9 @@ import (
"tailscale.com/net/flowtrack"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
+ "tailscale.com/net/tstun"
+ "tailscale.com/types/ipproto"
"tailscale.com/wgengine/filter"
- "tailscale.com/wgengine/tstun"
)
const tcpTimeoutBeforeDebug = 5 * time.Second
@@ -65,10 +66,10 @@ func (e *userspaceEngine) noteFlowProblemFromPeer(f flowtrack.Tuple, problem pac
of.problem = problem
}
-func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN) (res filter.Response) {
+func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.Wrapper) (res filter.Response) {
res = filter.Accept // always
- if pp.IPProto == packet.TSMP {
+ if pp.IPProto == ipproto.TSMP {
res = filter.DropSilently
rh, ok := pp.AsTailscaleRejectedHeader()
if !ok {
@@ -83,14 +84,14 @@ func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN)
}
if pp.IPVersion == 0 ||
- pp.IPProto != packet.TCP ||
+ pp.IPProto != ipproto.TCP ||
pp.TCPFlags&(packet.TCPSyn|packet.TCPRst) == 0 {
return
}
// Either a SYN or a RST came back. Remove it in either case.
- f := flowtrack.Tuple{Dst: pp.Src, Src: pp.Dst} // src/dst reversed
+ f := flowtrack.Tuple{Proto: pp.IPProto, Dst: pp.Src, Src: pp.Dst} // src/dst reversed
removed := e.removeFlow(f)
if removed && pp.TCPFlags&packet.TCPRst != 0 {
e.logf("open-conn-track: flow TCP %v got RST by peer", f)
@@ -98,23 +99,23 @@ func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN)
return
}
-func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN) (res filter.Response) {
+func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.Wrapper) (res filter.Response) {
res = filter.Accept // always
if pp.IPVersion == 0 ||
- pp.IPProto != packet.TCP ||
+ pp.IPProto != ipproto.TCP ||
pp.TCPFlags&packet.TCPSyn == 0 {
return
}
- flow := flowtrack.Tuple{Src: pp.Src, Dst: pp.Dst}
+ flow := flowtrack.Tuple{Proto: pp.IPProto, Src: pp.Src, Dst: pp.Dst}
// iOS likes to probe Apple IPs on all interfaces to check for connectivity.
// Don't start timers tracking those. They won't succeed anyway. Avoids log spam
// like:
// open-conn-track: timeout opening (100.115.73.60:52501 => 17.125.252.5:443); no associated peer node
if runtime.GOOS == "ios" && flow.Dst.Port == 443 && !tsaddr.IsTailscaleIP(flow.Dst.IP) {
- if _, ok := e.magicConn.PeerForIP(flow.Dst.IP); !ok {
+ if _, err := e.peerForIP(flow.Dst.IP); err != nil {
return
}
}
@@ -154,8 +155,12 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
}
// Diagnose why it might've timed out.
- n, ok := e.magicConn.PeerForIP(flow.Dst.IP)
- if !ok {
+ n, err := e.peerForIP(flow.Dst.IP)
+ if err != nil {
+ e.logf("open-conn-track: timeout opening %v; peerForIP: %v", flow, err)
+ return
+ }
+ if n == nil {
e.logf("open-conn-track: timeout opening %v; no associated peer node", flow)
return
}
diff --git a/wgengine/router/router.go b/wgengine/router/router.go
index 9c3f1003f..2e53363cf 100644
--- a/wgengine/router/router.go
+++ b/wgengine/router/router.go
@@ -7,12 +7,11 @@
package router
import (
- "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"inet.af/netaddr"
+ "tailscale.com/net/dns"
"tailscale.com/types/logger"
"tailscale.com/types/preftype"
- "tailscale.com/wgengine/router/dns"
)
// Router is responsible for managing the system network stack.
@@ -33,9 +32,9 @@ type Router interface {
// New returns a new Router for the current platform, using the
// provided tun device.
-func New(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) {
+func New(logf logger.Logf, tundev tun.Device) (Router, error) {
logf = logger.WithPrefix(logf, "router: ")
- return newUserspaceRouter(logf, wgdev, tundev)
+ return newUserspaceRouter(logf, tundev)
}
// Cleanup restores the system network configuration to its original state
diff --git a/wgengine/router/router_darwin.go b/wgengine/router/router_darwin.go
index 26b689355..58ba8e6d3 100644
--- a/wgengine/router/router_darwin.go
+++ b/wgengine/router/router_darwin.go
@@ -5,13 +5,12 @@
package router
import (
- "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"tailscale.com/types/logger"
)
-func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) {
- return newUserspaceBSDRouter(logf, wgdev, tundev)
+func newUserspaceRouter(logf logger.Logf, tundev tun.Device) (Router, error) {
+ return newUserspaceBSDRouter(logf, tundev)
}
func cleanup(logger.Logf, string) {
diff --git a/wgengine/router/router_default.go b/wgengine/router/router_default.go
index 4d7365e04..7f05da42f 100644
--- a/wgengine/router/router_default.go
+++ b/wgengine/router/router_default.go
@@ -7,13 +7,12 @@
package router
import (
- "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"tailscale.com/types/logger"
)
-func newUserspaceRouter(logf logger.Logf, tunname string, dev *device.Device, tunDev tun.Device, netChanged func()) Router {
- return NewFakeRouter(logf, tunname, dev, tunDev, netChanged)
+func newUserspaceRouter(logf logger.Logf, tunname string, tunDev tun.Device, netChanged func()) Router {
+ return NewFakeRouter(logf, tunname, tunDev, netChanged)
}
func cleanup(logf logger.Logf, interfaceName string) {
diff --git a/wgengine/router/router_fake.go b/wgengine/router/router_fake.go
index 0d14e5000..add4b576b 100644
--- a/wgengine/router/router_fake.go
+++ b/wgengine/router/router_fake.go
@@ -5,15 +5,13 @@
package router
import (
- "github.com/tailscale/wireguard-go/device"
- "github.com/tailscale/wireguard-go/tun"
"tailscale.com/types/logger"
)
-// NewFakeRouter returns a Router that does nothing when called and
-// always returns nil errors.
-func NewFake(logf logger.Logf, _ *device.Device, _ tun.Device) (Router, error) {
- return fakeRouter{logf: logf}, nil
+// NewFake returns a Router that does nothing when called and always
+// returns nil errors.
+func NewFake(logf logger.Logf) Router {
+ return fakeRouter{logf: logf}
}
type fakeRouter struct {
diff --git a/wgengine/router/router_freebsd.go b/wgengine/router/router_freebsd.go
index e56e3f82d..6e9380299 100644
--- a/wgengine/router/router_freebsd.go
+++ b/wgengine/router/router_freebsd.go
@@ -5,7 +5,6 @@
package router
import (
- "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"tailscale.com/types/logger"
)
@@ -15,8 +14,8 @@ import (
// Work is currently underway for an in-kernel FreeBSD implementation of wireguard
// https://svnweb.freebsd.org/base?view=revision&revision=357986
-func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
- return newUserspaceBSDRouter(logf, nil, tundev)
+func newUserspaceRouter(logf logger.Logf, tundev tun.Device) (Router, error) {
+ return newUserspaceBSDRouter(logf, tundev)
}
func cleanup(logf logger.Logf, interfaceName string) {
diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go
index b700efccc..311681ab0 100644
--- a/wgengine/router/router_linux.go
+++ b/wgengine/router/router_linux.go
@@ -16,14 +16,13 @@ import (
"github.com/coreos/go-iptables/iptables"
"github.com/go-multierror/multierror"
- "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"inet.af/netaddr"
+ "tailscale.com/net/dns"
"tailscale.com/net/tsaddr"
"tailscale.com/types/logger"
"tailscale.com/types/preftype"
"tailscale.com/version/distro"
- "tailscale.com/wgengine/router/dns"
)
const (
@@ -110,7 +109,7 @@ type linuxRouter struct {
cmd commandRunner
}
-func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (Router, error) {
+func newUserspaceRouter(logf logger.Logf, tunDev tun.Device) (Router, error) {
tunname, err := tunDev.Name()
if err != nil {
return nil, err
diff --git a/wgengine/router/router_linux_test.go b/wgengine/router/router_linux_test.go
index 7bacb2e2f..45109d35c 100644
--- a/wgengine/router/router_linux_test.go
+++ b/wgengine/router/router_linux_test.go
@@ -627,7 +627,7 @@ func TestDelRouteIdempotent(t *testing.T) {
}
}
- r, err := newUserspaceRouter(logf, nil, tun)
+ r, err := newUserspaceRouter(logf, tun)
if err != nil {
t.Fatal(err)
}
diff --git a/wgengine/router/router_openbsd.go b/wgengine/router/router_openbsd.go
index 8c7269658..a6dbf9282 100644
--- a/wgengine/router/router_openbsd.go
+++ b/wgengine/router/router_openbsd.go
@@ -10,11 +10,10 @@ import (
"log"
"os/exec"
- "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"inet.af/netaddr"
+ "tailscale.com/net/dns"
"tailscale.com/types/logger"
- "tailscale.com/wgengine/router/dns"
)
// For now this router only supports the WireGuard userspace implementation.
@@ -31,7 +30,7 @@ type openbsdRouter struct {
dns *dns.Manager
}
-func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
+func newUserspaceRouter(logf logger.Logf, tundev tun.Device) (Router, error) {
tunname, err := tundev.Name()
if err != nil {
return nil, err
diff --git a/wgengine/router/router_userspace_bsd.go b/wgengine/router/router_userspace_bsd.go
index 71ccd1706..79a81de03 100644
--- a/wgengine/router/router_userspace_bsd.go
+++ b/wgengine/router/router_userspace_bsd.go
@@ -12,12 +12,11 @@ import (
"os/exec"
"runtime"
- "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"inet.af/netaddr"
+ "tailscale.com/net/dns"
"tailscale.com/types/logger"
"tailscale.com/version"
- "tailscale.com/wgengine/router/dns"
)
type userspaceBSDRouter struct {
@@ -29,7 +28,7 @@ type userspaceBSDRouter struct {
dns *dns.Manager
}
-func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) {
+func newUserspaceBSDRouter(logf logger.Logf, tundev tun.Device) (Router, error) {
tunname, err := tundev.Name()
if err != nil {
return nil, err
diff --git a/wgengine/router/router_windows.go b/wgengine/router/router_windows.go
index 89c686b95..2efdce7ed 100644
--- a/wgengine/router/router_windows.go
+++ b/wgengine/router/router_windows.go
@@ -16,21 +16,19 @@ import (
"syscall"
"time"
- "github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"inet.af/netaddr"
"tailscale.com/logtail/backoff"
+ "tailscale.com/net/dns"
"tailscale.com/types/logger"
- "tailscale.com/wgengine/router/dns"
)
type winRouter struct {
logf func(fmt string, args ...interface{})
tunname string
nativeTun *tun.NativeTun
- wgdev *device.Device
routeChangeCallback *winipcfg.RouteChangeCallback
dns *dns.Manager
firewall *firewallTweaker
@@ -45,7 +43,7 @@ type winRouter struct {
firewallSubproc *exec.Cmd
}
-func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) {
+func newUserspaceRouter(logf logger.Logf, tundev tun.Device) (Router, error) {
tunname, err := tundev.Name()
if err != nil {
return nil, err
@@ -65,7 +63,6 @@ func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Devic
return &winRouter{
logf: logf,
- wgdev: wgdev,
tunname: tunname,
nativeTun: nativeTun,
dns: dns.NewManager(mconfig),
@@ -112,11 +109,8 @@ func (r *winRouter) Set(cfg *Config) error {
}
// Flush DNS on router config change to clear cached DNS entries (solves #1430)
- out, err := exec.Command("ipconfig", "/flushdns").CombinedOutput()
- if err != nil {
- r.logf("flushdns error: %v; output: %s", err, out)
- } else {
- r.logf("flushdns successful")
+ if err := dns.Flush(); err != nil {
+ r.logf("flushdns error: %v", err)
}
return nil
diff --git a/wgengine/userspace.go b/wgengine/userspace.go
index fccca8c8b..9202e4c7a 100644
--- a/wgengine/userspace.go
+++ b/wgengine/userspace.go
@@ -8,12 +8,12 @@ import (
"bufio"
"bytes"
"context"
+ crand "crypto/rand"
"errors"
"fmt"
"io"
"net"
"os"
- "os/exec"
"runtime"
"strconv"
"strings"
@@ -29,38 +29,28 @@ import (
"tailscale.com/health"
"tailscale.com/internal/deepprint"
"tailscale.com/ipn/ipnstate"
+ "tailscale.com/net/dns"
"tailscale.com/net/flowtrack"
"tailscale.com/net/interfaces"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tshttpproxy"
+ "tailscale.com/net/tstun"
"tailscale.com/tailcfg"
+ "tailscale.com/types/ipproto"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/types/wgkey"
"tailscale.com/version"
- "tailscale.com/version/distro"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/router"
- "tailscale.com/wgengine/tsdns"
- "tailscale.com/wgengine/tstun"
"tailscale.com/wgengine/wgcfg"
"tailscale.com/wgengine/wglog"
)
-// minimalMTU is the MTU we set on tailscale's TUN
-// interface. wireguard-go defaults to 1420 bytes, which only works if
-// the "outer" MTU is 1500 bytes. This breaks on DSL connections
-// (typically 1492 MTU) and on GCE (1460 MTU?!).
-//
-// 1280 is the smallest MTU allowed for IPv6, which is a sensible
-// "probably works everywhere" setting until we develop proper PMTU
-// discovery.
-const minimalMTU = 1280
-
const magicDNSPort = 53
var magicDNSIP = netaddr.IPv4(100, 100, 100, 100)
@@ -90,10 +80,10 @@ type userspaceEngine struct {
reqCh chan struct{}
waitCh chan struct{} // chan is closed when first Close call completes; contrast with closing bool
timeNow func() time.Time
- tundev *tstun.TUN
+ tundev *tstun.Wrapper
wgdev *device.Device
router router.Router
- resolver *tsdns.Resolver
+ resolver *dns.Resolver
magicConn *magicsock.Conn
linkMon *monitor.Mon
linkMonOwned bool // whether we created linkMon (and thus need to close it)
@@ -101,10 +91,10 @@ type userspaceEngine struct {
testMaybeReconfigHook func() // for tests; if non-nil, fires if maybeReconfigWireguardLocked called
- // localAddrs is the set of IP addresses assigned to the local
+ // isLocalAddr reports the whether an IP is assigned to the local
// tunnel interface. It's used to reflect local packets
// incorrectly sent to us.
- localAddrs atomic.Value // of map[netaddr.IP]bool
+ isLocalAddr atomic.Value // of func(netaddr.IP)bool
wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below
lastCfgFull wgcfg.Config
@@ -117,8 +107,9 @@ type userspaceEngine struct {
destIPActivityFuncs map[netaddr.IP]func()
statusBufioReader *bufio.Reader // reusable for UAPI
- mu sync.Mutex // guards following; see lock order comment below
- closing bool // Close was called (even if we're still closing)
+ mu sync.Mutex // guards following; see lock order comment below
+ netMap *netmap.NetworkMap // or nil
+ closing bool // Close was called (even if we're still closing)
statusCallback StatusCallback
peerSequence []wgkey.Key
endpoints []string
@@ -126,36 +117,30 @@ type userspaceEngine struct {
pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go
networkMapCallbacks map[*someHandle]NetworkMapCallback
tsIPByIPPort map[netaddr.IPPort]netaddr.IP // allows registration of IP:ports as belonging to a certain Tailscale IP for whois lookups
+ pongCallback map[[8]byte]func() // for TSMP pong responses
// Lock ordering: magicsock.Conn.mu, wgLock, then mu.
}
// InternalsGetter is implemented by Engines that can export their internals.
type InternalsGetter interface {
- GetInternals() (*tstun.TUN, *magicsock.Conn)
+ GetInternals() (*tstun.Wrapper, *magicsock.Conn)
}
-func (e *userspaceEngine) GetInternals() (*tstun.TUN, *magicsock.Conn) {
+func (e *userspaceEngine) GetInternals() (*tstun.Wrapper, *magicsock.Conn) {
return e.tundev, e.magicConn
}
-// RouterGen is the signature for a function that creates a
-// router.Router.
-type RouterGen func(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (router.Router, error)
-
// Config is the engine configuration.
type Config struct {
- // TUN is the TUN device used by the engine.
- // Exactly one of either TUN or TUNName must be specified.
- TUN tun.Device
-
- // TUNName is the TUN device to create.
- // Exactly one of either TUN or TUNName must be specified.
- TUNName string
+ // Tun is the device used by the Engine to exchange packets with
+ // the OS.
+ // If nil, a fake Device that does nothing is used.
+ Tun tun.Device
- // RouterGen is the function used to instantiate the router.
- // If nil, wgengine/router.New is used.
- RouterGen RouterGen
+ // Router interfaces the Engine to the OS network stack.
+ // If nil, a fake Router that does nothing is used.
+ Router router.Router
// LinkMonitor optionally provides an existing link monitor to re-use.
// If nil, a new link monitor is created.
@@ -165,59 +150,36 @@ type Config struct {
// If zero, a port is automatically selected.
ListenPort uint16
- // Fake determines whether this engine should automatically
- // reply to ICMP pings.
- Fake bool
+ // RespondToPing determines whether this engine should internally
+ // reply to ICMP pings, without involving the OS.
+ // Used in "fake" mode for development.
+ RespondToPing bool
}
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) {
logf("Starting userspace wireguard engine (with fake TUN device)")
return NewUserspaceEngine(logf, Config{
- TUN: tstun.NewFakeTUN(),
- RouterGen: router.NewFake,
- ListenPort: listenPort,
- Fake: true,
+ ListenPort: listenPort,
+ RespondToPing: true,
})
}
// NewUserspaceEngine creates the named tun device and returns a
// Tailscale Engine running on it.
-func NewUserspaceEngine(logf logger.Logf, conf Config) (Engine, error) {
- if conf.TUN != nil && conf.TUNName != "" {
- return nil, errors.New("TUN and TUNName are mutually exclusive")
- }
- if conf.TUN == nil && conf.TUNName == "" {
- return nil, errors.New("either TUN or TUNName are required")
- }
- tunDev := conf.TUN
- var err error
- if tunName := conf.TUNName; tunName != "" {
- logf("Starting userspace wireguard engine with tun device %q", tunName)
- tunDev, err = tun.CreateTUN(tunName, minimalMTU)
- if err != nil {
- diagnoseTUNFailure(tunName, logf)
- logf("CreateTUN: %v", err)
- return nil, err
- }
- logf("CreateTUN ok.")
+func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) {
+ var closePool closeOnErrorPool
+ defer closePool.closeAllIfError(&reterr)
- if err := waitInterfaceUp(tunDev, 90*time.Second, logf); err != nil {
- return nil, err
- }
+ if conf.Tun == nil {
+ logf("[v1] using fake (no-op) tun device")
+ conf.Tun = tstun.NewFake()
}
-
- if conf.RouterGen == nil {
- conf.RouterGen = router.New
+ if conf.Router == nil {
+ logf("[v1] using fake (no-op) OS network configurator")
+ conf.Router = router.NewFake(logf)
}
- return newUserspaceEngine(logf, tunDev, conf)
-}
-
-func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ Engine, reterr error) {
- var closePool closeOnErrorPool
- defer closePool.closeAllIfError(&reterr)
-
- tsTUNDev := tstun.WrapTUN(logf, rawTUNDev)
+ tsTUNDev := tstun.Wrap(logf, conf.Tun)
closePool.add(tsTUNDev)
e := &userspaceEngine{
@@ -226,9 +188,10 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
reqCh: make(chan struct{}, 1),
waitCh: make(chan struct{}),
tundev: tsTUNDev,
+ router: conf.Router,
pingers: make(map[wgkey.Key]*pinger),
}
- e.localAddrs.Store(map[netaddr.IP]bool{})
+ e.isLocalAddr.Store(genLocalAddrFunc(nil))
if conf.LinkMonitor != nil {
e.linkMon = conf.LinkMonitor
@@ -242,7 +205,7 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
e.linkMonOwned = true
}
- e.resolver = tsdns.NewResolver(tsdns.ResolverConfig{
+ e.resolver = dns.NewResolver(dns.ResolverConfig{
Logf: logf,
Forward: true,
LinkMonitor: e.linkMon,
@@ -281,8 +244,7 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
closePool.add(e.magicConn)
e.magicConn.SetNetworkUp(e.linkMon.InterfaceState().AnyInterfaceUp())
- // Respond to all pings only in fake mode.
- if conf.Fake {
+ if conf.RespondToPing {
e.tundev.PostFilterIn = echoRespondToAll
}
e.tundev.PreFilterOut = e.handleLocalPackets
@@ -300,7 +262,6 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
e.wgLogger = wglog.NewLogger(logf)
opts := &device.DeviceOptions{
- Logger: e.wgLogger.DeviceLogger,
HandshakeDone: func(peerKey device.NoisePublicKey, peer *device.Peer, deviceAllowedIPs *device.AllowedIPs) {
// Send an unsolicited status event every time a
// handshake completes. This makes sure our UI can
@@ -349,20 +310,21 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
SkipBindUpdate: true,
}
+ e.tundev.OnTSMPPongReceived = func(data [8]byte) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ cb := e.pongCallback[data]
+ e.logf("wgengine: got TSMP pong %02x; cb=%v", data, cb != nil)
+ if cb != nil {
+ go cb()
+ }
+ }
+
// wgdev takes ownership of tundev, will close it when closed.
e.logf("Creating wireguard device...")
- e.wgdev = device.NewDevice(e.tundev, opts)
+ e.wgdev = device.NewDevice(e.tundev, e.wgLogger.DeviceLogger, opts)
closePool.addFunc(e.wgdev.Close)
- // Pass the underlying tun.(*NativeDevice) to the router:
- // routers do not Read or Write, but do access native interfaces.
- e.logf("Creating router...")
- e.router, err = conf.RouterGen(logf, e.wgdev, e.tundev.Unwrap())
- if err != nil {
- return nil, err
- }
- closePool.add(e.router)
-
go func() {
up := false
for event := range e.tundev.Events() {
@@ -411,7 +373,7 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
}
// echoRespondToAll is an inbound post-filter responding to all echo requests.
-func echoRespondToAll(p *packet.Parsed, t *tstun.TUN) filter.Response {
+func echoRespondToAll(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
if p.IsEchoRequest() {
header := p.ICMP4Header()
header.ToResponse()
@@ -432,44 +394,40 @@ func echoRespondToAll(p *packet.Parsed, t *tstun.TUN) filter.Response {
// stack, and intercepts any packets that should be handled by
// tailscaled directly. Other packets are allowed to proceed into the
// main ACL filter.
-func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.TUN) filter.Response {
+func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
if verdict := e.handleDNS(p, t); verdict == filter.Drop {
// local DNS handled the packet.
return filter.Drop
}
- if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && e.isLocalAddr(p.Dst.IP) {
- // macOS NetworkExtension directs packets destined to the
- // tunnel's local IP address into the tunnel, instead of
- // looping back within the kernel network stack. We have to
- // notice that an outbound packet is actually destined for
- // ourselves, and loop it back into macOS.
- t.InjectInboundCopy(p.Buffer())
- return filter.Drop
+ if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
+ isLocalAddr, ok := e.isLocalAddr.Load().(func(netaddr.IP) bool)
+ if !ok {
+ e.logf("[unexpected] e.isLocalAddr was nil, can't check for loopback packet")
+ } else if isLocalAddr(p.Dst.IP) {
+ // macOS NetworkExtension directs packets destined to the
+ // tunnel's local IP address into the tunnel, instead of
+ // looping back within the kernel network stack. We have to
+ // notice that an outbound packet is actually destined for
+ // ourselves, and loop it back into macOS.
+ t.InjectInboundCopy(p.Buffer())
+ return filter.Drop
+ }
}
return filter.Accept
}
-func (e *userspaceEngine) isLocalAddr(ip netaddr.IP) bool {
- localAddrs, ok := e.localAddrs.Load().(map[netaddr.IP]bool)
- if !ok {
- e.logf("[unexpected] e.localAddrs was nil, can't check for loopback packet")
- return false
- }
- return localAddrs[ip]
-}
-
// handleDNS is an outbound pre-filter resolving Tailscale domains.
-func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Response {
- if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == packet.UDP {
- request := tsdns.Packet{
+func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
+ if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == ipproto.UDP {
+ request := dns.Packet{
Payload: append([]byte(nil), p.Payload()...),
Addr: netaddr.IPPort{IP: p.Src.IP, Port: p.Src.Port},
}
err := e.resolver.EnqueueRequest(request)
if err != nil {
- e.logf("tsdns: enqueue: %v", err)
+ e.logf("dns: enqueue: %v", err)
}
return filter.Drop
}
@@ -480,11 +438,11 @@ func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Respo
func (e *userspaceEngine) pollResolver() {
for {
resp, err := e.resolver.NextResponse()
- if err == tsdns.ErrClosed {
+ if err == dns.ErrClosed {
return
}
if err != nil {
- e.logf("tsdns: error: %v", err)
+ e.logf("dns: error: %v", err)
continue
}
@@ -498,7 +456,7 @@ func (e *userspaceEngine) pollResolver() {
}
hlen := h.Len()
- // TODO(dmytro): avoid this allocation without importing tstun quirks into tsdns.
+ // TODO(dmytro): avoid this allocation without importing tstun quirks into dns.
const offset = tstun.PacketStartOffset
buf := make([]byte, offset+hlen+len(resp.Payload))
copy(buf[offset+hlen:], resp.Payload)
@@ -925,16 +883,34 @@ func (e *userspaceEngine) updateActivityMapsLocked(trackDisco []tailcfg.DiscoKey
e.tundev.SetDestIPActivityFuncs(e.destIPActivityFuncs)
}
+// genLocalAddrFunc returns a func that reports whether an IP is in addrs.
+// addrs is assumed to be all /32 or /128 entries.
+func genLocalAddrFunc(addrs []netaddr.IPPrefix) func(netaddr.IP) bool {
+ // Specialize the three common cases: no address, just IPv4
+ // (or just IPv6), and both IPv4 and IPv6.
+ if len(addrs) == 0 {
+ return func(netaddr.IP) bool { return false }
+ }
+ if len(addrs) == 1 {
+ return func(t netaddr.IP) bool { return t == addrs[0].IP }
+ }
+ if len(addrs) == 2 {
+ return func(t netaddr.IP) bool { return t == addrs[0].IP || t == addrs[1].IP }
+ }
+ // Otherwise, the general implementation: a map lookup.
+ m := map[netaddr.IP]bool{}
+ for _, a := range addrs {
+ m[a.IP] = true
+ }
+ return func(t netaddr.IP) bool { return m[t] }
+}
+
func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) error {
if routerCfg == nil {
panic("routerCfg must not be nil")
}
- localAddrs := map[netaddr.IP]bool{}
- for _, addr := range routerCfg.LocalAddrs {
- localAddrs[addr.IP] = true
- }
- e.localAddrs.Store(localAddrs)
+ e.isLocalAddr.Store(genLocalAddrFunc(routerCfg.LocalAddrs))
e.wgLock.Lock()
defer e.wgLock.Unlock()
@@ -1034,7 +1010,7 @@ func (e *userspaceEngine) SetFilter(filt *filter.Filter) {
e.tundev.SetFilter(filt)
}
-func (e *userspaceEngine) SetDNSMap(dm *tsdns.Map) {
+func (e *userspaceEngine) SetDNSMap(dm *dns.Map) {
e.resolver.SetMap(dm)
}
@@ -1270,6 +1246,7 @@ func (e *userspaceEngine) linkChange(changed bool, cur *interfaces.State) {
e.logf("[v1] LinkChange: minor")
}
+ health.SetAnyInterfaceUp(up)
e.magicConn.SetNetworkUp(up)
why := "link-change-minor"
@@ -1306,6 +1283,7 @@ func (e *userspaceEngine) SetDERPMap(dm *tailcfg.DERPMap) {
func (e *userspaceEngine) SetNetworkMap(nm *netmap.NetworkMap) {
e.magicConn.SetNetworkMap(nm)
e.mu.Lock()
+ e.netMap = nm
callbacks := make([]NetworkMapCallback, 0, 4)
for _, fn := range e.networkMapCallbacks {
callbacks = append(callbacks, fn)
@@ -1338,8 +1316,107 @@ func (e *userspaceEngine) UpdateStatus(sb *ipnstate.StatusBuilder) {
e.magicConn.UpdateStatus(sb)
}
-func (e *userspaceEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) {
- e.magicConn.Ping(ip, cb)
+func (e *userspaceEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) {
+ res := &ipnstate.PingResult{IP: ip.String()}
+ peer, err := e.peerForIP(ip)
+ if err != nil {
+ e.logf("ping(%v): %v", ip, err)
+ res.Err = err.Error()
+ cb(res)
+ return
+ }
+ if peer == nil {
+ e.logf("ping(%v): no matching peer", ip)
+ res.Err = "no matching peer"
+ cb(res)
+ return
+ }
+ pingType := "disco"
+ if useTSMP {
+ pingType = "TSMP"
+ }
+ e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key.ShortString(), peer.ComputedName)
+ if useTSMP {
+ e.sendTSMPPing(ip, peer, res, cb)
+ } else {
+ e.magicConn.Ping(peer, res, cb)
+ }
+}
+
+func (e *userspaceEngine) mySelfIPMatchingFamily(dst netaddr.IP) (src netaddr.IP, err error) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ if e.netMap == nil {
+ return netaddr.IP{}, errors.New("no netmap")
+ }
+ for _, a := range e.netMap.Addresses {
+ if a.IsSingleIP() && a.IP.BitLen() == dst.BitLen() {
+ return a.IP, nil
+ }
+ }
+ if len(e.netMap.Addresses) == 0 {
+ return netaddr.IP{}, errors.New("no self address in netmap")
+ }
+ return netaddr.IP{}, errors.New("no self address in netmap matching address family")
+}
+
+func (e *userspaceEngine) sendTSMPPing(ip netaddr.IP, peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
+ srcIP, err := e.mySelfIPMatchingFamily(ip)
+ if err != nil {
+ res.Err = err.Error()
+ cb(res)
+ return
+ }
+ var iph packet.Header
+ if srcIP.Is4() {
+ iph = packet.IP4Header{
+ IPProto: ipproto.TSMP,
+ Src: srcIP,
+ Dst: ip,
+ }
+ } else {
+ iph = packet.IP6Header{
+ IPProto: ipproto.TSMP,
+ Src: srcIP,
+ Dst: ip,
+ }
+ }
+
+ var data [8]byte
+ crand.Read(data[:])
+
+ expireTimer := time.AfterFunc(10*time.Second, func() {
+ e.setTSMPPongCallback(data, nil)
+ })
+ t0 := time.Now()
+ e.setTSMPPongCallback(data, func() {
+ expireTimer.Stop()
+ d := time.Since(t0)
+ res.LatencySeconds = d.Seconds()
+ res.NodeIP = ip.String()
+ res.NodeName = peer.ComputedName
+ cb(res)
+ })
+
+ var tsmpPayload [9]byte
+ tsmpPayload[0] = byte(packet.TSMPTypePing)
+ copy(tsmpPayload[1:], data[:])
+
+ tsmpPing := packet.Generate(iph, tsmpPayload[:])
+ e.tundev.InjectOutbound(tsmpPing)
+}
+
+func (e *userspaceEngine) setTSMPPongCallback(data [8]byte, cb func()) {
+ e.mu.Lock()
+ defer e.mu.Unlock()
+ if e.pongCallback == nil {
+ e.pongCallback = map[[8]byte]func(){}
+ }
+ if cb == nil {
+ delete(e.pongCallback, data)
+ } else {
+ e.pongCallback[data] = cb
+ }
}
func (e *userspaceEngine) RegisterIPPortIdentity(ipport netaddr.IPPort, tsIP netaddr.IP) {
@@ -1367,92 +1444,77 @@ func (e *userspaceEngine) WhoIsIPPort(ipport netaddr.IPPort) (tsIP netaddr.IP, o
return tsIP, ok
}
-// diagnoseTUNFailure is called if tun.CreateTUN fails, to poke around
-// the system and log some diagnostic info that might help debug why
-// TUN failed. Because TUN's already failed and things the program's
-// about to end, we might as well log a lot.
-func diagnoseTUNFailure(tunName string, logf logger.Logf) {
- switch runtime.GOOS {
- case "linux":
- diagnoseLinuxTUNFailure(tunName, logf)
- case "darwin":
- diagnoseDarwinTUNFailure(tunName, logf)
- default:
- logf("no TUN failure diagnostics for OS %q", runtime.GOOS)
+// peerForIP returns the Node in the wireguard config
+// that's responsible for handling the given IP address.
+//
+// If none is found in the wireguard config but one is found in
+// the netmap, it's described in an error.
+//
+// If none is found in either place, (nil, nil) is returned.
+//
+// peerForIP acquires both e.mu and e.wgLock, but neither at the same
+// time.
+func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error) {
+ e.mu.Lock()
+ nm := e.netMap
+ e.mu.Unlock()
+ if nm == nil {
+ return nil, errors.New("no network map")
}
-}
-func diagnoseDarwinTUNFailure(tunName string, logf logger.Logf) {
- if os.Getuid() != 0 {
- logf("failed to create TUN device as non-root user; use 'sudo tailscaled', or run under launchd with 'sudo tailscaled install-system-daemon'")
- }
- if tunName != "utun" {
- logf("failed to create TUN device %q; try using tun device \"utun\" instead for automatic selection", tunName)
+ // Check for exact matches before looking for subnet matches.
+ var bestInNMPrefix netaddr.IPPrefix
+ var bestInNM *tailcfg.Node
+ for _, p := range nm.Peers {
+ for _, a := range p.Addresses {
+ if a.IP == ip && a.IsSingleIP() && tsaddr.IsTailscaleIP(ip) {
+ return p, nil
+ }
+ }
+ for _, cidr := range p.AllowedIPs {
+ if !cidr.Contains(ip) {
+ continue
+ }
+ if bestInNMPrefix.IsZero() || cidr.Bits > bestInNMPrefix.Bits {
+ bestInNMPrefix = cidr
+ bestInNM = p
+ }
+ }
}
-}
-func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf) {
- kernel, err := exec.Command("uname", "-r").Output()
- kernel = bytes.TrimSpace(kernel)
- if err != nil {
- logf("no TUN, and failed to look up kernel version: %v", err)
- return
- }
- logf("Linux kernel version: %s", kernel)
+ e.wgLock.Lock()
+ defer e.wgLock.Unlock()
- modprobeOut, err := exec.Command("/sbin/modprobe", "tun").CombinedOutput()
- if err == nil {
- logf("'modprobe tun' successful")
- // Either tun is currently loaded, or it's statically
- // compiled into the kernel (which modprobe checks
- // with /lib/modules/$(uname -r)/modules.builtin)
- //
- // So if there's a problem at this point, it's
- // probably because /dev/net/tun doesn't exist.
- const dev = "/dev/net/tun"
- if fi, err := os.Stat(dev); err != nil {
- logf("tun module loaded in kernel, but %s does not exist", dev)
- } else {
- logf("%s: %v", dev, fi.Mode())
+ // TODO(bradfitz): this is O(n peers). Add ART to netaddr?
+ var best netaddr.IPPrefix
+ var bestKey tailcfg.NodeKey
+ for _, p := range e.lastCfgFull.Peers {
+ for _, cidr := range p.AllowedIPs {
+ if !cidr.Contains(ip) {
+ continue
+ }
+ if best.IsZero() || cidr.Bits > best.Bits {
+ best = cidr
+ bestKey = tailcfg.NodeKey(p.PublicKey)
+ }
}
-
- // We failed to find why it failed. Just let our
- // caller report the error it got from wireguard-go.
- return
}
- logf("is CONFIG_TUN enabled in your kernel? `modprobe tun` failed with: %s", modprobeOut)
-
- switch distro.Get() {
- case distro.Debian:
- dpkgOut, err := exec.Command("dpkg", "-S", "kernel/drivers/net/tun.ko").CombinedOutput()
- if len(bytes.TrimSpace(dpkgOut)) == 0 || err != nil {
- logf("tun module not loaded nor found on disk")
- return
- }
- if !bytes.Contains(dpkgOut, kernel) {
- logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", dpkgOut)
- }
- case distro.Arch:
- findOut, err := exec.Command("find", "/lib/modules/", "-path", "*/net/tun.ko*").CombinedOutput()
- if len(bytes.TrimSpace(findOut)) == 0 || err != nil {
- logf("tun module not loaded nor found on disk")
- return
- }
- if !bytes.Contains(findOut, kernel) {
- logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", findOut)
- }
- case distro.OpenWrt:
- out, err := exec.Command("opkg", "list-installed").CombinedOutput()
- if err != nil {
- logf("error querying OpenWrt installed packages: %s", out)
- return
- }
- for _, pkg := range []string{"kmod-tun", "ca-bundle"} {
- if !bytes.Contains(out, []byte(pkg+" - ")) {
- logf("Missing required package %s; run: opkg install %s", pkg, pkg)
+ // And another pass. Probably better than allocating a map per peerForIP
+ // call. But TODO(bradfitz): add a lookup map to netmap.NetworkMap.
+ if !bestKey.IsZero() {
+ for _, p := range nm.Peers {
+ if p.Key == bestKey {
+ return p, nil
}
}
}
+ if bestInNM == nil {
+ return nil, nil
+ }
+ if bestInNMPrefix.Bits == 0 {
+ return nil, errors.New("exit node found but not enabled")
+ }
+ return nil, fmt.Errorf("node %q found, but not using its %v route", bestInNM.ComputedNameWithHost, bestInNMPrefix)
}
type closeOnErrorPool []func()
diff --git a/wgengine/userspace_test.go b/wgengine/userspace_test.go
index e9b83389a..ac081d83e 100644
--- a/wgengine/userspace_test.go
+++ b/wgengine/userspace_test.go
@@ -13,10 +13,10 @@ import (
"go4.org/mem"
"inet.af/netaddr"
+ "tailscale.com/net/tstun"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/wgengine/router"
- "tailscale.com/wgengine/tstun"
"tailscale.com/wgengine/wgcfg"
)
@@ -39,7 +39,7 @@ func TestNoteReceiveActivity(t *testing.T) {
logf: func(format string, a ...interface{}) {
fmt.Fprintf(&logBuf, format, a...)
},
- tundev: new(tstun.TUN),
+ tundev: new(tstun.Wrapper),
testMaybeReconfigHook: func() { confc <- true },
trimmedDisco: map[tailcfg.DiscoKey]bool{},
}
@@ -139,3 +139,50 @@ func dkFromHex(hex string) tailcfg.DiscoKey {
}
return tailcfg.DiscoKey(k)
}
+
+// an experiment to see if genLocalAddrFunc was worth it. As of Go
+// 1.16, it still very much is. (30-40x faster)
+func BenchmarkGenLocalAddrFunc(b *testing.B) {
+ la1 := netaddr.MustParseIP("1.2.3.4")
+ la2 := netaddr.MustParseIP("::4")
+ lanot := netaddr.MustParseIP("5.5.5.5")
+ var x bool
+ b.Run("map1", func(b *testing.B) {
+ m := map[netaddr.IP]bool{
+ la1: true,
+ }
+ for i := 0; i < b.N; i++ {
+ x = m[la1]
+ x = m[lanot]
+ }
+ })
+ b.Run("map2", func(b *testing.B) {
+ m := map[netaddr.IP]bool{
+ la1: true,
+ la2: true,
+ }
+ for i := 0; i < b.N; i++ {
+ x = m[la1]
+ x = m[lanot]
+ }
+ })
+ b.Run("or1", func(b *testing.B) {
+ f := func(t netaddr.IP) bool {
+ return t == la1
+ }
+ for i := 0; i < b.N; i++ {
+ x = f(la1)
+ x = f(lanot)
+ }
+ })
+ b.Run("or2", func(b *testing.B) {
+ f := func(t netaddr.IP) bool {
+ return t == la1 || t == la2
+ }
+ for i := 0; i < b.N; i++ {
+ x = f(la1)
+ x = f(lanot)
+ }
+ })
+ b.Logf("x = %v", x)
+}
diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go
index f4f7d3085..f3248d6fc 100644
--- a/wgengine/watchdog.go
+++ b/wgengine/watchdog.go
@@ -14,12 +14,12 @@ import (
"inet.af/netaddr"
"tailscale.com/ipn/ipnstate"
+ "tailscale.com/net/dns"
"tailscale.com/tailcfg"
"tailscale.com/types/netmap"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/router"
- "tailscale.com/wgengine/tsdns"
"tailscale.com/wgengine/wgcfg"
)
@@ -84,7 +84,7 @@ func (e *watchdogEngine) GetFilter() *filter.Filter {
func (e *watchdogEngine) SetFilter(filt *filter.Filter) {
e.watchdog("SetFilter", func() { e.wrap.SetFilter(filt) })
}
-func (e *watchdogEngine) SetDNSMap(dm *tsdns.Map) {
+func (e *watchdogEngine) SetDNSMap(dm *dns.Map) {
e.watchdog("SetDNSMap", func() { e.wrap.SetDNSMap(dm) })
}
func (e *watchdogEngine) SetStatusCallback(cb StatusCallback) {
@@ -117,8 +117,8 @@ func (e *watchdogEngine) DiscoPublicKey() (k tailcfg.DiscoKey) {
e.watchdog("DiscoPublicKey", func() { k = e.wrap.DiscoPublicKey() })
return k
}
-func (e *watchdogEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) {
- e.watchdog("Ping", func() { e.wrap.Ping(ip, cb) })
+func (e *watchdogEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) {
+ e.watchdog("Ping", func() { e.wrap.Ping(ip, useTSMP, cb) })
}
func (e *watchdogEngine) RegisterIPPortIdentity(ipp netaddr.IPPort, tsIP netaddr.IP) {
e.watchdog("RegisterIPPortIdentity", func() { e.wrap.RegisterIPPortIdentity(ipp, tsIP) })
diff --git a/wgengine/watchdog_test.go b/wgengine/watchdog_test.go
index 7487d4827..0a699f848 100644
--- a/wgengine/watchdog_test.go
+++ b/wgengine/watchdog_test.go
@@ -7,6 +7,7 @@ package wgengine
import (
"bytes"
"fmt"
+ "runtime"
"strings"
"testing"
"time"
@@ -15,6 +16,13 @@ import (
func TestWatchdog(t *testing.T) {
t.Parallel()
+ var maxWaitMultiple time.Duration = 1
+ if runtime.GOOS == "darwin" {
+ // Work around slow close syscalls on Big Sur with content filter Network Extensions installed.
+ // See https://github.com/tailscale/tailscale/issues/1598.
+ maxWaitMultiple = 15
+ }
+
t.Run("default watchdog does not fire", func(t *testing.T) {
t.Parallel()
e, err := NewFakeUserspaceEngine(t.Logf, 0)
@@ -23,7 +31,7 @@ func TestWatchdog(t *testing.T) {
}
e = NewWatchdog(e)
- e.(*watchdogEngine).maxWait = 150 * time.Millisecond
+ e.(*watchdogEngine).maxWait = maxWaitMultiple * 150 * time.Millisecond
e.(*watchdogEngine).logf = t.Logf
e.(*watchdogEngine).fatalf = t.Fatalf
@@ -42,7 +50,7 @@ func TestWatchdog(t *testing.T) {
usEngine := e.(*userspaceEngine)
e = NewWatchdog(e)
wdEngine := e.(*watchdogEngine)
- wdEngine.maxWait = 100 * time.Millisecond
+ wdEngine.maxWait = maxWaitMultiple * 100 * time.Millisecond
logBuf := new(bytes.Buffer)
fatalCalled := make(chan struct{})
diff --git a/wgengine/wgcfg/device_test.go b/wgengine/wgcfg/device_test.go
index d48da7c52..6bab065a5 100644
--- a/wgengine/wgcfg/device_test.go
+++ b/wgengine/wgcfg/device_test.go
@@ -55,12 +55,8 @@ func TestDeviceConfig(t *testing.T) {
}},
}
- device1 := device.NewDevice(newNilTun(), &device.DeviceOptions{
- Logger: device.NewLogger(device.LogLevelError, "device1"),
- })
- device2 := device.NewDevice(newNilTun(), &device.DeviceOptions{
- Logger: device.NewLogger(device.LogLevelError, "device2"),
- })
+ device1 := device.NewDevice(newNilTun(), device.NewLogger(device.LogLevelError, "device1"))
+ device2 := device.NewDevice(newNilTun(), device.NewLogger(device.LogLevelError, "device2"))
defer device1.Close()
defer device2.Close()
diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go
index c8e7963db..5946e1e77 100644
--- a/wgengine/wgengine.go
+++ b/wgengine/wgengine.go
@@ -9,12 +9,12 @@ import (
"inet.af/netaddr"
"tailscale.com/ipn/ipnstate"
+ "tailscale.com/net/dns"
"tailscale.com/tailcfg"
"tailscale.com/types/netmap"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/router"
- "tailscale.com/wgengine/tsdns"
"tailscale.com/wgengine/wgcfg"
)
@@ -66,7 +66,7 @@ type Engine interface {
SetFilter(*filter.Filter)
// SetDNSMap updates the DNS map.
- SetDNSMap(*tsdns.Map)
+ SetDNSMap(*dns.Map)
// SetStatusCallback sets the function to call when the
// WireGuard status changes.
@@ -136,7 +136,7 @@ type Engine interface {
// Ping is a request to start a discovery ping with the peer handling
// the given IP and then call cb with its ping latency & method.
- Ping(ip netaddr.IP, cb func(*ipnstate.PingResult))
+ Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult))
// RegisterIPPortIdentity registers a given node (identified by its
// Tailscale IP) as temporarily having the given IP:port for whois lookups.