From 0c3e9722ccbc11778e946b448f8599d77eb669a3 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 18 Mar 2021 15:42:55 -0700 Subject: [PATCH 01/59] cmd/tailscale/cli: fix typo in comment Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/up.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index 75acae665..118f002e1 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -315,7 +315,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 From d0dffe33c081ba719d437f38893eec663980d625 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 18 Mar 2021 19:34:59 -0700 Subject: [PATCH 02/59] cmd/tailscale, ipn/localapi: use localapi for status, not IPN acrobatics Yay simpler code. Tested on Linux, macOS and Windows. Signed-off-by: Brad Fitzpatrick --- client/tailscale/tailscale.go | 24 +++++++++++++++++++ cmd/tailscale/cli/down.go | 24 +++++++++---------- cmd/tailscale/cli/ping.go | 23 +++++++----------- cmd/tailscale/cli/status.go | 45 +++-------------------------------- cmd/tailscale/cli/up.go | 3 ++- cmd/tailscale/cli/version.go | 4 ++-- ipn/backend.go | 4 ---- ipn/fake_test.go | 4 ---- ipn/handle.go | 4 ---- ipn/ipnlocal/local.go | 6 ----- ipn/localapi/localapi.go | 13 ++++++++++ ipn/message.go | 3 --- 12 files changed, 65 insertions(+), 92 deletions(-) diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index a6d304332..c4a4b5c77 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -15,6 +15,7 @@ import ( "net/url" "strconv" + "tailscale.com/ipn/ipnstate" "tailscale.com/safesocket" "tailscale.com/tailcfg" ) @@ -79,6 +80,7 @@ func WhoIs(ctx context.Context, remoteAddr string) (*tailcfg.WhoIsResponse, erro return r, nil } +// Goroutines returns a dump of the Tailscale daemon's current goroutines. func Goroutines(ctx context.Context) ([]byte, error) { req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/goroutines", nil) if err != nil { @@ -98,3 +100,25 @@ 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) { + req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/status", nil) + if err != nil { + return nil, err + } + res, err := DoLocalRequest(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != 200 { + body, _ := ioutil.ReadAll(res.Body) + return nil, fmt.Errorf("HTTP %s: %s", res.Status, body) + } + st := new(ipnstate.Status) + if err := json.NewDecoder(res.Body).Decode(st); err != nil { + return nil, err + } + return st, nil +} diff --git a/cmd/tailscale/cli/down.go b/cmd/tailscale/cli/down.go index 7e53c0cf2..dd1e8491f 100644 --- a/cmd/tailscale/cli/down.go +++ b/cmd/tailscale/cli/down.go @@ -6,10 +6,12 @@ package cli import ( "context" + "fmt" "log" "time" "github.com/peterbourgon/ff/v2/ffcli" + "tailscale.com/client/tailscale" "tailscale.com/ipn" ) @@ -26,6 +28,16 @@ func runDown(ctx context.Context, args []string) error { log.Fatalf("too many non-flag arguments: %q", args) } + st, err := tailscale.Status(ctx) + if err != nil { + return fmt.Errorf("error fetching current status: %w", err) + } + if st.BackendState == "Stopped" { + log.Printf("already stopped") + return nil + } + log.Printf("was in state %q", st.BackendState) + c, bc, ctx, cancel := connect(ctx) defer cancel() @@ -38,17 +50,6 @@ func runDown(ctx context.Context, args []string) error { if n.ErrMessage != nil { log.Fatal(*n.ErrMessage) } - if n.Status != nil { - cur := n.Status.BackendState - switch cur { - case "Stopped": - log.Printf("already stopped") - cancel() - default: - log.Printf("was in state %q", cur) - } - return - } if n.State != nil { log.Printf("now in state %q", *n.State) if *n.State == ipn.Stopped { @@ -58,7 +59,6 @@ func runDown(ctx context.Context, args []string) error { } }) - bc.RequestStatus() bc.SetWantRunning(false) pump(ctx, bc, c) diff --git a/cmd/tailscale/cli/ping.go b/cmd/tailscale/cli/ping.go index f98a08d4f..e09a6a75b 100644 --- a/cmd/tailscale/cli/ping.go +++ b/cmd/tailscale/cli/ping.go @@ -15,6 +15,7 @@ import ( "time" "github.com/peterbourgon/ff/v2/ffcli" + "tailscale.com/client/tailscale" "tailscale.com/ipn" "tailscale.com/ipn/ipnstate" ) @@ -69,7 +70,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 +77,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 +89,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() } } diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index 851b0c2bd..46238fa45 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -10,7 +10,6 @@ import ( "encoding/json" "flag" "fmt" - "log" "net" "net/http" "os" @@ -19,6 +18,7 @@ import ( "github.com/peterbourgon/ff/v2/ffcli" "github.com/toqueteos/webbrowser" + "tailscale.com/client/tailscale" "tailscale.com/ipn" "tailscale.com/ipn/ipnstate" "tailscale.com/net/interfaces" @@ -53,47 +53,8 @@ var statusArgs struct { peers bool // in CLI mode, show status of peer machines } -func getStatusFromServer(ctx context.Context, c net.Conn, bc *ipn.BackendClient) func() (*ipnstate.Status, error) { - ch := make(chan *ipnstate.Status, 1) - bc.SetNotifyCallback(func(n ipn.Notify) { - if n.ErrMessage != nil { - log.Fatal(*n.ErrMessage) - } - if n.Status != nil { - select { - case ch <- n.Status: - default: - // A status update from somebody else's request. - // Ignoring this matters mostly for "tailscale status -web" - // mode, otherwise the channel send would block forever - // and pump would stop reading from tailscaled, which - // previously caused tailscaled to block (while holding - // a mutex), backing up unrelated clients. - // See https://github.com/tailscale/tailscale/issues/1234 - } - } - }) - go pump(ctx, bc, c) - - return func() (*ipnstate.Status, error) { - bc.RequestStatus() - select { - case st := <-ch: - return st, nil - case <-ctx.Done(): - return nil, ctx.Err() - } - } -} - func runStatus(ctx context.Context, args []string) error { - c, bc, ctx, cancel := connect(ctx) - defer cancel() - - bc.AllowVersionSkew = true - - getStatus := getStatusFromServer(ctx, c, bc) - st, err := getStatus() + st, err := tailscale.Status(ctx) if err != nil { return err } @@ -131,7 +92,7 @@ func runStatus(ctx context.Context, args []string) error { http.NotFound(w, r) return } - st, err := getStatus() + st, err := tailscale.Status(ctx) if err != nil { http.Error(w, err.Error(), 500) return diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index 118f002e1..f8084759c 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -21,6 +21,7 @@ import ( "github.com/peterbourgon/ff/v2/ffcli" "inet.af/netaddr" + "tailscale.com/client/tailscale" "tailscale.com/ipn" "tailscale.com/tailcfg" "tailscale.com/types/preftype" @@ -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) } diff --git a/cmd/tailscale/cli/version.go b/cmd/tailscale/cli/version.go index a6bbb6912..e9a39de2b 100644 --- a/cmd/tailscale/cli/version.go +++ b/cmd/tailscale/cli/version.go @@ -53,14 +53,14 @@ func runVersion(ctx context.Context, args []string) error { if n.ErrMessage != nil { log.Fatal(*n.ErrMessage) } - if n.Status != nil { + if n.Engine != nil { fmt.Printf("Daemon: %s\n", n.Version) close(done) } }) go pump(ctx, bc, c) - bc.RequestStatus() + bc.RequestEngineStatus() select { case <-done: return nil diff --git a/ipn/backend.go b/ipn/backend.go index 9352853b1..aa6115a6b 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -65,7 +65,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 @@ -159,9 +158,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 diff --git a/ipn/fake_test.go b/ipn/fake_test.go index e918f77f0..709b4b547 100644 --- a/ipn/fake_test.go +++ b/ipn/fake_test.go @@ -87,10 +87,6 @@ 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{}}) } diff --git a/ipn/handle.go b/ipn/handle.go index 91b757f56..fc69181ea 100644 --- a/ipn/handle.go +++ b/ipn/handle.go @@ -167,10 +167,6 @@ func (h *Handle) RequestEngineStatus() { h.b.RequestEngineStatus() } -func (h *Handle) RequestStatus() { - h.b.RequestStatus() -} - func (h *Handle) FakeExpireAfter(x time.Duration) { h.b.FakeExpireAfter(x) } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 38c9323d7..377f838ce 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1617,12 +1617,6 @@ func (b *LocalBackend) RequestEngineStatus() { b.e.RequestStatus() } -// RequestStatus implements Backend. -func (b *LocalBackend) RequestStatus() { - st := b.Status() - b.send(ipn.Notify{Status: st}) -} - // stateMachine updates the state machine state based on other things // that have happened. It is invoked from the various callbacks that // feed events into LocalBackend. diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 4e0dba3da..d9ad7bf13 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -56,6 +56,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 +111,14 @@ 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") + e := json.NewEncoder(w) + e.SetIndent("", "\t") + e.Encode(h.b.Status()) +} diff --git a/ipn/message.go b/ipn/message.go index 9b7783f98..f64d2efad 100644 --- a/ipn/message.go +++ b/ipn/message.go @@ -173,9 +173,6 @@ 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) return nil From 439d70dce2fa1df2f1f1d8801cd3c0a622f51c1a Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 18 Mar 2021 21:07:58 -0700 Subject: [PATCH 03/59] cmd/tailscale, ipn/localapi: get daemon version from localapi status Signed-off-by: Brad Fitzpatrick --- client/tailscale/tailscale.go | 11 ++++- cmd/tailscale/cli/version.go | 31 +++---------- ipn/ipnlocal/local.go | 86 ++++++++++++++++++++++------------- ipn/ipnstate/ipnstate.go | 13 ++++++ ipn/localapi/localapi.go | 21 ++++++++- 5 files changed, 104 insertions(+), 58 deletions(-) diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index c4a4b5c77..3947f4ff5 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -103,7 +103,16 @@ func Goroutines(ctx context.Context) ([]byte, error) { // Status returns the Tailscale daemon's status. func Status(ctx context.Context) (*ipnstate.Status, error) { - req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/status", nil) + 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 } diff --git a/cmd/tailscale/cli/version.go b/cmd/tailscale/cli/version.go index e9a39de2b..2c6f97a3b 100644 --- a/cmd/tailscale/cli/version.go +++ b/cmd/tailscale/cli/version.go @@ -11,7 +11,7 @@ import ( "log" "github.com/peterbourgon/ff/v2/ffcli" - "tailscale.com/ipn" + "tailscale.com/client/tailscale" "tailscale.com/version" ) @@ -42,29 +42,10 @@ func runVersion(ctx context.Context, args []string) error { fmt.Printf("Client: %s\n", version.String()) - c, bc, ctx, cancel := connect(ctx) - defer cancel() - - bc.AllowVersionSkew = true - - done := make(chan struct{}) - - bc.SetNotifyCallback(func(n ipn.Notify) { - if n.ErrMessage != nil { - log.Fatal(*n.ErrMessage) - } - if n.Engine != nil { - fmt.Printf("Daemon: %s\n", n.Version) - close(done) - } - }) - go pump(ctx, bc, c) - - bc.RequestEngineStatus() - select { - case <-done: - return nil - case <-ctx.Done(): - return ctx.Err() + st, err := tailscale.StatusWithoutPeers(ctx) + if err != nil { + return err } + fmt.Printf("Daemon: %s\n", st.Version) + return nil } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 377f838ce..3c4b75361 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -218,52 +218,76 @@ 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.SetVersion(version.Long) sb.SetBackendState(b.state.String()) sb.SetAuthURL(b.authURL) - // 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, + }) } } diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index b89f7bb08..a419067ad 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -26,7 +26,14 @@ import ( // Status represents the entire state of the IPN network. type Status struct { + // Version is the daemon's long version (see version.Long). + Version string + + // BackendState is an ipn.State string value: + // "NoState", "NeedsLogin", "NeedsMachineAuth", "Stopped", + // "Starting", "Running". BackendState string + AuthURL string // current URL provided by control to authorize client TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node Self *PeerStatus @@ -105,6 +112,12 @@ type StatusBuilder struct { st Status } +func (sb *StatusBuilder) SetVersion(v string) { + sb.mu.Lock() + defer sb.mu.Unlock() + sb.st.Version = v +} + func (sb *StatusBuilder) SetBackendState(v string) { sb.mu.Lock() defer sb.mu.Unlock() diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index d9ad7bf13..169f18ba4 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -10,9 +10,11 @@ import ( "io" "net/http" "runtime" + "strconv" "inet.af/netaddr" "tailscale.com/ipn/ipnlocal" + "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" ) @@ -118,7 +120,24 @@ func (h *Handler) serveStatus(w http.ResponseWriter, r *http.Request) { 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(h.b.Status()) + 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 } From 7b5731096621b3abb87b859c2e3d14c65f302c36 Mon Sep 17 00:00:00 2001 From: Aleksandar Pesic Date: Tue, 16 Mar 2021 15:52:26 +0100 Subject: [PATCH 04/59] net/interfaces: use windows API to get the default route instead of parsing `route print` output Fixes: #1470 Signed-off-by: Aleksandar Pesic --- cmd/tailscale/depaware.txt | 2 +- cmd/tailscaled/depaware.txt | 2 +- net/interfaces/interfaces_windows.go | 112 ++++++++++++++++----------- 3 files changed, 68 insertions(+), 48 deletions(-) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 2e722db5c..e21f4868c 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -50,7 +50,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 diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index c9cd378e5..c0ec16df6 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -126,7 +126,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/wgkey from tailscale.com/control/controlclient+ tailscale.com/util/dnsname from tailscale.com/wgengine/tsdns+ 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+ diff --git a/net/interfaces/interfaces_windows.go b/net/interfaces/interfaces_windows.go index 19e9b48b4..91f679c97 100644 --- a/net/interfaces/interfaces_windows.go +++ b/net/interfaces/interfaces_windows.go @@ -7,17 +7,19 @@ package interfaces import ( "fmt" "log" + "net" "net/url" - "os/exec" "syscall" "unsafe" - "go4.org/mem" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "inet.af/netaddr" "tailscale.com/tsconst" - "tailscale.com/util/lineread" +) + +const ( + fallbackInterfaceMetric = uint32(0) // Used if we cannot get the actual interface metric ) func init() { @@ -25,58 +27,76 @@ func init() { getPAC = getPACWindows } -/* -Parse out 10.0.0.1 from: - -Z:\>route print -4 -=========================================================================== -Interface List - 15...aa 15 48 ff 1c 72 ......Red Hat VirtIO Ethernet Adapter - 5...........................Tailscale Tunnel - 1...........................Software Loopback Interface 1 -=========================================================================== - -IPv4 Route Table -=========================================================================== -Active Routes: -Network Destination Netmask Gateway Interface Metric - 0.0.0.0 0.0.0.0 10.0.0.1 10.0.28.63 5 - 10.0.0.0 255.255.0.0 On-link 10.0.28.63 261 - 10.0.28.63 255.255.255.255 On-link 10.0.28.63 261 - 10.0.42.0 255.255.255.0 100.103.42.106 100.103.42.106 5 - 10.0.255.255 255.255.255.255 On-link 10.0.28.63 261 - 34.193.248.174 255.255.255.255 100.103.42.106 100.103.42.106 5 - -*/ func likelyHomeRouterIPWindows() (ret netaddr.IP, ok bool) { - cmd := exec.Command("route", "print", "-4") - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - stdout, err := cmd.StdoutPipe() + rs, err := winipcfg.GetIPForwardTable2(windows.AF_INET) if err != nil { + log.Printf("routerIP/GetIPForwardTable2 error: %v", err) return } - if err := cmd.Start(); err != nil { - 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() } From 0a02aaf8134e7ba18cdc307adc07e23d371240dc Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 19 Mar 2021 10:21:33 -0700 Subject: [PATCH 05/59] control, ipn, tailcfg: remove golang.org/x/oauth2 dep, add tailcfg.Oauth2Token golang.org/x/oauth2 pulls in App Engine and grpc module dependencies, screwing up builds that depend on this module. Some background on the problem: https://go.googlesource.com/proposal/+/master/design/36460-lazy-module-loading.md Fixes tailscale/corp#1471 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/depaware.txt | 7 +- cmd/tailscaled/depaware.txt | 5 +- control/controlclient/auto.go | 11 +- control/controlclient/direct.go | 7 +- go.mod | 2 - go.sum | 315 -------------------------------- ipn/backend.go | 5 +- ipn/fake_test.go | 4 +- ipn/handle.go | 4 +- ipn/ipnlocal/local.go | 3 +- ipn/message.go | 6 +- ipn/message_test.go | 4 +- tailcfg/tailcfg.go | 28 ++- 13 files changed, 49 insertions(+), 352 deletions(-) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index e21f4868c..5c2bd8503 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -65,7 +65,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 +72,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 +132,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+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index c0ec16df6..7fc2c90fc 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -159,7 +159,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 +168,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 +238,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de math/big from crypto/dsa+ math/bits from compress/flate+ math/rand from github.com/mdlayher/netlink+ - mime from golang.org/x/oauth2/internal+ + mime from mime/multipart+ mime/multipart from net/http mime/quotedprintable from mime/multipart net from crypto/tls+ diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go index 2549bd9af..c731666e1 100644 --- a/control/controlclient/auto.go +++ b/control/controlclient/auto.go @@ -17,7 +17,6 @@ import ( "sync" "time" - "golang.org/x/oauth2" "tailscale.com/health" "tailscale.com/logtail/backoff" "tailscale.com/tailcfg" @@ -102,10 +101,10 @@ func (s Status) String() string { type LoginGoal struct { _ structs.Incomparable - wantLoggedIn bool // true if we *want* to be logged in - token *oauth2.Token // oauth token to use when logging in - flags LoginFlags // flags to use when logging in - url string // auth url that needs to be visited + wantLoggedIn bool // true if we *want* to be logged in + token *tailcfg.Oauth2Token // oauth token to use when logging in + flags LoginFlags // flags to use when logging in + url string // auth url that needs to be visited } // Client connects to a tailcontrol server for a node. @@ -668,7 +667,7 @@ func (c *Client) sendStatus(who string, err error, url string, nm *netmap.Networ c.mu.Unlock() } -func (c *Client) Login(t *oauth2.Token, flags LoginFlags) { +func (c *Client) Login(t *tailcfg.Oauth2Token, flags LoginFlags) { c.logf("client.Login(%v, %v)", t != nil, flags) c.mu.Lock() diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 2977ece67..5254e5d17 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -31,7 +31,6 @@ import ( "time" "golang.org/x/crypto/nacl/box" - "golang.org/x/oauth2" "inet.af/netaddr" "tailscale.com/health" "tailscale.com/log/logheap" @@ -266,7 +265,7 @@ func (c *Direct) TryLogout(ctx context.Context) error { return nil } -func (c *Direct) TryLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags) (url string, err error) { +func (c *Direct) TryLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags) (url string, err error) { c.logf("direct.TryLogin(token=%v, flags=%v)", t != nil, flags) return c.doLoginOrRegen(ctx, t, flags, false, "") } @@ -276,7 +275,7 @@ func (c *Direct) WaitLoginURL(ctx context.Context, url string) (newUrl string, e return c.doLoginOrRegen(ctx, nil, LoginDefault, false, url) } -func (c *Direct) doLoginOrRegen(ctx context.Context, t *oauth2.Token, flags LoginFlags, regen bool, url string) (newUrl string, err error) { +func (c *Direct) doLoginOrRegen(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags, regen bool, url string) (newUrl string, err error) { mustregen, url, err := c.doLogin(ctx, t, flags, regen, url) if err != nil { return url, err @@ -288,7 +287,7 @@ func (c *Direct) doLoginOrRegen(ctx context.Context, t *oauth2.Token, flags Logi return url, err } -func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags, regen bool, url string) (mustregen bool, newurl string, err error) { +func (c *Direct) doLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags, regen bool, url string) (mustregen bool, newurl string, err error) { c.mu.Lock() persist := c.persist tryingNewKey := c.tryingNewKey diff --git a/go.mod b/go.mod index 4d4fa34c4..43c3ae36a 100644 --- a/go.mod +++ b/go.mod @@ -29,14 +29,12 @@ require ( 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 diff --git a/go.sum b/go.sum index f954a508b..e30861457 100644 --- a/go.sum +++ b/go.sum @@ -1,39 +1,5 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14= github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= @@ -47,12 +13,6 @@ 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= @@ -61,81 +21,30 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/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 +57,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 +103,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= @@ -217,15 +123,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,8 +133,6 @@ 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= @@ -244,67 +141,19 @@ golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/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,54 +163,20 @@ 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= @@ -383,60 +198,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= @@ -448,82 +219,6 @@ golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9 h1:qowcZ5 golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9/go.mod h1:LMeNfjlcPZTrBC1juwgbQyA4Zy2XVcsrdO/fIJxwyuA= 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 +229,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 +237,5 @@ inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22 h1:DNtszwGa6w76qlIr+PbPEnlBJ inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22/go.mod h1:GVx+5OZtbG4TVOW5ilmyRZAZXr1cNwfqUEkTOtWK0PM= inet.af/peercred v0.0.0-20210302202138-56e694897155 h1:KojYNEYqDkZ2O3LdyTstR1l13L3ePKTIEM2h7ONkfkE= inet.af/peercred v0.0.0-20210302202138-56e694897155/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w= rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/ipn/backend.go b/ipn/backend.go index aa6115a6b..5ef54fde1 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -8,7 +8,6 @@ import ( "net/http" "time" - "golang.org/x/oauth2" "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" "tailscale.com/types/empty" @@ -28,7 +27,7 @@ const ( Running ) -// GoogleIDToken Type is the oauth2.Token.TokenType for the Google +// GoogleIDToken Type is the tailcfg.Oauth2Token.TokenType for the Google // ID tokens used by the Android client. const GoogleIDTokenType = "ts_android_google_login" @@ -142,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() diff --git a/ipn/fake_test.go b/ipn/fake_test.go index 709b4b547..98685b013 100644 --- a/ipn/fake_test.go +++ b/ipn/fake_test.go @@ -8,8 +8,8 @@ import ( "log" "time" - "golang.org/x/oauth2" "tailscale.com/ipn/ipnstate" + "tailscale.com/tailcfg" "tailscale.com/types/netmap" ) @@ -46,7 +46,7 @@ func (b *FakeBackend) StartLoginInteractive() { b.login() } -func (b *FakeBackend) Login(token *oauth2.Token) { +func (b *FakeBackend) Login(token *tailcfg.Oauth2Token) { b.login() } diff --git a/ipn/handle.go b/ipn/handle.go index fc69181ea..54a61140e 100644 --- a/ipn/handle.go +++ b/ipn/handle.go @@ -8,8 +8,8 @@ import ( "sync" "time" - "golang.org/x/oauth2" "inet.af/netaddr" + "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/types/netmap" ) @@ -155,7 +155,7 @@ func (h *Handle) StartLoginInteractive() { h.b.StartLoginInteractive() } -func (h *Handle) Login(token *oauth2.Token) { +func (h *Handle) Login(token *tailcfg.Oauth2Token) { h.b.Login(token) } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 3c4b75361..c488a6e95 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -15,7 +15,6 @@ import ( "sync" "time" - "golang.org/x/oauth2" "inet.af/netaddr" "tailscale.com/control/controlclient" "tailscale.com/health" @@ -1102,7 +1101,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 diff --git a/ipn/message.go b/ipn/message.go index f64d2efad..96b4708db 100644 --- a/ipn/message.go +++ b/ipn/message.go @@ -15,7 +15,7 @@ import ( "log" "time" - "golang.org/x/oauth2" + "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/types/structs" "tailscale.com/version" @@ -76,7 +76,7 @@ type Command struct { Quit *NoArgs Start *StartArgs StartLoginInteractive *NoArgs - Login *oauth2.Token + Login *tailcfg.Oauth2Token Logout *NoArgs SetPrefs *SetPrefsArgs SetWantRunning *bool @@ -296,7 +296,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}) } diff --git a/ipn/message_test.go b/ipn/message_test.go index 4422a64ca..59a9eef25 100644 --- a/ipn/message_test.go +++ b/ipn/message_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "golang.org/x/oauth2" + "tailscale.com/tailcfg" "tailscale.com/tstest" ) @@ -176,7 +176,7 @@ func TestClientServer(t *testing.T) { h.Logout() flushUntil(NeedsLogin) - h.Login(&oauth2.Token{ + h.Login(&tailcfg.Oauth2Token{ AccessToken: "google_id_token", TokenType: GoogleIDTokenType, }) diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index f4992124e..70342f0a2 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -15,7 +15,6 @@ import ( "time" "go4.org/mem" - "golang.org/x/oauth2" "inet.af/netaddr" "tailscale.com/types/key" "tailscale.com/types/opt" @@ -552,7 +551,7 @@ 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 @@ -975,3 +974,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"` +} From 8c0a0450d911ff9b0f2ffa5ffd8e73d6abf0366c Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 17 Mar 2021 17:04:32 -0700 Subject: [PATCH 06/59] ipn/ipnlocal: allow client access to exit node's public IPs. "public IP" is defined as an IP address configured on the exit node itself that isn't in the list of forbidden ranges (RFC1918, CGNAT, Tailscale). Fixes #1522. Signed-off-by: David Anderson --- ipn/ipnlocal/local.go | 21 +++++++++++++++++++++ ipn/ipnlocal/local_test.go | 32 +++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index c488a6e95..0d8ef1582 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -720,10 +720,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(), } @@ -733,6 +739,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 @@ -740,11 +747,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) } diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index 667cc2287..2a6fc9315 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -9,6 +9,7 @@ import ( "testing" "inet.af/netaddr" + "tailscale.com/net/interfaces" "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/types/netmap" @@ -122,11 +123,21 @@ func TestNetworkMapCompare(t *testing.T) { } } +func inRemove(ip netaddr.IP) bool { + for _, pfx := range removeFromDefaultRoute { + if pfx.Contains(ip) { + return true + } + } + return false +} + func TestShrinkDefaultRoute(t *testing.T) { tests := []struct { - route string - in []string - out []string + route string + in []string + out []string + localIPFn func(netaddr.IP) bool // true if this machine's local IP address should be "in" after shrinking. }{ { route: "0.0.0.0/0", @@ -139,19 +150,24 @@ func TestShrinkDefaultRoute(t *testing.T) { "172.16.0.1", "172.31.255.255", "100.101.102.103", + "224.0.0.1", + "169.254.169.254", // Some random IPv6 stuff that shouldn't be in a v4 // default route. "fe80::", "2601::1", }, + localIPFn: func(ip netaddr.IP) bool { return !inRemove(ip) && ip.Is4() }, }, { route: "::/0", in: []string{"::1", "2601::1"}, out: []string{ "fe80::1", + "ff00::1", tsaddr.TailscaleULARange().IP.String(), }, + localIPFn: func(ip netaddr.IP) bool { return !inRemove(ip) && ip.Is6() }, }, } @@ -171,6 +187,16 @@ func TestShrinkDefaultRoute(t *testing.T) { t.Errorf("shrink(%q).Contains(%v) = true, want false", test.route, ip) } } + ips, _, err := interfaces.LocalAddresses() + if err != nil { + t.Fatal(err) + } + for _, ip := range ips { + want := test.localIPFn(ip) + if gotContains := got.Contains(ip); gotContains != want { + t.Errorf("shrink(%q).Contains(%v) = %v, want %v", test.route, ip, gotContains, want) + } + } } } From 0406a7436ab0c4e6f5c3bacdb8dc60da7702ae25 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 19 Mar 2021 13:09:10 -0700 Subject: [PATCH 07/59] cmd/tailscale/cli: use double hypens, make default usage func more clear Mash up some code from ffcli and std's flag package to make a default usage func that's super explicit for those not familiar with the Go style flags. Only show double hyphens in usage text (but still accept both), and show default values, and only show the proper usage of boolean flags. Fixes #1353 Fixes #1529 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/cli.go | 83 ++++++++++++++++++++++++++++++++++--- cmd/tailscale/cli/status.go | 2 +- cmd/tailscale/cli/up.go | 4 +- cmd/tailscale/depaware.txt | 2 +- 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 254210ab6..3bbdb1a41 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -9,6 +9,7 @@ package cli import ( "context" "flag" + "fmt" "log" "net" "os" @@ -16,6 +17,7 @@ import ( "runtime" "strings" "syscall" + "text/tabwriter" "github.com/peterbourgon/ff/v2/ffcli" "tailscale.com/ipn" @@ -53,9 +55,7 @@ func Run(args []string) error { ShortUsage: "tailscale [flags] [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. @@ -68,8 +68,12 @@ change in the future. 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 +151,72 @@ func strSliceContains(ss []string, s string) bool { } return false } + +func usageFunc(c *ffcli.Command) string { + var b strings.Builder + + fmt.Fprintf(&b, "USAGE\n") + if c.ShortUsage != "" { + fmt.Fprintf(&b, " %s\n", c.ShortUsage) + } else { + fmt.Fprintf(&b, " %s\n", c.Name) + } + fmt.Fprintf(&b, "\n") + + if c.LongHelp != "" { + fmt.Fprintf(&b, "%s\n\n", c.LongHelp) + } + + if len(c.Subcommands) > 0 { + fmt.Fprintf(&b, "SUBCOMMANDS\n") + tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0) + for _, subcommand := range c.Subcommands { + fmt.Fprintf(tw, " %s\t%s\n", subcommand.Name, subcommand.ShortHelp) + } + tw.Flush() + fmt.Fprintf(&b, "\n") + } + + if countFlags(c.FlagSet) > 0 { + fmt.Fprintf(&b, "FLAGS\n") + tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0) + c.FlagSet.VisitAll(func(f *flag.Flag) { + var s string + name, usage := flag.UnquoteUsage(f) + if isBoolFlag(f) { + s = fmt.Sprintf(" --%s, --%s=false", f.Name, f.Name) + } else { + s = fmt.Sprintf(" --%s", f.Name) // Two spaces before --; see next two comments. + if len(name) > 0 { + s += " " + name + } + } + // Four spaces before the tab triggers good alignment + // for both 4- and 8-space tab stops. + s += "\n \t" + s += strings.ReplaceAll(usage, "\n", "\n \t") + + if f.DefValue != "" { + s += fmt.Sprintf(" (default %s)", f.DefValue) + } + + fmt.Fprintln(&b, s) + }) + tw.Flush() + fmt.Fprintf(&b, "\n") + } + + return strings.TrimSpace(b.String()) +} + +func isBoolFlag(f *flag.Flag) bool { + bf, ok := f.Value.(interface { + IsBoolFlag() bool + }) + return ok && bf.IsBoolFlag() +} + +func countFlags(fs *flag.FlagSet) (n int) { + fs.VisitAll(func(*flag.Flag) { n++ }) + return n +} diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index 46238fa45..72b843f2d 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -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 { diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index f8084759c..0a66eb139 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -49,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" { diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 5c2bd8503..16746622c 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -162,7 +162,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+ From 32562a82a9ae8df72fad8144111a048419ee9000 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 19 Mar 2021 13:18:02 -0700 Subject: [PATCH 08/59] wgengine/magicsock: annotate a few more disco logs as verbose Fixes #1540 Signed-off-by: Brad Fitzpatrick --- wgengine/magicsock/magicsock.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 917e55828..884056263 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -2034,7 +2034,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)) @@ -3589,7 +3589,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 +3602,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) } } @@ -3729,7 +3729,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 { From 90a6fb7ffe9408e2d3118b6ad6235b65eacbe4d9 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 17 Mar 2021 14:24:32 -0700 Subject: [PATCH 09/59] tailcfg: add FilterRule.IPProto Updates #1516 Signed-off-by: Brad Fitzpatrick --- tailcfg/tailcfg.go | 14 +++- wgengine/filter/filter.go | 1 + wgengine/filter/filter_test.go | 116 ++++++++++++++++++++++++++++++--- wgengine/filter/match.go | 20 +++++- wgengine/filter/match_clone.go | 7 +- wgengine/filter/tailcfg.go | 19 ++++++ wgengine/tstun/tun_test.go | 8 ++- 7 files changed, 169 insertions(+), 16 deletions(-) diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 70342f0a2..7d615867c 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -35,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 @@ -693,6 +694,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{ diff --git a/wgengine/filter/filter.go b/wgengine/filter/filter.go index cbb985114..ee11bd44d 100644 --- a/wgengine/filter/filter.go +++ b/wgengine/filter/filter.go @@ -182,6 +182,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) diff --git a/wgengine/filter/filter_test.go b/wgengine/filter/filter_test.go index 9f98761f6..3b0748a8c 100644 --- a/wgengine/filter/filter_test.go +++ b/wgengine/filter/filter_test.go @@ -7,6 +7,7 @@ package filter import ( "encoding/hex" "fmt" + "reflect" "strconv" "strings" "testing" @@ -16,19 +17,27 @@ import ( "inet.af/netaddr" "tailscale.com/net/packet" "tailscale.com/net/tsaddr" + "tailscale.com/tailcfg" "tailscale.com/types/logger" ) func newFilter(logf logger.Logf) *Filter { + m := func(srcs []netaddr.IPPrefix, dsts []NetPortRange) Match { + return Match{ + IPProto: defaultProtos, + 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("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, @@ -89,6 +98,9 @@ func TestFilter(t *testing.T) { // 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)}, + + // Don't allow protocols not specified by filter + {Drop, parsed(132 /* SCTP */, "8.1.1.1", "1.2.3.4", 999, 22)}, } for i, test := range tests { aclFunc := acl.runIn4 @@ -707,3 +719,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: []packet.IPProto{ + packet.TCP, + packet.UDP, + packet.ICMPv4, + packet.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(packet.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: []packet.IPProto{ + packet.TCP, + }, + Dsts: []NetPortRange{ + { + Net: netaddr.MustParseIPPrefix("1.2.0.0/16"), + Ports: PortRange{22, 22}, + }, + }, + Srcs: []netaddr.IPPrefix{ + netaddr.MustParseIPPrefix("100.64.1.1/32"), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := MatchesFromFilterRules(tt.in) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("wrong\n got: %v\nwant: %v\n", got, tt.want) + } + }) + } +} diff --git a/wgengine/filter/match.go b/wgengine/filter/match.go index c30c37552..799614104 100644 --- a/wgengine/filter/match.go +++ b/wgengine/filter/match.go @@ -47,11 +47,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 []packet.IPProto // 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 +74,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 +122,12 @@ func ipInList(ip netaddr.IP, netlist []netaddr.IPPrefix) bool { } return false } + +func protoInList(proto packet.IPProto, valid []packet.IPProto) bool { + for _, v := range valid { + if proto == v { + return true + } + } + return false +} diff --git a/wgengine/filter/match_clone.go b/wgengine/filter/match_clone.go index 571664bd5..b031e1e3a 100644 --- a/wgengine/filter/match_clone.go +++ b/wgengine/filter/match_clone.go @@ -8,6 +8,7 @@ package filter import ( "inet.af/netaddr" + "tailscale.com/net/packet" ) // 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 []packet.IPProto + Dsts []NetPortRange + Srcs []netaddr.IPPrefix }{}) diff --git a/wgengine/filter/tailcfg.go b/wgengine/filter/tailcfg.go index 2f20cdb61..26b7ed3da 100644 --- a/wgengine/filter/tailcfg.go +++ b/wgengine/filter/tailcfg.go @@ -9,9 +9,17 @@ import ( "strings" "inet.af/netaddr" + "tailscale.com/net/packet" "tailscale.com/tailcfg" ) +var defaultProtos = []packet.IPProto{ + packet.TCP, + packet.UDP, + packet.ICMPv4, + packet.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([]packet.IPProto(nil), defaultProtos...) + } else { + m.IPProto = make([]packet.IPProto, 0, len(r.IPProto)) + for _, n := range r.IPProto { + if n >= 0 && n <= 0xff { + m.IPProto = append(m.IPProto, packet.IPProto(n)) + } + } + } + for i, s := range r.SrcIPs { var bits *int if len(r.SrcBits) > i { diff --git a/wgengine/tstun/tun_test.go b/wgengine/tstun/tun_test.go index 365d56b4d..555324d19 100644 --- a/wgengine/tstun/tun_test.go +++ b/wgengine/tstun/tun_test.go @@ -106,9 +106,13 @@ func netports(netPorts ...string) (ret []filter.NetPortRange) { } func setfilter(logf logger.Logf, tun *TUN) { + protos := []packet.IPProto{ + packet.TCP, + packet.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")) From 01b90df2fa4f9101e4f0ae8334b00dd9c3ccc148 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 19 Mar 2021 21:05:51 -0700 Subject: [PATCH 10/59] net/packet, wgengine/filter: support SCTP Add proto to flowtrack.Tuple. Add types/ipproto leaf package to break a cycle. Server-side ACL work remains. Updates #1516 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/depaware.txt | 1 + cmd/tailscaled/depaware.txt | 1 + net/flowtrack/flowtrack.go | 10 +++--- net/packet/header.go | 1 + net/packet/ip4.go | 3 +- net/packet/ip6.go | 3 +- net/packet/packet.go | 35 +++++++++++++++++-- net/packet/packet_test.go | 35 +++++++++++++++++++ net/packet/tsmp.go | 7 ++-- net/packet/ip.go => types/ipproto/ipproto.go | 36 ++++++++++++-------- wgengine/filter/filter.go | 34 +++++++++--------- wgengine/filter/filter_test.go | 25 +++++++++----- wgengine/filter/match.go | 5 +-- wgengine/filter/match_clone.go | 4 +-- wgengine/filter/tailcfg.go | 9 ++--- wgengine/pendopen.go | 4 +-- wgengine/tstun/tun_test.go | 3 +- 17 files changed, 154 insertions(+), 62 deletions(-) rename net/packet/ip.go => types/ipproto/ipproto.go (72%) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 16746622c..74ae1d54e 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -39,6 +39,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+ W tailscale.com/tsconst from tailscale.com/net/interfaces tailscale.com/types/empty from tailscale.com/ipn + tailscale.com/types/ipproto from tailscale.com/net/flowtrack+ tailscale.com/types/key from tailscale.com/derp+ tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+ tailscale.com/types/netmap from tailscale.com/ipn diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 7fc2c90fc..0d9df8cd8 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -113,6 +113,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+ diff --git a/net/flowtrack/flowtrack.go b/net/flowtrack/flowtrack.go index 5fcd4ab20..8387145d3 100644 --- a/net/flowtrack/flowtrack.go +++ b/net/flowtrack/flowtrack.go @@ -15,16 +15,18 @@ import ( "fmt" "inet.af/netaddr" + "tailscale.com/types/ipproto" ) -// Tuple is a 4-tuple of source and destination IP and port. +// Tuple is a 5-tuple of proto, source and destination IP and port. type Tuple struct { - Src netaddr.IPPort - Dst netaddr.IPPort + Proto ipproto.Proto + Src netaddr.IPPort + Dst netaddr.IPPort } func (t Tuple) String() string { - return fmt.Sprintf("(%v => %v)", t.Src, t.Dst) + return fmt.Sprintf("(%v %v => %v)", t.Proto, t.Src, t.Dst) } // Cache is an LRU cache keyed by Tuple. diff --git a/net/packet/header.go b/net/packet/header.go index 86680a5a7..5cf4ef650 100644 --- a/net/packet/header.go +++ b/net/packet/header.go @@ -10,6 +10,7 @@ import ( ) const tcpHeaderLength = 20 +const sctpHeaderLength = 12 // maxPacketLength is the largest length that all headers support. // IPv4 headers using uint16 for this forces an upper bound of 64KB. diff --git a/net/packet/ip4.go b/net/packet/ip4.go index 0240abaa1..2c090d9f1 100644 --- a/net/packet/ip4.go +++ b/net/packet/ip4.go @@ -9,6 +9,7 @@ import ( "errors" "inet.af/netaddr" + "tailscale.com/types/ipproto" ) // ip4HeaderLength is the length of an IPv4 header with no IP options. @@ -16,7 +17,7 @@ const ip4HeaderLength = 20 // IP4Header represents an IPv4 packet header. type IP4Header struct { - IPProto IPProto + IPProto ipproto.Proto IPID uint16 Src netaddr.IP Dst netaddr.IP diff --git a/net/packet/ip6.go b/net/packet/ip6.go index 59f605b32..e181f1dde 100644 --- a/net/packet/ip6.go +++ b/net/packet/ip6.go @@ -8,6 +8,7 @@ import ( "encoding/binary" "inet.af/netaddr" + "tailscale.com/types/ipproto" ) // ip6HeaderLength is the length of an IPv6 header with no IP options. @@ -15,7 +16,7 @@ const ip6HeaderLength = 40 // IP6Header represents an IPv6 packet header. type IP6Header struct { - IPProto IPProto + IPProto ipproto.Proto IPID uint32 // only lower 20 bits used Src netaddr.IP Dst netaddr.IP diff --git a/net/packet/packet.go b/net/packet/packet.go index a88a1af7a..60276e034 100644 --- a/net/packet/packet.go +++ b/net/packet/packet.go @@ -11,9 +11,22 @@ import ( "strings" "inet.af/netaddr" + "tailscale.com/types/ipproto" "tailscale.com/types/strbuilder" ) +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 +) + // RFC1858: prevent overlapping fragment attacks. const minFrag = 60 + 20 // max IPv4 header + basic TCP header @@ -44,7 +57,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 @@ -130,7 +143,7 @@ func (q *Parsed) decode4(b []byte) { } // 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. @@ -210,6 +223,14 @@ func (q *Parsed) decode4(b []byte) { q.Dst.Port = binary.BigEndian.Uint16(sub[2:4]) q.dataofs = q.subofs + udpHeaderLength return + case 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 TSMP: // Inter-tailscale messages. q.dataofs = q.subofs @@ -244,7 +265,7 @@ func (q *Parsed) decode6(b []byte) { 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. @@ -301,6 +322,14 @@ func (q *Parsed) decode6(b []byte) { q.Src.Port = binary.BigEndian.Uint16(sub[0:2]) q.Dst.Port = binary.BigEndian.Uint16(sub[2:4]) q.dataofs = q.subofs + udpHeaderLength + case 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 TSMP: // Inter-tailscale messages. q.dataofs = q.subofs diff --git a/net/packet/packet_test.go b/net/packet/packet_test.go index 8bac5db4a..6d567b7fd 100644 --- a/net/packet/packet_test.go +++ b/net/packet/packet_test.go @@ -305,6 +305,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 +353,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 +391,7 @@ func TestDecode(t *testing.T) { {"unknown", unknownPacketBuffer, unknownPacketDecode}, {"invalid4", invalid4RequestBuffer, invalid4RequestDecode}, {"ipv4_tsmp", ipv4TSMPBuffer, ipv4TSMPDecode}, + {"ipv4_sctp", sctpBuffer, sctpDecode}, } for _, tt := range tests { diff --git a/net/packet/tsmp.go b/net/packet/tsmp.go index 2346c9419..4da3c20e1 100644 --- a/net/packet/tsmp.go +++ b/net/packet/tsmp.go @@ -17,6 +17,7 @@ import ( "inet.af/netaddr" "tailscale.com/net/flowtrack" + "tailscale.com/types/ipproto" ) // TailscaleRejectedHeader is a TSMP message that says that one @@ -39,7 +40,7 @@ type TailscaleRejectedHeader struct { IPDst netaddr.IP // IPv4 or IPv6 header's dst IP Src netaddr.IPPort // rejected flow's src Dst netaddr.IPPort // rejected flow's dst - Proto IPProto // proto that was rejected (TCP or UDP) + Proto ipproto.Proto // proto that was rejected (TCP or UDP) Reason TailscaleRejectReason // why the connection was rejected // MaybeBroken is whether the rejection is non-terminal (the @@ -57,7 +58,7 @@ type TailscaleRejectedHeader struct { const rejectFlagBitMaybeBroken = 0x1 func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple { - return flowtrack.Tuple{Src: rh.Src, Dst: rh.Dst} + return flowtrack.Tuple{Proto: rh.Proto, Src: rh.Src, Dst: rh.Dst} } func (rh TailscaleRejectedHeader) String() string { @@ -181,7 +182,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, diff --git a/net/packet/ip.go b/types/ipproto/ipproto.go similarity index 72% rename from net/packet/ip.go rename to types/ipproto/ipproto.go index 34194f344..5c9d101f6 100644 --- a/net/packet/ip.go +++ b/types/ipproto/ipproto.go @@ -1,28 +1,32 @@ -// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package packet +// Package ipproto contains IP Protocol constants. +package ipproto -// IPProto is an IP subprotocol as defined by the IANA protocol +import "fmt" + +// Proto is an IP subprotocol as defined by the IANA protocol // numbers list // (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml), // or the special values Unknown or Fragment. -type IPProto uint8 +type Proto uint8 const ( // Unknown represents an unknown or unsupported protocol; it's // deliberately the zero value. Strictly speaking the zero // value is IPv6 hop-by-hop extensions, but we don't support // those, so this is still technically correct. - Unknown IPProto = 0x00 + Unknown Proto = 0x00 // Values from the IANA registry. - ICMPv4 IPProto = 0x01 - IGMP IPProto = 0x02 - ICMPv6 IPProto = 0x3a - TCP IPProto = 0x06 - UDP IPProto = 0x11 + ICMPv4 Proto = 0x01 + IGMP Proto = 0x02 + ICMPv6 Proto = 0x3a + TCP Proto = 0x06 + UDP Proto = 0x11 + SCTP Proto = 0x84 // TSMP is the Tailscale Message Protocol (our ICMP-ish // thing), an IP protocol used only between Tailscale nodes @@ -33,7 +37,7 @@ const ( // scheme". We never accept these from the host OS stack nor // send them to the host network stack. It's only used between // nodes. - TSMP IPProto = 99 + TSMP Proto = 99 // Fragment represents any non-first IP fragment, for which we // don't have the sub-protocol header (and therefore can't @@ -41,11 +45,13 @@ const ( // // 0xFF is reserved in the IANA registry, so we steal it for // internal use. - Fragment IPProto = 0xFF + Fragment Proto = 0xFF ) -func (p IPProto) String() string { +func (p Proto) String() string { switch p { + case Unknown: + return "Unknown" case Fragment: return "Frag" case ICMPv4: @@ -58,9 +64,11 @@ func (p IPProto) String() string { return "UDP" case TCP: return "TCP" + case SCTP: + return "SCTP" case TSMP: return "TSMP" default: - return "Unknown" + return fmt.Sprintf("IPProto-%d", int(p)) } } diff --git a/wgengine/filter/filter.go b/wgengine/filter/filter.go index ee11bd44d..876eb5369 100644 --- a/wgengine/filter/filter.go +++ b/wgengine/filter/filter.go @@ -14,6 +14,7 @@ import ( "inet.af/netaddr" "tailscale.com/net/flowtrack" "tailscale.com/net/packet" + "tailscale.com/types/ipproto" "tailscale.com/types/logger" ) @@ -352,18 +353,18 @@ 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 packet.UDP, packet.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: return Accept, "tsmp ok" @@ -409,18 +410,18 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) { if f.matches6.match(q) { return Accept, "tcp ok" } - case packet.UDP: - t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst} + case packet.UDP, packet.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" } default: return Drop, "Unknown proto" @@ -430,15 +431,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" } diff --git a/wgengine/filter/filter_test.go b/wgengine/filter/filter_test.go index 3b0748a8c..a067e7374 100644 --- a/wgengine/filter/filter_test.go +++ b/wgengine/filter/filter_test.go @@ -18,19 +18,24 @@ import ( "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) Match { + m := func(srcs []netaddr.IPPrefix, dsts []NetPortRange, protos ...ipproto.Proto) Match { + if protos == nil { + protos = defaultProtos + } return Match{ - IPProto: defaultProtos, + IPProto: protos, Srcs: srcs, Dsts: dsts, } } matches := []Match{ 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"), packet.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:*")), @@ -100,7 +105,9 @@ func TestFilter(t *testing.T) { {Drop, parsed(packet.TCP, "1::", "2602::1", 0, 443)}, // Don't allow protocols not specified by filter - {Drop, parsed(132 /* SCTP */, "8.1.1.1", "1.2.3.4", 999, 22)}, + {Drop, parsed(packet.SCTP, "8.1.1.1", "1.2.3.4", 999, 22)}, + // But SCTP is allowed for 9.1.1.1 + {Accept, parsed(packet.SCTP, "9.1.1.1", "1.2.3.4", 999, 22)}, } for i, test := range tests { aclFunc := acl.runIn4 @@ -532,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 @@ -553,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), @@ -582,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), @@ -622,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) } @@ -743,7 +750,7 @@ func TestMatchesFromFilterRules(t *testing.T) { }, want: []Match{ { - IPProto: []packet.IPProto{ + IPProto: []ipproto.Proto{ packet.TCP, packet.UDP, packet.ICMPv4, @@ -779,7 +786,7 @@ func TestMatchesFromFilterRules(t *testing.T) { }, want: []Match{ { - IPProto: []packet.IPProto{ + IPProto: []ipproto.Proto{ packet.TCP, }, Dsts: []NetPortRange{ diff --git a/wgengine/filter/match.go b/wgengine/filter/match.go index 799614104..a1b356113 100644 --- a/wgengine/filter/match.go +++ b/wgengine/filter/match.go @@ -10,6 +10,7 @@ import ( "inet.af/netaddr" "tailscale.com/net/packet" + "tailscale.com/types/ipproto" ) //go:generate go run tailscale.com/cmd/cloner --type=Match --output=match_clone.go @@ -47,7 +48,7 @@ func (npr NetPortRange) String() string { // Match matches packets from any IP address in Srcs to any ip:port in // Dsts. type Match struct { - IPProto []packet.IPProto // required set (no default value at this layer) + IPProto []ipproto.Proto // required set (no default value at this layer) Dsts []NetPortRange Srcs []netaddr.IPPrefix } @@ -123,7 +124,7 @@ func ipInList(ip netaddr.IP, netlist []netaddr.IPPrefix) bool { return false } -func protoInList(proto packet.IPProto, valid []packet.IPProto) bool { +func protoInList(proto ipproto.Proto, valid []ipproto.Proto) bool { for _, v := range valid { if proto == v { return true diff --git a/wgengine/filter/match_clone.go b/wgengine/filter/match_clone.go index b031e1e3a..04874ddec 100644 --- a/wgengine/filter/match_clone.go +++ b/wgengine/filter/match_clone.go @@ -8,7 +8,7 @@ package filter import ( "inet.af/netaddr" - "tailscale.com/net/packet" + "tailscale.com/types/ipproto" ) // Clone makes a deep copy of Match. @@ -28,7 +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 { - IPProto []packet.IPProto + IPProto []ipproto.Proto Dsts []NetPortRange Srcs []netaddr.IPPrefix }{}) diff --git a/wgengine/filter/tailcfg.go b/wgengine/filter/tailcfg.go index 26b7ed3da..6fcf0e52c 100644 --- a/wgengine/filter/tailcfg.go +++ b/wgengine/filter/tailcfg.go @@ -11,9 +11,10 @@ import ( "inet.af/netaddr" "tailscale.com/net/packet" "tailscale.com/tailcfg" + "tailscale.com/types/ipproto" ) -var defaultProtos = []packet.IPProto{ +var defaultProtos = []ipproto.Proto{ packet.TCP, packet.UDP, packet.ICMPv4, @@ -31,12 +32,12 @@ func MatchesFromFilterRules(pf []tailcfg.FilterRule) ([]Match, error) { m := Match{} if len(r.IPProto) == 0 { - m.IPProto = append([]packet.IPProto(nil), defaultProtos...) + m.IPProto = append([]ipproto.Proto(nil), defaultProtos...) } else { - m.IPProto = make([]packet.IPProto, 0, len(r.IPProto)) + m.IPProto = make([]ipproto.Proto, 0, len(r.IPProto)) for _, n := range r.IPProto { if n >= 0 && n <= 0xff { - m.IPProto = append(m.IPProto, packet.IPProto(n)) + m.IPProto = append(m.IPProto, ipproto.Proto(n)) } } } diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index be1fa1468..12902c174 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -90,7 +90,7 @@ func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN) // 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) @@ -107,7 +107,7 @@ func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN 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 diff --git a/wgengine/tstun/tun_test.go b/wgengine/tstun/tun_test.go index 555324d19..062b63f81 100644 --- a/wgengine/tstun/tun_test.go +++ b/wgengine/tstun/tun_test.go @@ -16,6 +16,7 @@ import ( "github.com/tailscale/wireguard-go/tun/tuntest" "inet.af/netaddr" "tailscale.com/net/packet" + "tailscale.com/types/ipproto" "tailscale.com/types/logger" "tailscale.com/wgengine/filter" ) @@ -106,7 +107,7 @@ func netports(netPorts ...string) (ret []filter.NetPortRange) { } func setfilter(logf logger.Logf, tun *TUN) { - protos := []packet.IPProto{ + protos := []ipproto.Proto{ packet.TCP, packet.UDP, } From 1eb95c7e32e6fb8e66ddd41ae28a5f109f62227e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 20 Mar 2021 21:45:47 -0700 Subject: [PATCH 11/59] net/packet, wgengine{,/filter}: remove net/packet IPProto forwarding consts Only use the ones in types/ipproto now. Signed-off-by: Brad Fitzpatrick --- net/packet/icmp4.go | 8 +- net/packet/packet.go | 86 +++++++++----------- net/packet/packet_test.go | 13 +++ net/packet/tsmp.go | 4 +- net/packet/udp4.go | 8 +- net/packet/udp6.go | 8 +- wgengine/filter/filter.go | 24 +++--- wgengine/filter/filter_test.go | 144 ++++++++++++++++----------------- wgengine/filter/tailcfg.go | 9 +-- wgengine/pendopen.go | 7 +- wgengine/tstun/tun.go | 3 +- wgengine/tstun/tun_test.go | 4 +- wgengine/userspace.go | 3 +- 13 files changed, 169 insertions(+), 152 deletions(-) diff --git a/net/packet/icmp4.go b/net/packet/icmp4.go index 8a1568114..da4774887 100644 --- a/net/packet/icmp4.go +++ b/net/packet/icmp4.go @@ -4,7 +4,11 @@ package packet -import "encoding/binary" +import ( + "encoding/binary" + + "tailscale.com/types/ipproto" +) // icmp4HeaderLength is the size of the ICMPv4 packet header, not // including the outer IP layer or the variable "response data" @@ -66,7 +70,7 @@ func (h ICMP4Header) Marshal(buf []byte) error { return errLargePacket } // The caller does not need to set this. - h.IPProto = ICMPv4 + h.IPProto = ipproto.ICMPv4 buf[20] = uint8(h.Type) buf[21] = uint8(h.Code) diff --git a/net/packet/packet.go b/net/packet/packet.go index 60276e034..91c92f641 100644 --- a/net/packet/packet.go +++ b/net/packet/packet.go @@ -15,17 +15,7 @@ import ( "tailscale.com/types/strbuilder" ) -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 -) +const unknown = ipproto.Unknown // RFC1858: prevent overlapping fragment attacks. const minFrag = 60 + 20 // max IPv4 header + basic TCP header @@ -113,7 +103,7 @@ func (q *Parsed) Decode(b []byte) { if len(b) < 1 { q.IPVersion = 0 - q.IPProto = Unknown + q.IPProto = unknown return } @@ -125,7 +115,7 @@ func (q *Parsed) Decode(b []byte) { q.decode6(b) default: q.IPVersion = 0 - q.IPProto = Unknown + q.IPProto = unknown } } @@ -138,7 +128,7 @@ 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 } @@ -147,7 +137,7 @@ func (q *Parsed) decode4(b []byte) { 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 } @@ -158,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:] @@ -183,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]) @@ -214,29 +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 SCTP: + case ipproto.SCTP: if len(sub) < sctpHeaderLength { - 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]) return - case TSMP: + case ipproto.TSMP: // Inter-tailscale messages. q.dataofs = q.subofs return default: - q.IPProto = Unknown + q.IPProto = unknown return } } else { @@ -244,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. @@ -253,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 } } @@ -261,7 +251,7 @@ 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 } @@ -269,7 +259,7 @@ func (q *Parsed) decode6(b []byte) { 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 } @@ -295,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]) @@ -314,28 +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 SCTP: + case ipproto.SCTP: if len(sub) < sctpHeaderLength { - 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]) return - case TSMP: + case ipproto.TSMP: // Inter-tailscale messages. q.dataofs = q.subofs return default: - q.IPProto = Unknown + q.IPProto = unknown return } } @@ -396,13 +386,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 } @@ -416,9 +406,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 @@ -428,9 +418,9 @@ func (q *Parsed) IsEchoRequest() bool { // IsEchoRequest reports whether q is an IPv4 ICMP Echo Response. func (q *Parsed) IsEchoResponse() bool { switch q.IPProto { - case ICMPv4: + case ipproto.ICMPv4: return len(q.b) >= q.subofs+8 && ICMP4Type(q.b[q.subofs]) == ICMP4EchoReply && ICMP4Code(q.b[q.subofs+1]) == ICMP4NoCode - case ICMPv6: + case ipproto.ICMPv6: return len(q.b) >= q.subofs+8 && ICMP6Type(q.b[q.subofs]) == ICMP6EchoReply && ICMP6Code(q.b[q.subofs+1]) == ICMP6NoCode default: return false diff --git a/net/packet/packet_test.go b/net/packet/packet_test.go index 6d567b7fd..ac4fa33f3 100644 --- a/net/packet/packet_test.go +++ b/net/packet/packet_test.go @@ -10,6 +10,19 @@ import ( "testing" "inet.af/netaddr" + "tailscale.com/types/ipproto" +) + +const ( + Unknown = ipproto.Unknown + TCP = ipproto.TCP + UDP = ipproto.UDP + SCTP = ipproto.SCTP + IGMP = ipproto.IGMP + ICMPv4 = ipproto.ICMPv4 + ICMPv6 = ipproto.ICMPv6 + TSMP = ipproto.TSMP + Fragment = ipproto.Fragment ) func mustIPPort(s string) netaddr.IPPort { diff --git a/net/packet/tsmp.go b/net/packet/tsmp.go index 4da3c20e1..8d3c65d56 100644 --- a/net/packet/tsmp.go +++ b/net/packet/tsmp.go @@ -139,7 +139,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, } @@ -147,7 +147,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, } diff --git a/net/packet/udp4.go b/net/packet/udp4.go index 82aa30179..ce179f89d 100644 --- a/net/packet/udp4.go +++ b/net/packet/udp4.go @@ -4,7 +4,11 @@ package packet -import "encoding/binary" +import ( + "encoding/binary" + + "tailscale.com/types/ipproto" +) // udpHeaderLength is the size of the UDP packet header, not including // the outer IP header. @@ -31,7 +35,7 @@ func (h UDP4Header) Marshal(buf []byte) error { return errLargePacket } // The caller does not need to set this. - h.IPProto = UDP + h.IPProto = ipproto.UDP length := len(buf) - h.IP4Header.Len() binary.BigEndian.PutUint16(buf[20:22], h.SrcPort) diff --git a/net/packet/udp6.go b/net/packet/udp6.go index 0450eae9e..18213c1fb 100644 --- a/net/packet/udp6.go +++ b/net/packet/udp6.go @@ -4,7 +4,11 @@ package packet -import "encoding/binary" +import ( + "encoding/binary" + + "tailscale.com/types/ipproto" +) // UDP6Header is an IPv6+UDP header. type UDP6Header struct { @@ -27,7 +31,7 @@ func (h UDP6Header) Marshal(buf []byte) error { return errLargePacket } // The caller does not need to set this. - h.IPProto = UDP + h.IPProto = ipproto.UDP length := len(buf) - h.IP6Header.Len() binary.BigEndian.PutUint16(buf[40:42], h.SrcPort) diff --git a/wgengine/filter/filter.go b/wgengine/filter/filter.go index 876eb5369..e904b3905 100644 --- a/wgengine/filter/filter.go +++ b/wgengine/filter/filter.go @@ -268,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 @@ -326,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. @@ -338,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 @@ -353,7 +353,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) { if f.matches4.match(q) { return Accept, "tcp ok" } - case packet.UDP, packet.SCTP: + case ipproto.UDP, ipproto.SCTP: t := flowtrack.Tuple{Proto: q.IPProto, Src: q.Src, Dst: q.Dst} f.state.mu.Lock() @@ -366,7 +366,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) { if f.matches4.match(q) { return Accept, "ok" } - case packet.TSMP: + case ipproto.TSMP: return Accept, "tsmp ok" default: return Drop, "Unknown proto" @@ -383,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. @@ -395,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 @@ -404,13 +404,13 @@ 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, packet.SCTP: + case ipproto.UDP, ipproto.SCTP: t := flowtrack.Tuple{Proto: q.IPProto, Src: q.Src, Dst: q.Dst} f.state.mu.Lock() @@ -488,11 +488,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") @@ -516,5 +516,5 @@ func omitDropLogging(p *packet.Parsed, dir direction) bool { return false } - return p.Dst.IP.IsMulticast() || (p.Dst.IP.IsLinkLocalUnicast() && p.Dst.IP != gcpDNSAddr) || p.IPProto == packet.IGMP + return p.Dst.IP.IsMulticast() || (p.Dst.IP.IsLinkLocalUnicast() && p.Dst.IP != gcpDNSAddr) || p.IPProto == ipproto.IGMP } diff --git a/wgengine/filter/filter_test.go b/wgengine/filter/filter_test.go index a067e7374..ca807a5fd 100644 --- a/wgengine/filter/filter_test.go +++ b/wgengine/filter/filter_test.go @@ -35,7 +35,7 @@ func newFilter(logf logger.Logf) *Filter { } matches := []Match{ 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"), packet.SCTP), + 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:*")), @@ -66,48 +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(packet.SCTP, "8.1.1.1", "1.2.3.4", 999, 22)}, + {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(packet.SCTP, "9.1.1.1", "1.2.3.4", 999, 22)}, + {Accept, parsed(ipproto.SCTP, "9.1.1.1", "1.2.3.4", 999, 22)}, } for i, test := range tests { aclFunc := acl.runIn4 @@ -117,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) @@ -128,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) } @@ -142,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 { @@ -158,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 { @@ -178,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 @@ -262,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 @@ -315,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 { @@ -341,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, }, @@ -439,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, }, @@ -607,7 +607,7 @@ func raw4(proto ipproto.Proto, 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 } @@ -615,7 +615,7 @@ func raw4(proto ipproto.Proto, 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. @@ -751,10 +751,10 @@ func TestMatchesFromFilterRules(t *testing.T) { want: []Match{ { IPProto: []ipproto.Proto{ - packet.TCP, - packet.UDP, - packet.ICMPv4, - packet.ICMPv6, + ipproto.TCP, + ipproto.UDP, + ipproto.ICMPv4, + ipproto.ICMPv6, }, Dsts: []NetPortRange{ { @@ -776,7 +776,7 @@ func TestMatchesFromFilterRules(t *testing.T) { name: "explicit_protos", in: []tailcfg.FilterRule{ { - IPProto: []int{int(packet.TCP)}, + IPProto: []int{int(ipproto.TCP)}, SrcIPs: []string{"100.64.1.1"}, DstPorts: []tailcfg.NetPortRange{{ IP: "1.2.0.0/16", @@ -787,7 +787,7 @@ func TestMatchesFromFilterRules(t *testing.T) { want: []Match{ { IPProto: []ipproto.Proto{ - packet.TCP, + ipproto.TCP, }, Dsts: []NetPortRange{ { diff --git a/wgengine/filter/tailcfg.go b/wgengine/filter/tailcfg.go index 6fcf0e52c..1338a75b4 100644 --- a/wgengine/filter/tailcfg.go +++ b/wgengine/filter/tailcfg.go @@ -9,16 +9,15 @@ import ( "strings" "inet.af/netaddr" - "tailscale.com/net/packet" "tailscale.com/tailcfg" "tailscale.com/types/ipproto" ) var defaultProtos = []ipproto.Proto{ - packet.TCP, - packet.UDP, - packet.ICMPv4, - packet.ICMPv6, + ipproto.TCP, + ipproto.UDP, + ipproto.ICMPv4, + ipproto.ICMPv6, } // MatchesFromFilterRules converts tailcfg FilterRules into Matches. diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index 12902c174..785f71259 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -14,6 +14,7 @@ import ( "tailscale.com/net/flowtrack" "tailscale.com/net/packet" "tailscale.com/net/tsaddr" + "tailscale.com/types/ipproto" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/tstun" ) @@ -68,7 +69,7 @@ func (e *userspaceEngine) noteFlowProblemFromPeer(f flowtrack.Tuple, problem pac func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN) (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,7 +84,7 @@ 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 } @@ -102,7 +103,7 @@ func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN res = filter.Accept // always if pp.IPVersion == 0 || - pp.IPProto != packet.TCP || + pp.IPProto != ipproto.TCP || pp.TCPFlags&packet.TCPSyn == 0 { return } diff --git a/wgengine/tstun/tun.go b/wgengine/tstun/tun.go index 92af1b8b0..367c85829 100644 --- a/wgengine/tstun/tun.go +++ b/wgengine/tstun/tun.go @@ -18,6 +18,7 @@ import ( "github.com/tailscale/wireguard-go/tun" "inet.af/netaddr" "tailscale.com/net/packet" + "tailscale.com/types/ipproto" "tailscale.com/types/logger" "tailscale.com/wgengine/filter" ) @@ -340,7 +341,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, diff --git a/wgengine/tstun/tun_test.go b/wgengine/tstun/tun_test.go index 062b63f81..2ba798222 100644 --- a/wgengine/tstun/tun_test.go +++ b/wgengine/tstun/tun_test.go @@ -108,8 +108,8 @@ func netports(netPorts ...string) (ret []filter.NetPortRange) { func setfilter(logf logger.Logf, tun *TUN) { protos := []ipproto.Proto{ - packet.TCP, - packet.UDP, + ipproto.TCP, + ipproto.UDP, } matches := []filter.Match{ {IPProto: protos, Srcs: nets("5.6.7.8"), Dsts: netports("1.2.3.4:89-90")}, diff --git a/wgengine/userspace.go b/wgengine/userspace.go index fccca8c8b..8505b800f 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -35,6 +35,7 @@ import ( "tailscale.com/net/tsaddr" "tailscale.com/net/tshttpproxy" "tailscale.com/tailcfg" + "tailscale.com/types/ipproto" "tailscale.com/types/key" "tailscale.com/types/logger" "tailscale.com/types/netmap" @@ -462,7 +463,7 @@ func (e *userspaceEngine) isLocalAddr(ip netaddr.IP) bool { // handleDNS is an outbound pre-filter resolving Tailscale domains. func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Response { - if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == packet.UDP { + if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == ipproto.UDP { request := tsdns.Packet{ Payload: append([]byte(nil), p.Payload()...), Addr: netaddr.IPPort{IP: p.Src.IP, Port: p.Src.Port}, From 7e0d12e7ccb8d3062bf16ab03edce7d01944367a Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 22 Mar 2021 10:23:26 -0700 Subject: [PATCH 12/59] wgengine/magicsock: don't update control if only endpoint order changes Updates #1559 Signed-off-by: Brad Fitzpatrick --- wgengine/magicsock/magicsock.go | 32 ++++++++++++--- wgengine/magicsock/magicsock_test.go | 58 ++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 884056263..68c3f60db 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -630,7 +630,7 @@ func (c *Conn) setEndpoints(endpoints []string, reasons map[string]string) (chan delete(c.onEndpointRefreshed, de) } - if stringsEqual(endpoints, c.lastEndpoints) { + if stringSetsEqual(endpoints, c.lastEndpoints) { return false } c.lastEndpoints = endpoints @@ -1111,12 +1111,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 } } diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 8e64a2696..1c9dfd1d7 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -1793,3 +1793,61 @@ 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) + } + } + +} From 0994a9f7c45f5af54fa6ff1e61a9c8aedea5bd20 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 22 Mar 2021 21:25:43 -0700 Subject: [PATCH 13/59] wgengine{,/magicsock}: fix, improve "tailscale ping" to default routes and subnets e.g. $ tailscale ping 1.1.1.1 exit node found but not enabled $ tailscale ping 10.2.200.2 node "tsbfvlan2" found, but not using its 10.2.200.0/24 route $ sudo tailscale up --accept-routes $ tailscale ping 10.2.200.2 pong from tsbfvlan2 (100.124.196.94) via 10.2.200.34:41641 in 1ms $ tailscale ping mon.ts.tailscale.com pong from monitoring (100.88.178.64) via DERP(sfo) in 83ms pong from monitoring (100.88.178.64) via DERP(sfo) in 21ms pong from monitoring (100.88.178.64) via [2604:a880:4:d1::37:d001]:41641 in 22ms This necessarily moves code up from magicsock to wgengine, so we can look at the actual wireguard config. Fixes #1564 Signed-off-by: Brad Fitzpatrick --- wgengine/magicsock/magicsock.go | 49 +----------------- wgengine/pendopen.go | 10 ++-- wgengine/userspace.go | 92 +++++++++++++++++++++++++++++++-- 3 files changed, 97 insertions(+), 54 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 68c3f60db..a042b689e 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -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() } diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index 785f71259..c5e8f0640 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -115,7 +115,7 @@ func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN // 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 } } @@ -155,8 +155,12 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) { } // Diagnose why it might've timed out. - n, ok := e.magicConn.PeerForIP(flow.Dst.IP) - if !ok { + n, err := e.peerForIP(flow.Dst.IP) + if err != nil { + e.logf("open-conn-track: timeout opening %v; peerForIP: %v", flow, err) + return + } + if n == nil { e.logf("open-conn-track: timeout opening %v; no associated peer node", flow) return } diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 8505b800f..72d25db4e 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -118,8 +118,9 @@ type userspaceEngine struct { destIPActivityFuncs map[netaddr.IP]func() statusBufioReader *bufio.Reader // reusable for UAPI - mu sync.Mutex // guards following; see lock order comment below - closing bool // Close was called (even if we're still closing) + mu sync.Mutex // guards following; see lock order comment below + netMap *netmap.NetworkMap // or nil + closing bool // Close was called (even if we're still closing) statusCallback StatusCallback peerSequence []wgkey.Key endpoints []string @@ -1307,6 +1308,7 @@ func (e *userspaceEngine) SetDERPMap(dm *tailcfg.DERPMap) { func (e *userspaceEngine) SetNetworkMap(nm *netmap.NetworkMap) { e.magicConn.SetNetworkMap(nm) e.mu.Lock() + e.netMap = nm callbacks := make([]NetworkMapCallback, 0, 4) for _, fn := range e.networkMapCallbacks { callbacks = append(callbacks, fn) @@ -1340,7 +1342,18 @@ func (e *userspaceEngine) UpdateStatus(sb *ipnstate.StatusBuilder) { } func (e *userspaceEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) { - e.magicConn.Ping(ip, cb) + res := &ipnstate.PingResult{IP: ip.String()} + peer, err := e.peerForIP(ip) + if err != nil { + res.Err = err.Error() + cb(res) + } + if peer == nil { + res.Err = "no matching peer" + cb(res) + return + } + e.magicConn.Ping(peer, res, cb) } func (e *userspaceEngine) RegisterIPPortIdentity(ipport netaddr.IPPort, tsIP netaddr.IP) { @@ -1368,6 +1381,79 @@ func (e *userspaceEngine) WhoIsIPPort(ipport netaddr.IPPort) (tsIP netaddr.IP, o return tsIP, ok } +// peerForIP returns the Node in the wireguard config +// that's responsible for handling the given IP address. +// +// If none is found in the wireguard config but one is found in +// the netmap, it's described in an error. +// +// If none is found in either place, (nil, nil) is returned. +// +// peerForIP acquires both e.mu and e.wgLock, but neither at the same +// time. +func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error) { + e.mu.Lock() + nm := e.netMap + e.mu.Unlock() + if nm == nil { + return nil, errors.New("no network map") + } + + // Check for exact matches before looking for subnet matches. + var bestInNMPrefix netaddr.IPPrefix + var bestInNM *tailcfg.Node + for _, p := range nm.Peers { + for _, a := range p.Addresses { + if a.IP == ip && a.IsSingleIP() && tsaddr.IsTailscaleIP(ip) { + return p, nil + } + } + for _, cidr := range p.AllowedIPs { + if !cidr.Contains(ip) { + continue + } + if bestInNMPrefix.IsZero() || cidr.Bits > bestInNMPrefix.Bits { + bestInNMPrefix = cidr + bestInNM = p + } + } + } + + e.wgLock.Lock() + defer e.wgLock.Unlock() + + // TODO(bradfitz): this is O(n peers). Add ART to netaddr? + var best netaddr.IPPrefix + var bestKey tailcfg.NodeKey + for _, p := range e.lastCfgFull.Peers { + for _, cidr := range p.AllowedIPs { + if !cidr.Contains(ip) { + continue + } + if best.IsZero() || cidr.Bits > best.Bits { + best = cidr + bestKey = tailcfg.NodeKey(p.PublicKey) + } + } + } + // And another pass. Probably better than allocating a map per peerForIP + // call. But TODO(bradfitz): add a lookup map to netmap.NetworkMap. + if !bestKey.IsZero() { + for _, p := range nm.Peers { + if p.Key == bestKey { + return p, nil + } + } + } + if bestInNM == nil { + return nil, nil + } + if bestInNMPrefix.Bits == 0 { + return nil, errors.New("exit node found but not enabled") + } + return nil, fmt.Errorf("node %q found, but not using its %v route", bestInNM.ComputedNameWithHost, bestInNMPrefix) +} + // diagnoseTUNFailure is called if tun.CreateTUN fails, to poke around // the system and log some diagnostic info that might help debug why // TUN failed. Because TUN's already failed and things the program's From 85138d31831107601cca9f40804e88277a99caf3 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 22 Mar 2021 21:41:53 -0700 Subject: [PATCH 14/59] health: track whether any network interface is up Fixes #1562 Signed-off-by: Brad Fitzpatrick --- health/health.go | 12 ++++++++++++ wgengine/userspace.go | 1 + 2 files changed, 13 insertions(+) diff --git a/health/health.go b/health/health.go index 81d346d15..3da92b8a4 100644 --- a/health/health.go +++ b/health/health.go @@ -35,6 +35,7 @@ var ( lastMapRequestHeard time.Time // time we got a 200 from control for a MapRequest ipnState string ipnWantRunning bool + anyInterfaceUp = true // until told otherwise ) // Subsystem is the name of a subsystem whose health can be monitored. @@ -195,6 +196,14 @@ func SetIPNState(state string, wantRunning bool) { selfCheckLocked() } +// SetAnyInterfaceUp sets whether any network interface is up. +func SetAnyInterfaceUp(up bool) { + mu.Lock() + defer mu.Unlock() + anyInterfaceUp = up + selfCheckLocked() +} + func timerSelfCheck() { mu.Lock() defer mu.Unlock() @@ -213,6 +222,9 @@ func selfCheckLocked() { } func overallErrorLocked() error { + if !anyInterfaceUp { + return errors.New("network down") + } if ipnState != "Running" || !ipnWantRunning { return fmt.Errorf("state=%v, wantRunning=%v", ipnState, ipnWantRunning) } diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 72d25db4e..c9d966a99 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -1272,6 +1272,7 @@ func (e *userspaceEngine) linkChange(changed bool, cur *interfaces.State) { e.logf("[v1] LinkChange: minor") } + health.SetAnyInterfaceUp(up) e.magicConn.SetNetworkUp(up) why := "link-change-minor" From 96dfeb2d7f0797f504935305d1e56cb91509809c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 22 Mar 2021 21:47:42 -0700 Subject: [PATCH 15/59] wgengine: log tailscale pings Fixes #1561 Signed-off-by: Brad Fitzpatrick --- wgengine/userspace.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index c9d966a99..49bc617e0 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -1346,14 +1346,18 @@ func (e *userspaceEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) { res := &ipnstate.PingResult{IP: ip.String()} peer, err := e.peerForIP(ip) if err != nil { + e.logf("ping(%v): %v", ip, err) res.Err = err.Error() cb(res) + return } if peer == nil { + e.logf("ping(%v): no matching peer", ip) res.Err = "no matching peer" cb(res) return } + e.logf("ping(%v): sending ping to %v %v ...", ip, peer.Key.ShortString(), peer.ComputedName) e.magicConn.Ping(peer, res, cb) } From 9643d8b34d2ca0b21b391cc7cad2fb25d29d9499 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 23 Mar 2021 10:07:34 -0700 Subject: [PATCH 16/59] wgengine/magicsock: add an addrLatency type to combine an IPPort+time.Duration Updates #1566 (but no behavior changes as of this change) Signed-off-by: Brad Fitzpatrick --- wgengine/magicsock/magicsock.go | 51 ++++++++++++++++++---------- wgengine/magicsock/magicsock_test.go | 30 ++++++++++++++++ 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index a042b689e..fd2f80681 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -3079,10 +3079,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 @@ -3187,8 +3186,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{} } } @@ -3256,7 +3255,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. @@ -3309,7 +3308,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 { @@ -3641,20 +3640,39 @@ 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 + } + return a.latency < b.latency +} + // discoEndpoint.mu must be held. func (st *endpointState) addPongReplyLocked(r pongReply) { if n := len(st.recentPongs); n < pongHistoryCount { @@ -3761,8 +3779,7 @@ func (de *discoEndpoint) stopAndReset() { // state isn't a mix of before & after two sessions. de.lastSend = time.Time{} de.lastFullPing = time.Time{} - de.bestAddr = netaddr.IPPort{} - de.bestAddrLatency = 0 + de.bestAddr = addrLatency{} de.bestAddrAt = time.Time{} de.trustBestAddrUntil = time.Time{} for _, es := range de.endpointState { diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 1c9dfd1d7..a48676e30 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -1851,3 +1851,33 @@ func TestStringSetsEqual(t *testing.T) { } } + +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 + } + 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) + } + } + +} From 77ec80538acd1c01ba4058852be1d9e701a5f1e7 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 23 Mar 2021 12:15:08 -0700 Subject: [PATCH 17/59] syncs: add Semaphore Signed-off-by: Brad Fitzpatrick --- syncs/syncs.go | 47 ++++++++++++++++++++++++++++++++++++++++++++- syncs/syncs_test.go | 27 +++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/syncs/syncs.go b/syncs/syncs.go index 0139ad925..f319f6489 100644 --- a/syncs/syncs.go +++ b/syncs/syncs.go @@ -5,7 +5,10 @@ // Package syncs contains additional sync types and functionality. package syncs -import "sync/atomic" +import ( + "context" + "sync/atomic" +) // ClosedChan returns a channel that's already closed. func ClosedChan() <-chan struct{} { return closedChan } @@ -79,3 +82,45 @@ func (b *AtomicBool) Set(v bool) { func (b *AtomicBool) Get() bool { return atomic.LoadInt32((*int32)(b)) != 0 } + +// Semaphore is a counting semaphore. +// +// Use NewSemaphore to create one. +type Semaphore struct { + c chan struct{} +} + +// NewSemaphore returns a semaphore with resource count n. +func NewSemaphore(n int) Semaphore { + return Semaphore{c: make(chan struct{}, n)} +} + +// Acquire blocks until a resource is acquired. +func (s Semaphore) Acquire() { + s.c <- struct{}{} +} + +// AcquireContext reports whether the resource was acquired before the ctx was done. +func (s Semaphore) AcquireContext(ctx context.Context) bool { + select { + case s.c <- struct{}{}: + return true + case <-ctx.Done(): + return false + } +} + +// TryAcquire reports, without blocking, whether the resource was acquired. +func (s Semaphore) TryAcquire() bool { + select { + case s.c <- struct{}{}: + return true + default: + return false + } +} + +// Release releases a resource. +func (s Semaphore) Release() { + <-s.c +} diff --git a/syncs/syncs_test.go b/syncs/syncs_test.go index 9de72e22f..a6768e90b 100644 --- a/syncs/syncs_test.go +++ b/syncs/syncs_test.go @@ -4,7 +4,10 @@ package syncs -import "testing" +import ( + "context" + "testing" +) func TestWaitGroupChan(t *testing.T) { wg := NewWaitGroupChan() @@ -48,3 +51,25 @@ func TestClosedChan(t *testing.T) { } } } + +func TestSemaphore(t *testing.T) { + s := NewSemaphore(2) + s.Acquire() + if !s.TryAcquire() { + t.Fatal("want true") + } + if s.TryAcquire() { + t.Fatal("want false") + } + ctx, cancel := context.WithCancel(context.Background()) + cancel() + if s.AcquireContext(ctx) { + t.Fatal("want false") + } + s.Release() + if !s.AcquireContext(context.Background()) { + t.Fatal("want true") + } + s.Release() + s.Release() +} From e2b3d9aa5fe6e73f12796e7af46deb4d3e13131c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 23 Mar 2021 10:21:01 -0700 Subject: [PATCH 18/59] all: s/Magic DNS/MagicDNS/ for consistency Signed-off-by: Brad Fitzpatrick --- net/tsaddr/tsaddr.go | 2 +- tailcfg/tailcfg.go | 2 +- wgengine/netstack/netstack.go | 2 +- wgengine/router/dns/config.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/net/tsaddr/tsaddr.go b/net/tsaddr/tsaddr.go index 44cf5cf23..9bf81326e 100644 --- a/net/tsaddr/tsaddr.go +++ b/net/tsaddr/tsaddr.go @@ -37,7 +37,7 @@ var ( ) // TailscaleServiceIP returns the listen address of services -// provided by Tailscale itself such as the Magic DNS proxy. +// provided by Tailscale itself such as the MagicDNS proxy. func TailscaleServiceIP() netaddr.IP { serviceIP.Do(func() { mustIP(&serviceIP.v, "100.100.100.100") }) return serviceIP.v diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 7d615867c..e070c82af 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -731,7 +731,7 @@ type DNSConfig struct { // 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. + // This enables MagicDNS. It is togglable independently of PerDomain. Proxied bool } diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index dbf6736c2..8aba261e6 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -234,7 +234,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 { diff --git a/wgengine/router/dns/config.go b/wgengine/router/dns/config.go index 2b6ff615a..0f752d0f8 100644 --- a/wgengine/router/dns/config.go +++ b/wgengine/router/dns/config.go @@ -23,7 +23,7 @@ type Config struct { // 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. + // This enables MagicDNS. Proxied bool } From c99f260e403f8aa6665c54bf9a7f25ca696fa480 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 23 Mar 2021 10:17:19 -0700 Subject: [PATCH 19/59] wgengine/magicsock: prefer IPv6 transport if roughly equivalent latency Fixes #1566 Signed-off-by: Brad Fitzpatrick --- wgengine/magicsock/magicsock.go | 11 +++++++++++ wgengine/magicsock/magicsock_test.go | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index fd2f80681..6fc26a2b5 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -3670,6 +3670,17 @@ func betterAddr(a, b addrLatency) bool { 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 } diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index a48676e30..bd3e3781e 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -1867,6 +1867,29 @@ func TestBetterAddr(t *testing.T) { {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) From d31eff8473ff4498b49ef9bbdd47910ccbe31a6a Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 23 Mar 2021 11:55:28 -0700 Subject: [PATCH 20/59] tstest/natlab: use net.ErrClosed We are now on 1.16. And wgconn.NetErrClosed has been removed upstream. Signed-off-by: Josh Bleecher Snyder --- tstest/natlab/natlab.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tstest/natlab/natlab.go b/tstest/natlab/natlab.go index df2611be4..2d3f3ae70 100644 --- a/tstest/natlab/natlab.go +++ b/tstest/natlab/natlab.go @@ -26,7 +26,6 @@ import ( "sync" "time" - wgconn "github.com/tailscale/wireguard-go/conn" "inet.af/netaddr" ) @@ -759,8 +758,7 @@ func (c *conn) canRead() error { c.mu.Lock() defer c.mu.Unlock() if c.closed { - // TODO: when we switch to Go 1.16, replace this with net.ErrClosed - return wgconn.NetErrClosed + return net.ErrClosed } if !c.readDeadline.IsZero() && c.readDeadline.Before(time.Now()) { return errors.New("read deadline exceeded") From 79f02de55f8ef0712f7397a9d7ee7319c55433f7 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 23 Mar 2021 11:55:37 -0700 Subject: [PATCH 21/59] go.sum: add entries for upstream wireguard-go Signed-off-by: Josh Bleecher Snyder --- go.sum | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/go.sum b/go.sum index e30861457..554dc17ae 100644 --- a/go.sum +++ b/go.sum @@ -138,7 +138,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U 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/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -182,6 +184,7 @@ golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7w 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= @@ -191,6 +194,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= @@ -217,6 +221,7 @@ 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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 4b77eca2de9b0d5bcbf5cd74204e09110bf57088 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 23 Mar 2021 11:58:45 -0700 Subject: [PATCH 22/59] wgengine/magicsock: check returned error in addTestEndpoint Signed-off-by: Josh Bleecher Snyder --- wgengine/magicsock/magicsock_test.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index bd3e3781e..3f9d8c152 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -1431,7 +1431,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 +1509,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 +1525,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 +1544,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) From 2384c112c9a94689ddb5860d867758bebbb793f6 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 23 Mar 2021 15:16:15 -0700 Subject: [PATCH 23/59] net/packet, wgengine/{filter,tstun}: add TSMP ping Fixes #1467 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/ping.go | 12 ++++- ipn/backend.go | 2 +- ipn/fake_test.go | 2 +- ipn/ipnlocal/local.go | 4 +- ipn/ipnstate/ipnstate.go | 14 ++++-- ipn/message.go | 12 +++-- net/packet/packet.go | 13 +++++ net/packet/tsmp.go | 61 +++++++++++++++++++++++ wgengine/filter/filter.go | 2 + wgengine/tstun/tun.go | 35 +++++++++++++ wgengine/userspace.go | 102 ++++++++++++++++++++++++++++++++++++-- wgengine/watchdog.go | 4 +- wgengine/wgengine.go | 2 +- 13 files changed, 247 insertions(+), 18 deletions(-) diff --git a/cmd/tailscale/cli/ping.go b/cmd/tailscale/cli/ping.go index e09a6a75b..c82eb7902 100644 --- a/cmd/tailscale/cli/ping.go +++ b/cmd/tailscale/cli/ping.go @@ -48,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 @@ -58,6 +59,7 @@ var pingArgs struct { num int untilDirect bool verbose bool + tsmp bool timeout time.Duration } @@ -120,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: @@ -135,8 +137,16 @@ func runPing(ctx context.Context, args []string) error { if pr.DERPRegionID != 0 { via = fmt.Sprintf("DERP(%s)", pr.DERPRegionCode) } + if pingArgs.tsmp { + // TODO(bradfitz): populate the rest of ipnstate.PingResult for TSMP queries? + // For now just say it came via TSMP. + via = "TSMP" + } anyPong = true fmt.Printf("pong from %s (%s) via %v in %v\n", pr.NodeName, pr.NodeIP, via, latency) + if pingArgs.tsmp { + return nil + } if pr.Endpoint != "" && pingArgs.untilDirect { return nil } diff --git a/ipn/backend.go b/ipn/backend.go index 5ef54fde1..dee548c54 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -165,5 +165,5 @@ type Backend interface { // Ping attempts to start connecting to the given IP and sends a Notify // with its PingResult. If the host is down, there might never // be a PingResult sent. The cmd/tailscale CLI client adds a timeout. - Ping(ip string) + Ping(ip string, useTSMP bool) } diff --git a/ipn/fake_test.go b/ipn/fake_test.go index 98685b013..eef580f57 100644 --- a/ipn/fake_test.go +++ b/ipn/fake_test.go @@ -91,6 +91,6 @@ func (b *FakeBackend) FakeExpireAfter(x time.Duration) { b.notify(Notify{NetMap: &netmap.NetworkMap{}}) } -func (b *FakeBackend) Ping(ip string) { +func (b *FakeBackend) Ping(ip string, useTSMP bool) { b.notify(Notify{PingResult: &ipnstate.PingResult{}}) } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 0d8ef1582..a5bd272a7 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1173,13 +1173,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}) }) } diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index a419067ad..a61555760 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -407,10 +407,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) } diff --git a/ipn/message.go b/ipn/message.go index 96b4708db..905664c93 100644 --- a/ipn/message.go +++ b/ipn/message.go @@ -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 @@ -174,7 +175,7 @@ func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error { bs.b.RequestEngineStatus() return nil } else if c := cmd.Ping; c != nil { - bs.b.Ping(c.IP) + bs.b.Ping(c.IP, c.UseTSMP) return nil } @@ -320,8 +321,11 @@ func (bc *BackendClient) FakeExpireAfter(x time.Duration) { bc.send(Command{FakeExpireAfter: &FakeExpireAfterArgs{Duration: x}}) } -func (bc *BackendClient) Ping(ip string) { - bc.send(Command{Ping: &PingArgs{IP: ip}}) +func (bc *BackendClient) Ping(ip string, useTSMP bool) { + bc.send(Command{Ping: &PingArgs{ + IP: ip, + UseTSMP: useTSMP, + }}) } func (bc *BackendClient) SetWantRunning(v bool) { diff --git a/net/packet/packet.go b/net/packet/packet.go index 91c92f641..05c4a382f 100644 --- a/net/packet/packet.go +++ b/net/packet/packet.go @@ -343,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") diff --git a/net/packet/tsmp.go b/net/packet/tsmp.go index 8d3c65d56..fb257556c 100644 --- a/net/packet/tsmp.go +++ b/net/packet/tsmp.go @@ -70,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 @@ -195,3 +201,58 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo } return h, true } + +// TSMPPingRequest is a TSMP message that's like an ICMP ping request. +// +// On the wire, after the IP header, it's currently 9 bytes: +// * 'p' (TSMPTypePing) +// * 8 opaque ping bytes to copy back in the response +type TSMPPingRequest struct { + Data [8]byte +} + +func (pp *Parsed) AsTSMPPing() (h TSMPPingRequest, ok bool) { + if pp.IPProto != ipproto.TSMP { + return + } + p := pp.Payload() + if len(p) < 9 || p[0] != byte(TSMPTypePing) { + return + } + copy(h.Data[:], p[1:]) + return h, true +} + +type TSMPPongReply struct { + IPHeader Header + Data [8]byte +} + +func (pp *Parsed) AsTSMPPong() (data [8]byte, ok bool) { + if pp.IPProto != ipproto.TSMP { + return + } + p := pp.Payload() + if len(p) < 9 || p[0] != byte(TSMPTypePong) { + return + } + copy(data[:], p[1:]) + return data, true +} + +func (h TSMPPongReply) Len() int { + return h.IPHeader.Len() + 9 +} + +func (h TSMPPongReply) Marshal(buf []byte) error { + if len(buf) < h.Len() { + return errSmallBuffer + } + if err := h.IPHeader.Marshal(buf); err != nil { + return err + } + buf = buf[h.IPHeader.Len():] + buf[0] = byte(TSMPTypePong) + copy(buf[1:], h.Data[:]) + return nil +} diff --git a/wgengine/filter/filter.go b/wgengine/filter/filter.go index e904b3905..3c4964c34 100644 --- a/wgengine/filter/filter.go +++ b/wgengine/filter/filter.go @@ -423,6 +423,8 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) { if f.matches6.match(q) { return Accept, "ok" } + case ipproto.TSMP: + return Accept, "tsmp ok" default: return Drop, "Unknown proto" } diff --git a/wgengine/tstun/tun.go b/wgengine/tstun/tun.go index 367c85829..0f0725eb3 100644 --- a/wgengine/tstun/tun.go +++ b/wgengine/tstun/tun.go @@ -109,6 +109,9 @@ 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 } @@ -323,6 +326,18 @@ func (t *TUN) filterIn(buf []byte) filter.Response { 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 @@ -440,6 +455,26 @@ func (t *TUN) InjectInboundCopy(packet []byte) error { return t.InjectInboundDirect(buf, PacketStartOffset) } +func (t *TUN) 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 TUN 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. diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 49bc617e0..09347db1a 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -8,6 +8,7 @@ import ( "bufio" "bytes" "context" + crand "crypto/rand" "errors" "fmt" "io" @@ -128,6 +129,7 @@ type userspaceEngine struct { pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go networkMapCallbacks map[*someHandle]NetworkMapCallback tsIPByIPPort map[netaddr.IPPort]netaddr.IP // allows registration of IP:ports as belonging to a certain Tailscale IP for whois lookups + pongCallback map[[8]byte]func() // for TSMP pong responses // Lock ordering: magicsock.Conn.mu, wgLock, then mu. } @@ -351,6 +353,16 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ SkipBindUpdate: true, } + e.tundev.OnTSMPPongReceived = func(data [8]byte) { + e.mu.Lock() + defer e.mu.Unlock() + cb := e.pongCallback[data] + e.logf("wgengine: got TSMP pong %02x; cb=%v", data, cb != nil) + if cb != nil { + go cb() + } + } + // wgdev takes ownership of tundev, will close it when closed. e.logf("Creating wireguard device...") e.wgdev = device.NewDevice(e.tundev, opts) @@ -1342,7 +1354,7 @@ func (e *userspaceEngine) UpdateStatus(sb *ipnstate.StatusBuilder) { e.magicConn.UpdateStatus(sb) } -func (e *userspaceEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) { +func (e *userspaceEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) { res := &ipnstate.PingResult{IP: ip.String()} peer, err := e.peerForIP(ip) if err != nil { @@ -1357,8 +1369,92 @@ func (e *userspaceEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) { cb(res) return } - e.logf("ping(%v): sending ping to %v %v ...", ip, peer.Key.ShortString(), peer.ComputedName) - e.magicConn.Ping(peer, res, cb) + pingType := "disco" + if useTSMP { + pingType = "TSMP" + } + e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key.ShortString(), peer.ComputedName) + if useTSMP { + e.sendTSMPPing(ip, peer, res, cb) + } else { + e.magicConn.Ping(peer, res, cb) + } +} + +func (e *userspaceEngine) mySelfIPMatchingFamily(dst netaddr.IP) (src netaddr.IP, err error) { + e.mu.Lock() + defer e.mu.Unlock() + if e.netMap == nil { + return netaddr.IP{}, errors.New("no netmap") + } + for _, a := range e.netMap.Addresses { + if a.IsSingleIP() && a.IP.BitLen() == dst.BitLen() { + return a.IP, nil + } + } + if len(e.netMap.Addresses) == 0 { + return netaddr.IP{}, errors.New("no self address in netmap") + } + return netaddr.IP{}, errors.New("no self address in netmap matching address family") +} + +func (e *userspaceEngine) sendTSMPPing(ip netaddr.IP, peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) { + srcIP, err := e.mySelfIPMatchingFamily(ip) + if err != nil { + res.Err = err.Error() + cb(res) + return + } + var iph packet.Header + if srcIP.Is4() { + iph = packet.IP4Header{ + IPProto: ipproto.TSMP, + Src: srcIP, + Dst: ip, + } + } else { + iph = packet.IP6Header{ + IPProto: ipproto.TSMP, + Src: srcIP, + Dst: ip, + } + } + + var data [8]byte + crand.Read(data[:]) + + expireTimer := time.AfterFunc(10*time.Second, func() { + e.setTSMPPongCallback(data, nil) + }) + t0 := time.Now() + e.setTSMPPongCallback(data, func() { + expireTimer.Stop() + d := time.Since(t0) + res.LatencySeconds = d.Seconds() + res.NodeIP = ip.String() + res.NodeName = peer.ComputedName + cb(res) + }) + + var tsmpPayload [9]byte + tsmpPayload[0] = byte(packet.TSMPTypePing) + copy(tsmpPayload[1:], data[:]) + + tsmpPing := packet.Generate(iph, tsmpPayload[:]) + e.tundev.InjectOutbound(tsmpPing) +} + +func (e *userspaceEngine) setTSMPPongCallback(data [8]byte, cb func()) { + e.mu.Lock() + defer e.mu.Unlock() + if e.pongCallback == nil { + e.pongCallback = map[[8]byte]func(){} + } + if cb == nil { + delete(e.pongCallback, data) + } else { + e.pongCallback[data] = cb + } } func (e *userspaceEngine) RegisterIPPortIdentity(ipport netaddr.IPPort, tsIP netaddr.IP) { diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index f4f7d3085..6a607ce03 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -117,8 +117,8 @@ func (e *watchdogEngine) DiscoPublicKey() (k tailcfg.DiscoKey) { e.watchdog("DiscoPublicKey", func() { k = e.wrap.DiscoPublicKey() }) return k } -func (e *watchdogEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) { - e.watchdog("Ping", func() { e.wrap.Ping(ip, cb) }) +func (e *watchdogEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) { + e.watchdog("Ping", func() { e.wrap.Ping(ip, useTSMP, cb) }) } func (e *watchdogEngine) RegisterIPPortIdentity(ipp netaddr.IPPort, tsIP netaddr.IP) { e.watchdog("RegisterIPPortIdentity", func() { e.wrap.RegisterIPPortIdentity(ipp, tsIP) }) diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index c8e7963db..2e3a8fd36 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -136,7 +136,7 @@ type Engine interface { // Ping is a request to start a discovery ping with the peer handling // the given IP and then call cb with its ping latency & method. - Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) + Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) // RegisterIPPortIdentity registers a given node (identified by its // Tailscale IP) as temporarily having the given IP:port for whois lookups. From b7f0e39bf21f8f82f3d3864fd0a4f313e70096e7 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 24 Mar 2021 09:24:25 -0700 Subject: [PATCH 24/59] cmd/tailscale: add "tailscale ip [-4] [-6]" command This adds an easy and portable way for us to document how to get your Tailscale IP address. $ tailscale ip 100.74.70.3 fd7a:115c:a1e0:ab12:4843:cd96:624a:4603 $ tailscale ip -4 100.74.70.3 $ tailscale ip -6 fd7a:115c:a1e0:ab12:4843:cd96:624a:4603 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/cli.go | 1 + cmd/tailscale/cli/ip.go | 69 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 cmd/tailscale/cli/ip.go diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 3bbdb1a41..1d7f95766 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -64,6 +64,7 @@ change in the future. upCmd, downCmd, netcheckCmd, + ipCmd, statusCmd, pingCmd, versionCmd, diff --git a/cmd/tailscale/cli/ip.go b/cmd/tailscale/cli/ip.go new file mode 100644 index 000000000..053ea165e --- /dev/null +++ b/cmd/tailscale/cli/ip.go @@ -0,0 +1,69 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cli + +import ( + "context" + "errors" + "flag" + "fmt" + + "github.com/peterbourgon/ff/v2/ffcli" + "tailscale.com/client/tailscale" +) + +var ipCmd = &ffcli.Command{ + Name: "ip", + ShortUsage: "ip [-4] [-6]", + ShortHelp: "Show this machine's current Tailscale IP address(es)", + Exec: runIP, + FlagSet: (func() *flag.FlagSet { + fs := flag.NewFlagSet("ip", flag.ExitOnError) + fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address") + fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address") + return fs + })(), +} + +var ipArgs struct { + want4 bool + want6 bool +} + +func runIP(ctx context.Context, args []string) error { + if len(args) > 0 { + return errors.New("unknown arguments") + } + v4, v6 := ipArgs.want4, ipArgs.want6 + if v4 && v6 { + return errors.New("tailscale up -4 and -6 are mutually exclusive") + } + if !v4 && !v6 { + v4, v6 = true, true + } + st, err := tailscale.Status(ctx) + if err != nil { + return err + } + if len(st.TailscaleIPs) == 0 { + return fmt.Errorf("no current Tailscale IPs; state: %v", st.BackendState) + } + match := false + for _, ip := range st.TailscaleIPs { + if ip.Is4() && v4 || ip.Is6() && v6 { + match = true + fmt.Println(ip) + } + } + if !match { + if ipArgs.want4 { + return errors.New("no Tailscale IPv4 address") + } + if ipArgs.want6 { + return errors.New("no Tailscale IPv6 address") + } + } + return nil +} From 28af46fb3bf8e00afe5d3206b50f76cb2442584f Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 23 Mar 2021 11:39:06 -0700 Subject: [PATCH 25/59] wgengine: pass logger as a separate arg to device.NewDevice Adapt to minor API changes in wireguard-go. And factor out device.DeviceOptions variables. Signed-off-by: Josh Bleecher Snyder --- go.mod | 2 +- go.sum | 2 ++ wgengine/magicsock/magicsock_test.go | 13 +++++++------ wgengine/userspace.go | 3 +-- wgengine/wgcfg/device_test.go | 8 ++------ 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 43c3ae36a..05158381e 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ 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-20210324165952-2963b66bc23a github.com/tcnksm/go-httpstat v0.2.0 github.com/toqueteos/webbrowser v1.2.0 go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 diff --git a/go.sum b/go.sum index 554dc17ae..5562d64b6 100644 --- a/go.sum +++ b/go.sum @@ -115,6 +115,8 @@ 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/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= diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 3f9d8c152..01bb34e3f 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -170,12 +170,12 @@ func newMagicStack(t testing.TB, logf logger.Logf, l nettype.PacketListener, der 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() } diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 09347db1a..7334677bf 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -304,7 +304,6 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ e.wgLogger = wglog.NewLogger(logf) opts := &device.DeviceOptions{ - Logger: e.wgLogger.DeviceLogger, HandshakeDone: func(peerKey device.NoisePublicKey, peer *device.Peer, deviceAllowedIPs *device.AllowedIPs) { // Send an unsolicited status event every time a // handshake completes. This makes sure our UI can @@ -365,7 +364,7 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ // wgdev takes ownership of tundev, will close it when closed. e.logf("Creating wireguard device...") - e.wgdev = device.NewDevice(e.tundev, opts) + e.wgdev = device.NewDevice(e.tundev, e.wgLogger.DeviceLogger, opts) closePool.addFunc(e.wgdev.Close) // Pass the underlying tun.(*NativeDevice) to the router: diff --git a/wgengine/wgcfg/device_test.go b/wgengine/wgcfg/device_test.go index d48da7c52..6bab065a5 100644 --- a/wgengine/wgcfg/device_test.go +++ b/wgengine/wgcfg/device_test.go @@ -55,12 +55,8 @@ func TestDeviceConfig(t *testing.T) { }}, } - device1 := device.NewDevice(newNilTun(), &device.DeviceOptions{ - Logger: device.NewLogger(device.LogLevelError, "device1"), - }) - device2 := device.NewDevice(newNilTun(), &device.DeviceOptions{ - Logger: device.NewLogger(device.LogLevelError, "device2"), - }) + device1 := device.NewDevice(newNilTun(), device.NewLogger(device.LogLevelError, "device1")) + device2 := device.NewDevice(newNilTun(), device.NewLogger(device.LogLevelError, "device2")) defer device1.Close() defer device2.Close() From 82c4cb765cfba6c10a07e4dc93744ef361ffca91 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 24 Mar 2021 21:08:17 -0700 Subject: [PATCH 26/59] cmd/tailscaled: split package main into main shim + package So we can empty import the guts of cmd/tailscaled from another module for go mod tidy reasons. Signed-off-by: Brad Fitzpatrick --- .gitignore | 2 +- cmd/tailscaled/depaware.txt | 37 ++++++++++--------- cmd/tailscaled/tailscaled-main.go | 19 ++++++++++ cmd/tailscaled/{ => tailscaled}/debug.go | 2 +- .../{ => tailscaled}/install_darwin.go | 2 +- cmd/tailscaled/{ => tailscaled}/tailscaled.go | 11 ++---- .../{ => tailscaled}/tailscaled_notwindows.go | 2 +- .../{ => tailscaled}/tailscaled_windows.go | 2 +- 8 files changed, 47 insertions(+), 30 deletions(-) create mode 100644 cmd/tailscaled/tailscaled-main.go rename cmd/tailscaled/{ => tailscaled}/debug.go (99%) rename cmd/tailscaled/{ => tailscaled}/install_darwin.go (99%) rename cmd/tailscaled/{ => tailscaled}/tailscaled.go (97%) rename cmd/tailscaled/{ => tailscaled}/tailscaled_notwindows.go (88%) rename cmd/tailscaled/{ => tailscaled}/tailscaled_windows.go (99%) diff --git a/.gitignore b/.gitignore index 39a4e8702..f9ca59c6d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ *.dylib cmd/tailscale/tailscale -cmd/tailscaled/tailscaled +cmd/tailscaled/tailscaled/tailscaled # Test binary, built with `go test -c` *.test diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 0d9df8cd8..6656108b1 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -68,22 +68,23 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de inet.af/peercred from tailscale.com/ipn/ipnserver rsc.io/goversion/version from tailscale.com/version tailscale.com/atomicfile from tailscale.com/ipn+ + tailscale.com/cmd/tailscaled/tailscaled from tailscale.com/cmd/tailscaled tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+ tailscale.com/derp from tailscale.com/derp/derphttp+ tailscale.com/derp/derphttp from tailscale.com/net/netcheck+ - tailscale.com/derp/derpmap from tailscale.com/cmd/tailscaled+ + tailscale.com/derp/derpmap from tailscale.com/cmd/tailscaled/tailscaled+ tailscale.com/disco from tailscale.com/derp+ tailscale.com/health from tailscale.com/control/controlclient+ tailscale.com/internal/deepprint from tailscale.com/ipn/ipnlocal+ tailscale.com/ipn from tailscale.com/ipn/ipnserver+ tailscale.com/ipn/ipnlocal from tailscale.com/ipn/ipnserver+ - tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled + tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/ipn+ tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal tailscale.com/log/filelogger from tailscale.com/ipn/ipnserver tailscale.com/log/logheap from tailscale.com/control/controlclient - tailscale.com/logpolicy from tailscale.com/cmd/tailscaled + tailscale.com/logpolicy from tailscale.com/cmd/tailscaled/tailscaled tailscale.com/logtail from tailscale.com/logpolicy tailscale.com/logtail/backoff from tailscale.com/control/controlclient+ tailscale.com/logtail/filch from tailscale.com/logpolicy @@ -91,31 +92,31 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 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+ - 💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscaled+ + 💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscaled/tailscaled+ tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock tailscale.com/net/netns from tailscale.com/control/controlclient+ 💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnserver tailscale.com/net/packet from tailscale.com/wgengine+ tailscale.com/net/portmapper from tailscale.com/net/netcheck+ - tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled + tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled/tailscaled tailscale.com/net/stun from tailscale.com/net/netcheck+ 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/paths from tailscale.com/cmd/tailscaled+ + tailscale.com/paths from tailscale.com/cmd/tailscaled/tailscaled+ tailscale.com/portlist from tailscale.com/ipn/ipnlocal tailscale.com/safesocket from tailscale.com/ipn/ipnserver tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+ tailscale.com/syncs from tailscale.com/net/interfaces+ tailscale.com/tailcfg from tailscale.com/control/controlclient+ - W 💣 tailscale.com/tempfork/wireguard-windows/firewall from tailscale.com/cmd/tailscaled + W 💣 tailscale.com/tempfork/wireguard-windows/firewall from tailscale.com/cmd/tailscaled/tailscaled W tailscale.com/tsconst from tailscale.com/net/interfaces 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/flagtype from tailscale.com/cmd/tailscaled/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/logger from tailscale.com/cmd/tailscaled/tailscaled+ tailscale.com/types/netmap from tailscale.com/control/controlclient+ tailscale.com/types/nettype from tailscale.com/wgengine/magicsock tailscale.com/types/opt from tailscale.com/control/controlclient+ @@ -132,14 +133,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 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/version from tailscale.com/cmd/tailscaled+ + tailscale.com/version from tailscale.com/cmd/tailscaled/tailscaled+ tailscale.com/version/distro from tailscale.com/control/controlclient+ - tailscale.com/wgengine from tailscale.com/cmd/tailscaled+ + tailscale.com/wgengine from tailscale.com/cmd/tailscaled/tailscaled+ tailscale.com/wgengine/filter from tailscale.com/control/controlclient+ - tailscale.com/wgengine/magicsock from tailscale.com/cmd/tailscaled+ + tailscale.com/wgengine/magicsock from tailscale.com/cmd/tailscaled/tailscaled+ 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/netstack from tailscale.com/cmd/tailscaled/tailscaled + tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled/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+ @@ -175,7 +176,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+ W golang.org/x/sys/windows from github.com/tailscale/wireguard-go/conn+ W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ - W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled + W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled/tailscaled golang.org/x/term from tailscale.com/logpolicy golang.org/x/text/secure/bidirule from golang.org/x/net/idna golang.org/x/text/transform from golang.org/x/text/secure/bidirule+ @@ -223,7 +224,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de encoding/pem from crypto/tls+ errors from bufio+ expvar from tailscale.com/derp+ - flag from tailscale.com/cmd/tailscaled+ + flag from tailscale.com/cmd/tailscaled/tailscaled+ fmt from compress/flate+ hash from compress/zlib+ hash/adler32 from compress/zlib @@ -246,12 +247,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de net/http from expvar+ net/http/httptrace from github.com/tcnksm/go-httpstat+ net/http/internal from net/http - net/http/pprof from tailscale.com/cmd/tailscaled + net/http/pprof from tailscale.com/cmd/tailscaled/tailscaled net/textproto from golang.org/x/net/http/httpguts+ net/url from crypto/x509+ os from crypto/rand+ os/exec from github.com/coreos/go-iptables/iptables+ - os/signal from tailscale.com/cmd/tailscaled+ + os/signal from tailscale.com/cmd/tailscaled/tailscaled+ os/user from github.com/godbus/dbus/v5+ path from debug/dwarf+ path/filepath from crypto/x509+ diff --git a/cmd/tailscaled/tailscaled-main.go b/cmd/tailscaled/tailscaled-main.go new file mode 100644 index 000000000..504ff174d --- /dev/null +++ b/cmd/tailscaled/tailscaled-main.go @@ -0,0 +1,19 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The tailscaled program is the Tailscale daemon. It's configured +// and controlled via either the tailscale CLI program or GUIs. +package main // import "tailscale.com/cmd/tailscaled" + +import "tailscale.com/cmd/tailscaled/tailscaled" + +// Don't add any new imports or code to this file. The real +// code is in tailscale.com/cmd/tailscale/tailscaled as a package +// so things can depend on it for dependency reasons. +// (Go programs can't import package main so we split the real code +// off where we could have a dummy package empty import it) + +func main() { + tailscaled.Main() +} diff --git a/cmd/tailscaled/debug.go b/cmd/tailscaled/tailscaled/debug.go similarity index 99% rename from cmd/tailscaled/debug.go rename to cmd/tailscaled/tailscaled/debug.go index 3419dfb3b..85d6a66ae 100644 --- a/cmd/tailscaled/debug.go +++ b/cmd/tailscaled/tailscaled/debug.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package tailscaled import ( "context" diff --git a/cmd/tailscaled/install_darwin.go b/cmd/tailscaled/tailscaled/install_darwin.go similarity index 99% rename from cmd/tailscaled/install_darwin.go rename to cmd/tailscaled/tailscaled/install_darwin.go index c373c78e8..c2442de73 100644 --- a/cmd/tailscaled/install_darwin.go +++ b/cmd/tailscaled/tailscaled/install_darwin.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main +package tailscaled import ( "errors" diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled/tailscaled.go similarity index 97% rename from cmd/tailscaled/tailscaled.go rename to cmd/tailscaled/tailscaled/tailscaled.go index a760db708..d0b5330d8 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled/tailscaled.go @@ -2,12 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// The tailscaled program is the Tailscale client daemon. It's configured -// and controlled via the tailscale CLI program. -// -// It primarily supports Linux, though other systems will likely be -// supported in the future. -package main // import "tailscale.com/cmd/tailscaled" +// Package tailscaled is the Tailscale daemon. It's configured +// and controlled via the tailscale CLI program and GUIs. +package tailscaled // import "tailscale.com/cmd/tailscaled/tailscaled" import ( "context" @@ -98,7 +95,7 @@ var subCommands = map[string]*func([]string) error{ "debug": &debugModeFunc, } -func main() { +func Main() { // We aren't very performance sensitive, and the parts that are // performance sensitive (wireguard) try hard not to do any memory // allocations. So let's be aggressive about garbage collection, diff --git a/cmd/tailscaled/tailscaled_notwindows.go b/cmd/tailscaled/tailscaled/tailscaled_notwindows.go similarity index 88% rename from cmd/tailscaled/tailscaled_notwindows.go rename to cmd/tailscaled/tailscaled/tailscaled_notwindows.go index 58221a2ea..444ef4f37 100644 --- a/cmd/tailscaled/tailscaled_notwindows.go +++ b/cmd/tailscaled/tailscaled/tailscaled_notwindows.go @@ -4,7 +4,7 @@ // +build !windows -package main // import "tailscale.com/cmd/tailscaled" +package tailscaled import "tailscale.com/logpolicy" diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled/tailscaled_windows.go similarity index 99% rename from cmd/tailscaled/tailscaled_windows.go rename to cmd/tailscaled/tailscaled/tailscaled_windows.go index cf97bef4a..7bf9add5e 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled/tailscaled_windows.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package main // import "tailscale.com/cmd/tailscaled" +package tailscaled // TODO: check if administrator, like tswin does. // From dad10fee9cf5cf9d2c6a7565213921892c05bae1 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 25 Mar 2021 08:59:00 -0700 Subject: [PATCH 27/59] Revert "cmd/tailscaled: split package main into main shim + package" This reverts commit b81bd8025b71f35295201d18011855c1cfae630e. Not needed. See: https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module --- .gitignore | 2 +- cmd/tailscaled/{tailscaled => }/debug.go | 2 +- cmd/tailscaled/depaware.txt | 37 +++++++++---------- .../{tailscaled => }/install_darwin.go | 2 +- cmd/tailscaled/tailscaled-main.go | 19 ---------- cmd/tailscaled/{tailscaled => }/tailscaled.go | 11 ++++-- .../{tailscaled => }/tailscaled_notwindows.go | 2 +- .../{tailscaled => }/tailscaled_windows.go | 2 +- 8 files changed, 30 insertions(+), 47 deletions(-) rename cmd/tailscaled/{tailscaled => }/debug.go (99%) rename cmd/tailscaled/{tailscaled => }/install_darwin.go (99%) delete mode 100644 cmd/tailscaled/tailscaled-main.go rename cmd/tailscaled/{tailscaled => }/tailscaled.go (97%) rename cmd/tailscaled/{tailscaled => }/tailscaled_notwindows.go (88%) rename cmd/tailscaled/{tailscaled => }/tailscaled_windows.go (99%) diff --git a/.gitignore b/.gitignore index f9ca59c6d..39a4e8702 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ *.dylib cmd/tailscale/tailscale -cmd/tailscaled/tailscaled/tailscaled +cmd/tailscaled/tailscaled # Test binary, built with `go test -c` *.test diff --git a/cmd/tailscaled/tailscaled/debug.go b/cmd/tailscaled/debug.go similarity index 99% rename from cmd/tailscaled/tailscaled/debug.go rename to cmd/tailscaled/debug.go index 85d6a66ae..3419dfb3b 100644 --- a/cmd/tailscaled/tailscaled/debug.go +++ b/cmd/tailscaled/debug.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tailscaled +package main import ( "context" diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 6656108b1..0d9df8cd8 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -68,23 +68,22 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de inet.af/peercred from tailscale.com/ipn/ipnserver rsc.io/goversion/version from tailscale.com/version tailscale.com/atomicfile from tailscale.com/ipn+ - tailscale.com/cmd/tailscaled/tailscaled from tailscale.com/cmd/tailscaled tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+ tailscale.com/derp from tailscale.com/derp/derphttp+ tailscale.com/derp/derphttp from tailscale.com/net/netcheck+ - tailscale.com/derp/derpmap from tailscale.com/cmd/tailscaled/tailscaled+ + tailscale.com/derp/derpmap from tailscale.com/cmd/tailscaled+ tailscale.com/disco from tailscale.com/derp+ tailscale.com/health from tailscale.com/control/controlclient+ tailscale.com/internal/deepprint from tailscale.com/ipn/ipnlocal+ tailscale.com/ipn from tailscale.com/ipn/ipnserver+ tailscale.com/ipn/ipnlocal from tailscale.com/ipn/ipnserver+ - tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled/tailscaled + tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/ipn+ tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal tailscale.com/log/filelogger from tailscale.com/ipn/ipnserver tailscale.com/log/logheap from tailscale.com/control/controlclient - tailscale.com/logpolicy from tailscale.com/cmd/tailscaled/tailscaled + tailscale.com/logpolicy from tailscale.com/cmd/tailscaled tailscale.com/logtail from tailscale.com/logpolicy tailscale.com/logtail/backoff from tailscale.com/control/controlclient+ tailscale.com/logtail/filch from tailscale.com/logpolicy @@ -92,31 +91,31 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 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+ - 💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscaled/tailscaled+ + 💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscaled+ tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock tailscale.com/net/netns from tailscale.com/control/controlclient+ 💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnserver tailscale.com/net/packet from tailscale.com/wgengine+ tailscale.com/net/portmapper from tailscale.com/net/netcheck+ - tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled/tailscaled + tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled tailscale.com/net/stun from tailscale.com/net/netcheck+ 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/paths from tailscale.com/cmd/tailscaled/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 tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+ tailscale.com/syncs from tailscale.com/net/interfaces+ tailscale.com/tailcfg from tailscale.com/control/controlclient+ - W 💣 tailscale.com/tempfork/wireguard-windows/firewall from tailscale.com/cmd/tailscaled/tailscaled + W 💣 tailscale.com/tempfork/wireguard-windows/firewall from tailscale.com/cmd/tailscaled W tailscale.com/tsconst from tailscale.com/net/interfaces 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/tailscaled + 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/tailscaled+ + tailscale.com/types/logger from tailscale.com/cmd/tailscaled+ tailscale.com/types/netmap from tailscale.com/control/controlclient+ tailscale.com/types/nettype from tailscale.com/wgengine/magicsock tailscale.com/types/opt from tailscale.com/control/controlclient+ @@ -133,14 +132,14 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 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/version from tailscale.com/cmd/tailscaled/tailscaled+ + 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/tailscaled+ + tailscale.com/wgengine from tailscale.com/cmd/tailscaled+ tailscale.com/wgengine/filter from tailscale.com/control/controlclient+ - tailscale.com/wgengine/magicsock from tailscale.com/cmd/tailscaled/tailscaled+ + tailscale.com/wgengine/magicsock from tailscale.com/cmd/tailscaled+ tailscale.com/wgengine/monitor from tailscale.com/wgengine+ - tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled/tailscaled - tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled/tailscaled+ + 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+ @@ -176,7 +175,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+ W golang.org/x/sys/windows from github.com/tailscale/wireguard-go/conn+ W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ - W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled/tailscaled + W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled golang.org/x/term from tailscale.com/logpolicy golang.org/x/text/secure/bidirule from golang.org/x/net/idna golang.org/x/text/transform from golang.org/x/text/secure/bidirule+ @@ -224,7 +223,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de encoding/pem from crypto/tls+ errors from bufio+ expvar from tailscale.com/derp+ - flag from tailscale.com/cmd/tailscaled/tailscaled+ + flag from tailscale.com/cmd/tailscaled+ fmt from compress/flate+ hash from compress/zlib+ hash/adler32 from compress/zlib @@ -247,12 +246,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de net/http from expvar+ net/http/httptrace from github.com/tcnksm/go-httpstat+ net/http/internal from net/http - net/http/pprof from tailscale.com/cmd/tailscaled/tailscaled + net/http/pprof from tailscale.com/cmd/tailscaled net/textproto from golang.org/x/net/http/httpguts+ net/url from crypto/x509+ os from crypto/rand+ os/exec from github.com/coreos/go-iptables/iptables+ - os/signal from tailscale.com/cmd/tailscaled/tailscaled+ + os/signal from tailscale.com/cmd/tailscaled+ os/user from github.com/godbus/dbus/v5+ path from debug/dwarf+ path/filepath from crypto/x509+ diff --git a/cmd/tailscaled/tailscaled/install_darwin.go b/cmd/tailscaled/install_darwin.go similarity index 99% rename from cmd/tailscaled/tailscaled/install_darwin.go rename to cmd/tailscaled/install_darwin.go index c2442de73..c373c78e8 100644 --- a/cmd/tailscaled/tailscaled/install_darwin.go +++ b/cmd/tailscaled/install_darwin.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tailscaled +package main import ( "errors" diff --git a/cmd/tailscaled/tailscaled-main.go b/cmd/tailscaled/tailscaled-main.go deleted file mode 100644 index 504ff174d..000000000 --- a/cmd/tailscaled/tailscaled-main.go +++ /dev/null @@ -1,19 +0,0 @@ -// 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. - -// The tailscaled program is the Tailscale daemon. It's configured -// and controlled via either the tailscale CLI program or GUIs. -package main // import "tailscale.com/cmd/tailscaled" - -import "tailscale.com/cmd/tailscaled/tailscaled" - -// Don't add any new imports or code to this file. The real -// code is in tailscale.com/cmd/tailscale/tailscaled as a package -// so things can depend on it for dependency reasons. -// (Go programs can't import package main so we split the real code -// off where we could have a dummy package empty import it) - -func main() { - tailscaled.Main() -} diff --git a/cmd/tailscaled/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go similarity index 97% rename from cmd/tailscaled/tailscaled/tailscaled.go rename to cmd/tailscaled/tailscaled.go index d0b5330d8..a760db708 100644 --- a/cmd/tailscaled/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -2,9 +2,12 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package tailscaled is the Tailscale daemon. It's configured -// and controlled via the tailscale CLI program and GUIs. -package tailscaled // import "tailscale.com/cmd/tailscaled/tailscaled" +// The tailscaled program is the Tailscale client daemon. It's configured +// and controlled via the tailscale CLI program. +// +// It primarily supports Linux, though other systems will likely be +// supported in the future. +package main // import "tailscale.com/cmd/tailscaled" import ( "context" @@ -95,7 +98,7 @@ var subCommands = map[string]*func([]string) error{ "debug": &debugModeFunc, } -func Main() { +func main() { // We aren't very performance sensitive, and the parts that are // performance sensitive (wireguard) try hard not to do any memory // allocations. So let's be aggressive about garbage collection, diff --git a/cmd/tailscaled/tailscaled/tailscaled_notwindows.go b/cmd/tailscaled/tailscaled_notwindows.go similarity index 88% rename from cmd/tailscaled/tailscaled/tailscaled_notwindows.go rename to cmd/tailscaled/tailscaled_notwindows.go index 444ef4f37..58221a2ea 100644 --- a/cmd/tailscaled/tailscaled/tailscaled_notwindows.go +++ b/cmd/tailscaled/tailscaled_notwindows.go @@ -4,7 +4,7 @@ // +build !windows -package tailscaled +package main // import "tailscale.com/cmd/tailscaled" import "tailscale.com/logpolicy" diff --git a/cmd/tailscaled/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go similarity index 99% rename from cmd/tailscaled/tailscaled/tailscaled_windows.go rename to cmd/tailscaled/tailscaled_windows.go index 7bf9add5e..cf97bef4a 100644 --- a/cmd/tailscaled/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tailscaled +package main // import "tailscale.com/cmd/tailscaled" // TODO: check if administrator, like tswin does. // From 81143b6d9aa8a2ea5d728617b7faf72217c5958a Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 25 Mar 2021 15:38:40 -0700 Subject: [PATCH 28/59] ipn/ipnlocal: start of peerapi between nodes Also some necessary refactoring of the ipn/ipnstate too. Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/local.go | 65 +++++++++++---- ipn/ipnlocal/peerapi.go | 137 ++++++++++++++++++++++++++++++++ ipn/ipnstate/ipnstate.go | 44 +++++----- wgengine/magicsock/magicsock.go | 43 +++++----- 4 files changed, 229 insertions(+), 60 deletions(-) create mode 100644 ipn/ipnlocal/peerapi.go diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index a5bd272a7..c714bb933 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -95,15 +95,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(). @@ -237,16 +238,22 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) { func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func(*ipnstate.StatusBuilder)) { b.mu.Lock() defer b.mu.Unlock() - sb.SetVersion(version.Long) - 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, "http://"+pln.ln.Addr().String()) + } + }) // TODO: hostinfo, and its networkinfo // TODO: EngineStatus copy (and deprecate it?) - if b.netMap != nil { - sb.SetMagicDNSSuffix(b.netMap.MagicDNSSuffix()) - } - if extraLocked != nil { extraLocked(sb) } @@ -1426,6 +1433,32 @@ 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 + + for _, a := range b.netMap.Addresses { + ln, err := peerAPIListen(a.IP) + if err != nil { + b.logf("[unexpected] peerAPI listen(%q) error: %v", a.IP, err) + continue + } + pln := &peerAPIListener{ + ln: ln, + lb: b, + } + go pln.serve() + b.peerAPIListeners = append(b.peerAPIListeners, pln) + } } // magicDNSRootDomains returns the subset of nm.DNS.Domains that are the search domains for MagicDNS. diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go new file mode 100644 index 000000000..03e09896b --- /dev/null +++ b/ipn/ipnlocal/peerapi.go @@ -0,0 +1,137 @@ +// 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" + "strconv" + + "inet.af/netaddr" + "tailscale.com/tailcfg" +) + +var initListenConfig func(*net.ListenConfig, netaddr.IP) error + +func peerAPIListen(ip netaddr.IP) (ln net.Listener, err error) { + 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); err != nil { + return nil, err + } + } + // 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(), "tcp", net.JoinHostPort(ip.String(), strconv.Itoa(int(tryPort)))) + if err == nil { + return ln, nil + } + } + // Fall back to random ephemeral port. + return lc.Listen(context.Background(), "tcp", net.JoinHostPort(ip.String(), "0")) +} + +type peerAPIListener struct { + ln net.Listener + lb *LocalBackend +} + +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 + } + pas := &peerAPIServer{ + remoteAddr: ipp, + peerNode: peerNode, + peerUser: peerUser, + lb: pln.lb, + } + httpServer := &http.Server{ + Handler: pas, + } + 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 } + +type peerAPIServer struct { + remoteAddr netaddr.IPPort + peerNode *tailcfg.Node + peerUser tailcfg.UserProfile + lb *LocalBackend +} + +func (s *peerAPIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ` + + +

