mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-30 07:43:42 +00:00
merge with main
Signed-off-by: Naman Sood <mail@nsood.in>
This commit is contained in:
commit
c0a88a0129
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
69
cmd/tailscale/cli/ip.go
Normal file
69
cmd/tailscale/cli/ip.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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+
|
||||
|
@ -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+
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
31
control/controlclient/sign.go
Normal file
31
control/controlclient/sign.go
Normal file
@ -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)
|
||||
}
|
160
control/controlclient/sign_supported.go
Normal file
160
control/controlclient/sign_supported.go
Normal file
@ -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
|
||||
}
|
17
control/controlclient/sign_unsupported.go
Normal file
17
control/controlclient/sign_unsupported.go
Normal file
@ -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
|
||||
}
|
7
go.mod
7
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
|
||||
|
328
go.sum
328
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=
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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{}})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
167
ipn/ipnlocal/peerapi.go
Normal file
167
ipn/ipnlocal/peerapi.go
Normal file
@ -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")
|
||||
}
|
||||
}
|
54
ipn/ipnlocal/peerapi_macios_ext.go
Normal file
54
ipn/ipnlocal/peerapi_macios_ext.go
Normal file
@ -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
|
||||
}
|
@ -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) {
|
||||
// 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.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) {
|
||||
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 int // non-zero if DERP was used
|
||||
DERPRegionCode string // three-letter airport/region code if DERP was used
|
||||
// DERPRegionID is non-zero DERP region ID if DERP was used.
|
||||
// It is not currently set for TSMP pings.
|
||||
DERPRegionID int
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
|
19
net/dns/flush_windows.go
Normal file
19
net/dns/flush_windows.go
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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"
|
@ -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"
|
@ -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"
|
@ -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 }
|
@ -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"
|
@ -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),
|
@ -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"
|
@ -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"
|
@ -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.
|
||||
|
@ -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,10 +289,71 @@ func (s *State) String() string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (s *State) Equal(s2 *State) bool {
|
||||
return reflect.DeepEqual(s, s2)
|
||||
// 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
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
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.
|
||||
@ -297,41 +361,6 @@ 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)
|
||||
continue
|
||||
}
|
||||
if len(keep) < len(ips) {
|
||||
s.InterfaceIPs[ifName] = keep
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 hasTailscaleIP(pfxs []netaddr.IPPrefix) bool {
|
||||
for _, pfx := range pfxs {
|
||||
if tsaddr.IsTailscaleIP(pfx.IP) {
|
||||
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
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
|
||||
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
|
||||
}
|
||||
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
|
||||
|
||||
if iface, err := luid.IPInterface(windows.AF_INET); err == nil {
|
||||
metric = iface.Metric
|
||||
} else {
|
||||
log.Printf("routerIP/luid.IPInterface error: %v", err)
|
||||
metric = fallbackInterfaceMetric
|
||||
}
|
||||
ipm := f[2]
|
||||
ip, err := netaddr.ParseIP(string(mem.Append(nil, ipm)))
|
||||
if err == nil && isPrivateIP(ip) {
|
||||
|
||||
ifaceMetricCache[luid] = metric
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
ip, ok := netaddr.FromStdIP(r.NextHop.IP())
|
||||
if !ok {
|
||||
// Not a valid gateway, so skip (won't happen though)
|
||||
continue
|
||||
}
|
||||
|
||||
if best == nil {
|
||||
best = r
|
||||
ret = ip
|
||||
continue
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if !ret.IsZero() && !isPrivateIP(ret) {
|
||||
// Default route has a non-private gateway
|
||||
return netaddr.IP{}, false
|
||||
}
|
||||
|
||||
return ret, !ret.IsZero()
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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{}),
|
@ -4,7 +4,7 @@
|
||||
|
||||
// +build !windows
|
||||
|
||||
package wgengine
|
||||
package tstun
|
||||
|
||||
import (
|
||||
"time"
|
@ -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"
|
129
net/tstun/tun.go
Normal file
129
net/tstun/tun.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}{})
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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{}
|
||||
stop chan struct{} // closed on Stop
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user