diff --git a/.gitignore b/.gitignore index 5d2bdc181..c443556f5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,6 @@ *.so *.dylib -cmd/relaynode/relaynode -cmd/taillogin/taillogin cmd/tailscale/tailscale cmd/tailscaled/tailscaled diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index 54476f1b0..782c4d8ef 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -169,6 +169,9 @@ func runStatus(ctx context.Context, args []string) error { } for _, peer := range st.Peers() { ps := st.Peer[peer] + if ps.ShareeNode { + continue + } active := peerActive(ps) if statusArgs.active && !active { continue diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 66f794e62..1bd1bef11 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -28,11 +28,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device 💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+ W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun - W 💣 github.com/tailscale/wireguard-go/tun/wintun/iphlpapi from github.com/tailscale/wireguard-go/tun/wintun - W 💣 github.com/tailscale/wireguard-go/tun/wintun/namespaceapi from github.com/tailscale/wireguard-go/tun/wintun - W 💣 github.com/tailscale/wireguard-go/tun/wintun/nci from github.com/tailscale/wireguard-go/tun/wintun - W 💣 github.com/tailscale/wireguard-go/tun/wintun/registry from github.com/tailscale/wireguard-go/tun/wintun+ - W 💣 github.com/tailscale/wireguard-go/tun/wintun/setupapi from github.com/tailscale/wireguard-go/tun/wintun github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/conn+ github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli @@ -91,7 +86,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/wgengine/tstun from tailscale.com/wgengine W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box - golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+ + golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305 golang.org/x/crypto/chacha20poly1305 from crypto/tls+ golang.org/x/crypto/cryptobyte from crypto/ecdsa+ @@ -120,11 +115,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+ LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+ W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+ - W golang.org/x/sys/windows/registry from github.com/tailscale/wireguard-go/tun/wintun+ + W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ golang.org/x/text/secure/bidirule from golang.org/x/net/idna golang.org/x/text/transform from golang.org/x/text/secure/bidirule+ golang.org/x/text/unicode/bidi from golang.org/x/net/idna+ - golang.org/x/text/unicode/norm from github.com/tailscale/wireguard-go/tun/wintun+ + golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from tailscale.com/types/logger+ bufio from compress/flate+ bytes from bufio+ @@ -199,6 +194,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep reflect from crypto/x509+ regexp from github.com/coreos/go-iptables/iptables+ regexp/syntax from regexp + runtime/debug from golang.org/x/sync/singleflight runtime/pprof from tailscale.com/log/logheap+ sort from compress/flate+ strconv from compress/flate+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 18e169d0b..2e2a56c9a 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -31,11 +31,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device 💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+ W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun - W 💣 github.com/tailscale/wireguard-go/tun/wintun/iphlpapi from github.com/tailscale/wireguard-go/tun/wintun - W 💣 github.com/tailscale/wireguard-go/tun/wintun/namespaceapi from github.com/tailscale/wireguard-go/tun/wintun - W 💣 github.com/tailscale/wireguard-go/tun/wintun/nci from github.com/tailscale/wireguard-go/tun/wintun - W 💣 github.com/tailscale/wireguard-go/tun/wintun/registry from github.com/tailscale/wireguard-go/tun/wintun+ - W 💣 github.com/tailscale/wireguard-go/tun/wintun/setupapi from github.com/tailscale/wireguard-go/tun/wintun github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/conn+ github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck 💣 go4.org/mem from tailscale.com/control/controlclient+ @@ -101,7 +96,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/wgengine/tstun from tailscale.com/wgengine W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box - golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+ + golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305 golang.org/x/crypto/chacha20poly1305 from crypto/tls+ golang.org/x/crypto/cryptobyte from crypto/ecdsa+ @@ -112,7 +107,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box 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/crypto/ssh/terminal from tailscale.com/logpolicy 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+ @@ -131,11 +125,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+ LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+ W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+ - W golang.org/x/sys/windows/registry from github.com/tailscale/wireguard-go/tun/wintun+ + W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ + 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+ golang.org/x/text/unicode/bidi from golang.org/x/net/idna+ - golang.org/x/text/unicode/norm from github.com/tailscale/wireguard-go/tun/wintun+ + golang.org/x/text/unicode/norm from golang.org/x/net/idna golang.org/x/time/rate from tailscale.com/types/logger+ bufio from compress/flate+ bytes from bufio+ diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go index 768418d5d..b35df9d5b 100644 --- a/control/controlclient/auto.go +++ b/control/controlclient/auto.go @@ -268,11 +268,13 @@ func (c *Client) authRoutine() { for { c.mu.Lock() - c.logf("authRoutine: %s", c.state) - expiry := c.expiry goal := c.loginGoal ctx := c.authCtx - synced := c.synced + if goal != nil { + c.logf("authRoutine: %s; wantLoggedIn=%v", c.state, goal.wantLoggedIn) + } else { + c.logf("authRoutine: %s; goal=nil", c.state) + } c.mu.Unlock() select { @@ -293,51 +295,13 @@ func (c *Client) authRoutine() { } if goal == nil { - // Wait for something interesting to happen - var exp <-chan time.Time - var expTimer *time.Timer - if expiry != nil && !expiry.IsZero() { - // if expiry is in the future, don't delay - // past that time. - // If it's in the past, then it's already - // being handled by someone, so no need to - // wake ourselves up again. - now := c.timeNow() - if expiry.Before(now) { - delay := expiry.Sub(now) - if delay > 5*time.Second { - delay = time.Second - } - expTimer = time.NewTimer(delay) - exp = expTimer.C - } - } - select { - case <-ctx.Done(): - if expTimer != nil { - expTimer.Stop() - } - c.logf("authRoutine: context done.") - case <-exp: - // Unfortunately the key expiry isn't provided - // by the control server until mapRequest. - // So we have to do some hackery with c.expiry - // in here. - // TODO(apenwarr): add a key expiry field in RegisterResponse. - c.logf("authRoutine: key expiration check.") - if synced && expiry != nil && !expiry.IsZero() && expiry.Before(c.timeNow()) { - c.logf("Key expired; setting loggedIn=false.") - - c.mu.Lock() - c.loginGoal = &LoginGoal{ - wantLoggedIn: c.loggedIn, - } - c.loggedIn = false - c.expiry = nil - c.mu.Unlock() - } - } - } else if !goal.wantLoggedIn { + // Wait for user to Login or Logout. + <-ctx.Done() + c.logf("authRoutine: context done.") + continue + } + + if !goal.wantLoggedIn { err := c.direct.TryLogout(ctx) if err != nil { report(err, "TryLogout") diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index f9c3c6022..4d8d6d577 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -46,6 +46,7 @@ import ( "tailscale.com/types/structs" "tailscale.com/util/systemd" "tailscale.com/version" + "tailscale.com/wgengine/filter" ) type Persist struct { @@ -544,7 +545,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM } request := tailcfg.MapRequest{ - Version: 5, + Version: 6, KeepAlive: c.keepAlive, NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()), DiscoKey: c.discoPubKey, @@ -639,6 +640,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM var lastDERPMap *tailcfg.DERPMap var lastUserProfile = map[tailcfg.UserID]tailcfg.UserProfile{} + var lastParsedPacketFilter []filter.Match // If allowStream, then the server will use an HTTP long poll to // return incremental results. There is always one response right @@ -716,6 +718,10 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM resp.Peers = filtered } + if pf := resp.PacketFilter; pf != nil { + lastParsedPacketFilter = c.parsePacketFilter(pf) + } + nm := &NetworkMap{ NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()), PrivateKey: persist.PrivateNodeKey, @@ -730,7 +736,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM Domain: resp.Domain, DNS: resp.DNSConfig, Hostinfo: resp.Node.Hostinfo, - PacketFilter: c.parsePacketFilter(resp.PacketFilter), + PacketFilter: lastParsedPacketFilter, DERPMap: lastDERPMap, Debug: resp.Debug, } diff --git a/go.mod b/go.mod index 8055de382..1d3deb67c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module tailscale.com -go 1.14 +go 1.15 require ( github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 @@ -23,20 +23,22 @@ require ( github.com/miekg/dns v1.1.30 github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 github.com/peterbourgon/ff/v2 v2.0.0 - github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be - github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f + github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 + github.com/tailscale/wireguard-go v0.0.0-20201210001956-32a957fb6709 github.com/tcnksm/go-httpstat v0.2.0 github.com/toqueteos/webbrowser v1.2.0 go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 - golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 + golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 + golang.org/x/mod v0.4.0 // indirect golang.org/x/net v0.0.0-20201110031124-69a78807bb2b golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d - golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 - golang.org/x/sys v0.0.0-20201112073958-5cba982894dd + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 + golang.org/x/sys v0.0.0-20201211002650-1f0c578a6b29 + golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b golang.org/x/time v0.0.0-20191024005414-555d28b269f0 - golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d + golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8 - honnef.co/go/tools v0.0.1-2020.1.4 + honnef.co/go/tools v0.1.0 inet.af/netaddr v0.0.0-20200810144936-56928fe48a98 rsc.io/goversion v1.2.0 ) diff --git a/go.sum b/go.sum index d89dc694e..47ef6c628 100644 --- a/go.sum +++ b/go.sum @@ -99,6 +99,10 @@ github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be h1:ZKe3kVGbu/go github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be/go.mod h1:jissDaJNHiyV2tFdr3QyNEfsZrax/i2yQiSO+CljThI= github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f h1:KMx58dbn2YCutzOvjNHgmvbwQH7nGE8H+J42Nenjl/M= github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4= +github.com/tailscale/depaware v0.0.0-20201210233412-71b54857b5d9 h1:IquU2Mhy4Q+xUYoRLHMiEamd80OShhYXhTNywEbQu3g= +github.com/tailscale/depaware v0.0.0-20201210233412-71b54857b5d9/go.mod h1:jissDaJNHiyV2tFdr3QyNEfsZrax/i2yQiSO+CljThI= +github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBWY6aGilF+IRlQIdmhzLrsEmF6JgN+Ryw= +github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= 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= @@ -107,23 +111,26 @@ 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.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 h1:vSug/WNOi2+4jrKdivxayTN/zd8EA1UrStjpWvvo1jk= go4.org/mem v0.0.0-20201119185036-c04c5a6ff174/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= 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-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 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 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc= +golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -136,6 +143,7 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +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= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -143,8 +151,11 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BG golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -153,6 +164,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w 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-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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -161,8 +173,15 @@ golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435 h1:25AvDqqB9PrNqj1FLf2/70I4W0L19qqoaFq3gjNwbKk= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201211002650-1f0c578a6b29 h1:hAYi5mzhvBeCfkgaIHGZ8R+Q04WjSW5ZvQO3BZ94dHY= +golang.org/x/sys v0.0.0-20201211002650-1f0c578a6b29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b h1:a0ErnNnPKmhDyIXQvdZr+Lq8dc8xpMeqkF8y5PgQU4Q= +golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/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 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= @@ -171,11 +190,11 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqG golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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-20191119224855-298f0cb1881e/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-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d h1:vWQvJ/Z0Lu+9/8oQ/pAYXNzbc7CMnBl+tULGVHOy3oE= -golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +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= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -196,8 +215,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= -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-20200810144936-56928fe48a98 h1:bWyWDZP0l6VnQ1TDKf6yNwuiEDV6Q3q1Mv34m+lzT1I= inet.af/netaddr v0.0.0-20200810144936-56928fe48a98/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww= rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w= diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index 703dca26c..06559ce44 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -64,6 +64,12 @@ type PeerStatus struct { LastHandshake time.Time // with local wireguard KeepAlive bool + // 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" + // etc by default. + ShareeNode bool `json:",omitempty"` + // InNetworkMap means that this peer was seen in our latest network map. // In theory, all of InNetworkMap and InMagicSock and InEngine should all be true. InNetworkMap bool @@ -218,6 +224,9 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) { if st.KeepAlive { e.KeepAlive = true } + if st.ShareeNode { + e.ShareeNode = true + } } type StatusUpdater interface { diff --git a/ipn/local.go b/ipn/local.go index 9deb7956d..52d24e67f 100644 --- a/ipn/local.go +++ b/ipn/local.go @@ -222,6 +222,7 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) { KeepAlive: p.KeepAlive, Created: p.Created, LastSeen: lastSeen, + ShareeNode: p.Hostinfo.ShareeNode, }) } } @@ -1257,7 +1258,7 @@ func routerConfig(cfg *wgcfg.Config, prefs *Prefs) *router.Config { for _, addr := range cfg.Addresses { addrs = append(addrs, wgcfg.CIDR{ IP: addr.IP, - Mask: 32, + Mask: addr.Mask, }) } @@ -1561,7 +1562,6 @@ func (b *LocalBackend) TestOnlyPublicKeys() (machineKey tailcfg.MachineKey, node // 1.0.x. But eventually we want to stop sending the machine key to // clients. We can't do that until 1.0.x is no longer supported. func temporarilySetMachineKeyInPersist() bool { - //lint:ignore S1008 for comments switch runtime.GOOS { case "darwin", "ios", "android": // iOS, macOS, Android users can't downgrade anyway. diff --git a/ipn/prefs.go b/ipn/prefs.go index d70dded6a..c65ca6095 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -5,6 +5,7 @@ package ipn import ( + "bytes" "encoding/json" "fmt" "io/ioutil" @@ -276,6 +277,14 @@ func LoadPrefs(filename string) (*Prefs, error) { if err != nil { return nil, fmt.Errorf("LoadPrefs open: %w", err) // err includes path } + if bytes.Contains(data, jsonEscapedZero) { + // Tailscale 1.2.0 - 1.2.8 on Windows had a memory corruption bug + // in the backend process that ended up sending NULL bytes over JSON + // to the frontend which wrote them out to JSON files on disk. + // So if we see one, treat is as corrupt and the user will need + // to log in again. (better than crashing) + return nil, os.ErrNotExist + } p, err := PrefsFromBytes(data, false) if err != nil { return nil, fmt.Errorf("LoadPrefs(%q) decode: %w", filename, err) diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index 8b95d87a4..105683511 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -7,6 +7,7 @@ package ipn import ( "errors" "fmt" + "io/ioutil" "os" "reflect" "testing" @@ -371,3 +372,25 @@ func TestLoadPrefsNotExist(t *testing.T) { } t.Fatalf("unexpected prefs=%#v, err=%v", p, err) } + +// TestLoadPrefsFileWithZeroInIt verifies that LoadPrefs hanldes corrupted input files. +// See issue #954 for details. +func TestLoadPrefsFileWithZeroInIt(t *testing.T) { + f, err := ioutil.TempFile("", "TestLoadPrefsFileWithZeroInIt") + if err != nil { + t.Fatal(err) + } + path := f.Name() + if _, err := f.Write(jsonEscapedZero); err != nil { + t.Fatal(err) + } + f.Close() + defer os.Remove(path) + + p, err := LoadPrefs(path) + if errors.Is(err, os.ErrNotExist) { + // expected. + return + } + t.Fatalf("unexpected prefs=%#v, err=%v", p, err) +} diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index 4174a6b4b..fba389e15 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -25,7 +25,7 @@ import ( "strings" "time" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" "tailscale.com/atomicfile" "tailscale.com/logtail" "tailscale.com/logtail/filch" @@ -311,7 +311,7 @@ func tryFixLogStateLocation(dir, cmdname string) { // given collection name. func New(collection string) *Policy { var lflags int - if terminal.IsTerminal(2) || runtime.GOOS == "windows" { + if term.IsTerminal(2) || runtime.GOOS == "windows" { lflags = 0 } else { lflags = log.LstdFlags diff --git a/logtail/logtail.go b/logtail/logtail.go index 9cedb6bc3..b9c94e98f 100644 --- a/logtail/logtail.go +++ b/logtail/logtail.go @@ -14,6 +14,7 @@ import ( "io/ioutil" "net/http" "os" + "strconv" "time" "tailscale.com/logtail/backoff" @@ -259,8 +260,17 @@ func (l *logger) uploading(ctx context.Context) { for { body := l.drainPending() - if l.zstdEncoder != nil { - body = l.zstdEncoder.EncodeAll(body, nil) + origlen := -1 // sentinel value: uncompressed + // Don't attempt to compress tiny bodies; not worth the CPU cycles. + if l.zstdEncoder != nil && len(body) > 256 { + zbody := l.zstdEncoder.EncodeAll(body, nil) + // Only send it compressed if the bandwidth savings are sufficient. + // Just the extra headers associated with enabling compression + // are 50 bytes by themselves. + if len(body)-len(zbody) > 64 { + origlen = len(body) + body = zbody + } } for len(body) > 0 { @@ -269,7 +279,7 @@ func (l *logger) uploading(ctx context.Context) { return default: } - uploaded, err := l.upload(ctx, body) + uploaded, err := l.upload(ctx, body, origlen) if err != nil { fmt.Fprintf(l.stderr, "logtail: upload: %v\n", err) } @@ -287,7 +297,10 @@ func (l *logger) uploading(ctx context.Context) { } } -func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err error) { +// upload uploads body to the log server. +// origlen indicates the pre-compression body length. +// origlen of -1 indicates that the body is not compressed. +func (l *logger) upload(ctx context.Context, body []byte, origlen int) (uploaded bool, err error) { req, err := http.NewRequest("POST", l.url, bytes.NewReader(body)) if err != nil { // I know of no conditions under which this could fail. @@ -295,8 +308,9 @@ func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err er // TODO record logs to disk panic("logtail: cannot build http request: " + err.Error()) } - if l.zstdEncoder != nil { + if origlen != -1 { req.Header.Add("Content-Encoding", "zstd") + req.Header.Add("Orig-Content-Length", strconv.Itoa(origlen)) } req.Header["User-Agent"] = nil // not worth writing one; save some bytes @@ -306,7 +320,7 @@ func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err er req = req.WithContext(ctx) compressedNote := "not-compressed" - if l.zstdEncoder != nil { + if origlen != -1 { compressedNote = "compressed" } diff --git a/net/interfaces/interfaces_darwin.go b/net/interfaces/interfaces_darwin.go index 77d975f1c..c502255a9 100644 --- a/net/interfaces/interfaces_darwin.go +++ b/net/interfaces/interfaces_darwin.go @@ -64,11 +64,11 @@ func likelyHomeRouterIPDarwinExec() (ret netaddr.IP, ok bool) { if err == nil && isPrivateIP(ip) { ret = ip // We've found what we're looking for. - return stopReadingNetstatTable + return errStopReadingNetstatTable } return nil }) return ret, !ret.IsZero() } -var stopReadingNetstatTable = errors.New("found private gateway") +var errStopReadingNetstatTable = errors.New("found private gateway") diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go index cdb767bff..26c8d118b 100644 --- a/net/netcheck/netcheck.go +++ b/net/netcheck/netcheck.go @@ -710,6 +710,7 @@ func (rs *reportState) probePortMapServices() { uc.WriteTo(pcpPacket(myIP, tempPort, false), port5351) res := make([]byte, 1500) + sentPCPDelete := false for { n, addr, err := uc.ReadFrom(res) if err != nil { @@ -727,11 +728,14 @@ func (rs *reportState) probePortMapServices() { if n == 60 && res[0] == 0x02 { // right length and version 2 rs.setOptBool(&rs.report.PCP, true) - // And now delete the mapping. - // (PCP is the only protocol of the three that requires - // we cause a side effect to detect whether it's present, - // so we need to redo that side effect now.) - uc.WriteTo(pcpPacket(myIP, tempPort, true), port5351) + if !sentPCPDelete { + sentPCPDelete = true + // And now delete the mapping. + // (PCP is the only protocol of the three that requires + // we cause a side effect to detect whether it's present, + // so we need to redo that side effect now.) + uc.WriteTo(pcpPacket(myIP, tempPort, true), port5351) + } } } } @@ -747,6 +751,7 @@ var uPnPPacket = []byte("M-SEARCH * HTTP/1.1\r\n" + var v4unspec, _ = netaddr.ParseIP("0.0.0.0") +// pcpPacket generates a PCP packet with a MAP opcode. func pcpPacket(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte { const udpProtoNumber = 17 lifetimeSeconds := uint32(1) @@ -754,17 +759,24 @@ func pcpPacket(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte { lifetimeSeconds = 0 } const opMap = 1 + + // 24 byte header + 36 byte map opcode pkt := make([]byte, (32+32+128)/8+(96+8+24+16+16+128)/8) + + // The header (https://tools.ietf.org/html/rfc6887#section-7.1) pkt[0] = 2 // version pkt[1] = opMap binary.BigEndian.PutUint32(pkt[4:8], lifetimeSeconds) myIP16 := myIP.As16() copy(pkt[8:], myIP16[:]) - rand.Read(pkt[24 : 24+12]) - pkt[36] = udpProtoNumber - binary.BigEndian.PutUint16(pkt[40:], uint16(mapToLocalPort)) + + // The map opcode body (https://tools.ietf.org/html/rfc6887#section-11.1) + mapOp := pkt[24:] + rand.Read(mapOp[:12]) // 96 bit mappping nonce + mapOp[12] = udpProtoNumber + binary.BigEndian.PutUint16(mapOp[16:], uint16(mapToLocalPort)) v4unspec16 := v4unspec.As16() - copy(pkt[40:], v4unspec16[:]) + copy(mapOp[20:], v4unspec16[:]) return pkt } diff --git a/syncs/syncs.go b/syncs/syncs.go index c0208c996..0139ad925 100644 --- a/syncs/syncs.go +++ b/syncs/syncs.go @@ -33,7 +33,7 @@ func NewWaitGroupChan() *WaitGroupChan { } // DoneChan returns a channel that's closed on completion. -func (c *WaitGroupChan) DoneChan() <-chan struct{} { return c.done } +func (wg *WaitGroupChan) DoneChan() <-chan struct{} { return wg.done } // Add adds delta, which may be negative, to the WaitGroupChan // counter. If the counter becomes zero, all goroutines blocked on @@ -46,10 +46,10 @@ func (c *WaitGroupChan) DoneChan() <-chan struct{} { return c.done } // than zero, may happen at any time. Typically this means the calls // to Add should execute before the statement creating the goroutine // or other event to be waited for. -func (c *WaitGroupChan) Add(delta int) { - n := atomic.AddInt64(&c.n, int64(delta)) +func (wg *WaitGroupChan) Add(delta int) { + n := atomic.AddInt64(&wg.n, int64(delta)) if n == 0 { - close(c.done) + close(wg.done) } } diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index a93c11c5b..191ec0615 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -112,8 +112,6 @@ type User struct { Logins []LoginID Roles []RoleID Created time.Time - - // Note: be sure to update Clone when adding new fields } type Login struct { @@ -153,12 +151,9 @@ type Node struct { Created time.Time LastSeen *time.Time `json:",omitempty"` - KeepAlive bool // open and keep open a connection to this peer - - MachineAuthorized bool // TODO(crawshaw): replace with MachineStatus + KeepAlive bool `json:",omitempty"` // open and keep open a connection to this peer - // NOTE: any new fields containing pointers in this type - // require changes to Node.Clone. + MachineAuthorized bool `json:",omitempty"` // TODO(crawshaw): replace with MachineStatus } type MachineStatus int @@ -275,9 +270,6 @@ type Service struct { Description string `json:",omitempty"` // text description of service // TODO(apenwarr): allow advertising services on subnet IPs? // TODO(apenwarr): add "tags" here for each service? - - // NOTE: any new fields containing pointers in this type - // require changes to Hostinfo.Clone. } // Hostinfo contains a summary of a Tailscale host. @@ -287,7 +279,7 @@ type Service struct { type Hostinfo struct { // TODO(crawshaw): mark all these fields ",omitempty" when all the // iOS apps are updated with the latest swift version of this struct. - IPNVersion string // version of this code + IPNVersion string `json:",omitempty"` // version of this code FrontendLogID string `json:",omitempty"` // logtail ID of frontend instance BackendLogID string `json:",omitempty"` // logtail ID of backend instance OS string // operating system the client runs on (a version.OS value) @@ -295,6 +287,7 @@ type Hostinfo struct { DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone 11 Pro") Hostname string // name of the host the client runs on ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections + ShareeNode bool `json:",omitempty"` // indicates this node exists in netmap because it's owned by a shared-to user GoArch string `json:",omitempty"` // the host's GOARCH value (of the running binary) RoutableIPs []wgcfg.CIDR `json:",omitempty"` // set of IP ranges this client can route RequestTags []string `json:",omitempty"` // set of ACL tags this node wants to claim @@ -302,7 +295,7 @@ type Hostinfo struct { NetInfo *NetInfo `json:",omitempty"` // NOTE: any new fields containing pointers in this type - // require changes to Hostinfo.Clone and Hostinfo.Equal. + // require changes to Hostinfo.Equal. } // NetInfo contains information about the host's network state. @@ -354,7 +347,7 @@ type NetInfo struct { // the control plane. DERPLatency map[string]float64 `json:",omitempty"` - // Update Clone and BasicallyEqual when adding fields. + // Update BasicallyEqual when adding fields. } func (ni *NetInfo) String() string { @@ -482,7 +475,8 @@ type MapRequest struct { // History of versions: // 3: implicit compression, keep-alives // 4: opt-in keep-alives via KeepAlive field, opt-in compression via Compress - // 5: 2020-10-19, implies IncludeIPv6, DeltaPeers/DeltaUserProfiles, supports MagicDNS + // 5: 2020-10-19, implies IncludeIPv6, delta Peers/UserProfiles, supports MagicDNS + // 6: 2020-12-07: means MapResponse.PacketFilter nil means unchanged Version int Compress string // "zstd" or "" (no compression) KeepAlive bool // whether server should send keep-alives back to us @@ -519,6 +513,8 @@ type MapRequest struct { // router but their IP forwarding is broken. // * "v6-overlay": IPv6 development flag to have control send // v6 node addrs + // * "minimize-netmap": have control minimize the netmap, removing + // peers that are unreachable per ACLS. DebugFlags []string `json:",omitempty"` } @@ -601,14 +597,15 @@ type MapResponse struct { // Peers, if non-empty, is the complete list of peers. // It will be set in the first MapResponse for a long-polled request/response. - // Subsequent responses will be delta-encoded if DeltaPeers was set in the request. + // Subsequent responses will be delta-encoded if MapRequest.Version >= 5 and server + // chooses, in which case Peers will be nil or zero length. // If Peers is non-empty, PeersChanged and PeersRemoved should // be ignored (and should be empty). // Peers is always returned sorted by Node.ID. Peers []*Node `json:",omitempty"` // PeersChanged are the Nodes (identified by their ID) that // have changed or been added since the past update on the - // HTTP response. It's only set if MapRequest.DeltaPeers was true. + // HTTP response. It's not used by the server if MapRequest.Version < 5. // PeersChanged is always returned sorted by Node.ID. PeersChanged []*Node `json:",omitempty"` // PeersRemoved are the NodeIDs that are no longer in the peer list. @@ -624,11 +621,25 @@ type MapResponse struct { SearchPaths []string `json:",omitempty"` DNSConfig DNSConfig `json:",omitempty"` - // ACLs - Domain string + // Domain is the name of the network that this node is + // in. It's either of the form "example.com" (for user + // foo@example.com, for multi-user networks) or + // "foo@gmail.com" (for siloed users on shared email + // providers). Its exact form should not be depended on; new + // forms are coming later. + Domain string + + // PacketFilter are the firewall rules. + // + // For MapRequest.Version >= 6, a nil value means the most + // previously streamed non-nil MapResponse.PacketFilter within + // the same HTTP response. A non-nil but empty list always means + // no PacketFilter (that is, to block everything). PacketFilter []FilterRule - UserProfiles []UserProfile // as of 1.1.541: may be new or updated user profiles only + + UserProfiles []UserProfile // as of 1.1.541 (mapver 5): may be new or updated user profiles only Roles []Role // deprecated; clients should not rely on Roles + // TODO: Groups []Group // TODO: Capabilities []Capability diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index 01473d0ed..d89607dd4 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -106,6 +106,7 @@ var _HostinfoNeedsRegeneration = Hostinfo(struct { DeviceModel string Hostname string ShieldsUp bool + ShareeNode bool GoArch string RoutableIPs []wgcfg.CIDR RequestTags []string diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 8a2e1e5ab..3f8cbcf69 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -23,9 +23,12 @@ func fieldsOf(t reflect.Type) (fields []string) { func TestHostinfoEqual(t *testing.T) { hiHandles := []string{ - "IPNVersion", "FrontendLogID", "BackendLogID", "OS", "OSVersion", - "DeviceModel", "Hostname", "ShieldsUp", "GoArch", "RoutableIPs", - "RequestTags", "Services", "NetInfo", + "IPNVersion", "FrontendLogID", "BackendLogID", + "OS", "OSVersion", "DeviceModel", "Hostname", + "ShieldsUp", "ShareeNode", + "GoArch", + "RoutableIPs", "RequestTags", + "Services", "NetInfo", } if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) { t.Errorf("Hostinfo.Equal check might be out of sync\nfields: %q\nhandled: %q\n", @@ -169,6 +172,11 @@ func TestHostinfoEqual(t *testing.T) { &Hostinfo{Services: []Service{Service{Proto: TCP, Port: 1234, Description: "foo"}}}, true, }, + { + &Hostinfo{ShareeNode: true}, + &Hostinfo{}, + false, + }, } for i, tt := range tests { got := tt.a.Equal(tt.b) diff --git a/tempfork/pprof/pprof.go b/tempfork/pprof/pprof.go index 99e0ac125..b6af2319f 100644 --- a/tempfork/pprof/pprof.go +++ b/tempfork/pprof/pprof.go @@ -44,11 +44,12 @@ func AddHandlers(mux *http.ServeMux) { func Cmdline(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("Content-Type", "text/plain; charset=utf-8") - fmt.Fprintf(w, strings.Join(os.Args, "\x00")) + fmt.Fprint(w, strings.Join(os.Args, "\x00")) } func sleep(w http.ResponseWriter, d time.Duration) { var clientGone <-chan bool + //lint:ignore SA1019 CloseNotifier is deprecated but it functions and this is a temporary fork if cn, ok := w.(http.CloseNotifier); ok { clientGone = cn.CloseNotify() } diff --git a/tsweb/tsweb.go b/tsweb/tsweb.go index 134f034cf..411e615a4 100644 --- a/tsweb/tsweb.go +++ b/tsweb/tsweb.go @@ -44,6 +44,16 @@ func registerCommonDebug(mux *http.ServeMux) { mux.Handle("/debug/pprof/", Protected(http.DefaultServeMux)) // to net/http/pprof mux.Handle("/debug/vars", Protected(http.DefaultServeMux)) // to expvar mux.Handle("/debug/varz", Protected(http.HandlerFunc(varzHandler))) + mux.Handle("/debug/gc", Protected(http.HandlerFunc(gcHandler))) +} + +func gcHandler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("running GC...\n")) + if f, ok := w.(http.Flusher); ok { + f.Flush() + } + runtime.GC() + w.Write([]byte("Done.\n")) } func DefaultCertDir(leafDir string) string { diff --git a/version/version.go b/version/version.go index eb6033dbd..fbab0e6f4 100644 --- a/version/version.go +++ b/version/version.go @@ -10,7 +10,7 @@ package version // Long is a full version number for this build, of the form // "x.y.z-commithash", or "date.yyyymmdd" if no actual version was // provided. -const Long = "date.20201107" +const Long = "date.20201207" // Short is a short version number for this build, of the form // "x.y.z", or "date.yyyymmdd" if no actual version was provided. diff --git a/version/version.sh b/version/version.sh index 1fe1f9a49..121ed1726 100755 --- a/version/version.sh +++ b/version/version.sh @@ -31,7 +31,7 @@ case $# in if [ -z "$extra_hash_or_dir" ]; then # Nothing, empty extra hash is fine. extra_hash="" - elif [ -d "$extra_hash_or_dir/.git" ]; then + elif [ -e "$extra_hash_or_dir/.git" ]; then extra_hash=$(git_hash_dirty "$extra_hash_or_dir" HEAD) elif ! expr "$extra_hash_or_dir" : "^[0-9a-f]*$"; then echo "Invalid extra hash '$extra_hash_or_dir', must be a git commit or path to a git repo" >&2 diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 09ead62d4..a7bc74dd4 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -110,37 +110,58 @@ func inTest() bool { // A Conn routes UDP packets and actively manages a list of its endpoints. // It implements wireguard/conn.Bind. type Conn struct { - pconnPort uint16 // the preferred port from opts.Port; 0 means auto - pconn4 *RebindingUDPConn - pconn6 *RebindingUDPConn // non-nil if IPv6 available + // This block mirrors the contents and field order of the Options + // struct. Initialized once at construction, then constant. + + logf logger.Logf + port uint16 // the preferred port from opts.Port; 0 means auto epFunc func(endpoints []string) derpActiveFunc func() - logf logger.Logf - sendLogLimit *rate.Limiter - netChecker *netcheck.Client - idleFunc func() time.Duration // nil means unknown + idleFunc func() time.Duration // nil means unknown + packetListener nettype.PacketListener noteRecvActivity func(tailcfg.DiscoKey) // or nil, see Options.NoteRecvActivity simulatedNetwork bool + // ================================================================ + // No locking required to access these fields, either because + // they're static after construction, or are wholly owned by a + // single goroutine. + + connCtx context.Context // closed on Conn.Close + connCtxCancel func() // closes connCtx + + // pconn4 and pconn6 are the underlying UDP sockets used to + // send/receive packets for wireguard and other magicsock + // protocols. + pconn4 *RebindingUDPConn + pconn6 *RebindingUDPConn + + // netChecker is the prober that discovers local network + // conditions, including the closest DERP relay and NAT mappings. + netChecker *netcheck.Client + + // sendLogLimit is a rate limiter for errors logged in the (hot) + // packet sending codepath. It's so that, if magicsock gets into a + // bad state, we don't spam one error per wireguard packet being + // transmitted. + // TODO(danderson): now that we have global rate-limiting, is this still useful? + sendLogLimit *rate.Limiter + // bufferedIPv4From and bufferedIPv4Packet are owned by // ReceiveIPv4, and used when both a DERP and IPv4 packet arrive // at the same time. It stores the IPv4 packet for use in the next call. bufferedIPv4From netaddr.IPPort // if non-zero, then bufferedIPv4Packet is valid bufferedIPv4Packet []byte // the received packet (reused, owned by ReceiveIPv4) - connCtx context.Context // closed on Conn.Close - connCtxCancel func() // closes connCtx - // stunReceiveFunc holds the current STUN packet processing func. // Its Loaded value is always non-nil. stunReceiveFunc atomic.Value // of func(p []byte, fromAddr *net.UDPAddr) + // udpRecvCh and derpRecvCh are used by ReceiveIPv4 to multiplex + // reads from DERP and the pconn4. udpRecvCh chan udpReadResult derpRecvCh chan derpReadResult - // packetListener optionally specifies a test hook to open a PacketConn. - packetListener nettype.PacketListener - // ============================================================ mu sync.Mutex // guards all following fields; see userspaceEngine lock ordering rules muCond *sync.Cond @@ -148,17 +169,43 @@ type Conn struct { started bool // Start was called closed bool // Close was called + // endpointsUpdateActive indicates that updateEndpoints is + // currently running. It's used to deduplicate concurrent endpoint + // update requests. endpointsUpdateActive bool - wantEndpointsUpdate string // true if non-empty; string is reason - lastEndpoints []string - peerSet map[key.Public]struct{} - - discoPrivate key.Private - discoPublic tailcfg.DiscoKey // public of discoPrivate - discoShort string // ShortString of discoPublic (to save logging work later) - nodeOfDisco map[tailcfg.DiscoKey]*tailcfg.Node - discoOfNode map[tailcfg.NodeKey]tailcfg.DiscoKey - discoOfAddr map[netaddr.IPPort]tailcfg.DiscoKey // validated non-DERP paths only + // wantEndpointsUpdate, if non-empty, means that a new endpoints + // update should begin immediately after the currently-running one + // completes. It can only be non-empty if + // endpointsUpdateActive==true. + wantEndpointsUpdate string // true if non-empty; string is reason + // lastEndpoints records the endpoints found during the previous + // endpoint discovery. It's used to avoid duplicate endpoint + // change notifications. + lastEndpoints []string + + // peerSet is the set of peers that are currently configured in + // WireGuard. These are not used to filter inbound or outbound + // traffic at all, but only to track what state can be cleaned up + // in other maps below that are keyed by peer public key. + peerSet map[key.Public]struct{} + + // discoPrivate is the private naclbox key used for active + // discovery traffic. It's created once near (but not during) + // construction. + discoPrivate key.Private + discoPublic tailcfg.DiscoKey // public of discoPrivate + discoShort string // ShortString of discoPublic (to save logging work later) + // nodeOfDisco tracks the networkmap Node entity for each peer + // discovery key. + // + // TODO(danderson): the only thing we ever use from this is the + // peer's WireGuard public key. This could be a map of DiscoKey to + // NodeKey. + nodeOfDisco map[tailcfg.DiscoKey]*tailcfg.Node + discoOfNode map[tailcfg.NodeKey]tailcfg.DiscoKey + discoOfAddr map[netaddr.IPPort]tailcfg.DiscoKey // validated non-DERP paths only + // endpointsOfDisco tracks the wireguard-go endpoints for peers + // with recent activity. endpointOfDisco map[tailcfg.DiscoKey]*discoEndpoint // those with activity only sharedDiscoKey map[tailcfg.DiscoKey]*[32]byte // nacl/box precomputed key @@ -173,18 +220,35 @@ type Conn struct { // 10.0.0.1:1 -> [10.0.0.1:1, 10.0.0.2:2] // 10.0.0.2:2 -> [10.0.0.1:1, 10.0.0.2:2] // 10.0.0.3:3 -> [10.0.0.3:3] + // + // Used only to communicate with legacy, pre-active-discovery + // clients. addrsByUDP map[netaddr.IPPort]*AddrSet - // addrsByKey maps from public keys (as seen by incoming DERP // packets) to its AddrSet (the same values as in addrsByUDP). + // + // Used only to communicate with legacy, pre-active-discovery + // clients. addrsByKey map[key.Public]*AddrSet + // netInfoFunc is a callback that provides a tailcfg.NetInfo when + // discovered network conditions change. + // + // TODO(danderson): why can't it be set at construction time? + // There seem to be a few natural places in ipn/local.go to + // swallow untimely invocations. netInfoFunc func(*tailcfg.NetInfo) // nil until set + // netInfoLast is the NetInfo provided in the last call to + // netInfoFunc. It's used to deduplicate calls to netInfoFunc. + // + // TODO(danderson): should all the deduping happen in + // ipn/local.go? We seem to be doing dedupe at several layers, and + // magicsock could do with any complexity reduction it can get. netInfoLast *tailcfg.NetInfo derpMap *tailcfg.DERPMap // nil (or zero regions/nodes) means DERP is disabled netMap *controlclient.NetworkMap - privateKey key.Private + privateKey key.Private // WireGuard private key for this node everHadKey bool // whether we ever had a non-zero private key myDerp int // nearest DERP region ID; 0 means none/unknown derpStarted chan struct{} // closed on first connection to DERP; for tests & cleaner Close @@ -370,7 +434,7 @@ func newConn() *Conn { // It doesn't start doing anything until Start is called. func NewConn(opts Options) (*Conn, error) { c := newConn() - c.pconnPort = opts.Port + c.port = opts.Port c.logf = opts.logf() c.epFunc = opts.endpointsFunc() c.derpActiveFunc = opts.derpActiveFunc() @@ -834,9 +898,9 @@ func (c *Conn) determineEndpoints(ctx context.Context) (ipPorts []string, reason // port mapping on their router to the same explicit // port that tailscaled is running with. Worst case // it's an invalid candidate mapping. - if nr.MappingVariesByDestIP.EqualBool(true) && c.pconnPort != 0 { + if nr.MappingVariesByDestIP.EqualBool(true) && c.port != 0 { if ip, _, err := net.SplitHostPort(nr.GlobalV4); err == nil { - addAddr(net.JoinHostPort(ip, strconv.Itoa(int(c.pconnPort))), "port_in") + addAddr(net.JoinHostPort(ip, strconv.Itoa(int(c.port))), "port_in") } } } @@ -2499,18 +2563,18 @@ func (c *Conn) bind1(ruc **RebindingUDPConn, which string) error { var pc net.PacketConn var err error listenCtx := context.Background() // unused without DNS name to resolve - if c.pconnPort == 0 && DefaultPort != 0 { - pc, err = c.listenPacket(listenCtx, which, fmt.Sprintf("%s:%d", host, DefaultPort)) + if c.port == 0 && DefaultPort != 0 { + pc, err = c.listenPacket(listenCtx, which, net.JoinHostPort(host, fmt.Sprint(DefaultPort))) if err != nil { c.logf("magicsock: bind: default port %s/%v unavailable; picking random", which, DefaultPort) } } if pc == nil { - pc, err = c.listenPacket(listenCtx, which, fmt.Sprintf("%s:%d", host, c.pconnPort)) + pc, err = c.listenPacket(listenCtx, which, net.JoinHostPort(host, fmt.Sprint(c.port))) } if err != nil { - c.logf("magicsock: bind(%s/%v): %v", which, c.pconnPort, err) - return fmt.Errorf("magicsock: bind: %s/%d: %v", which, c.pconnPort, err) + c.logf("magicsock: bind(%s/%v): %v", which, c.port, err) + return fmt.Errorf("magicsock: bind: %s/%d: %v", which, c.port, err) } if *ruc == nil { *ruc = new(RebindingUDPConn) @@ -2527,19 +2591,19 @@ func (c *Conn) Rebind() { host = "127.0.0.1" } listenCtx := context.Background() // unused without DNS name to resolve - if c.pconnPort != 0 { + if c.port != 0 { c.pconn4.mu.Lock() if err := c.pconn4.pconn.Close(); err != nil { c.logf("magicsock: link change close failed: %v", err) } - packetConn, err := c.listenPacket(listenCtx, "udp4", fmt.Sprintf("%s:%d", host, c.pconnPort)) + packetConn, err := c.listenPacket(listenCtx, "udp4", fmt.Sprintf("%s:%d", host, c.port)) if err == nil { - c.logf("magicsock: link change rebound port: %d", c.pconnPort) + c.logf("magicsock: link change rebound port: %d", c.port) c.pconn4.pconn = packetConn.(*net.UDPConn) c.pconn4.mu.Unlock() return } - c.logf("magicsock: link change unable to bind fixed port %d: %v, falling back to random port", c.pconnPort, err) + c.logf("magicsock: link change unable to bind fixed port %d: %v, falling back to random port", c.port, err) c.pconn4.mu.Unlock() } c.logf("magicsock: link change, binding new port") diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 17d931e1f..a308e8dd7 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -56,7 +56,7 @@ func init() { // conditions in tests. In particular, you can't expect two test magicsocks // to be able to connect to each other through a test DERP unless they are // both fully initialized before you try. -func (c *Conn) WaitReady(t *testing.T) { +func (c *Conn) WaitReady(t testing.TB) { t.Helper() timer := time.NewTimer(10 * time.Second) defer timer.Stop() @@ -130,7 +130,7 @@ type magicStack struct { // newMagicStack builds and initializes an idle magicsock and // friends. You need to call conn.SetNetworkMap and dev.Reconfig // before anything interesting happens. -func newMagicStack(t *testing.T, logf logger.Logf, l nettype.PacketListener, derpMap *tailcfg.DERPMap) *magicStack { +func newMagicStack(t testing.TB, logf logger.Logf, l nettype.PacketListener, derpMap *tailcfg.DERPMap) *magicStack { t.Helper() privateKey, err := wgcfg.NewPrivateKey() @@ -377,7 +377,7 @@ collectEndpoints: } } -func pickPort(t *testing.T) uint16 { +func pickPort(t testing.TB) uint16 { t.Helper() conn, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { @@ -1344,3 +1344,75 @@ func TestDiscoEndpointAlignment(t *testing.T) { t.Error("expected false on second call") } } + +func BenchmarkReceiveFrom(b *testing.B) { + port := pickPort(b) + conn, err := NewConn(Options{ + Logf: b.Logf, + Port: port, + EndpointsFunc: func(eps []string) { + b.Logf("endpoints: %q", eps) + }, + }) + if err != nil { + b.Fatal(err) + } + defer conn.Close() + + sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0") + if err != nil { + b.Fatal(err) + } + defer sendConn.Close() + + var dstAddr net.Addr = conn.pconn4.LocalAddr() + sendBuf := make([]byte, 1<<10) + for i := range sendBuf { + sendBuf[i] = 'x' + } + + buf := make([]byte, 2<<10) + for i := 0; i < b.N; i++ { + if _, err := sendConn.WriteTo(sendBuf, dstAddr); err != nil { + b.Fatalf("WriteTo: %v", err) + } + n, ep, addr, err := conn.ReceiveIPv4(buf) + if err != nil { + b.Fatal(err) + } + _ = n + _ = ep + _ = addr + } +} + +func BenchmarkReceiveFrom_Native(b *testing.B) { + recvConn, err := net.ListenPacket("udp4", "127.0.0.1:0") + if err != nil { + b.Fatal(err) + } + defer recvConn.Close() + recvConnUDP := recvConn.(*net.UDPConn) + + sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0") + if err != nil { + b.Fatal(err) + } + defer sendConn.Close() + + var dstAddr net.Addr = recvConn.LocalAddr() + sendBuf := make([]byte, 1<<10) + for i := range sendBuf { + sendBuf[i] = 'x' + } + + buf := make([]byte, 2<<10) + for i := 0; i < b.N; i++ { + if _, err := sendConn.WriteTo(sendBuf, dstAddr); err != nil { + b.Fatalf("WriteTo: %v", err) + } + if _, _, err := recvConnUDP.ReadFromUDP(buf); err != nil { + b.Fatalf("ReadFromUDP: %v", err) + } + } +} diff --git a/wgengine/router/dns/manager_windows.go b/wgengine/router/dns/manager_windows.go index 685a471d4..718f5ecbe 100644 --- a/wgengine/router/dns/manager_windows.go +++ b/wgengine/router/dns/manager_windows.go @@ -12,7 +12,6 @@ import ( "time" "github.com/tailscale/wireguard-go/tun" - wgregistry "github.com/tailscale/wireguard-go/tun/wintun/registry" "golang.org/x/sys/windows/registry" "tailscale.com/types/logger" ) @@ -42,7 +41,7 @@ func newManager(mconfig ManagerConfig) managerImpl { const keyOpenTimeout = time.Minute func setRegistryString(path, name, value string) error { - key, err := wgregistry.OpenKeyWait(registry.LOCAL_MACHINE, path, registry.SET_VALUE, keyOpenTimeout) + key, err := openKeyWait(registry.LOCAL_MACHINE, path, registry.SET_VALUE, keyOpenTimeout) if err != nil { return fmt.Errorf("opening %s: %w", path, err) } diff --git a/wgengine/router/dns/registry_windows.go b/wgengine/router/dns/registry_windows.go new file mode 100644 index 000000000..f8e1f514a --- /dev/null +++ b/wgengine/router/dns/registry_windows.go @@ -0,0 +1,76 @@ +// 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. +// +// The code in this file originates from https://git.zx2c4.com/wireguard-go: +// Copyright (C) 2017-2020 WireGuard LLC. All Rights Reserved. +// Copying license: https://git.zx2c4.com/wireguard-go/tree/COPYING + +package dns + +import ( + "fmt" + "runtime" + "strings" + "time" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +func openKeyWait(k registry.Key, path string, access uint32, timeout time.Duration) (registry.Key, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + deadline := time.Now().Add(timeout) + pathSpl := strings.Split(path, "\\") + for i := 0; ; i++ { + keyName := pathSpl[i] + isLast := i+1 == len(pathSpl) + + event, err := windows.CreateEvent(nil, 0, 0, nil) + if err != nil { + return 0, fmt.Errorf("windows.CreateEvent: %v", err) + } + defer windows.CloseHandle(event) + + var key registry.Key + for { + err = windows.RegNotifyChangeKeyValue(windows.Handle(k), false, windows.REG_NOTIFY_CHANGE_NAME, event, true) + if err != nil { + return 0, fmt.Errorf("windows.RegNotifyChangeKeyValue: %v", err) + } + + var accessFlags uint32 + if isLast { + accessFlags = access + } else { + accessFlags = registry.NOTIFY + } + key, err = registry.OpenKey(k, keyName, accessFlags) + if err == windows.ERROR_FILE_NOT_FOUND || err == windows.ERROR_PATH_NOT_FOUND { + timeout := time.Until(deadline) / time.Millisecond + if timeout < 0 { + timeout = 0 + } + s, err := windows.WaitForSingleObject(event, uint32(timeout)) + if err != nil { + return 0, fmt.Errorf("windows.WaitForSingleObject: %v", err) + } + if s == uint32(windows.WAIT_TIMEOUT) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows + return 0, fmt.Errorf("timeout waiting for registry key") + } + } else if err != nil { + return 0, fmt.Errorf("registry.OpenKey(%v): %v", path, err) + } else { + if isLast { + return key, nil + } + defer key.Close() + break + } + } + + k = key + } +} diff --git a/wgengine/router/ifconfig_windows.go b/wgengine/router/ifconfig_windows.go index ee4a62b13..b4a8d406e 100644 --- a/wgengine/router/ifconfig_windows.go +++ b/wgengine/router/ifconfig_windows.go @@ -39,12 +39,8 @@ import ( // address a few rare corner cases, but is unlikely to significantly // help with MTU issues compared to a static 1280B implementation. func monitorDefaultRoutes(tun *tun.NativeTun) (*winipcfg.RouteChangeCallback, error) { - guid := tun.GUID() - ourLuid, err := winipcfg.LUIDFromGUID(&guid) + ourLuid := winipcfg.LUID(tun.LUID()) lastMtu := uint32(0) - if err != nil { - return nil, fmt.Errorf("error mapping GUID %v to LUID: %w", guid, err) - } doIt := func() error { mtu, err := getDefaultRouteMTU() if err != nil { @@ -91,7 +87,7 @@ func monitorDefaultRoutes(tun *tun.NativeTun) (*winipcfg.RouteChangeCallback, er } return nil } - err = doIt() + err := doIt() if err != nil { return nil, err } @@ -159,7 +155,7 @@ func getDefaultRouteMTU() (uint32, error) { // setPrivateNetwork marks the provided network adapter's category to private. // It returns (false, nil) if the adapter was not found. -func setPrivateNetwork(ifcGUID *windows.GUID) (bool, error) { +func setPrivateNetwork(ifcLUID winipcfg.LUID) (bool, error) { // NLM_NETWORK_CATEGORY values. const ( categoryPublic = 0 @@ -167,6 +163,11 @@ func setPrivateNetwork(ifcGUID *windows.GUID) (bool, error) { categoryDomain = 2 ) + ifcGUID, err := ifcLUID.GUID() + if err != nil { + return false, fmt.Errorf("ifcLUID.GUID: %v", err) + } + // Lock OS thread when using OLE, which seems to be a requirement // from the Microsoft docs. go-ole doesn't seem to handle it automatically. // https://github.com/tailscale/tailscale/issues/921#issuecomment-727526807 @@ -222,12 +223,8 @@ func setPrivateNetwork(ifcGUID *windows.GUID) (bool, error) { return false, nil } -// interfaceFromGUID returns IPAdapterAddresses with specified GUID. -func interfaceFromGUID(guid *windows.GUID, flags winipcfg.GAAFlags) (*winipcfg.IPAdapterAddresses, error) { - luid, err := winipcfg.LUIDFromGUID(guid) - if err != nil { - return nil, err - } +// interfaceFromLUID returns IPAdapterAddresses with specified LUID. +func interfaceFromLUID(luid winipcfg.LUID, flags winipcfg.GAAFlags) (*winipcfg.IPAdapterAddresses, error) { addresses, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, flags) if err != nil { return nil, err @@ -237,13 +234,13 @@ func interfaceFromGUID(guid *windows.GUID, flags winipcfg.GAAFlags) (*winipcfg.I return addr, nil } } - return nil, fmt.Errorf("interfaceFromGUID: interface with LUID %v (from GUID %v) not found", luid, guid) + return nil, fmt.Errorf("interfaceFromLUID: interface with LUID %v not found", luid) } func configureInterface(cfg *Config, tun *tun.NativeTun) error { const mtu = 0 - guid := tun.GUID() - iface, err := interfaceFromGUID(&guid, + luid := winipcfg.LUID(tun.LUID()) + iface, err := interfaceFromLUID(luid, // Issue 474: on early boot, when the network is still // coming up, if the Tailscale service comes up first, // the Tailscale adapter it finds might not have the @@ -260,7 +257,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error { // does. const tries = 20 for i := 0; i < tries; i++ { - found, err := setPrivateNetwork(&guid) + found, err := setPrivateNetwork(luid) if err != nil { log.Printf("setPrivateNetwork(try=%d): %v", i, err) } else { @@ -271,7 +268,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error { } time.Sleep(1 * time.Second) } - log.Printf("setPrivateNetwork: adapter %v not found after %d tries, giving up", guid, tries) + log.Printf("setPrivateNetwork: adapter LUID %v not found after %d tries, giving up", luid, tries) }() var firstGateway4 *net.IP @@ -353,7 +350,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error { } // Re-read interface after syncAddresses. - iface, err = interfaceFromGUID(&guid, + iface, err = interfaceFromLUID(luid, // Issue 474: on early boot, when the network is still // coming up, if the Tailscale service comes up first, // the Tailscale adapter it finds might not have the diff --git a/wgengine/router/router_windows.go b/wgengine/router/router_windows.go index 50e65dd32..0194ef0a1 100644 --- a/wgengine/router/router_windows.go +++ b/wgengine/router/router_windows.go @@ -37,10 +37,15 @@ func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Devic } nativeTun := tundev.(*tun.NativeTun) - guid := nativeTun.GUID().String() + luid := winipcfg.LUID(nativeTun.LUID()) + guid, err := luid.GUID() + if err != nil { + return nil, err + } + mconfig := dns.ManagerConfig{ Logf: logf, - InterfaceName: guid, + InterfaceName: guid.String(), } return &winRouter{ diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 9c3253ea5..997b39719 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -515,6 +515,13 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, src start := time.Now() var dstIPs []packet.IP4 for _, ip := range ips { + if ip.Is6() { + // This code is only used for legacy (pre-discovery) + // peers. They're not going to work right with IPv6 on the + // overlay anyway, so don't bother trying to make ping + // work. + continue + } dstIPs = append(dstIPs, packet.IP4FromNetaddr(netaddr.IPFrom16(ip.Addr))) }