Hello, %s (%v)

+This is my Tailscale device. Your device is %v. +`, html.EscapeString(s.peerUser.DisplayName), s.remoteAddr.IP, html.EscapeString(s.peerNode.ComputedName)) + +} diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index a61555760..de9d208f7 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -87,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" @@ -112,28 +114,16 @@ type StatusBuilder struct { st Status } -func (sb *StatusBuilder) SetVersion(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.Version = v -} - -func (sb *StatusBuilder) SetBackendState(v string) { - sb.mu.Lock() - defer sb.mu.Unlock() - sb.st.BackendState = v -} - -func (sb *StatusBuilder) SetAuthURL(v string) { - sb.mu.Lock() - defer sb.mu.Unlock() - sb.st.AuthURL = v -} - -func (sb *StatusBuilder) SetMagicDNSSuffix(v string) { - sb.mu.Lock() - defer sb.mu.Unlock() - sb.st.MagicDNSSuffix = v + f(&sb.st) } func (sb *StatusBuilder) Status() *Status { @@ -143,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. diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 6fc26a2b5..7a437f9e8 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -2985,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() { @@ -3014,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} From 843299983585ddadd41a3ff4a1970bb3fb28bbe8 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 25 Mar 2021 14:50:21 -0700 Subject: [PATCH 29/59] Move wgengine/tsdns to net/dns. Straight move+fixup, no other changes. In prep for merging with wgengine/router/dns. Signed-off-by: David Anderson --- ipn/ipnlocal/local.go | 10 +++++----- {wgengine/tsdns => net/dns}/forwarder.go | 4 ++-- {wgengine/tsdns => net/dns}/map.go | 2 +- {wgengine/tsdns => net/dns}/map_test.go | 2 +- {wgengine/tsdns => net/dns}/neterr_darwin.go | 2 +- {wgengine/tsdns => net/dns}/neterr_other.go | 2 +- {wgengine/tsdns => net/dns}/neterr_windows.go | 2 +- {wgengine/tsdns => net/dns}/tsdns.go | 6 +++--- .../tsdns => net/dns}/tsdns_server_test.go | 2 +- {wgengine/tsdns => net/dns}/tsdns_test.go | 2 +- tailcfg/tailcfg.go | 2 +- wgengine/router/dns/config.go | 2 +- wgengine/userspace.go | 18 +++++++++--------- wgengine/watchdog.go | 4 ++-- wgengine/wgengine.go | 4 ++-- 15 files changed, 32 insertions(+), 32 deletions(-) rename {wgengine/tsdns => net/dns}/forwarder.go (99%) rename {wgengine/tsdns => net/dns}/map.go (99%) rename {wgengine/tsdns => net/dns}/map_test.go (99%) rename {wgengine/tsdns => net/dns}/neterr_darwin.go (97%) rename {wgengine/tsdns => net/dns}/neterr_other.go (95%) rename {wgengine/tsdns => net/dns}/neterr_windows.go (97%) rename {wgengine/tsdns => net/dns}/tsdns.go (99%) rename {wgengine/tsdns => net/dns}/tsdns_server_test.go (99%) rename {wgengine/tsdns => net/dns}/tsdns_test.go (99%) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index c714bb933..245c50b76 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -22,6 +22,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" @@ -37,8 +38,7 @@ import ( "tailscale.com/wgengine" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" - "tailscale.com/wgengine/router/dns" - "tailscale.com/wgengine/tsdns" + rdns "tailscale.com/wgengine/router/dns" "tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wgcfg/nmcfg" ) @@ -847,8 +847,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) } @@ -1420,7 +1420,7 @@ func (b *LocalBackend) authReconfig() { b.logf("[unexpected] dns proxied but no nameservers") proxied = false } - rcfg.DNS = dns.Config{ + rcfg.DNS = rdns.Config{ Nameservers: nm.DNS.Nameservers, Domains: nm.DNS.Domains, PerDomain: nm.DNS.PerDomain, diff --git a/wgengine/tsdns/forwarder.go b/net/dns/forwarder.go similarity index 99% rename from wgengine/tsdns/forwarder.go rename to net/dns/forwarder.go index 470748c4a..519c00027 100644 --- a/wgengine/tsdns/forwarder.go +++ b/net/dns/forwarder.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tsdns +package dns import ( "bytes" @@ -316,7 +316,7 @@ func (c *fwdConn) send(packet []byte, dst net.Addr) { var b *backoff.Backoff // lazily initialized, since it is not needed in the common case backOff := func(err error) { if b == nil { - b = backoff.NewBackoff("tsdns-fwdConn-send", c.logf, 30*time.Second) + b = backoff.NewBackoff("dns-fwdConn-send", c.logf, 30*time.Second) } b.BackOff(context.Background(), err) } diff --git a/wgengine/tsdns/map.go b/net/dns/map.go similarity index 99% rename from wgengine/tsdns/map.go rename to net/dns/map.go index c51dbf59b..119b6cc0a 100644 --- a/wgengine/tsdns/map.go +++ b/net/dns/map.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tsdns +package dns import ( "sort" diff --git a/wgengine/tsdns/map_test.go b/net/dns/map_test.go similarity index 99% rename from wgengine/tsdns/map_test.go rename to net/dns/map_test.go index dba9bb586..c438f95a0 100644 --- a/wgengine/tsdns/map_test.go +++ b/net/dns/map_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tsdns +package dns import ( "fmt" diff --git a/wgengine/tsdns/neterr_darwin.go b/net/dns/neterr_darwin.go similarity index 97% rename from wgengine/tsdns/neterr_darwin.go rename to net/dns/neterr_darwin.go index 62bab6488..7fd621fc7 100644 --- a/wgengine/tsdns/neterr_darwin.go +++ b/net/dns/neterr_darwin.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tsdns +package dns import ( "errors" diff --git a/wgengine/tsdns/neterr_other.go b/net/dns/neterr_other.go similarity index 95% rename from wgengine/tsdns/neterr_other.go rename to net/dns/neterr_other.go index d245d0c38..b652f6e8b 100644 --- a/wgengine/tsdns/neterr_other.go +++ b/net/dns/neterr_other.go @@ -4,7 +4,7 @@ // +build !darwin,!windows -package tsdns +package dns func networkIsDown(err error) bool { return false } func networkIsUnreachable(err error) bool { return false } diff --git a/wgengine/tsdns/neterr_windows.go b/net/dns/neterr_windows.go similarity index 97% rename from wgengine/tsdns/neterr_windows.go rename to net/dns/neterr_windows.go index 90f0db2ab..2b197ee2b 100644 --- a/wgengine/tsdns/neterr_windows.go +++ b/net/dns/neterr_windows.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tsdns +package dns import ( "net" diff --git a/wgengine/tsdns/tsdns.go b/net/dns/tsdns.go similarity index 99% rename from wgengine/tsdns/tsdns.go rename to net/dns/tsdns.go index b68b8c04e..2b530b81e 100644 --- a/wgengine/tsdns/tsdns.go +++ b/net/dns/tsdns.go @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package tsdns provides a Resolver capable of resolving +// Package dns provides a Resolver capable of resolving // domains on a Tailscale network. -package tsdns +package dns import ( "encoding/hex" @@ -100,7 +100,7 @@ type ResolverConfig struct { // The root domain must be in canonical form (with a trailing period). func NewResolver(config ResolverConfig) *Resolver { r := &Resolver{ - logf: logger.WithPrefix(config.Logf, "tsdns: "), + logf: logger.WithPrefix(config.Logf, "dns: "), linkMon: config.LinkMonitor, queue: make(chan Packet, queueSize), responses: make(chan Packet), diff --git a/wgengine/tsdns/tsdns_server_test.go b/net/dns/tsdns_server_test.go similarity index 99% rename from wgengine/tsdns/tsdns_server_test.go rename to net/dns/tsdns_server_test.go index df9047fc6..95544ba18 100644 --- a/wgengine/tsdns/tsdns_server_test.go +++ b/net/dns/tsdns_server_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tsdns +package dns import ( "log" diff --git a/wgengine/tsdns/tsdns_test.go b/net/dns/tsdns_test.go similarity index 99% rename from wgengine/tsdns/tsdns_test.go rename to net/dns/tsdns_test.go index 66a62d107..59bcd8ec1 100644 --- a/wgengine/tsdns/tsdns_test.go +++ b/net/dns/tsdns_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tsdns +package dns import ( "bytes" diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index e070c82af..cd1d159d9 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -730,7 +730,7 @@ 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. + // Proxied indicates whether DNS requests are proxied through a dns.Resolver. // This enables MagicDNS. It is togglable independently of PerDomain. Proxied bool } diff --git a/wgengine/router/dns/config.go b/wgengine/router/dns/config.go index 0f752d0f8..db08df7aa 100644 --- a/wgengine/router/dns/config.go +++ b/wgengine/router/dns/config.go @@ -22,7 +22,7 @@ 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. + // Proxied indicates whether DNS requests are proxied through a dns.Resolver. // This enables MagicDNS. Proxied bool } diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 7334677bf..ab4d9f78b 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -47,7 +47,7 @@ import ( "tailscale.com/wgengine/magicsock" "tailscale.com/wgengine/monitor" "tailscale.com/wgengine/router" - "tailscale.com/wgengine/tsdns" + "tailscale.com/net/dns" "tailscale.com/wgengine/tstun" "tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wglog" @@ -95,7 +95,7 @@ type userspaceEngine struct { tundev *tstun.TUN wgdev *device.Device router router.Router - resolver *tsdns.Resolver + resolver *dns.Resolver magicConn *magicsock.Conn linkMon *monitor.Mon linkMonOwned bool // whether we created linkMon (and thus need to close it) @@ -246,7 +246,7 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ e.linkMonOwned = true } - e.resolver = tsdns.NewResolver(tsdns.ResolverConfig{ + e.resolver = dns.NewResolver(dns.ResolverConfig{ Logf: logf, Forward: true, LinkMonitor: e.linkMon, @@ -476,13 +476,13 @@ func (e *userspaceEngine) isLocalAddr(ip netaddr.IP) bool { // handleDNS is an outbound pre-filter resolving Tailscale domains. func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Response { if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == ipproto.UDP { - request := tsdns.Packet{ + request := dns.Packet{ Payload: append([]byte(nil), p.Payload()...), Addr: netaddr.IPPort{IP: p.Src.IP, Port: p.Src.Port}, } err := e.resolver.EnqueueRequest(request) if err != nil { - e.logf("tsdns: enqueue: %v", err) + e.logf("dns: enqueue: %v", err) } return filter.Drop } @@ -493,11 +493,11 @@ func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Respo func (e *userspaceEngine) pollResolver() { for { resp, err := e.resolver.NextResponse() - if err == tsdns.ErrClosed { + if err == dns.ErrClosed { return } if err != nil { - e.logf("tsdns: error: %v", err) + e.logf("dns: error: %v", err) continue } @@ -511,7 +511,7 @@ func (e *userspaceEngine) pollResolver() { } hlen := h.Len() - // TODO(dmytro): avoid this allocation without importing tstun quirks into tsdns. + // TODO(dmytro): avoid this allocation without importing tstun quirks into dns. const offset = tstun.PacketStartOffset buf := make([]byte, offset+hlen+len(resp.Payload)) copy(buf[offset+hlen:], resp.Payload) @@ -1047,7 +1047,7 @@ func (e *userspaceEngine) SetFilter(filt *filter.Filter) { e.tundev.SetFilter(filt) } -func (e *userspaceEngine) SetDNSMap(dm *tsdns.Map) { +func (e *userspaceEngine) SetDNSMap(dm *dns.Map) { e.resolver.SetMap(dm) } diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index 6a607ce03..bc83044ca 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -19,7 +19,7 @@ import ( "tailscale.com/wgengine/filter" "tailscale.com/wgengine/monitor" "tailscale.com/wgengine/router" - "tailscale.com/wgengine/tsdns" + "tailscale.com/net/dns" "tailscale.com/wgengine/wgcfg" ) @@ -84,7 +84,7 @@ func (e *watchdogEngine) GetFilter() *filter.Filter { func (e *watchdogEngine) SetFilter(filt *filter.Filter) { e.watchdog("SetFilter", func() { e.wrap.SetFilter(filt) }) } -func (e *watchdogEngine) SetDNSMap(dm *tsdns.Map) { +func (e *watchdogEngine) SetDNSMap(dm *dns.Map) { e.watchdog("SetDNSMap", func() { e.wrap.SetDNSMap(dm) }) } func (e *watchdogEngine) SetStatusCallback(cb StatusCallback) { diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index 2e3a8fd36..efb2b34d5 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -14,7 +14,7 @@ import ( "tailscale.com/wgengine/filter" "tailscale.com/wgengine/monitor" "tailscale.com/wgengine/router" - "tailscale.com/wgengine/tsdns" + "tailscale.com/net/dns" "tailscale.com/wgengine/wgcfg" ) @@ -66,7 +66,7 @@ type Engine interface { SetFilter(*filter.Filter) // SetDNSMap updates the DNS map. - SetDNSMap(*tsdns.Map) + SetDNSMap(*dns.Map) // SetStatusCallback sets the function to call when the // WireGuard status changes. From 9f7f2af0085f1564d3698edad0fcde421345b80c Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 25 Mar 2021 15:30:05 -0700 Subject: [PATCH 30/59] wgengine/router/dns: move to net/dns. Preparation for merging the APIs and whatnot. Signed-off-by: David Anderson --- cmd/tailscaled/depaware.txt | 7 +++---- internal/deepprint/deepprint_test.go | 2 +- ipn/ipnlocal/local.go | 3 +-- {wgengine/router => net}/dns/config.go | 0 {wgengine/router => net}/dns/direct.go | 0 {wgengine/router => net}/dns/manager.go | 0 {wgengine/router => net}/dns/manager_default.go | 0 {wgengine/router => net}/dns/manager_freebsd.go | 0 {wgengine/router => net}/dns/manager_linux.go | 0 {wgengine/router => net}/dns/manager_openbsd.go | 0 {wgengine/router => net}/dns/manager_windows.go | 0 {wgengine/router => net}/dns/nm.go | 0 {wgengine/router => net}/dns/noop.go | 0 {wgengine/router => net}/dns/registry_windows.go | 0 {wgengine/router => net}/dns/resolvconf.go | 0 {wgengine/router => net}/dns/resolved.go | 0 wgengine/router/router.go | 2 +- wgengine/router/router_linux.go | 2 +- wgengine/router/router_openbsd.go | 2 +- wgengine/router/router_userspace_bsd.go | 2 +- wgengine/router/router_windows.go | 2 +- 21 files changed, 10 insertions(+), 12 deletions(-) rename {wgengine/router => net}/dns/config.go (100%) rename {wgengine/router => net}/dns/direct.go (100%) rename {wgengine/router => net}/dns/manager.go (100%) rename {wgengine/router => net}/dns/manager_default.go (100%) rename {wgengine/router => net}/dns/manager_freebsd.go (100%) rename {wgengine/router => net}/dns/manager_linux.go (100%) rename {wgengine/router => net}/dns/manager_openbsd.go (100%) rename {wgengine/router => net}/dns/manager_windows.go (100%) rename {wgengine/router => net}/dns/nm.go (100%) rename {wgengine/router => net}/dns/noop.go (100%) rename {wgengine/router => net}/dns/registry_windows.go (100%) rename {wgengine/router => net}/dns/resolvconf.go (100%) rename {wgengine/router => net}/dns/resolved.go (100%) diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 0d9df8cd8..bfabd1935 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -6,7 +6,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 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 @@ -88,6 +88,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+ @@ -125,7 +126,7 @@ 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+ L tailscale.com/util/lineread from tailscale.com/control/controlclient+ tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver @@ -140,8 +141,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 diff --git a/internal/deepprint/deepprint_test.go b/internal/deepprint/deepprint_test.go index e5b2b0924..043562eea 100644 --- a/internal/deepprint/deepprint_test.go +++ b/internal/deepprint/deepprint_test.go @@ -10,7 +10,7 @@ import ( "inet.af/netaddr" "tailscale.com/wgengine/router" - "tailscale.com/wgengine/router/dns" + "tailscale.com/net/dns" "tailscale.com/wgengine/wgcfg" ) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 245c50b76..c2714afff 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -38,7 +38,6 @@ import ( "tailscale.com/wgengine" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" - rdns "tailscale.com/wgengine/router/dns" "tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wgcfg/nmcfg" ) @@ -1420,7 +1419,7 @@ func (b *LocalBackend) authReconfig() { b.logf("[unexpected] dns proxied but no nameservers") proxied = false } - rcfg.DNS = rdns.Config{ + rcfg.DNS = dns.Config{ Nameservers: nm.DNS.Nameservers, Domains: nm.DNS.Domains, PerDomain: nm.DNS.PerDomain, diff --git a/wgengine/router/dns/config.go b/net/dns/config.go similarity index 100% rename from wgengine/router/dns/config.go rename to net/dns/config.go diff --git a/wgengine/router/dns/direct.go b/net/dns/direct.go similarity index 100% rename from wgengine/router/dns/direct.go rename to net/dns/direct.go diff --git a/wgengine/router/dns/manager.go b/net/dns/manager.go similarity index 100% rename from wgengine/router/dns/manager.go rename to net/dns/manager.go diff --git a/wgengine/router/dns/manager_default.go b/net/dns/manager_default.go similarity index 100% rename from wgengine/router/dns/manager_default.go rename to net/dns/manager_default.go diff --git a/wgengine/router/dns/manager_freebsd.go b/net/dns/manager_freebsd.go similarity index 100% rename from wgengine/router/dns/manager_freebsd.go rename to net/dns/manager_freebsd.go diff --git a/wgengine/router/dns/manager_linux.go b/net/dns/manager_linux.go similarity index 100% rename from wgengine/router/dns/manager_linux.go rename to net/dns/manager_linux.go diff --git a/wgengine/router/dns/manager_openbsd.go b/net/dns/manager_openbsd.go similarity index 100% rename from wgengine/router/dns/manager_openbsd.go rename to net/dns/manager_openbsd.go diff --git a/wgengine/router/dns/manager_windows.go b/net/dns/manager_windows.go similarity index 100% rename from wgengine/router/dns/manager_windows.go rename to net/dns/manager_windows.go diff --git a/wgengine/router/dns/nm.go b/net/dns/nm.go similarity index 100% rename from wgengine/router/dns/nm.go rename to net/dns/nm.go diff --git a/wgengine/router/dns/noop.go b/net/dns/noop.go similarity index 100% rename from wgengine/router/dns/noop.go rename to net/dns/noop.go diff --git a/wgengine/router/dns/registry_windows.go b/net/dns/registry_windows.go similarity index 100% rename from wgengine/router/dns/registry_windows.go rename to net/dns/registry_windows.go diff --git a/wgengine/router/dns/resolvconf.go b/net/dns/resolvconf.go similarity index 100% rename from wgengine/router/dns/resolvconf.go rename to net/dns/resolvconf.go diff --git a/wgengine/router/dns/resolved.go b/net/dns/resolved.go similarity index 100% rename from wgengine/router/dns/resolved.go rename to net/dns/resolved.go diff --git a/wgengine/router/router.go b/wgengine/router/router.go index 9c3f1003f..3d8bf1574 100644 --- a/wgengine/router/router.go +++ b/wgengine/router/router.go @@ -12,7 +12,7 @@ import ( "inet.af/netaddr" "tailscale.com/types/logger" "tailscale.com/types/preftype" - "tailscale.com/wgengine/router/dns" + "tailscale.com/net/dns" ) // Router is responsible for managing the system network stack. diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index b700efccc..3d9dfcf41 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -23,7 +23,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/types/preftype" "tailscale.com/version/distro" - "tailscale.com/wgengine/router/dns" + "tailscale.com/net/dns" ) const ( diff --git a/wgengine/router/router_openbsd.go b/wgengine/router/router_openbsd.go index 8c7269658..aecf42496 100644 --- a/wgengine/router/router_openbsd.go +++ b/wgengine/router/router_openbsd.go @@ -14,7 +14,7 @@ import ( "github.com/tailscale/wireguard-go/tun" "inet.af/netaddr" "tailscale.com/types/logger" - "tailscale.com/wgengine/router/dns" + "tailscale.com/net/dns" ) // For now this router only supports the WireGuard userspace implementation. diff --git a/wgengine/router/router_userspace_bsd.go b/wgengine/router/router_userspace_bsd.go index 71ccd1706..f89a3e5f1 100644 --- a/wgengine/router/router_userspace_bsd.go +++ b/wgengine/router/router_userspace_bsd.go @@ -17,7 +17,7 @@ import ( "inet.af/netaddr" "tailscale.com/types/logger" "tailscale.com/version" - "tailscale.com/wgengine/router/dns" + "tailscale.com/net/dns" ) type userspaceBSDRouter struct { diff --git a/wgengine/router/router_windows.go b/wgengine/router/router_windows.go index 89c686b95..935ab77db 100644 --- a/wgengine/router/router_windows.go +++ b/wgengine/router/router_windows.go @@ -23,7 +23,7 @@ import ( "inet.af/netaddr" "tailscale.com/logtail/backoff" "tailscale.com/types/logger" - "tailscale.com/wgengine/router/dns" + "tailscale.com/net/dns" ) type winRouter struct { From 6521f02ff6df460fe79009441decbf2c5e9aa457 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 25 Mar 2021 17:40:52 -0700 Subject: [PATCH 31/59] Move DNS flush logic to net/dns. Signed-off-by: David Anderson --- net/dns/flush_windows.go | 19 +++++++++++++++++++ wgengine/router/router_windows.go | 9 +++------ 2 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 net/dns/flush_windows.go diff --git a/net/dns/flush_windows.go b/net/dns/flush_windows.go new file mode 100644 index 000000000..3c7e7d645 --- /dev/null +++ b/net/dns/flush_windows.go @@ -0,0 +1,19 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dns + +import ( + "fmt" + "os/exec" +) + +// Flush clears the local resolver cache. +func Flush() error { + out, err := exec.Command("ipconfig", "/flushdns").CombinedOutput() + if err != nil { + return fmt.Errorf("%v (output: %s)", err, out) + } + return nil +} diff --git a/wgengine/router/router_windows.go b/wgengine/router/router_windows.go index 935ab77db..cbe0ec316 100644 --- a/wgengine/router/router_windows.go +++ b/wgengine/router/router_windows.go @@ -22,8 +22,8 @@ import ( "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "inet.af/netaddr" "tailscale.com/logtail/backoff" - "tailscale.com/types/logger" "tailscale.com/net/dns" + "tailscale.com/types/logger" ) type winRouter struct { @@ -112,11 +112,8 @@ func (r *winRouter) Set(cfg *Config) error { } // Flush DNS on router config change to clear cached DNS entries (solves #1430) - out, err := exec.Command("ipconfig", "/flushdns").CombinedOutput() - if err != nil { - r.logf("flushdns error: %v; output: %s", err, out) - } else { - r.logf("flushdns successful") + if err := dns.Flush(); err != nil { + r.logf("flushdns error: %v", err) } return nil From 672731ac6ff0e010b2837ebfd7a8100bb458d3a7 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 25 Mar 2021 17:41:51 -0700 Subject: [PATCH 32/59] many: gofmt. Signed-off-by: David Anderson --- internal/deepprint/deepprint_test.go | 2 +- wgengine/router/router.go | 2 +- wgengine/router/router_linux.go | 2 +- wgengine/router/router_openbsd.go | 2 +- wgengine/router/router_userspace_bsd.go | 2 +- wgengine/userspace.go | 2 +- wgengine/watchdog.go | 2 +- wgengine/wgengine.go | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/deepprint/deepprint_test.go b/internal/deepprint/deepprint_test.go index 043562eea..c7e2031f2 100644 --- a/internal/deepprint/deepprint_test.go +++ b/internal/deepprint/deepprint_test.go @@ -9,8 +9,8 @@ import ( "testing" "inet.af/netaddr" - "tailscale.com/wgengine/router" "tailscale.com/net/dns" + "tailscale.com/wgengine/router" "tailscale.com/wgengine/wgcfg" ) diff --git a/wgengine/router/router.go b/wgengine/router/router.go index 3d8bf1574..5f459c6d6 100644 --- a/wgengine/router/router.go +++ b/wgengine/router/router.go @@ -10,9 +10,9 @@ 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/net/dns" ) // Router is responsible for managing the system network stack. diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index 3d9dfcf41..081f7c434 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -19,11 +19,11 @@ import ( "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/net/dns" ) const ( diff --git a/wgengine/router/router_openbsd.go b/wgengine/router/router_openbsd.go index aecf42496..9b5d38471 100644 --- a/wgengine/router/router_openbsd.go +++ b/wgengine/router/router_openbsd.go @@ -13,8 +13,8 @@ import ( "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/tun" "inet.af/netaddr" - "tailscale.com/types/logger" "tailscale.com/net/dns" + "tailscale.com/types/logger" ) // For now this router only supports the WireGuard userspace implementation. diff --git a/wgengine/router/router_userspace_bsd.go b/wgengine/router/router_userspace_bsd.go index f89a3e5f1..494a1d51c 100644 --- a/wgengine/router/router_userspace_bsd.go +++ b/wgengine/router/router_userspace_bsd.go @@ -15,9 +15,9 @@ 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/version" - "tailscale.com/net/dns" ) type userspaceBSDRouter struct { diff --git a/wgengine/userspace.go b/wgengine/userspace.go index ab4d9f78b..b5a4ca832 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -30,6 +30,7 @@ import ( "tailscale.com/health" "tailscale.com/internal/deepprint" "tailscale.com/ipn/ipnstate" + "tailscale.com/net/dns" "tailscale.com/net/flowtrack" "tailscale.com/net/interfaces" "tailscale.com/net/packet" @@ -47,7 +48,6 @@ import ( "tailscale.com/wgengine/magicsock" "tailscale.com/wgengine/monitor" "tailscale.com/wgengine/router" - "tailscale.com/net/dns" "tailscale.com/wgengine/tstun" "tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wglog" diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index bc83044ca..f3248d6fc 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -14,12 +14,12 @@ import ( "inet.af/netaddr" "tailscale.com/ipn/ipnstate" + "tailscale.com/net/dns" "tailscale.com/tailcfg" "tailscale.com/types/netmap" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/monitor" "tailscale.com/wgengine/router" - "tailscale.com/net/dns" "tailscale.com/wgengine/wgcfg" ) diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index efb2b34d5..5946e1e77 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -9,12 +9,12 @@ import ( "inet.af/netaddr" "tailscale.com/ipn/ipnstate" + "tailscale.com/net/dns" "tailscale.com/tailcfg" "tailscale.com/types/netmap" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/monitor" "tailscale.com/wgengine/router" - "tailscale.com/net/dns" "tailscale.com/wgengine/wgcfg" ) From 04dd6d1daec46c635c4397086a03fe9934f404a0 Mon Sep 17 00:00:00 2001 From: Adrian Dewhurst Date: Fri, 26 Mar 2021 10:01:08 -0400 Subject: [PATCH 33/59] control/controlclient: sign RegisterRequest (#1549) control/controlclient: sign RegisterRequest Some customers wish to verify eligibility for devices to join their tailnets using machine identity certificates. TLS client certs could potentially fulfill this role but the initial customer for this feature has technical requirements that prevent their use. Instead, the certificate is loaded from the Windows local machine certificate store and uses its RSA public key to sign the RegisterRequest message. There is room to improve the flexibility of this feature in future and it is currently only tested on Windows (although Darwin theoretically works too), but this offers a reasonable starting place for now. Updates tailscale/coral#6 Signed-off-by: Adrian Dewhurst --- cmd/tailscaled/depaware.txt | 4 +- control/controlclient/direct.go | 16 +++ control/controlclient/sign.go | 31 +++++ control/controlclient/sign_supported.go | 160 ++++++++++++++++++++++ control/controlclient/sign_unsupported.go | 17 +++ go.mod | 3 + go.sum | 4 + tailcfg/tailcfg.go | 64 +++++++++ 8 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 control/controlclient/sign.go create mode 100644 control/controlclient/sign_supported.go create mode 100644 control/controlclient/sign_unsupported.go diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index bfabd1935..cc9d54e12 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -3,6 +3,7 @@ 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 @@ -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 @@ -132,7 +134,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 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+ diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 5254e5d17..57631b72e 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -351,12 +351,14 @@ func (c *Direct) doLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags Logi 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(), @@ -365,6 +367,20 @@ func (c *Direct) doLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags Logi request.Auth.Provider = persist.Provider request.Auth.LoginName = persist.LoginName request.Auth.AuthKey = authKey + err = signRegisterRequest(&request, c.serverURL, c.serverKey, c.machinePrivKey.Public()) + if err != nil { + // If signing failed, clear all related fields + request.SignatureType = tailcfg.SignatureNone + request.Timestamp = nil + request.DeviceCert = nil + request.Signature = nil + + // Don't log the common error types. Signatures are not usually enabled, + // so these are expected. + if err != errCertificateNotConfigured && err != errNoCertStore { + c.logf("RegisterReq sign error: %v", err) + } + } bodyData, err := encode(request, &serverKey, &c.machinePrivKey) if err != nil { return regen, url, err diff --git a/control/controlclient/sign.go b/control/controlclient/sign.go new file mode 100644 index 000000000..83b35f6f7 --- /dev/null +++ b/control/controlclient/sign.go @@ -0,0 +1,31 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package controlclient + +import ( + "crypto" + "errors" + "fmt" + "time" + + "tailscale.com/types/wgkey" +) + +var ( + errNoCertStore = errors.New("no certificate store") + errCertificateNotConfigured = errors.New("no certificate subject configured") +) + +// HashRegisterRequest generates the hash required sign or verify a +// tailcfg.RegisterRequest with tailcfg.SignatureV1. +func HashRegisterRequest(ts time.Time, serverURL string, deviceCert []byte, serverPubKey, machinePubKey wgkey.Key) []byte { + h := crypto.SHA256.New() + + // hash.Hash.Write never returns an error, so we don't check for one here. + fmt.Fprintf(h, "%s%s%s%s%s", + ts.UTC().Format(time.RFC3339), serverURL, deviceCert, serverPubKey, machinePubKey) + + return h.Sum(nil) +} diff --git a/control/controlclient/sign_supported.go b/control/controlclient/sign_supported.go new file mode 100644 index 000000000..9eadcafd0 --- /dev/null +++ b/control/controlclient/sign_supported.go @@ -0,0 +1,160 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows,cgo + +// darwin,cgo is also supported by certstore but machineCertificateSubject will +// need to be loaded by a different mechanism, so this is not currently enabled +// on darwin. + +package controlclient + +import ( + "crypto" + "crypto/rsa" + "crypto/x509" + "errors" + "fmt" + "sync" + + "github.com/github/certstore" + "tailscale.com/tailcfg" + "tailscale.com/types/wgkey" + "tailscale.com/util/winutil" +) + +var getMachineCertificateSubjectOnce struct { + sync.Once + v string // Subject of machine certificate to search for +} + +// getMachineCertificateSubject returns the exact name of a Subject that needs +// to be present in an identity's certificate chain to sign a RegisterRequest, +// formatted as per pkix.Name.String(). The Subject may be that of the identity +// itself, an intermediate CA or the root CA. +// +// If getMachineCertificateSubject() returns "" then no lookup will occur and +// each RegisterRequest will be unsigned. +// +// Example: "CN=Tailscale Inc Test Root CA,OU=Tailscale Inc Test Certificate Authority,O=Tailscale Inc,ST=ON,C=CA" +func getMachineCertificateSubject() string { + getMachineCertificateSubjectOnce.Do(func() { + getMachineCertificateSubjectOnce.v = winutil.GetRegString("MachineCertificateSubject", "") + }) + + return getMachineCertificateSubjectOnce.v +} + +var ( + errNoMatch = errors.New("no matching certificate") + errBadRequest = errors.New("malformed request") +) + +// findIdentity locates an identity from the Windows or Darwin certificate +// store. It returns the first certificate with a matching Subject anywhere in +// its certificate chain, so it is possible to search for the leaf certificate, +// intermediate CA or root CA. If err is nil then the returned identity will +// never be nil (if no identity is found, the error errNoMatch will be +// returned). If an identity is returned then its certificate chain is also +// returned. +func findIdentity(subject string, st certstore.Store) (certstore.Identity, []*x509.Certificate, error) { + ids, err := st.Identities() + if err != nil { + return nil, nil, err + } + + var selected certstore.Identity + var chain []*x509.Certificate + + for _, id := range ids { + chain, err = id.CertificateChain() + if err != nil { + continue + } + + if chain[0].PublicKeyAlgorithm != x509.RSA { + continue + } + + for _, c := range chain { + if c.Subject.String() == subject { + selected = id + break + } + } + } + + for _, id := range ids { + if id != selected { + id.Close() + } + } + + if selected == nil { + return nil, nil, errNoMatch + } + + return selected, chain, nil +} + +// signRegisterRequest looks for a suitable machine identity from the local +// system certificate store, and if one is found, signs the RegisterRequest +// using that identity's public key. In addition to the signature, the full +// certificate chain is included so that the control server can validate the +// certificate from a copy of the root CA's certificate. +func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey wgkey.Key) (err error) { + defer func() { + if err != nil { + err = fmt.Errorf("signRegisterRequest: %w", err) + } + }() + + if req.Timestamp == nil { + return errBadRequest + } + + machineCertificateSubject := getMachineCertificateSubject() + if machineCertificateSubject == "" { + return errCertificateNotConfigured + } + + st, err := certstore.Open(certstore.System) + if err != nil { + return fmt.Errorf("open cert store: %w", err) + } + defer st.Close() + + id, chain, err := findIdentity(machineCertificateSubject, st) + if err != nil { + return fmt.Errorf("find identity: %w", err) + } + defer id.Close() + + signer, err := id.Signer() + if err != nil { + return fmt.Errorf("create signer: %w", err) + } + + cl := 0 + for _, c := range chain { + cl += len(c.Raw) + } + req.DeviceCert = make([]byte, 0, cl) + for _, c := range chain { + req.DeviceCert = append(req.DeviceCert, c.Raw...) + } + + h := HashRegisterRequest(req.Timestamp.UTC(), serverURL, req.DeviceCert, serverPubKey, machinePubKey) + + req.Signature, err = signer.Sign(nil, h, &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthEqualsHash, + Hash: crypto.SHA256, + }) + if err != nil { + return fmt.Errorf("sign: %w", err) + } + req.SignatureType = tailcfg.SignatureV1 + + return nil +} diff --git a/control/controlclient/sign_unsupported.go b/control/controlclient/sign_unsupported.go new file mode 100644 index 000000000..e20ced316 --- /dev/null +++ b/control/controlclient/sign_unsupported.go @@ -0,0 +1,17 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !windows !cgo + +package controlclient + +import ( + "tailscale.com/tailcfg" + "tailscale.com/types/wgkey" +) + +// signRegisterRequest on non-supported platforms always returns errNoCertStore. +func signRegisterRequest(req *tailcfg.RegisterRequest, serverURL string, serverPubKey, machinePubKey wgkey.Key) error { + return errNoCertStore +} diff --git a/go.mod b/go.mod index 05158381e..479431041 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect github.com/coreos/go-iptables v0.4.5 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect + github.com/github/certstore v0.1.0 github.com/gliderlabs/ssh v0.2.2 github.com/go-multierror/multierror v1.0.2 github.com/go-ole/go-ole v1.2.4 @@ -42,3 +43,5 @@ require ( inet.af/peercred v0.0.0-20210302202138-56e694897155 rsc.io/goversion v1.2.0 ) + +replace github.com/github/certstore => github.com/cyolosecurity/certstore v0.0.0-20200922073901-ece7f1d353c2 diff --git a/go.sum b/go.sum index 5562d64b6..1d2d82a4f 100644 --- a/go.sum +++ b/go.sum @@ -17,12 +17,16 @@ github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRY 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/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-multierror/multierror v1.0.2 h1:AwsKbEXkmf49ajdFJgcFXqSG0aLo0HEyAE9zk9JguJo= diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index cd1d159d9..30119b671 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -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: @@ -558,6 +613,13 @@ type RegisterRequest struct { 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 } From 7dc88e4c1ea4f3f62b4da0920d67286c36e16865 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 25 Mar 2021 21:26:01 -0700 Subject: [PATCH 34/59] net/interfaces: track more interface metadata in State We have it already but threw it away. But macOS/iOS code will be needing the interface index, so hang on to it. Signed-off-by: Brad Fitzpatrick --- net/interfaces/interfaces.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go index 3a0ffeb0b..625164dfd 100644 --- a/net/interfaces/interfaces.go +++ b/net/interfaces/interfaces.go @@ -204,7 +204,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 +235,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 +253,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] { @@ -301,7 +301,7 @@ func (s *State) AnyInterfaceUp() bool { // 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 { + for ifName := range s.Interface { ips := s.InterfaceIPs[ifName] keep := ips[:0] for _, pfx := range ips { @@ -310,7 +310,7 @@ func (s *State) RemoveUninterestingInterfacesAndAddresses() { } } if len(keep) == 0 { - delete(s.InterfaceUp, ifName) + delete(s.Interface, ifName) delete(s.InterfaceIPs, ifName) continue } @@ -327,7 +327,7 @@ func (s *State) RemoveTailscaleInterfaces() { for name, pfxs := range s.InterfaceIPs { if isTailscaleInterface(name, pfxs) { delete(s.InterfaceIPs, name) - delete(s.InterfaceUp, name) + delete(s.Interface, name) } } } @@ -364,11 +364,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 From 5a62aa8047ec8f5c53d60235e573dd81d9fc82ec Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 25 Mar 2021 21:41:37 -0700 Subject: [PATCH 35/59] ipn/ipnlocal: pass down interface state to peerapi ListenConfig hook Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/local.go | 3 ++- ipn/ipnlocal/peerapi.go | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index c2714afff..edebb356d 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -143,6 +143,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()) @@ -1446,7 +1447,7 @@ func (b *LocalBackend) initPeerAPIListener() { b.peerAPIListeners = nil for _, a := range b.netMap.Addresses { - ln, err := peerAPIListen(a.IP) + ln, err := peerAPIListen(a.IP, b.prevIfState) if err != nil { b.logf("[unexpected] peerAPI listen(%q) error: %v", a.IP, err) continue diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index 03e09896b..29bcd0abe 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -16,18 +16,19 @@ import ( "strconv" "inet.af/netaddr" + "tailscale.com/net/interfaces" "tailscale.com/tailcfg" ) -var initListenConfig func(*net.ListenConfig, netaddr.IP) error +var initListenConfig func(*net.ListenConfig, netaddr.IP, *interfaces.State) error -func peerAPIListen(ip netaddr.IP) (ln net.Listener, err error) { +func peerAPIListen(ip netaddr.IP, ifState *interfaces.State) (ln net.Listener, err error) { 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); err != nil { + if err := initListenConfig(&lc, ip, ifState); err != nil { return nil, err } } From 7f174e84e627dc04bc91352e9897f22cf4724191 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 26 Mar 2021 09:09:12 -0700 Subject: [PATCH 36/59] net/interfaces: remove mutating methods, add EqualFiltered instead Now callers (wgengine/monitor) don't need to mutate the state to remove boring interfaces before calling State.Equal. Instead, the methods to remove boring interfaces from the State are removed, as is the reflect-using Equal method itself, and in their place is a new EqualFiltered method that takes a func predicate to match interfaces to compare. And then the FilterInteresting predicate is added for use with EqualFiltered to do the job that that wgengine/monitor previously wanted. Now wgengine/monitor can keep the full interface state around, including the "boring" interfaces, which we'll need for peerapi on macOS/iOS to bind to the interface index of the utunN device. Signed-off-by: Brad Fitzpatrick --- net/interfaces/interfaces.go | 105 +++++++++++++++++++----------- net/interfaces/interfaces_test.go | 12 ++-- wgengine/monitor/monitor.go | 9 +-- 3 files changed, 77 insertions(+), 49 deletions(-) diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go index 625164dfd..f2988af34 100644 --- a/net/interfaces/interfaces.go +++ b/net/interfaces/interfaces.go @@ -6,10 +6,10 @@ package interfaces import ( + "bytes" "fmt" "net" "net/http" - "reflect" "runtime" "sort" "strings" @@ -190,6 +190,9 @@ func ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error { } } } + sort.Slice(pfxs, func(i, j int) bool { + return pfxs[i].IP.Less(pfxs[j].IP) + }) fn(Interface{iface}, pfxs) } return nil @@ -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.Interface { - 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.Interface, 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.Interface, name) - } - } -} - func hasTailscaleIP(pfxs []netaddr.IPPrefix) bool { for _, pfx := range pfxs { if tsaddr.IsTailscaleIP(pfx.IP) { diff --git a/net/interfaces/interfaces_test.go b/net/interfaces/interfaces_test.go index 88948b579..ab0ee3734 100644 --- a/net/interfaces/interfaces_test.go +++ b/net/interfaces/interfaces_test.go @@ -5,6 +5,7 @@ package interfaces import ( + "encoding/json" "testing" ) @@ -13,7 +14,11 @@ func TestGetState(t *testing.T) { if err != nil { t.Fatal(err) } - t.Logf("Got: %#v", st) + j, err := json.MarshalIndent(st, "", "\t") + if err != nil { + t.Errorf("JSON: %v", err) + } + t.Logf("Got: %s", j) t.Logf("As string: %s", st) st2, err := GetState() @@ -21,14 +26,13 @@ func TestGetState(t *testing.T) { t.Fatal(err) } - if !st.Equal(st2) { + if !st.EqualFiltered(st2, FilterAll) { // let's assume nobody was changing the system network interfaces between // the two GetState calls. t.Fatal("two States back-to-back were not equal") } - st.RemoveTailscaleInterfaces() - t.Logf("As string without Tailscale:\n\t%s", st) + t.Logf("As string:\n\t%s", st) } func TestLikelyHomeRouterIP(t *testing.T) { diff --git a/wgengine/monitor/monitor.go b/wgengine/monitor/monitor.go index 254df7cb6..3d88a9a8b 100644 --- a/wgengine/monitor/monitor.go +++ b/wgengine/monitor/monitor.go @@ -101,12 +101,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 @@ -233,7 +228,7 @@ func (m *Mon) debounce() { } else { m.mu.Lock() oldState := m.ifState - changed := !curState.Equal(oldState) + changed := !curState.EqualFiltered(oldState, interfaces.FilterInteresting) if changed { m.gwValid = false m.ifState = curState From bcf571ec971783bc53d7ae5acc3ba5b630cd0e78 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 26 Mar 2021 09:16:43 -0700 Subject: [PATCH 37/59] wgengine/monitor: fix OpenBSD build Signed-off-by: Brad Fitzpatrick --- wgengine/monitor/monitor_polling.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wgengine/monitor/monitor_polling.go b/wgengine/monitor/monitor_polling.go index 079c956bb..cdc995ca4 100644 --- a/wgengine/monitor/monitor_polling.go +++ b/wgengine/monitor/monitor_polling.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "tailscale.com/net/interfaces" "tailscale.com/types/logger" ) @@ -53,7 +54,7 @@ func (pm *pollingMon) Receive() (message, error) { defer ticker.Stop() base := pm.m.InterfaceState() for { - if cur, err := pm.m.interfaceStateUncached(); err == nil && !cur.Equal(base) { + if cur, err := pm.m.interfaceStateUncached(); err == nil && !cur.EqualFiltered(base, interfaces.FilterInteresting) { return unspecifiedMessage{}, nil } select { From 1642dfdb0719782ef47744e93837787a9fc2b41c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 26 Mar 2021 13:44:55 -0700 Subject: [PATCH 38/59] ipn/ipnlocal: get peerapi ~working in macOS/iOS NetworkExtension sandbox IPv4 and IPv6 both work remotely, but IPv6 doesn't yet work from the machine itself due to routing mysteries. Untested yet on iOS, but previous prototype worked on iOS, so should work the same. Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/local.go | 14 ++++++-- ipn/ipnlocal/peerapi.go | 35 +++++++++++++++---- ipn/ipnlocal/peerapi_macios_ext.go | 54 ++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 ipn/ipnlocal/peerapi_macios_ext.go diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index edebb356d..0b21c6bf5 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -9,8 +9,10 @@ import ( "context" "errors" "fmt" + "net" "os" "runtime" + "strconv" "strings" "sync" "time" @@ -248,7 +250,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func }) sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) { for _, pln := range b.peerAPIListeners { - ss.PeerAPIURL = append(ss.PeerAPIURL, "http://"+pln.ln.Addr().String()) + ss.PeerAPIURL = append(ss.PeerAPIURL, pln.urlStr) } }) // TODO: hostinfo, and its networkinfo @@ -1446,8 +1448,14 @@ func (b *LocalBackend) initPeerAPIListener() { } b.peerAPIListeners = nil + 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) + ln, err := peerAPIListen(a.IP, b.prevIfState, tunName) if err != nil { b.logf("[unexpected] peerAPI listen(%q) error: %v", a.IP, err) continue @@ -1456,6 +1464,8 @@ func (b *LocalBackend) initPeerAPIListener() { ln: ln, lb: b, } + pln.urlStr = "http://" + net.JoinHostPort(a.IP.String(), strconv.Itoa(pln.Port())) + go pln.serve() b.peerAPIListeners = append(b.peerAPIListeners, pln) } diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index 29bcd0abe..e04429840 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -13,6 +13,7 @@ import ( "io" "net" "net/http" + "runtime" "strconv" "inet.af/netaddr" @@ -20,18 +21,29 @@ import ( "tailscale.com/tailcfg" ) -var initListenConfig func(*net.ListenConfig, netaddr.IP, *interfaces.State) error +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() -func peerAPIListen(ip netaddr.IP, ifState *interfaces.State) (ln net.Listener, err error) { 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); err != nil { + 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 @@ -45,18 +57,27 @@ func peerAPIListen(ip netaddr.IP, ifState *interfaces.State) (ln net.Listener, e hashData := a16[len(a16)-3:] hashData[0] += try tryPort := (32 << 10) | uint16(crc32.ChecksumIEEE(hashData)) - ln, err = lc.Listen(context.Background(), "tcp", net.JoinHostPort(ip.String(), strconv.Itoa(int(tryPort)))) + 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(), "tcp", net.JoinHostPort(ip.String(), "0")) + return lc.Listen(context.Background(), tcp4or6, net.JoinHostPort(ipStr, "0")) } type peerAPIListener struct { - ln net.Listener - lb *LocalBackend + ln net.Listener + lb *LocalBackend + urlStr string +} + +func (pln *peerAPIListener) Port() int { + ta, ok := pln.ln.Addr().(*net.TCPAddr) + if !ok { + return 0 + } + return ta.Port } func (pln *peerAPIListener) serve() { diff --git a/ipn/ipnlocal/peerapi_macios_ext.go b/ipn/ipnlocal/peerapi_macios_ext.go new file mode 100644 index 000000000..a75e18eed --- /dev/null +++ b/ipn/ipnlocal/peerapi_macios_ext.go @@ -0,0 +1,54 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin,redo ios,redo + +package ipnlocal + +import ( + "fmt" + "log" + "net" + "strings" + "syscall" + + "golang.org/x/sys/unix" + "inet.af/netaddr" + "tailscale.com/net/interfaces" +) + +func init() { + initListenConfig = initListenConfigNetworkExtension +} + +// initListenConfigNetworkExtension configures nc for listening on IP +// through the iOS/macOS Network/System Extension (Packet Tunnel +// Provider) sandbox. +func initListenConfigNetworkExtension(nc *net.ListenConfig, ip netaddr.IP, st *interfaces.State, tunIfName string) error { + tunIf, ok := st.Interface[tunIfName] + if !ok { + return fmt.Errorf("no interface with name %q", tunIfName) + } + nc.Control = func(network, address string, c syscall.RawConn) error { + var sockErr error + err := c.Control(func(fd uintptr) { + + v6 := strings.Contains(address, "]:") || strings.HasSuffix(network, "6") // hacky test for v6 + proto := unix.IPPROTO_IP + opt := unix.IP_BOUND_IF + if v6 { + proto = unix.IPPROTO_IPV6 + opt = unix.IPV6_BOUND_IF + } + + sockErr = unix.SetsockoptInt(int(fd), proto, opt, tunIf.Index) + log.Printf("peerapi: bind(%q, %q) on index %v = %v", network, address, tunIf.Index, sockErr) + }) + if err != nil { + return err + } + return sockErr + } + return nil +} From 0a84aaca0a4bf2f39552a2f3f7fb36a76a425cfc Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 26 Mar 2021 19:22:35 -0700 Subject: [PATCH 39/59] wgengine/router: remove unused wireguard *Device argument. Signed-off-by: David Anderson --- wgengine/router/router.go | 5 ++--- wgengine/router/router_darwin.go | 5 ++--- wgengine/router/router_default.go | 5 ++--- wgengine/router/router_fake.go | 3 +-- wgengine/router/router_freebsd.go | 5 ++--- wgengine/router/router_linux.go | 3 +-- wgengine/router/router_linux_test.go | 2 +- wgengine/router/router_openbsd.go | 3 +-- wgengine/router/router_userspace_bsd.go | 3 +-- wgengine/router/router_windows.go | 5 +---- wgengine/userspace.go | 4 ++-- 11 files changed, 16 insertions(+), 27 deletions(-) diff --git a/wgengine/router/router.go b/wgengine/router/router.go index 5f459c6d6..2e53363cf 100644 --- a/wgengine/router/router.go +++ b/wgengine/router/router.go @@ -7,7 +7,6 @@ package router import ( - "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/tun" "inet.af/netaddr" "tailscale.com/net/dns" @@ -33,9 +32,9 @@ type Router interface { // New returns a new Router for the current platform, using the // provided tun device. -func New(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) { +func New(logf logger.Logf, tundev tun.Device) (Router, error) { logf = logger.WithPrefix(logf, "router: ") - return newUserspaceRouter(logf, wgdev, tundev) + return newUserspaceRouter(logf, tundev) } // Cleanup restores the system network configuration to its original state diff --git a/wgengine/router/router_darwin.go b/wgengine/router/router_darwin.go index 26b689355..58ba8e6d3 100644 --- a/wgengine/router/router_darwin.go +++ b/wgengine/router/router_darwin.go @@ -5,13 +5,12 @@ package router import ( - "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/tun" "tailscale.com/types/logger" ) -func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) { - return newUserspaceBSDRouter(logf, wgdev, tundev) +func newUserspaceRouter(logf logger.Logf, tundev tun.Device) (Router, error) { + return newUserspaceBSDRouter(logf, tundev) } func cleanup(logger.Logf, string) { diff --git a/wgengine/router/router_default.go b/wgengine/router/router_default.go index 4d7365e04..7f05da42f 100644 --- a/wgengine/router/router_default.go +++ b/wgengine/router/router_default.go @@ -7,13 +7,12 @@ package router import ( - "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/tun" "tailscale.com/types/logger" ) -func newUserspaceRouter(logf logger.Logf, tunname string, dev *device.Device, tunDev tun.Device, netChanged func()) Router { - return NewFakeRouter(logf, tunname, dev, tunDev, netChanged) +func newUserspaceRouter(logf logger.Logf, tunname string, tunDev tun.Device, netChanged func()) Router { + return NewFakeRouter(logf, tunname, tunDev, netChanged) } func cleanup(logf logger.Logf, interfaceName string) { diff --git a/wgengine/router/router_fake.go b/wgengine/router/router_fake.go index 0d14e5000..cb9bd6b19 100644 --- a/wgengine/router/router_fake.go +++ b/wgengine/router/router_fake.go @@ -5,14 +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) { +func NewFake(logf logger.Logf, _ tun.Device) (Router, error) { return fakeRouter{logf: logf}, nil } diff --git a/wgengine/router/router_freebsd.go b/wgengine/router/router_freebsd.go index e56e3f82d..6e9380299 100644 --- a/wgengine/router/router_freebsd.go +++ b/wgengine/router/router_freebsd.go @@ -5,7 +5,6 @@ package router import ( - "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/tun" "tailscale.com/types/logger" ) @@ -15,8 +14,8 @@ import ( // Work is currently underway for an in-kernel FreeBSD implementation of wireguard // https://svnweb.freebsd.org/base?view=revision&revision=357986 -func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) { - return newUserspaceBSDRouter(logf, nil, tundev) +func newUserspaceRouter(logf logger.Logf, tundev tun.Device) (Router, error) { + return newUserspaceBSDRouter(logf, tundev) } func cleanup(logf logger.Logf, interfaceName string) { diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index 081f7c434..311681ab0 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -16,7 +16,6 @@ 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" @@ -110,7 +109,7 @@ type linuxRouter struct { cmd commandRunner } -func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (Router, error) { +func newUserspaceRouter(logf logger.Logf, tunDev tun.Device) (Router, error) { tunname, err := tunDev.Name() if err != nil { return nil, err diff --git a/wgengine/router/router_linux_test.go b/wgengine/router/router_linux_test.go index 7bacb2e2f..45109d35c 100644 --- a/wgengine/router/router_linux_test.go +++ b/wgengine/router/router_linux_test.go @@ -627,7 +627,7 @@ func TestDelRouteIdempotent(t *testing.T) { } } - r, err := newUserspaceRouter(logf, nil, tun) + r, err := newUserspaceRouter(logf, tun) if err != nil { t.Fatal(err) } diff --git a/wgengine/router/router_openbsd.go b/wgengine/router/router_openbsd.go index 9b5d38471..a6dbf9282 100644 --- a/wgengine/router/router_openbsd.go +++ b/wgengine/router/router_openbsd.go @@ -10,7 +10,6 @@ import ( "log" "os/exec" - "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/tun" "inet.af/netaddr" "tailscale.com/net/dns" @@ -31,7 +30,7 @@ type openbsdRouter struct { dns *dns.Manager } -func newUserspaceRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) { +func newUserspaceRouter(logf logger.Logf, tundev tun.Device) (Router, error) { tunname, err := tundev.Name() if err != nil { return nil, err diff --git a/wgengine/router/router_userspace_bsd.go b/wgengine/router/router_userspace_bsd.go index 494a1d51c..79a81de03 100644 --- a/wgengine/router/router_userspace_bsd.go +++ b/wgengine/router/router_userspace_bsd.go @@ -12,7 +12,6 @@ import ( "os/exec" "runtime" - "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/tun" "inet.af/netaddr" "tailscale.com/net/dns" @@ -29,7 +28,7 @@ type userspaceBSDRouter struct { dns *dns.Manager } -func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device) (Router, error) { +func newUserspaceBSDRouter(logf logger.Logf, tundev tun.Device) (Router, error) { tunname, err := tundev.Name() if err != nil { return nil, err diff --git a/wgengine/router/router_windows.go b/wgengine/router/router_windows.go index cbe0ec316..2efdce7ed 100644 --- a/wgengine/router/router_windows.go +++ b/wgengine/router/router_windows.go @@ -16,7 +16,6 @@ import ( "syscall" "time" - "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/tun" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" @@ -30,7 +29,6 @@ type winRouter struct { logf func(fmt string, args ...interface{}) tunname string nativeTun *tun.NativeTun - wgdev *device.Device routeChangeCallback *winipcfg.RouteChangeCallback dns *dns.Manager firewall *firewallTweaker @@ -45,7 +43,7 @@ type winRouter struct { firewallSubproc *exec.Cmd } -func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) { +func newUserspaceRouter(logf logger.Logf, tundev tun.Device) (Router, error) { tunname, err := tundev.Name() if err != nil { return nil, err @@ -65,7 +63,6 @@ func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Devic return &winRouter{ logf: logf, - wgdev: wgdev, tunname: tunname, nativeTun: nativeTun, dns: dns.NewManager(mconfig), diff --git a/wgengine/userspace.go b/wgengine/userspace.go index b5a4ca832..f0ca94f48 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -145,7 +145,7 @@ func (e *userspaceEngine) GetInternals() (*tstun.TUN, *magicsock.Conn) { // RouterGen is the signature for a function that creates a // router.Router. -type RouterGen func(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (router.Router, error) +type RouterGen func(logf logger.Logf, tundev tun.Device) (router.Router, error) // Config is the engine configuration. type Config struct { @@ -370,7 +370,7 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ // Pass the underlying tun.(*NativeDevice) to the router: // routers do not Read or Write, but do access native interfaces. e.logf("Creating router...") - e.router, err = conf.RouterGen(logf, e.wgdev, e.tundev.Unwrap()) + e.router, err = conf.RouterGen(logf, e.tundev.Unwrap()) if err != nil { return nil, err } From 44d9929208e1b9e5f656df37c05808c93b4c3597 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 26 Mar 2021 21:03:21 -0700 Subject: [PATCH 40/59] wgengine: remove Config.TUNName, require caller to create device. Also factors out device creation and associated OS workarounds to net/tun. Signed-off-by: David Anderson --- cmd/tailscaled/depaware.txt | 1 + cmd/tailscaled/tailscaled.go | 37 ++++-- cmd/tailscaled/tailscaled_windows.go | 8 +- {wgengine => net/tun}/ifstatus_noop.go | 2 +- {wgengine => net/tun}/ifstatus_windows.go | 2 +- net/tun/tun.go | 128 ++++++++++++++++++++ wgengine/userspace.go | 137 +--------------------- 7 files changed, 167 insertions(+), 148 deletions(-) rename {wgengine => net/tun}/ifstatus_noop.go (96%) rename {wgengine => net/tun}/ifstatus_windows.go (99%) create mode 100644 net/tun/tun.go diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index cc9d54e12..cf2763092 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -105,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/tun 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 diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index a760db708..f0215f6fb 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -32,6 +32,7 @@ import ( "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" "tailscale.com/net/socks5" + "tailscale.com/net/tun" "tailscale.com/paths" "tailscale.com/types/flagtype" "tailscale.com/types/logger" @@ -316,18 +317,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 +327,29 @@ 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 { + conf.TUN = tstun.NewFakeTUN() + conf.RouterGen = router.NewFake + } else { + dev, err := tun.New(logf, name) + if err != nil { + return nil, false, err + } + conf.TUN = dev + } + e, err = wgengine.NewUserspaceEngine(logf, conf) + if err != nil { + return nil, isUserspace, err + } + return e, isUserspace, nil +} + func newDebugMux() *http.ServeMux { mux := http.NewServeMux() mux.HandleFunc("/debug/pprof/", pprof.Index) diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index cf97bef4a..fa7f5e760 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -30,6 +30,7 @@ import ( "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" + "tailscale.com/net/tun" "tailscale.com/tempfork/wireguard-windows/firewall" "tailscale.com/types/logger" "tailscale.com/version" @@ -159,11 +160,16 @@ func startIPNServer(ctx context.Context, logid string) error { var err error getEngine := func() (wgengine.Engine, error) { + dev, err := tun.New(logf, "Tailscale") + if err != nil { + return nil, err + } eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ - TUNName: "Tailscale", + TUN: dev, ListenPort: 41641, }) if err != nil { + dev.Close() return nil, err } return wgengine.NewWatchdog(eng), nil diff --git a/wgengine/ifstatus_noop.go b/net/tun/ifstatus_noop.go similarity index 96% rename from wgengine/ifstatus_noop.go rename to net/tun/ifstatus_noop.go index 7564d67ec..e40c8c15e 100644 --- a/wgengine/ifstatus_noop.go +++ b/net/tun/ifstatus_noop.go @@ -4,7 +4,7 @@ // +build !windows -package wgengine +package tun import ( "time" diff --git a/wgengine/ifstatus_windows.go b/net/tun/ifstatus_windows.go similarity index 99% rename from wgengine/ifstatus_windows.go rename to net/tun/ifstatus_windows.go index 840b6cf39..d5b05ff40 100644 --- a/wgengine/ifstatus_windows.go +++ b/net/tun/ifstatus_windows.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package wgengine +package tun import ( "fmt" diff --git a/net/tun/tun.go b/net/tun/tun.go new file mode 100644 index 000000000..61f10ad76 --- /dev/null +++ b/net/tun/tun.go @@ -0,0 +1,128 @@ +// 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 tun + +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 + +func New(logf logger.Logf, tunName string) (tun.Device, error) { + dev, err := tun.CreateTUN(tunName, minimalMTU) + if err != nil { + return nil, err + } + if err := waitInterfaceUp(dev, 90*time.Second, logf); err != nil { + return nil, err + } + return dev, nil +} + +// Diagnose tries to explain a tuntap device creation failure. +// It pokes around the system and logs some diagnostic info that might +// help debug why tun creation failed. Because device creation has +// already failed and the program's about to end, log a lot. +func Diagnose(logf logger.Logf, tunName string) { + switch runtime.GOOS { + case "linux": + diagnoseLinuxTUNFailure(tunName, logf) + case "darwin": + diagnoseDarwinTUNFailure(tunName, logf) + default: + logf("no TUN failure diagnostics for OS %q", runtime.GOOS) + } +} + +func diagnoseDarwinTUNFailure(tunName string, logf logger.Logf) { + if os.Getuid() != 0 { + logf("failed to create TUN device as non-root user; use 'sudo tailscaled', or run under launchd with 'sudo tailscaled install-system-daemon'") + } + if tunName != "utun" { + logf("failed to create TUN device %q; try using tun device \"utun\" instead for automatic selection", tunName) + } +} + +func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf) { + kernel, err := exec.Command("uname", "-r").Output() + kernel = bytes.TrimSpace(kernel) + if err != nil { + logf("no TUN, and failed to look up kernel version: %v", err) + return + } + logf("Linux kernel version: %s", kernel) + + modprobeOut, err := exec.Command("/sbin/modprobe", "tun").CombinedOutput() + if err == nil { + logf("'modprobe tun' successful") + // Either tun is currently loaded, or it's statically + // compiled into the kernel (which modprobe checks + // with /lib/modules/$(uname -r)/modules.builtin) + // + // So if there's a problem at this point, it's + // probably because /dev/net/tun doesn't exist. + const dev = "/dev/net/tun" + if fi, err := os.Stat(dev); err != nil { + logf("tun module loaded in kernel, but %s does not exist", dev) + } else { + logf("%s: %v", dev, fi.Mode()) + } + + // We failed to find why it failed. Just let our + // caller report the error it got from wireguard-go. + return + } + logf("is CONFIG_TUN enabled in your kernel? `modprobe tun` failed with: %s", modprobeOut) + + switch distro.Get() { + case distro.Debian: + dpkgOut, err := exec.Command("dpkg", "-S", "kernel/drivers/net/tun.ko").CombinedOutput() + if len(bytes.TrimSpace(dpkgOut)) == 0 || err != nil { + logf("tun module not loaded nor found on disk") + return + } + if !bytes.Contains(dpkgOut, kernel) { + logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", dpkgOut) + } + case distro.Arch: + findOut, err := exec.Command("find", "/lib/modules/", "-path", "*/net/tun.ko*").CombinedOutput() + if len(bytes.TrimSpace(findOut)) == 0 || err != nil { + logf("tun module not loaded nor found on disk") + return + } + if !bytes.Contains(findOut, kernel) { + logf("kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: %s", findOut) + } + case distro.OpenWrt: + out, err := exec.Command("opkg", "list-installed").CombinedOutput() + if err != nil { + logf("error querying OpenWrt installed packages: %s", out) + return + } + for _, pkg := range []string{"kmod-tun", "ca-bundle"} { + if !bytes.Contains(out, []byte(pkg+" - ")) { + logf("Missing required package %s; run: opkg install %s", pkg, pkg) + } + } + } +} diff --git a/wgengine/userspace.go b/wgengine/userspace.go index f0ca94f48..0a524e868 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -14,7 +14,6 @@ import ( "io" "net" "os" - "os/exec" "runtime" "strconv" "strings" @@ -43,7 +42,6 @@ import ( "tailscale.com/types/netmap" "tailscale.com/types/wgkey" "tailscale.com/version" - "tailscale.com/version/distro" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/magicsock" "tailscale.com/wgengine/monitor" @@ -53,16 +51,6 @@ import ( "tailscale.com/wgengine/wglog" ) -// minimalMTU is the MTU we set on tailscale's TUN -// interface. wireguard-go defaults to 1420 bytes, which only works if -// the "outer" MTU is 1500 bytes. This breaks on DSL connections -// (typically 1492 MTU) and on GCE (1460 MTU?!). -// -// 1280 is the smallest MTU allowed for IPv6, which is a sensible -// "probably works everywhere" setting until we develop proper PMTU -// discovery. -const minimalMTU = 1280 - const magicDNSPort = 53 var magicDNSIP = netaddr.IPv4(100, 100, 100, 100) @@ -150,13 +138,8 @@ type RouterGen func(logf logger.Logf, tundev tun.Device) (router.Router, error) // Config is the engine configuration. type Config struct { // TUN is the TUN device used by the engine. - // Exactly one of either TUN or TUNName must be specified. TUN tun.Device - // TUNName is the TUN device to create. - // Exactly one of either TUN or TUNName must be specified. - TUNName string - // RouterGen is the function used to instantiate the router. // If nil, wgengine/router.New is used. RouterGen RouterGen @@ -186,42 +169,18 @@ func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) // NewUserspaceEngine creates the named tun device and returns a // Tailscale Engine running on it. -func NewUserspaceEngine(logf logger.Logf, conf Config) (Engine, error) { - if conf.TUN != nil && conf.TUNName != "" { - return nil, errors.New("TUN and TUNName are mutually exclusive") +func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) { + if conf.TUN == nil { + return nil, errors.New("TUN is required") } - if conf.TUN == nil && conf.TUNName == "" { - return nil, errors.New("either TUN or TUNName are required") - } - tunDev := conf.TUN - var err error - if tunName := conf.TUNName; tunName != "" { - logf("Starting userspace wireguard engine with tun device %q", tunName) - tunDev, err = tun.CreateTUN(tunName, minimalMTU) - if err != nil { - diagnoseTUNFailure(tunName, logf) - logf("CreateTUN: %v", err) - return nil, err - } - logf("CreateTUN ok.") - - if err := waitInterfaceUp(tunDev, 90*time.Second, logf); err != nil { - return nil, err - } - } - if conf.RouterGen == nil { conf.RouterGen = router.New } - return newUserspaceEngine(logf, tunDev, conf) -} - -func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ Engine, reterr error) { var closePool closeOnErrorPool defer closePool.closeAllIfError(&reterr) - tsTUNDev := tstun.WrapTUN(logf, rawTUNDev) + tsTUNDev := tstun.WrapTUN(logf, conf.TUN) closePool.add(tsTUNDev) e := &userspaceEngine{ @@ -1554,94 +1513,6 @@ func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error) return nil, fmt.Errorf("node %q found, but not using its %v route", bestInNM.ComputedNameWithHost, bestInNMPrefix) } -// diagnoseTUNFailure is called if tun.CreateTUN fails, to poke around -// the system and log some diagnostic info that might help debug why -// TUN failed. Because TUN's already failed and things the program's -// about to end, we might as well log a lot. -func diagnoseTUNFailure(tunName string, logf logger.Logf) { - switch runtime.GOOS { - case "linux": - diagnoseLinuxTUNFailure(tunName, logf) - case "darwin": - diagnoseDarwinTUNFailure(tunName, logf) - default: - logf("no TUN failure diagnostics for OS %q", runtime.GOOS) - } -} - -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) - } - } - } -} - type closeOnErrorPool []func() func (p *closeOnErrorPool) add(c io.Closer) { *p = append(*p, func() { c.Close() }) } From f26dfd054ad0214355cb6f2cb1e7f5dae4c205a7 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 26 Mar 2021 21:24:02 -0700 Subject: [PATCH 41/59] ipn/ipnlocal: rename/document peerapi stuff a bit, pass self identity So handlers can vary based on whether owner of peer matches owner of local node. Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/local.go | 9 +++++++-- ipn/ipnlocal/peerapi.go | 28 ++++++++++++++++++---------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 0b21c6bf5..f6173a763 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1448,6 +1448,10 @@ func (b *LocalBackend) initPeerAPIListener() { } 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() @@ -1461,8 +1465,9 @@ func (b *LocalBackend) initPeerAPIListener() { continue } pln := &peerAPIListener{ - ln: ln, - lb: b, + ln: ln, + lb: b, + selfNode: b.netMap.SelfNode, } pln.urlStr = "http://" + net.JoinHostPort(a.IP.String(), strconv.Itoa(pln.Port())) diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index e04429840..390b45efc 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -67,9 +67,10 @@ func peerAPIListen(ip netaddr.IP, ifState *interfaces.State, tunIfName string) ( } type peerAPIListener struct { - ln net.Listener - lb *LocalBackend - urlStr string + ln net.Listener + lb *LocalBackend + urlStr string + selfNode *tailcfg.Node } func (pln *peerAPIListener) Port() int { @@ -110,14 +111,15 @@ func (pln *peerAPIListener) serve() { c.Close() continue } - pas := &peerAPIServer{ + h := &peerAPIHandler{ + isSelf: pln.selfNode.User == peerNode.User, remoteAddr: ipp, peerNode: peerNode, peerUser: peerUser, lb: pln.lb, } httpServer := &http.Server{ - Handler: pas, + Handler: h, } go httpServer.Serve(&oneConnListener{Listener: pln.ln, conn: c}) } @@ -141,19 +143,25 @@ func (l *oneConnListener) Accept() (c net.Conn, err error) { func (l *oneConnListener) Close() error { return nil } -type peerAPIServer struct { +// peerAPIHandler serves the Peer API for a source specific client. +type peerAPIHandler struct { remoteAddr netaddr.IPPort - peerNode *tailcfg.Node - peerUser tailcfg.UserProfile + 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 (s *peerAPIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + who := h.peerUser.DisplayName fmt.Fprintf(w, `

Hello, %s (%v)

This is my Tailscale device. Your device is %v. -`, html.EscapeString(s.peerUser.DisplayName), s.remoteAddr.IP, html.EscapeString(s.peerNode.ComputedName)) +`, html.EscapeString(who), h.remoteAddr.IP, html.EscapeString(h.peerNode.ComputedName)) + if h.isSelf { + fmt.Fprintf(w, "

You are the owner of this node.\n") + } } From 9ea5cbf81f68b4a9e7414cac11b90ea06ff1b0b3 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 26 Mar 2021 21:47:07 -0700 Subject: [PATCH 42/59] cmd/tailscaled: readd tun.Diagnose call, mistakenly lost during refactor. Signed-off-by: David Anderson --- cmd/tailscaled/tailscaled.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index f0215f6fb..77e348c1c 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -339,6 +339,7 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine. } else { dev, err := tun.New(logf, name) if err != nil { + tun.Diagnose(logf, name) return nil, false, err } conf.TUN = dev From 2b4bfeda1a9bebe7f520230101a01d30caa9f04f Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 26 Mar 2021 21:47:28 -0700 Subject: [PATCH 43/59] wgengine: pass in an explicit router.Router, rather than a generator. Signed-off-by: David Anderson --- cmd/tailscaled/tailscaled.go | 2 +- wgengine/router/router_fake.go | 9 ++++----- wgengine/userspace.go | 36 +++++++++++++++------------------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 77e348c1c..522fcf042 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -335,7 +335,7 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine. isUserspace = name == "userspace-networking" if isUserspace { conf.TUN = tstun.NewFakeTUN() - conf.RouterGen = router.NewFake + conf.Router = router.NewFake(logf) } else { dev, err := tun.New(logf, name) if err != nil { diff --git a/wgengine/router/router_fake.go b/wgengine/router/router_fake.go index cb9bd6b19..add4b576b 100644 --- a/wgengine/router/router_fake.go +++ b/wgengine/router/router_fake.go @@ -5,14 +5,13 @@ package router import ( - "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, _ tun.Device) (Router, error) { - return fakeRouter{logf: logf}, nil +// NewFake returns a Router that does nothing when called and always +// returns nil errors. +func NewFake(logf logger.Logf) Router { + return fakeRouter{logf: logf} } type fakeRouter struct { diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 0a524e868..81edb5d37 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -131,18 +131,14 @@ func (e *userspaceEngine) GetInternals() (*tstun.TUN, *magicsock.Conn) { return e.tundev, e.magicConn } -// RouterGen is the signature for a function that creates a -// router.Router. -type RouterGen func(logf logger.Logf, tundev tun.Device) (router.Router, error) - // Config is the engine configuration. type Config struct { // TUN is the TUN device used by the engine. TUN tun.Device - // RouterGen is the function used to instantiate the router. - // If nil, wgengine/router.New is used. - RouterGen RouterGen + // Router is the interface to OS networking APIs used to interface + // the OS with the Engine. + Router router.Router // LinkMonitor optionally provides an existing link monitor to re-use. // If nil, a new link monitor is created. @@ -161,7 +157,7 @@ func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) logf("Starting userspace wireguard engine (with fake TUN device)") return NewUserspaceEngine(logf, Config{ TUN: tstun.NewFakeTUN(), - RouterGen: router.NewFake, + Router: router.NewFake(logf), ListenPort: listenPort, Fake: true, }) @@ -173,13 +169,21 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) if conf.TUN == nil { return nil, errors.New("TUN is required") } - if conf.RouterGen == nil { - conf.RouterGen = router.New - } var closePool closeOnErrorPool defer closePool.closeAllIfError(&reterr) + // TODO: default to a no-op router, require caller to pass in + // effectful ones. + if conf.Router == nil { + r, err := router.New(logf, conf.TUN) + if err != nil { + return nil, err + } + conf.Router = r + closePool.add(r) + } + tsTUNDev := tstun.WrapTUN(logf, conf.TUN) closePool.add(tsTUNDev) @@ -189,6 +193,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) reqCh: make(chan struct{}, 1), waitCh: make(chan struct{}), tundev: tsTUNDev, + router: conf.Router, pingers: make(map[wgkey.Key]*pinger), } e.localAddrs.Store(map[netaddr.IP]bool{}) @@ -326,15 +331,6 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) e.wgdev = device.NewDevice(e.tundev, e.wgLogger.DeviceLogger, opts) closePool.addFunc(e.wgdev.Close) - // Pass the underlying tun.(*NativeDevice) to the router: - // routers do not Read or Write, but do access native interfaces. - e.logf("Creating router...") - e.router, err = conf.RouterGen(logf, e.tundev.Unwrap()) - if err != nil { - return nil, err - } - closePool.add(e.router) - go func() { up := false for event := range e.tundev.Events() { From 018200aebaf77daa14bbc33c7131d9c07a3d9ee7 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 26 Mar 2021 22:07:19 -0700 Subject: [PATCH 44/59] net/tstun: rename from net/tun. We depend on wireguard-go/tun, identical leaf packages can be confusing in code. Signed-off-by: David Anderson --- cmd/tailscaled/depaware.txt | 2 +- cmd/tailscaled/tailscaled.go | 6 +++--- cmd/tailscaled/tailscaled_windows.go | 4 ++-- net/{tun => tstun}/ifstatus_noop.go | 2 +- net/{tun => tstun}/ifstatus_windows.go | 2 +- net/{tun => tstun}/tun.go | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) rename net/{tun => tstun}/ifstatus_noop.go (96%) rename net/{tun => tstun}/ifstatus_windows.go (99%) rename net/{tun => tstun}/tun.go (99%) diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index cf2763092..12aad21d2 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -105,7 +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/tun from tailscale.com/cmd/tailscaled + 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 diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 522fcf042..137ccb61c 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -32,7 +32,7 @@ import ( "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" "tailscale.com/net/socks5" - "tailscale.com/net/tun" + ntun "tailscale.com/net/tstun" "tailscale.com/paths" "tailscale.com/types/flagtype" "tailscale.com/types/logger" @@ -337,9 +337,9 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine. conf.TUN = tstun.NewFakeTUN() conf.Router = router.NewFake(logf) } else { - dev, err := tun.New(logf, name) + dev, err := ntun.New(logf, name) if err != nil { - tun.Diagnose(logf, name) + ntun.Diagnose(logf, name) return nil, false, err } conf.TUN = dev diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index fa7f5e760..fbd760950 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -30,7 +30,7 @@ import ( "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" - "tailscale.com/net/tun" + "tailscale.com/net/tstun" "tailscale.com/tempfork/wireguard-windows/firewall" "tailscale.com/types/logger" "tailscale.com/version" @@ -160,7 +160,7 @@ func startIPNServer(ctx context.Context, logid string) error { var err error getEngine := func() (wgengine.Engine, error) { - dev, err := tun.New(logf, "Tailscale") + dev, err := tstun.New(logf, "Tailscale") if err != nil { return nil, err } diff --git a/net/tun/ifstatus_noop.go b/net/tstun/ifstatus_noop.go similarity index 96% rename from net/tun/ifstatus_noop.go rename to net/tstun/ifstatus_noop.go index e40c8c15e..223be7949 100644 --- a/net/tun/ifstatus_noop.go +++ b/net/tstun/ifstatus_noop.go @@ -4,7 +4,7 @@ // +build !windows -package tun +package tstun import ( "time" diff --git a/net/tun/ifstatus_windows.go b/net/tstun/ifstatus_windows.go similarity index 99% rename from net/tun/ifstatus_windows.go rename to net/tstun/ifstatus_windows.go index d5b05ff40..840e50f4d 100644 --- a/net/tun/ifstatus_windows.go +++ b/net/tstun/ifstatus_windows.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package tun +package tstun import ( "fmt" diff --git a/net/tun/tun.go b/net/tstun/tun.go similarity index 99% rename from net/tun/tun.go rename to net/tstun/tun.go index 61f10ad76..6750912f6 100644 --- a/net/tun/tun.go +++ b/net/tstun/tun.go @@ -4,7 +4,7 @@ // Package tun creates a tuntap device, working around OS-specific // quirks if necessary. -package tun +package tstun import ( "bytes" From 588b70f46869ed9818c34f82e89c3357b35d28c7 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 26 Mar 2021 22:14:08 -0700 Subject: [PATCH 45/59] net/tstun: merge in wgengine/tstun. Signed-off-by: David Anderson --- cmd/tailscaled/depaware.txt | 3 +-- cmd/tailscaled/tailscaled.go | 7 +++---- wgengine/tstun/faketun.go => net/tstun/fake.go | 0 wgengine/tstun/tun.go => net/tstun/wrap.go | 0 wgengine/tstun/tun_test.go => net/tstun/wrap_test.go | 0 wgengine/tstun/tun_windows.go => net/tstun/wrap_windows.go | 0 wgengine/magicsock/magicsock_test.go | 2 +- wgengine/netstack/netstack.go | 2 +- wgengine/pendopen.go | 2 +- wgengine/userspace.go | 2 +- wgengine/userspace_test.go | 2 +- 11 files changed, 9 insertions(+), 11 deletions(-) rename wgengine/tstun/faketun.go => net/tstun/fake.go (100%) rename wgengine/tstun/tun.go => net/tstun/wrap.go (100%) rename wgengine/tstun/tun_test.go => net/tstun/wrap_test.go (100%) rename wgengine/tstun/tun_windows.go => net/tstun/wrap_windows.go (100%) diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 12aad21d2..d91a960c4 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -105,7 +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/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 @@ -144,7 +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/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 diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 137ccb61c..e025260ad 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -32,7 +32,7 @@ import ( "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" "tailscale.com/net/socks5" - ntun "tailscale.com/net/tstun" + "tailscale.com/net/tstun" "tailscale.com/paths" "tailscale.com/types/flagtype" "tailscale.com/types/logger" @@ -44,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 @@ -337,9 +336,9 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine. conf.TUN = tstun.NewFakeTUN() conf.Router = router.NewFake(logf) } else { - dev, err := ntun.New(logf, name) + dev, err := tstun.New(logf, name) if err != nil { - ntun.Diagnose(logf, name) + tstun.Diagnose(logf, name) return nil, false, err } conf.TUN = dev diff --git a/wgengine/tstun/faketun.go b/net/tstun/fake.go similarity index 100% rename from wgengine/tstun/faketun.go rename to net/tstun/fake.go diff --git a/wgengine/tstun/tun.go b/net/tstun/wrap.go similarity index 100% rename from wgengine/tstun/tun.go rename to net/tstun/wrap.go diff --git a/wgengine/tstun/tun_test.go b/net/tstun/wrap_test.go similarity index 100% rename from wgengine/tstun/tun_test.go rename to net/tstun/wrap_test.go diff --git a/wgengine/tstun/tun_windows.go b/net/tstun/wrap_windows.go similarity index 100% rename from wgengine/tstun/tun_windows.go rename to net/tstun/wrap_windows.go diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 01bb34e3f..1e4d00b19 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -37,6 +37,7 @@ import ( "tailscale.com/derp/derpmap" "tailscale.com/ipn/ipnstate" "tailscale.com/net/stun/stuntest" + "tailscale.com/net/tstun" "tailscale.com/tailcfg" "tailscale.com/tstest" "tailscale.com/tstest/natlab" @@ -47,7 +48,6 @@ import ( "tailscale.com/types/wgkey" "tailscale.com/util/cibuild" "tailscale.com/wgengine/filter" - "tailscale.com/wgengine/tstun" "tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wgcfg/nmcfg" "tailscale.com/wgengine/wglog" diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 8aba261e6..cceb10eb8 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -31,13 +31,13 @@ import ( "inet.af/netstack/tcpip/transport/udp" "inet.af/netstack/waiter" "tailscale.com/net/packet" + "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 diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index c5e8f0640..f18e12e9f 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -14,9 +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 diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 81edb5d37..d7ee6314d 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -35,6 +35,7 @@ import ( "tailscale.com/net/packet" "tailscale.com/net/tsaddr" "tailscale.com/net/tshttpproxy" + "tailscale.com/net/tstun" "tailscale.com/tailcfg" "tailscale.com/types/ipproto" "tailscale.com/types/key" @@ -46,7 +47,6 @@ import ( "tailscale.com/wgengine/magicsock" "tailscale.com/wgengine/monitor" "tailscale.com/wgengine/router" - "tailscale.com/wgengine/tstun" "tailscale.com/wgengine/wgcfg" "tailscale.com/wgengine/wglog" ) diff --git a/wgengine/userspace_test.go b/wgengine/userspace_test.go index e9b83389a..6690ece01 100644 --- a/wgengine/userspace_test.go +++ b/wgengine/userspace_test.go @@ -13,10 +13,10 @@ import ( "go4.org/mem" "inet.af/netaddr" + "tailscale.com/net/tstun" "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/wgengine/router" - "tailscale.com/wgengine/tstun" "tailscale.com/wgengine/wgcfg" ) From 82ab7972f422a6095890089ce1ab32f8c78628d2 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 26 Mar 2021 22:46:37 -0700 Subject: [PATCH 46/59] net/tstun: rename NewFakeTUN to NewFake. Signed-off-by: David Anderson --- cmd/tailscaled/tailscaled.go | 2 +- net/tstun/fake.go | 6 ++---- net/tstun/wrap_test.go | 2 +- wgengine/userspace.go | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index e025260ad..8cd6695c3 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -333,7 +333,7 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine. } isUserspace = name == "userspace-networking" if isUserspace { - conf.TUN = tstun.NewFakeTUN() + conf.TUN = tstun.NewFake() conf.Router = router.NewFake(logf) } else { dev, err := tstun.New(logf, name) diff --git a/net/tstun/fake.go b/net/tstun/fake.go index 50880131a..f9c3a9d6f 100644 --- a/net/tstun/fake.go +++ b/net/tstun/fake.go @@ -16,10 +16,8 @@ type fakeTUN struct { closechan chan struct{} } -// NewFakeTUN returns a fake TUN device that does not depend on the -// operating system or any special permissions. -// It primarily exists for testing. -func NewFakeTUN() tun.Device { +// NewFake returns a tun.Device that does nothing. +func NewFake() tun.Device { return &fakeTUN{ evchan: make(chan tun.Event), closechan: make(chan struct{}), diff --git a/net/tstun/wrap_test.go b/net/tstun/wrap_test.go index 2ba798222..2fe82cf06 100644 --- a/net/tstun/wrap_test.go +++ b/net/tstun/wrap_test.go @@ -132,7 +132,7 @@ func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *TUN) { } func newFakeTUN(logf logger.Logf, secure bool) (*fakeTUN, *TUN) { - ftun := NewFakeTUN() + ftun := NewFake() tun := WrapTUN(logf, ftun) if secure { setfilter(logf, tun) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index d7ee6314d..f72e8640f 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -156,7 +156,7 @@ type Config struct { func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) { logf("Starting userspace wireguard engine (with fake TUN device)") return NewUserspaceEngine(logf, Config{ - TUN: tstun.NewFakeTUN(), + TUN: tstun.NewFake(), Router: router.NewFake(logf), ListenPort: listenPort, Fake: true, From 016de16b2e7db78d0d312bf5740edf87f761e01e Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 26 Mar 2021 23:13:20 -0700 Subject: [PATCH 47/59] net/tstun: rename TUN to Wrapper. The tstun packagen contains both constructors for generic tun Devices, and a wrapper that provides additional functionality. Signed-off-by: David Anderson --- net/tstun/wrap.go | 73 +++++++++++++--------------- net/tstun/wrap_test.go | 16 +++--- wgengine/magicsock/magicsock_test.go | 4 +- wgengine/netstack/netstack.go | 6 +-- wgengine/pendopen.go | 4 +- wgengine/userspace.go | 14 +++--- wgengine/userspace_test.go | 2 +- 7 files changed, 58 insertions(+), 61 deletions(-) diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index 0f0725eb3..70225c52e 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -31,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") @@ -52,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 @@ -116,8 +113,8 @@ type TUN struct { 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: @@ -140,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. @@ -156,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: @@ -189,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 { @@ -221,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() @@ -257,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()) } @@ -265,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 @@ -321,7 +318,7 @@ 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) @@ -388,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 { @@ -403,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. @@ -420,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 } @@ -439,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 { @@ -455,7 +452,7 @@ func (t *TUN) InjectInboundCopy(packet []byte) error { return t.InjectInboundDirect(buf, PacketStartOffset) } -func (t *TUN) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingRequest) { +func (t *Wrapper) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingRequest) { pong := packet.TSMPPongReply{ Data: req.Data, } @@ -475,12 +472,12 @@ func (t *TUN) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingRequest) t.InjectOutbound(packet.Generate(pong, nil)) } -// InjectOutbound makes the TUN device behave as if a packet +// 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 } @@ -495,7 +492,7 @@ func (t *TUN) InjectOutbound(packet []byte) error { } } -// Unwrap returns the underlying TUN device. -func (t *TUN) Unwrap() tun.Device { +// Unwrap returns the underlying tun.Device. +func (t *Wrapper) Unwrap() tun.Device { return t.tdev } diff --git a/net/tstun/wrap_test.go b/net/tstun/wrap_test.go index 2fe82cf06..4032b168b 100644 --- a/net/tstun/wrap_test.go +++ b/net/tstun/wrap_test.go @@ -106,7 +106,7 @@ 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, @@ -120,9 +120,9 @@ func setfilter(logf logger.Logf, tun *TUN) { 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 { @@ -131,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) { +func newFakeTUN(logf logger.Logf, secure bool) (*fakeTUN, *Wrapper) { ftun := NewFake() - tun := WrapTUN(logf, ftun) + tun := Wrap(logf, ftun) if secure { setfilter(logf, tun) } else { @@ -274,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 { @@ -377,11 +377,11 @@ func BenchmarkWrite(b *testing.B) { } func TestAtomic64Alignment(t *testing.T) { - off := unsafe.Offsetof(TUN{}.lastActivityAtomic) + off := unsafe.Offsetof(Wrapper{}.lastActivityAtomic) if off%8 != 0 { t.Errorf("offset %v not 8-byte aligned", off) } - c := new(TUN) + c := new(Wrapper) atomic.StoreInt64(&c.lastActivityAtomic, 123) } diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 1e4d00b19..2e7e29635 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -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,7 +166,7 @@ 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) diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index cceb10eb8..ef3c24b1d 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -48,7 +48,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 @@ -61,7 +61,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") } @@ -297,7 +297,7 @@ func (ns *Impl) injectOutbound() { } } -func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.TUN) filter.Response { +func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Response { var pn tcpip.NetworkProtocolNumber switch p.IPVersion { case 4: diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index f18e12e9f..2951a0c7e 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -66,7 +66,7 @@ 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 == ipproto.TSMP { @@ -99,7 +99,7 @@ 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 || diff --git a/wgengine/userspace.go b/wgengine/userspace.go index f72e8640f..01ae3cc8b 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -80,7 +80,7 @@ type userspaceEngine struct { reqCh chan struct{} waitCh chan struct{} // chan is closed when first Close call completes; contrast with closing bool timeNow func() time.Time - tundev *tstun.TUN + tundev *tstun.Wrapper wgdev *device.Device router router.Router resolver *dns.Resolver @@ -124,10 +124,10 @@ type userspaceEngine struct { // InternalsGetter is implemented by Engines that can export their internals. type InternalsGetter interface { - GetInternals() (*tstun.TUN, *magicsock.Conn) + GetInternals() (*tstun.Wrapper, *magicsock.Conn) } -func (e *userspaceEngine) GetInternals() (*tstun.TUN, *magicsock.Conn) { +func (e *userspaceEngine) GetInternals() (*tstun.Wrapper, *magicsock.Conn) { return e.tundev, e.magicConn } @@ -184,7 +184,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) closePool.add(r) } - tsTUNDev := tstun.WrapTUN(logf, conf.TUN) + tsTUNDev := tstun.Wrap(logf, conf.TUN) closePool.add(tsTUNDev) e := &userspaceEngine{ @@ -379,7 +379,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) } // echoRespondToAll is an inbound post-filter responding to all echo requests. -func echoRespondToAll(p *packet.Parsed, t *tstun.TUN) filter.Response { +func echoRespondToAll(p *packet.Parsed, t *tstun.Wrapper) filter.Response { if p.IsEchoRequest() { header := p.ICMP4Header() header.ToResponse() @@ -400,7 +400,7 @@ func echoRespondToAll(p *packet.Parsed, t *tstun.TUN) filter.Response { // stack, and intercepts any packets that should be handled by // tailscaled directly. Other packets are allowed to proceed into the // main ACL filter. -func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.TUN) filter.Response { +func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Response { if verdict := e.handleDNS(p, t); verdict == filter.Drop { // local DNS handled the packet. return filter.Drop @@ -429,7 +429,7 @@ func (e *userspaceEngine) isLocalAddr(ip netaddr.IP) bool { } // handleDNS is an outbound pre-filter resolving Tailscale domains. -func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Response { +func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.Wrapper) filter.Response { if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == ipproto.UDP { request := dns.Packet{ Payload: append([]byte(nil), p.Payload()...), diff --git a/wgengine/userspace_test.go b/wgengine/userspace_test.go index 6690ece01..6321fdf31 100644 --- a/wgengine/userspace_test.go +++ b/wgengine/userspace_test.go @@ -39,7 +39,7 @@ func TestNoteReceiveActivity(t *testing.T) { logf: func(format string, a ...interface{}) { fmt.Fprintf(&logBuf, format, a...) }, - tundev: new(tstun.TUN), + tundev: new(tstun.Wrapper), testMaybeReconfigHook: func() { confc <- true }, trimmedDisco: map[tailcfg.DiscoKey]bool{}, } From 22d53fe784091d7e76de1794407cf909425375d7 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 26 Mar 2021 23:16:55 -0700 Subject: [PATCH 48/59] net/tstun: document exported function. Signed-off-by: David Anderson --- net/tstun/tun.go | 1 + 1 file changed, 1 insertion(+) diff --git a/net/tstun/tun.go b/net/tstun/tun.go index 6750912f6..d480d3244 100644 --- a/net/tstun/tun.go +++ b/net/tstun/tun.go @@ -28,6 +28,7 @@ import ( // 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 { From 25e0bb0a4ee2de9a8f9133d1f3cd63e95a2bc49b Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 26 Mar 2021 23:17:39 -0700 Subject: [PATCH 49/59] net/tstun: rename wrap_windows.go to tun_windows.go. The code has nothing to do with wrapping, it's windows-specific driver initialization code. Signed-off-by: David Anderson --- net/tstun/{wrap_windows.go => tun_windows.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename net/tstun/{wrap_windows.go => tun_windows.go} (100%) diff --git a/net/tstun/wrap_windows.go b/net/tstun/tun_windows.go similarity index 100% rename from net/tstun/wrap_windows.go rename to net/tstun/tun_windows.go From 2df8adef9d87889e4f0c8b8d41801eccf0bd8d15 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sat, 27 Mar 2021 00:25:22 -0700 Subject: [PATCH 50/59] wgengine: make the tun.Device required at construction. Signed-off-by: David Anderson --- cmd/tailscaled/tailscaled.go | 8 +++++--- cmd/tailscaled/tailscaled_windows.go | 3 +-- wgengine/userspace.go | 13 ++++--------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 8cd6695c3..cbdc3d378 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -29,6 +29,7 @@ import ( "time" "github.com/go-multierror/multierror" + "github.com/tailscale/wireguard-go/tun" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" "tailscale.com/net/socks5" @@ -332,18 +333,19 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine. LinkMonitor: linkMon, } isUserspace = name == "userspace-networking" + var dev tun.Device if isUserspace { - conf.TUN = tstun.NewFake() + dev = tstun.NewFake() conf.Router = router.NewFake(logf) } else { - dev, err := tstun.New(logf, name) + dev, err = tstun.New(logf, name) if err != nil { tstun.Diagnose(logf, name) return nil, false, err } conf.TUN = dev } - e, err = wgengine.NewUserspaceEngine(logf, conf) + e, err = wgengine.NewUserspaceEngine(logf, dev, conf) if err != nil { return nil, isUserspace, err } diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index fbd760950..276594dd5 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -164,8 +164,7 @@ func startIPNServer(ctx context.Context, logid string) error { if err != nil { return nil, err } - eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ - TUN: dev, + eng, err := wgengine.NewUserspaceEngine(logf, dev, wgengine.Config{ ListenPort: 41641, }) if err != nil { diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 01ae3cc8b..199604614 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -155,8 +155,7 @@ type Config struct { func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) { logf("Starting userspace wireguard engine (with fake TUN device)") - return NewUserspaceEngine(logf, Config{ - TUN: tstun.NewFake(), + return NewUserspaceEngine(logf, tstun.NewFake(), Config{ Router: router.NewFake(logf), ListenPort: listenPort, Fake: true, @@ -165,18 +164,14 @@ func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) // NewUserspaceEngine creates the named tun device and returns a // Tailscale Engine running on it. -func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) { - if conf.TUN == nil { - return nil, errors.New("TUN is required") - } - +func NewUserspaceEngine(logf logger.Logf, dev tun.Device, conf Config) (_ Engine, reterr error) { var closePool closeOnErrorPool defer closePool.closeAllIfError(&reterr) // TODO: default to a no-op router, require caller to pass in // effectful ones. if conf.Router == nil { - r, err := router.New(logf, conf.TUN) + r, err := router.New(logf, dev) if err != nil { return nil, err } @@ -184,7 +179,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) closePool.add(r) } - tsTUNDev := tstun.Wrap(logf, conf.TUN) + tsTUNDev := tstun.Wrap(logf, dev) closePool.add(tsTUNDev) e := &userspaceEngine{ From 4954fbfda670361ffb963725fbb6dc2773f5681e Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Sat, 27 Mar 2021 08:10:26 -0700 Subject: [PATCH 51/59] wgengine: extend TestWatchdog timeout on macOS This works around the close syscall being slow. We can revert this if we find a fix or if Apple makes close fast again. Signed-off-by: Josh Bleecher Snyder --- wgengine/watchdog_test.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/wgengine/watchdog_test.go b/wgengine/watchdog_test.go index 7487d4827..0a699f848 100644 --- a/wgengine/watchdog_test.go +++ b/wgengine/watchdog_test.go @@ -7,6 +7,7 @@ package wgengine import ( "bytes" "fmt" + "runtime" "strings" "testing" "time" @@ -15,6 +16,13 @@ import ( func TestWatchdog(t *testing.T) { t.Parallel() + var maxWaitMultiple time.Duration = 1 + if runtime.GOOS == "darwin" { + // Work around slow close syscalls on Big Sur with content filter Network Extensions installed. + // See https://github.com/tailscale/tailscale/issues/1598. + maxWaitMultiple = 15 + } + t.Run("default watchdog does not fire", func(t *testing.T) { t.Parallel() e, err := NewFakeUserspaceEngine(t.Logf, 0) @@ -23,7 +31,7 @@ func TestWatchdog(t *testing.T) { } e = NewWatchdog(e) - e.(*watchdogEngine).maxWait = 150 * time.Millisecond + e.(*watchdogEngine).maxWait = maxWaitMultiple * 150 * time.Millisecond e.(*watchdogEngine).logf = t.Logf e.(*watchdogEngine).fatalf = t.Fatalf @@ -42,7 +50,7 @@ func TestWatchdog(t *testing.T) { usEngine := e.(*userspaceEngine) e = NewWatchdog(e) wdEngine := e.(*watchdogEngine) - wdEngine.maxWait = 100 * time.Millisecond + wdEngine.maxWait = maxWaitMultiple * 100 * time.Millisecond logBuf := new(bytes.Buffer) fatalCalled := make(chan struct{}) From 0807e3e2f78f16ce65f850c37b44458b5a5e278e Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Sat, 27 Mar 2021 12:17:50 -0700 Subject: [PATCH 52/59] syncs: disable TestWatchMultipleValues on Windows CI builds The Windows CI machine experiences significant random execution delays. For example, in this code from watchdog.go: done := make(chan bool) go func() { start := time.Now() mu.Lock() There was a 500ms delay from initializing done to locking mu. This test checks that we receive a sufficient number of events quickly enough. In the face of random 500ms delays, unsurprisingly, the test fails. There's not much principled we can do about it. We could build a system of retries or attempt to detect these random delays, but that game isn't worth the candle. Skip the test. Signed-off-by: Josh Bleecher Snyder --- syncs/watchdog_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/syncs/watchdog_test.go b/syncs/watchdog_test.go index b5cc3452e..116d00625 100644 --- a/syncs/watchdog_test.go +++ b/syncs/watchdog_test.go @@ -6,9 +6,12 @@ package syncs import ( "context" + "runtime" "sync" "testing" "time" + + "tailscale.com/util/cibuild" ) // Time-based tests are fundamentally flaky. @@ -46,6 +49,12 @@ func TestWatchContended(t *testing.T) { } func TestWatchMultipleValues(t *testing.T) { + if cibuild.On() && runtime.GOOS == "windows" { + // On the CI machine, it sometimes takes 500ms to start a new goroutine. + // When this happens, we don't get enough events quickly enough. + // Nothing's wrong, and it's not worth working around. Just skip the test. + t.Skip("flaky on Windows CI") + } mu := new(sync.Mutex) ctx, cancel := context.WithCancel(context.Background()) defer cancel() // not necessary, but keep vet happy From 440effb21ac109d6b7b7c50a19ca131a91b79b76 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sun, 28 Mar 2021 18:44:59 -0700 Subject: [PATCH 53/59] wgengine: remove Config.TUN argument. --- cmd/tailscaled/tailscaled.go | 1 - wgengine/userspace.go | 3 --- 2 files changed, 4 deletions(-) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index cbdc3d378..51e5a2c21 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -343,7 +343,6 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine. tstun.Diagnose(logf, name) return nil, false, err } - conf.TUN = dev } e, err = wgengine.NewUserspaceEngine(logf, dev, conf) if err != nil { diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 199604614..b9fe43ec8 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -133,9 +133,6 @@ func (e *userspaceEngine) GetInternals() (*tstun.Wrapper, *magicsock.Conn) { // Config is the engine configuration. type Config struct { - // TUN is the TUN device used by the engine. - TUN tun.Device - // Router is the interface to OS networking APIs used to interface // the OS with the Engine. Router router.Router From 93a4aa697c707a2992e671873f031ed99b3a5532 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sun, 28 Mar 2021 18:59:33 -0700 Subject: [PATCH 54/59] wgengine: default Router to a no-op router. Signed-off-by: David Anderson --- cmd/tailscaled/tailscaled.go | 7 ++++++- cmd/tailscaled/tailscaled_windows.go | 8 ++++++++ wgengine/userspace.go | 14 +++----------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 51e5a2c21..924dc8b6d 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -336,13 +336,18 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine. var dev tun.Device if isUserspace { dev = tstun.NewFake() - conf.Router = router.NewFake(logf) } else { dev, err = tstun.New(logf, name) if err != nil { tstun.Diagnose(logf, name) return nil, false, err } + r, err := router.New(logf, dev) + if err != nil { + dev.Close() + return nil, false, err + } + conf.Router = r } e, err = wgengine.NewUserspaceEngine(logf, dev, conf) if err != nil { diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index 276594dd5..fef215d74 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -35,6 +35,7 @@ import ( "tailscale.com/types/logger" "tailscale.com/version" "tailscale.com/wgengine" + "tailscale.com/wgengine/router" ) const serviceName = "Tailscale" @@ -164,10 +165,17 @@ func startIPNServer(ctx context.Context, logid string) error { 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, dev, wgengine.Config{ + Router: r, ListenPort: 41641, }) if err != nil { + r.Close() dev.Close() return nil, err } diff --git a/wgengine/userspace.go b/wgengine/userspace.go index b9fe43ec8..0bf4db102 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -133,8 +133,8 @@ func (e *userspaceEngine) GetInternals() (*tstun.Wrapper, *magicsock.Conn) { // Config is the engine configuration. type Config struct { - // Router is the interface to OS networking APIs used to interface - // the OS with the Engine. + // Router interfaces the Engine to the OS network stack. + // If nil, a fake Router that does nothing is used. Router router.Router // LinkMonitor optionally provides an existing link monitor to re-use. @@ -153,7 +153,6 @@ type Config struct { func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) { logf("Starting userspace wireguard engine (with fake TUN device)") return NewUserspaceEngine(logf, tstun.NewFake(), Config{ - Router: router.NewFake(logf), ListenPort: listenPort, Fake: true, }) @@ -165,15 +164,8 @@ func NewUserspaceEngine(logf logger.Logf, dev tun.Device, conf Config) (_ Engine var closePool closeOnErrorPool defer closePool.closeAllIfError(&reterr) - // TODO: default to a no-op router, require caller to pass in - // effectful ones. if conf.Router == nil { - r, err := router.New(logf, dev) - if err != nil { - return nil, err - } - conf.Router = r - closePool.add(r) + conf.Router = router.NewFake(logf) } tsTUNDev := tstun.Wrap(logf, dev) From 95ca86c048023271f915237673de28273b468cc4 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sun, 28 Mar 2021 19:05:48 -0700 Subject: [PATCH 55/59] go.mod: update to new wireguard-go version. Signed-off-by: David Anderson --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 479431041..3cfff12dd 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ 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-20210324165952-2963b66bc23a + 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 diff --git a/go.sum b/go.sum index 1d2d82a4f..ce753994b 100644 --- a/go.sum +++ b/go.sum @@ -121,6 +121,8 @@ github.com/tailscale/wireguard-go v0.0.0-20210210202228-3cc76ed5f222 h1:VzTS7LIw 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= From e18c3a7d8441aa92f4d86de82f18b5fe4776b04c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 27 Mar 2021 22:40:38 -0700 Subject: [PATCH 56/59] wgengine: optimize isLocalAddr a bit On macOS/iOS, this removes a map lookup per outgoing packet. Noticed it while reading code, not from profiles, but can't hurt. BenchmarkGenLocalAddrFunc BenchmarkGenLocalAddrFunc/map1 BenchmarkGenLocalAddrFunc/map1-4 16184868 69.78 ns/op BenchmarkGenLocalAddrFunc/map2 BenchmarkGenLocalAddrFunc/map2-4 16878140 70.73 ns/op BenchmarkGenLocalAddrFunc/or1 BenchmarkGenLocalAddrFunc/or1-4 623055721 1.950 ns/op BenchmarkGenLocalAddrFunc/or2 BenchmarkGenLocalAddrFunc/or2-4 472493098 2.589 ns/op Signed-off-by: Brad Fitzpatrick --- wgengine/userspace.go | 64 +++++++++++++++++++++++--------------- wgengine/userspace_test.go | 47 ++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 25 deletions(-) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 0bf4db102..27b87586e 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -91,10 +91,10 @@ type userspaceEngine struct { testMaybeReconfigHook func() // for tests; if non-nil, fires if maybeReconfigWireguardLocked called - // localAddrs is the set of IP addresses assigned to the local + // isLocalAddr reports the whether an IP is assigned to the local // tunnel interface. It's used to reflect local packets // incorrectly sent to us. - localAddrs atomic.Value // of map[netaddr.IP]bool + isLocalAddr atomic.Value // of func(netaddr.IP)bool wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below lastCfgFull wgcfg.Config @@ -180,7 +180,7 @@ func NewUserspaceEngine(logf logger.Logf, dev tun.Device, conf Config) (_ Engine router: conf.Router, pingers: make(map[wgkey.Key]*pinger), } - e.localAddrs.Store(map[netaddr.IP]bool{}) + e.isLocalAddr.Store(genLocalAddrFunc(nil)) if conf.LinkMonitor != nil { e.linkMon = conf.LinkMonitor @@ -390,28 +390,24 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) return filter.Drop } - if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && e.isLocalAddr(p.Dst.IP) { - // macOS NetworkExtension directs packets destined to the - // tunnel's local IP address into the tunnel, instead of - // looping back within the kernel network stack. We have to - // notice that an outbound packet is actually destined for - // ourselves, and loop it back into macOS. - t.InjectInboundCopy(p.Buffer()) - return filter.Drop + if runtime.GOOS == "darwin" || runtime.GOOS == "ios" { + isLocalAddr, ok := e.isLocalAddr.Load().(func(netaddr.IP) bool) + if !ok { + e.logf("[unexpected] e.isLocalAddr was nil, can't check for loopback packet") + } else if isLocalAddr(p.Dst.IP) { + // macOS NetworkExtension directs packets destined to the + // tunnel's local IP address into the tunnel, instead of + // looping back within the kernel network stack. We have to + // notice that an outbound packet is actually destined for + // ourselves, and loop it back into macOS. + t.InjectInboundCopy(p.Buffer()) + return filter.Drop + } } return filter.Accept } -func (e *userspaceEngine) isLocalAddr(ip netaddr.IP) bool { - localAddrs, ok := e.localAddrs.Load().(map[netaddr.IP]bool) - if !ok { - e.logf("[unexpected] e.localAddrs was nil, can't check for loopback packet") - return false - } - return localAddrs[ip] -} - // handleDNS is an outbound pre-filter resolving Tailscale domains. func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.Wrapper) filter.Response { if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == ipproto.UDP { @@ -877,16 +873,34 @@ func (e *userspaceEngine) updateActivityMapsLocked(trackDisco []tailcfg.DiscoKey e.tundev.SetDestIPActivityFuncs(e.destIPActivityFuncs) } +// genLocalAddrFunc returns a func that reports whether an IP is in addrs. +// addrs is assumed to be all /32 or /128 entries. +func genLocalAddrFunc(addrs []netaddr.IPPrefix) func(netaddr.IP) bool { + // Specialize the three common cases: no address, just IPv4 + // (or just IPv6), and both IPv4 and IPv6. + if len(addrs) == 0 { + return func(netaddr.IP) bool { return false } + } + if len(addrs) == 1 { + return func(t netaddr.IP) bool { return t == addrs[0].IP } + } + if len(addrs) == 2 { + return func(t netaddr.IP) bool { return t == addrs[0].IP || t == addrs[1].IP } + } + // Otherwise, the general implementation: a map lookup. + m := map[netaddr.IP]bool{} + for _, a := range addrs { + m[a.IP] = true + } + return func(t netaddr.IP) bool { return m[t] } +} + func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) error { if routerCfg == nil { panic("routerCfg must not be nil") } - localAddrs := map[netaddr.IP]bool{} - for _, addr := range routerCfg.LocalAddrs { - localAddrs[addr.IP] = true - } - e.localAddrs.Store(localAddrs) + e.isLocalAddr.Store(genLocalAddrFunc(routerCfg.LocalAddrs)) e.wgLock.Lock() defer e.wgLock.Unlock() diff --git a/wgengine/userspace_test.go b/wgengine/userspace_test.go index 6321fdf31..ac081d83e 100644 --- a/wgengine/userspace_test.go +++ b/wgengine/userspace_test.go @@ -139,3 +139,50 @@ func dkFromHex(hex string) tailcfg.DiscoKey { } return tailcfg.DiscoKey(k) } + +// an experiment to see if genLocalAddrFunc was worth it. As of Go +// 1.16, it still very much is. (30-40x faster) +func BenchmarkGenLocalAddrFunc(b *testing.B) { + la1 := netaddr.MustParseIP("1.2.3.4") + la2 := netaddr.MustParseIP("::4") + lanot := netaddr.MustParseIP("5.5.5.5") + var x bool + b.Run("map1", func(b *testing.B) { + m := map[netaddr.IP]bool{ + la1: true, + } + for i := 0; i < b.N; i++ { + x = m[la1] + x = m[lanot] + } + }) + b.Run("map2", func(b *testing.B) { + m := map[netaddr.IP]bool{ + la1: true, + la2: true, + } + for i := 0; i < b.N; i++ { + x = m[la1] + x = m[lanot] + } + }) + b.Run("or1", func(b *testing.B) { + f := func(t netaddr.IP) bool { + return t == la1 + } + for i := 0; i < b.N; i++ { + x = f(la1) + x = f(lanot) + } + }) + b.Run("or2", func(b *testing.B) { + f := func(t netaddr.IP) bool { + return t == la1 || t == la2 + } + for i := 0; i < b.N; i++ { + x = f(la1) + x = f(lanot) + } + }) + b.Logf("x = %v", x) +} From 0fb738760fde1a14836188a690e8b14bf2aab5da Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sun, 28 Mar 2021 19:25:01 -0700 Subject: [PATCH 57/59] wgengine: make Tun optional again, default to fake. This makes setup more explicit in prod codepaths, without requiring a bunch of arguments or helpers for tests and userspace mode. Signed-off-by: David Anderson --- cmd/tailscaled/tailscaled.go | 11 ++++------- cmd/tailscaled/tailscaled_windows.go | 3 ++- wgengine/userspace.go | 14 +++++++++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 924dc8b6d..931072b55 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -29,7 +29,6 @@ import ( "time" "github.com/go-multierror/multierror" - "github.com/tailscale/wireguard-go/tun" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" "tailscale.com/net/socks5" @@ -333,15 +332,13 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine. LinkMonitor: linkMon, } isUserspace = name == "userspace-networking" - var dev tun.Device - if isUserspace { - dev = tstun.NewFake() - } else { - dev, err = tstun.New(logf, name) + 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() @@ -349,7 +346,7 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine. } conf.Router = r } - e, err = wgengine.NewUserspaceEngine(logf, dev, conf) + e, err = wgengine.NewUserspaceEngine(logf, conf) if err != nil { return nil, isUserspace, err } diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index fef215d74..b57190b7e 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -170,7 +170,8 @@ func startIPNServer(ctx context.Context, logid string) error { dev.Close() return nil, err } - eng, err := wgengine.NewUserspaceEngine(logf, dev, wgengine.Config{ + eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ + Tun: dev, Router: r, ListenPort: 41641, }) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 27b87586e..f9bdd9ad3 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -133,6 +133,11 @@ func (e *userspaceEngine) GetInternals() (*tstun.Wrapper, *magicsock.Conn) { // Config is the engine configuration. type Config struct { + // Tun is the device used by the Engine to exchange packets with + // the OS. + // If nil, a fake Device that does nothing is used. + Tun tun.Device + // Router interfaces the Engine to the OS network stack. // If nil, a fake Router that does nothing is used. Router router.Router @@ -152,7 +157,7 @@ type Config struct { func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) { logf("Starting userspace wireguard engine (with fake TUN device)") - return NewUserspaceEngine(logf, tstun.NewFake(), Config{ + return NewUserspaceEngine(logf, Config{ ListenPort: listenPort, Fake: true, }) @@ -160,15 +165,18 @@ func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) // NewUserspaceEngine creates the named tun device and returns a // Tailscale Engine running on it. -func NewUserspaceEngine(logf logger.Logf, dev tun.Device, conf Config) (_ Engine, reterr error) { +func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) { var closePool closeOnErrorPool defer closePool.closeAllIfError(&reterr) + if conf.Tun == nil { + conf.Tun = tstun.NewFake() + } if conf.Router == nil { conf.Router = router.NewFake(logf) } - tsTUNDev := tstun.Wrap(logf, dev) + tsTUNDev := tstun.Wrap(logf, conf.Tun) closePool.add(tsTUNDev) e := &userspaceEngine{ From 07bf4eb685447937978c0371a6718c90ba24b018 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sun, 28 Mar 2021 19:50:56 -0700 Subject: [PATCH 58/59] wgengine: rename Fake to RespondToPing. "Fake" doesn't mean a lot any more, given that many components of the engine can be faked out, including in valid production configurations like userspace-networking. Signed-off-by: David Anderson --- wgengine/userspace.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index f9bdd9ad3..9202e4c7a 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -150,16 +150,17 @@ type Config struct { // If zero, a port is automatically selected. ListenPort uint16 - // Fake determines whether this engine should automatically - // reply to ICMP pings. - Fake bool + // RespondToPing determines whether this engine should internally + // reply to ICMP pings, without involving the OS. + // Used in "fake" mode for development. + RespondToPing bool } func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) { logf("Starting userspace wireguard engine (with fake TUN device)") return NewUserspaceEngine(logf, Config{ - ListenPort: listenPort, - Fake: true, + ListenPort: listenPort, + RespondToPing: true, }) } @@ -170,9 +171,11 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) defer closePool.closeAllIfError(&reterr) if conf.Tun == nil { + logf("[v1] using fake (no-op) tun device") conf.Tun = tstun.NewFake() } if conf.Router == nil { + logf("[v1] using fake (no-op) OS network configurator") conf.Router = router.NewFake(logf) } @@ -241,8 +244,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) closePool.add(e.magicConn) e.magicConn.SetNetworkUp(e.linkMon.InterfaceState().AnyInterfaceUp()) - // Respond to all pings only in fake mode. - if conf.Fake { + if conf.RespondToPing { e.tundev.PostFilterIn = echoRespondToAll } e.tundev.PreFilterOut = e.handleLocalPackets From a4c679e64691a3f0ba41ad9078312ca67e5e67fd Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 25 Mar 2021 10:23:13 -0700 Subject: [PATCH 59/59] wgengine/monitor: on wall time jump, synthesize network change event ... to force rebinds of TCP connections Fixes #1555 Updates tailscale/felicity#4 Signed-off-by: Brad Fitzpatrick --- wgengine/monitor/monitor.go | 135 ++++++++++++++++++++++++++++-------- 1 file changed, 107 insertions(+), 28 deletions(-) diff --git a/wgengine/monitor/monitor.go b/wgengine/monitor/monitor.go index 3d88a9a8b..8ee7087ce 100644 --- a/wgengine/monitor/monitor.go +++ b/wgengine/monitor/monitor.go @@ -10,6 +10,7 @@ package monitor import ( "encoding/json" "errors" + "runtime" "sync" "time" @@ -18,6 +19,13 @@ import ( "tailscale.com/types/logger" ) +// pollWallTimeInterval is how often we check the time to check +// for big jumps in wall (non-monotonic) time as a backup mechanism +// to get notified of a sleeping device waking back up. +// Usually there are also minor network change events on wake that let +// us check the wall time sooner than this. +const pollWallTimeInterval = 15 * time.Second + // message represents a message returned from an osMon. type message interface { // Ignore is whether we should ignore this message. @@ -50,18 +58,20 @@ type Mon struct { logf logger.Logf om osMon // nil means not supported on this platform change chan struct{} - stop chan struct{} + 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 { @@ -140,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 @@ -227,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.EqualFiltered(oldState, interfaces.FilterInteresting) - if changed { + ifChanged := !curState.EqualFiltered(oldState, interfaces.FilterInteresting) + if ifChanged { m.gwValid = false m.ifState = curState @@ -238,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) } @@ -259,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 +}