Compare commits
34 Commits
main
...
bradfitz/p
Author | SHA1 | Date |
---|---|---|
![]() |
31f2cd9e45 | |
![]() |
90c249cf26 | |
![]() |
8319b45be7 | |
![]() |
4afc189919 | |
![]() |
a660748272 | |
![]() |
1aeeeb7e45 | |
![]() |
3b9fcc2b81 | |
![]() |
d51ebe38d4 | |
![]() |
b8b83c8a3f | |
![]() |
2d0bd18a08 | |
![]() |
16c67870d3 | |
![]() |
64a9656c01 | |
![]() |
b26876427c | |
![]() |
cc2ec141fe | |
![]() |
d2c1ae7ed4 | |
![]() |
121f5a00f7 | |
![]() |
d06ceffd02 | |
![]() |
0cf60b5185 | |
![]() |
910682c851 | |
![]() |
c027962893 | |
![]() |
acc50d6b67 | |
![]() |
a2ab23ba6c | |
![]() |
71b13b5ac2 | |
![]() |
1c238cdce6 | |
![]() |
a9f58fe822 | |
![]() |
5417ca69a7 | |
![]() |
03e640e94d | |
![]() |
138bcae525 | |
![]() |
bb0ef32dd2 | |
![]() |
dde7ba4ecf | |
![]() |
fc30cff688 | |
![]() |
775fe13e27 | |
![]() |
2e33fdfe67 | |
![]() |
3d7cff91b3 |
|
@ -1 +1 @@
|
||||||
1.3.0
|
1.4.4
|
||||||
|
|
|
@ -65,7 +65,17 @@ func runStatus(ctx context.Context, args []string) error {
|
||||||
log.Fatal(*n.ErrMessage)
|
log.Fatal(*n.ErrMessage)
|
||||||
}
|
}
|
||||||
if n.Status != nil {
|
if n.Status != nil {
|
||||||
ch <- n.Status
|
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)
|
go pump(ctx, bc, c)
|
||||||
|
|
|
@ -228,7 +228,16 @@ func runUp(ctx context.Context, args []string) error {
|
||||||
AuthKey: upArgs.authKey,
|
AuthKey: upArgs.authKey,
|
||||||
Notify: func(n ipn.Notify) {
|
Notify: func(n ipn.Notify) {
|
||||||
if n.ErrMessage != nil {
|
if n.ErrMessage != nil {
|
||||||
fatalf("backend error: %v\n", *n.ErrMessage)
|
msg := *n.ErrMessage
|
||||||
|
if msg == ipn.ErrMsgPermissionDenied {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
msg += " (Tailscale service in use by other user?)"
|
||||||
|
default:
|
||||||
|
msg += " (try 'sudo tailscale up [...]')"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fatalf("backend error: %v\n", msg)
|
||||||
}
|
}
|
||||||
if s := n.State; s != nil {
|
if s := n.State; s != nil {
|
||||||
switch *s {
|
switch *s {
|
||||||
|
|
|
@ -43,6 +43,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
tailscale.com/derp/derphttp from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/derp/derphttp from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/derp/derpmap from tailscale.com/cmd/tailscale/cli
|
tailscale.com/derp/derpmap from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/disco from tailscale.com/derp+
|
tailscale.com/disco from tailscale.com/derp+
|
||||||
|
tailscale.com/health from tailscale.com/control/controlclient+
|
||||||
tailscale.com/internal/deepprint from tailscale.com/ipn+
|
tailscale.com/internal/deepprint from tailscale.com/ipn+
|
||||||
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli
|
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
|
||||||
|
@ -63,7 +64,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
tailscale.com/paths from tailscale.com/cmd/tailscale/cli
|
tailscale.com/paths from tailscale.com/cmd/tailscale/cli
|
||||||
tailscale.com/portlist from tailscale.com/ipn
|
tailscale.com/portlist from tailscale.com/ipn
|
||||||
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli
|
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli
|
||||||
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
|
tailscale.com/syncs from tailscale.com/net/interfaces+
|
||||||
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
||||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||||
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
||||||
|
@ -176,7 +177,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||||
hash/maphash from go4.org/mem
|
hash/maphash from go4.org/mem
|
||||||
html from tailscale.com/ipn/ipnstate
|
html from tailscale.com/ipn/ipnstate
|
||||||
io from bufio+
|
io from bufio+
|
||||||
io/ioutil from crypto/tls+
|
io/fs from crypto/rand+
|
||||||
|
io/ioutil from github.com/godbus/dbus/v5+
|
||||||
log from expvar+
|
log from expvar+
|
||||||
math from compress/flate+
|
math from compress/flate+
|
||||||
math/big from crypto/dsa+
|
math/big from crypto/dsa+
|
||||||
|
|
|
@ -73,6 +73,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
tailscale.com/derp from tailscale.com/derp/derphttp+
|
tailscale.com/derp from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/derp/derphttp from tailscale.com/net/netcheck+
|
tailscale.com/derp/derphttp from tailscale.com/net/netcheck+
|
||||||
tailscale.com/disco from tailscale.com/derp+
|
tailscale.com/disco from tailscale.com/derp+
|
||||||
|
tailscale.com/health from tailscale.com/control/controlclient+
|
||||||
tailscale.com/internal/deepprint from tailscale.com/ipn+
|
tailscale.com/internal/deepprint from tailscale.com/ipn+
|
||||||
tailscale.com/ipn from tailscale.com/ipn/ipnserver
|
tailscale.com/ipn from tailscale.com/ipn/ipnserver
|
||||||
tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled
|
tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled
|
||||||
|
@ -100,7 +101,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
tailscale.com/portlist from tailscale.com/ipn
|
tailscale.com/portlist from tailscale.com/ipn
|
||||||
tailscale.com/safesocket from tailscale.com/ipn/ipnserver
|
tailscale.com/safesocket from tailscale.com/ipn/ipnserver
|
||||||
tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+
|
tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+
|
||||||
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
|
tailscale.com/syncs from tailscale.com/net/interfaces+
|
||||||
tailscale.com/tailcfg from tailscale.com/control/controlclient+
|
tailscale.com/tailcfg from tailscale.com/control/controlclient+
|
||||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||||
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
||||||
|
@ -217,10 +218,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
hash/crc32 from compress/gzip+
|
hash/crc32 from compress/gzip+
|
||||||
hash/fnv from tailscale.com/wgengine/magicsock
|
hash/fnv from tailscale.com/wgengine/magicsock
|
||||||
hash/maphash from go4.org/mem
|
hash/maphash from go4.org/mem
|
||||||
html from html/template+
|
html from net/http/pprof+
|
||||||
html/template from net/http/pprof
|
|
||||||
io from bufio+
|
io from bufio+
|
||||||
io/ioutil from crypto/tls+
|
io/fs from crypto/rand+
|
||||||
|
io/ioutil from github.com/godbus/dbus/v5+
|
||||||
log from expvar+
|
log from expvar+
|
||||||
math from compress/flate+
|
math from compress/flate+
|
||||||
math/big from crypto/dsa+
|
math/big from crypto/dsa+
|
||||||
|
@ -255,8 +256,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||||
sync/atomic from context+
|
sync/atomic from context+
|
||||||
syscall from crypto/rand+
|
syscall from crypto/rand+
|
||||||
text/tabwriter from runtime/pprof
|
text/tabwriter from runtime/pprof
|
||||||
text/template from html/template
|
|
||||||
text/template/parse from html/template+
|
|
||||||
time from compress/gzip+
|
time from compress/gzip+
|
||||||
unicode from bytes+
|
unicode from bytes+
|
||||||
unicode/utf16 from encoding/asn1+
|
unicode/utf16 from encoding/asn1+
|
||||||
|
|
|
@ -20,22 +20,5 @@ CacheDirectory=tailscale
|
||||||
CacheDirectoryMode=0750
|
CacheDirectoryMode=0750
|
||||||
Type=notify
|
Type=notify
|
||||||
|
|
||||||
DeviceAllow=/dev/net/tun
|
|
||||||
DeviceAllow=/dev/null
|
|
||||||
DeviceAllow=/dev/random
|
|
||||||
DeviceAllow=/dev/urandom
|
|
||||||
DevicePolicy=strict
|
|
||||||
LockPersonality=true
|
|
||||||
MemoryDenyWriteExecute=true
|
|
||||||
PrivateTmp=true
|
|
||||||
ProtectClock=true
|
|
||||||
ProtectControlGroups=true
|
|
||||||
ProtectHome=true
|
|
||||||
ProtectKernelTunables=true
|
|
||||||
ProtectSystem=strict
|
|
||||||
ReadWritePaths=/etc/
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
SystemCallArchitectures=native
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
"tailscale.com/health"
|
||||||
"tailscale.com/logtail/backoff"
|
"tailscale.com/logtail/backoff"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/empty"
|
"tailscale.com/types/empty"
|
||||||
|
@ -114,6 +115,8 @@ type Client struct {
|
||||||
closed bool
|
closed bool
|
||||||
newMapCh chan struct{} // readable when we must restart a map request
|
newMapCh chan struct{} // readable when we must restart a map request
|
||||||
|
|
||||||
|
unregisterHealthWatch func()
|
||||||
|
|
||||||
mu sync.Mutex // mutex guards the following fields
|
mu sync.Mutex // mutex guards the following fields
|
||||||
statusFunc func(Status) // called to update Client status
|
statusFunc func(Status) // called to update Client status
|
||||||
|
|
||||||
|
@ -169,7 +172,14 @@ func NewNoStart(opts Options) (*Client, error) {
|
||||||
}
|
}
|
||||||
c.authCtx, c.authCancel = context.WithCancel(context.Background())
|
c.authCtx, c.authCancel = context.WithCancel(context.Background())
|
||||||
c.mapCtx, c.mapCancel = context.WithCancel(context.Background())
|
c.mapCtx, c.mapCancel = context.WithCancel(context.Background())
|
||||||
|
c.unregisterHealthWatch = health.RegisterWatcher(c.onHealthChange)
|
||||||
return c, nil
|
return c, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) onHealthChange(key string, err error) {
|
||||||
|
c.logf("controlclient: restarting map request for %q health change to new state: %v", key, err)
|
||||||
|
c.cancelMapSafely()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPaused controls whether HTTP activity should be paused.
|
// SetPaused controls whether HTTP activity should be paused.
|
||||||
|
@ -213,7 +223,7 @@ func (c *Client) sendNewMapRequest() {
|
||||||
// If we're not already streaming a netmap, or if we're already stuck
|
// If we're not already streaming a netmap, or if we're already stuck
|
||||||
// in a lite update, then tear down everything and start a new stream
|
// in a lite update, then tear down everything and start a new stream
|
||||||
// (which starts by sending a new map request)
|
// (which starts by sending a new map request)
|
||||||
if !c.inPollNetMap || c.inLiteMapUpdate {
|
if !c.inPollNetMap || c.inLiteMapUpdate || !c.loggedIn {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
c.cancelMapSafely()
|
c.cancelMapSafely()
|
||||||
return
|
return
|
||||||
|
@ -698,6 +708,7 @@ func (c *Client) Shutdown() {
|
||||||
|
|
||||||
c.logf("client.Shutdown: inSendStatus=%v", inSendStatus)
|
c.logf("client.Shutdown: inSendStatus=%v", inSendStatus)
|
||||||
if !closed {
|
if !closed {
|
||||||
|
c.unregisterHealthWatch()
|
||||||
close(c.quit)
|
close(c.quit)
|
||||||
c.cancelAuth()
|
c.cancelAuth()
|
||||||
<-c.authDone
|
<-c.authDone
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -34,6 +35,7 @@ import (
|
||||||
"golang.org/x/crypto/nacl/box"
|
"golang.org/x/crypto/nacl/box"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/health"
|
||||||
"tailscale.com/log/logheap"
|
"tailscale.com/log/logheap"
|
||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
"tailscale.com/net/netns"
|
"tailscale.com/net/netns"
|
||||||
|
@ -229,10 +231,25 @@ func NewHostinfo() *tailcfg.Hostinfo {
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
OS: version.OS(),
|
OS: version.OS(),
|
||||||
OSVersion: osv,
|
OSVersion: osv,
|
||||||
|
Package: packageType(),
|
||||||
GoArch: runtime.GOARCH,
|
GoArch: runtime.GOARCH,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func packageType() string {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
if _, err := os.Stat(`C:\ProgramData\chocolatey\lib\tailscale`); err == nil {
|
||||||
|
return "choco"
|
||||||
|
}
|
||||||
|
case "darwin":
|
||||||
|
// Using tailscaled or IPNExtension?
|
||||||
|
exe, _ := os.Executable()
|
||||||
|
return filepath.Base(exe)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// SetHostinfo clones the provided Hostinfo and remembers it for the
|
// SetHostinfo clones the provided Hostinfo and remembers it for the
|
||||||
// next update. It reports whether the Hostinfo has changed.
|
// next update. It reports whether the Hostinfo has changed.
|
||||||
func (c *Direct) SetHostinfo(hi *tailcfg.Hostinfo) bool {
|
func (c *Direct) SetHostinfo(hi *tailcfg.Hostinfo) bool {
|
||||||
|
@ -550,6 +567,9 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw
|
||||||
everEndpoints := c.everEndpoints
|
everEndpoints := c.everEndpoints
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
if persist.PrivateNodeKey.IsZero() {
|
||||||
|
return errors.New("privateNodeKey is zero")
|
||||||
|
}
|
||||||
if backendLogID == "" {
|
if backendLogID == "" {
|
||||||
return errors.New("hostinfo: BackendLogID missing")
|
return errors.New("hostinfo: BackendLogID missing")
|
||||||
}
|
}
|
||||||
|
@ -575,9 +595,16 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw
|
||||||
DebugFlags: c.debugFlags,
|
DebugFlags: c.debugFlags,
|
||||||
OmitPeers: cb == nil,
|
OmitPeers: cb == nil,
|
||||||
}
|
}
|
||||||
|
var extraDebugFlags []string
|
||||||
if hostinfo != nil && ipForwardingBroken(hostinfo.RoutableIPs) {
|
if hostinfo != nil && ipForwardingBroken(hostinfo.RoutableIPs) {
|
||||||
|
extraDebugFlags = append(extraDebugFlags, "warn-ip-forwarding-off")
|
||||||
|
}
|
||||||
|
if health.RouterHealth() != nil {
|
||||||
|
extraDebugFlags = append(extraDebugFlags, "warn-router-unhealthy")
|
||||||
|
}
|
||||||
|
if len(extraDebugFlags) > 0 {
|
||||||
old := request.DebugFlags
|
old := request.DebugFlags
|
||||||
request.DebugFlags = append(old[:len(old):len(old)], "warn-ip-forwarding-off")
|
request.DebugFlags = append(old[:len(old):len(old)], extraDebugFlags...)
|
||||||
}
|
}
|
||||||
if c.newDecompressor != nil {
|
if c.newDecompressor != nil {
|
||||||
request.Compress = "zstd"
|
request.Compress = "zstd"
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
// 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 health is a registry for other packages to report & check
|
||||||
|
// overall health status of the node.
|
||||||
|
package health
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
m = map[string]error{} // error key => err (or nil for no error)
|
||||||
|
watchers = map[*watchHandle]func(string, error){} // opt func to run if error state changes
|
||||||
|
)
|
||||||
|
|
||||||
|
type watchHandle byte
|
||||||
|
|
||||||
|
// RegisterWatcher adds a function that will be called if an
|
||||||
|
// error changes state either to unhealthy or from unhealthy. It is
|
||||||
|
// not called on transition from unknown to healthy. It must be non-nil
|
||||||
|
// and is run in its own goroutine. The returned func unregisters it.
|
||||||
|
func RegisterWatcher(cb func(errKey string, err error)) (unregister func()) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
handle := new(watchHandle)
|
||||||
|
watchers[handle] = cb
|
||||||
|
return func() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
delete(watchers, handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRouter sets the state of the wgengine/router.Router.
|
||||||
|
func SetRouterHealth(err error) { set("router", err) }
|
||||||
|
|
||||||
|
// RouterHealth returns the wgengine/router.Router error state.
|
||||||
|
func RouterHealth() error { return get("router") }
|
||||||
|
|
||||||
|
func get(key string) error {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
return m[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(key string, err error) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
old, ok := m[key]
|
||||||
|
if !ok && err == nil {
|
||||||
|
// Initial happy path.
|
||||||
|
m[key] = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok && (old == nil) == (err == nil) {
|
||||||
|
// No change in overall error status (nil-vs-not), so
|
||||||
|
// don't run callbacks, but exact error might've
|
||||||
|
// changed, so note it.
|
||||||
|
if err != nil {
|
||||||
|
m[key] = err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m[key] = err
|
||||||
|
for _, cb := range watchers {
|
||||||
|
go cb(key, err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -146,6 +146,10 @@ func (bs *BackendServer) GotFakeCommand(ctx context.Context, cmd *Command) error
|
||||||
return bs.GotCommand(ctx, cmd)
|
return bs.GotCommand(ctx, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrMsgPermissionDenied is the Notify.ErrMessage value used an
|
||||||
|
// operation was done from a user/context that didn't have permission.
|
||||||
|
const ErrMsgPermissionDenied = "permission denied"
|
||||||
|
|
||||||
func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error {
|
func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error {
|
||||||
if cmd.Version != version.Long && !cmd.AllowVersionSkew {
|
if cmd.Version != version.Long && !cmd.AllowVersionSkew {
|
||||||
vs := fmt.Sprintf("GotCommand: Version mismatch! frontend=%#v backend=%#v",
|
vs := fmt.Sprintf("GotCommand: Version mismatch! frontend=%#v backend=%#v",
|
||||||
|
@ -178,7 +182,7 @@ func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsReadonlyContext(ctx) {
|
if IsReadonlyContext(ctx) {
|
||||||
msg := "permission denied"
|
msg := ErrMsgPermissionDenied
|
||||||
bs.send(Notify{ErrMessage: &msg})
|
bs.send(Notify{ErrMessage: &msg})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ package interfaces
|
||||||
|
|
||||||
// privateGatewayIPFromRoute returns the private gateway ip address from rtm, if it exists.
|
// privateGatewayIPFromRoute returns the private gateway ip address from rtm, if it exists.
|
||||||
// Otherwise, it returns 0.
|
// Otherwise, it returns 0.
|
||||||
int privateGatewayIPFromRoute(struct rt_msghdr2 *rtm)
|
uint32_t privateGatewayIPFromRoute(struct rt_msghdr2 *rtm)
|
||||||
{
|
{
|
||||||
// sockaddrs are after the message header
|
// sockaddrs are after the message header
|
||||||
struct sockaddr* dst_sa = (struct sockaddr *)(rtm + 1);
|
struct sockaddr* dst_sa = (struct sockaddr *)(rtm + 1);
|
||||||
|
@ -38,7 +38,7 @@ int privateGatewayIPFromRoute(struct rt_msghdr2 *rtm)
|
||||||
return 0; // gateway not IPv4
|
return 0; // gateway not IPv4
|
||||||
|
|
||||||
struct sockaddr_in* gateway_si= (struct sockaddr_in *)gateway_sa;
|
struct sockaddr_in* gateway_si= (struct sockaddr_in *)gateway_sa;
|
||||||
int ip;
|
uint32_t ip;
|
||||||
ip = gateway_si->sin_addr.s_addr;
|
ip = gateway_si->sin_addr.s_addr;
|
||||||
|
|
||||||
unsigned char a, b;
|
unsigned char a, b;
|
||||||
|
@ -62,7 +62,7 @@ int privateGatewayIPFromRoute(struct rt_msghdr2 *rtm)
|
||||||
// If no private gateway IP address was found, it returns 0.
|
// If no private gateway IP address was found, it returns 0.
|
||||||
// On an error, it returns an error code in (0, 255].
|
// On an error, it returns an error code in (0, 255].
|
||||||
// Any private gateway IP address is > 255.
|
// Any private gateway IP address is > 255.
|
||||||
int privateGatewayIP()
|
uint32_t privateGatewayIP()
|
||||||
{
|
{
|
||||||
size_t needed;
|
size_t needed;
|
||||||
int mib[6];
|
int mib[6];
|
||||||
|
@ -90,7 +90,7 @@ int privateGatewayIP()
|
||||||
struct rt_msghdr2 *rtm;
|
struct rt_msghdr2 *rtm;
|
||||||
for (next = buf; next < lim; next += rtm->rtm_msglen) {
|
for (next = buf; next < lim; next += rtm->rtm_msglen) {
|
||||||
rtm = (struct rt_msghdr2 *)next;
|
rtm = (struct rt_msghdr2 *)next;
|
||||||
int ip;
|
uint32_t ip;
|
||||||
ip = privateGatewayIPFromRoute(rtm);
|
ip = privateGatewayIPFromRoute(rtm);
|
||||||
if (ip) {
|
if (ip) {
|
||||||
free(buf);
|
free(buf);
|
||||||
|
|
|
@ -8,9 +8,7 @@ package netcheck
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -701,16 +699,14 @@ func (rs *reportState) probePortMapServices() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer uc.Close()
|
defer uc.Close()
|
||||||
tempPort := uc.LocalAddr().(*net.UDPAddr).Port
|
|
||||||
uc.SetReadDeadline(time.Now().Add(portMapServiceProbeTimeout))
|
uc.SetReadDeadline(time.Now().Add(portMapServiceProbeTimeout))
|
||||||
|
|
||||||
// Send request packets for all three protocols.
|
// Send request packets for all three protocols.
|
||||||
uc.WriteTo(uPnPPacket, port1900)
|
uc.WriteTo(uPnPPacket, port1900)
|
||||||
uc.WriteTo(pmpPacket, port5351)
|
uc.WriteTo(pmpPacket, port5351)
|
||||||
uc.WriteTo(pcpPacket(myIP, tempPort, false), port5351)
|
uc.WriteTo(pcpAnnounceRequest(myIP), port5351)
|
||||||
|
|
||||||
res := make([]byte, 1500)
|
res := make([]byte, 1500)
|
||||||
sentPCPDelete := false
|
|
||||||
for {
|
for {
|
||||||
n, addr, err := uc.ReadFrom(res)
|
n, addr, err := uc.ReadFrom(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -725,17 +721,8 @@ func (rs *reportState) probePortMapServices() {
|
||||||
if n == 12 && res[0] == 0x00 { // right length and version 0
|
if n == 12 && res[0] == 0x00 { // right length and version 0
|
||||||
rs.setOptBool(&rs.report.PMP, true)
|
rs.setOptBool(&rs.report.PMP, true)
|
||||||
}
|
}
|
||||||
if n == 60 && res[0] == 0x02 { // right length and version 2
|
if n == 24 && res[0] == 0x02 { // right length and version 2
|
||||||
rs.setOptBool(&rs.report.PCP, true)
|
rs.setOptBool(&rs.report.PCP, true)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -751,32 +738,19 @@ var uPnPPacket = []byte("M-SEARCH * HTTP/1.1\r\n" +
|
||||||
|
|
||||||
var v4unspec, _ = netaddr.ParseIP("0.0.0.0")
|
var v4unspec, _ = netaddr.ParseIP("0.0.0.0")
|
||||||
|
|
||||||
// pcpPacket generates a PCP packet with a MAP opcode.
|
const (
|
||||||
func pcpPacket(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte {
|
pcpVersion = 2
|
||||||
const udpProtoNumber = 17
|
pcpOpAnnounce = 0
|
||||||
lifetimeSeconds := uint32(1)
|
)
|
||||||
if delete {
|
|
||||||
lifetimeSeconds = 0
|
|
||||||
}
|
|
||||||
const opMap = 1
|
|
||||||
|
|
||||||
// 24 byte header + 36 byte map opcode
|
// pcpAnnounceRequest generates a PCP packet with an ANNOUNCE opcode.
|
||||||
pkt := make([]byte, (32+32+128)/8+(96+8+24+16+16+128)/8)
|
func pcpAnnounceRequest(myIP netaddr.IP) []byte {
|
||||||
|
// See https://tools.ietf.org/html/rfc6887#section-7.1
|
||||||
// The header (https://tools.ietf.org/html/rfc6887#section-7.1)
|
pkt := make([]byte, 24)
|
||||||
pkt[0] = 2 // version
|
pkt[0] = pcpVersion // version
|
||||||
pkt[1] = opMap
|
pkt[1] = pcpOpAnnounce
|
||||||
binary.BigEndian.PutUint32(pkt[4:8], lifetimeSeconds)
|
|
||||||
myIP16 := myIP.As16()
|
myIP16 := myIP.As16()
|
||||||
copy(pkt[8:], myIP16[:])
|
copy(pkt[8:], myIP16[:])
|
||||||
|
|
||||||
// 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(mapOp[20:], v4unspec16[:])
|
|
||||||
return pkt
|
return pkt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,14 @@ import (
|
||||||
// Tailscale node has rejected the connection from another. Unlike a
|
// Tailscale node has rejected the connection from another. Unlike a
|
||||||
// TCP RST, this includes a reason.
|
// TCP RST, this includes a reason.
|
||||||
//
|
//
|
||||||
// On the wire, after the IP header, it's currently 7 bytes:
|
// On the wire, after the IP header, it's currently 7 or 8 bytes:
|
||||||
// * '!'
|
// * '!'
|
||||||
// * IPProto byte (IANA protocol number: TCP or UDP)
|
// * IPProto byte (IANA protocol number: TCP or UDP)
|
||||||
// * 'A' or 'S' (RejectedDueToACLs, RejectedDueToShieldsUp)
|
// * 'A' or 'S' (RejectedDueToACLs, RejectedDueToShieldsUp)
|
||||||
// * srcPort big endian uint16
|
// * srcPort big endian uint16
|
||||||
// * dstPort big endian uint16
|
// * dstPort big endian uint16
|
||||||
|
// * [optional] byte of flag bits:
|
||||||
|
// lowest bit (0x1): MaybeBroken
|
||||||
//
|
//
|
||||||
// In the future it might also accept 16 byte IP flow src/dst IPs
|
// In the future it might also accept 16 byte IP flow src/dst IPs
|
||||||
// after the header, if they're different than the IP-level ones.
|
// after the header, if they're different than the IP-level ones.
|
||||||
|
@ -39,8 +41,21 @@ type TailscaleRejectedHeader struct {
|
||||||
Dst netaddr.IPPort // rejected flow's dst
|
Dst netaddr.IPPort // rejected flow's dst
|
||||||
Proto IPProto // proto that was rejected (TCP or UDP)
|
Proto IPProto // proto that was rejected (TCP or UDP)
|
||||||
Reason TailscaleRejectReason // why the connection was rejected
|
Reason TailscaleRejectReason // why the connection was rejected
|
||||||
|
|
||||||
|
// MaybeBroken is whether the rejection is non-terminal (the
|
||||||
|
// client should not fail immediately). This is sent by a
|
||||||
|
// target when it's not sure whether it's totally broken, but
|
||||||
|
// it might be. For example, the target tailscaled might think
|
||||||
|
// its host firewall or IP forwarding aren't configured
|
||||||
|
// properly, but tailscaled might be wrong (not having enough
|
||||||
|
// visibility into what the OS is doing). When true, the
|
||||||
|
// message is simply an FYI as a potential reason to use for
|
||||||
|
// later when the pendopen connection tracking timer expires.
|
||||||
|
MaybeBroken bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rejectFlagBitMaybeBroken = 0x1
|
||||||
|
|
||||||
func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple {
|
func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple {
|
||||||
return flowtrack.Tuple{Src: rh.Src, Dst: rh.Dst}
|
return flowtrack.Tuple{Src: rh.Src, Dst: rh.Dst}
|
||||||
}
|
}
|
||||||
|
@ -52,14 +67,32 @@ func (rh TailscaleRejectedHeader) String() string {
|
||||||
type TSMPType uint8
|
type TSMPType uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// TSMPTypeRejectedConn is the type byte for a TailscaleRejectedHeader.
|
||||||
TSMPTypeRejectedConn TSMPType = '!'
|
TSMPTypeRejectedConn TSMPType = '!'
|
||||||
)
|
)
|
||||||
|
|
||||||
type TailscaleRejectReason byte
|
type TailscaleRejectReason byte
|
||||||
|
|
||||||
|
// IsZero reports whether r is the zero value, representing no rejection.
|
||||||
|
func (r TailscaleRejectReason) IsZero() bool { return r == TailscaleRejectReasonNone }
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RejectedDueToACLs TailscaleRejectReason = 'A'
|
// TailscaleRejectReasonNone is the TailscaleRejectReason zero value.
|
||||||
|
TailscaleRejectReasonNone TailscaleRejectReason = 0
|
||||||
|
|
||||||
|
// RejectedDueToACLs means that the host rejected the connection due to ACLs.
|
||||||
|
RejectedDueToACLs TailscaleRejectReason = 'A'
|
||||||
|
|
||||||
|
// RejectedDueToShieldsUp means that the host rejected the connection due to shields being up.
|
||||||
RejectedDueToShieldsUp TailscaleRejectReason = 'S'
|
RejectedDueToShieldsUp TailscaleRejectReason = 'S'
|
||||||
|
|
||||||
|
// RejectedDueToIPForwarding means that the relay node's IP
|
||||||
|
// forwarding is disabled.
|
||||||
|
RejectedDueToIPForwarding TailscaleRejectReason = 'F'
|
||||||
|
|
||||||
|
// RejectedDueToHostFirewall means that the target host's
|
||||||
|
// firewall is blocking the traffic.
|
||||||
|
RejectedDueToHostFirewall TailscaleRejectReason = 'W'
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r TailscaleRejectReason) String() string {
|
func (r TailscaleRejectReason) String() string {
|
||||||
|
@ -68,22 +101,32 @@ func (r TailscaleRejectReason) String() string {
|
||||||
return "acl"
|
return "acl"
|
||||||
case RejectedDueToShieldsUp:
|
case RejectedDueToShieldsUp:
|
||||||
return "shields"
|
return "shields"
|
||||||
|
case RejectedDueToIPForwarding:
|
||||||
|
return "host-ip-forwarding-unavailable"
|
||||||
|
case RejectedDueToHostFirewall:
|
||||||
|
return "host-firewall"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("0x%02x", byte(r))
|
return fmt.Sprintf("0x%02x", byte(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h TailscaleRejectedHeader) hasFlags() bool {
|
||||||
|
return h.MaybeBroken // the only one currently
|
||||||
|
}
|
||||||
|
|
||||||
func (h TailscaleRejectedHeader) Len() int {
|
func (h TailscaleRejectedHeader) Len() int {
|
||||||
var ipHeaderLen int
|
v := 1 + // TSMPType byte
|
||||||
if h.IPSrc.Is4() {
|
|
||||||
ipHeaderLen = ip4HeaderLength
|
|
||||||
} else if h.IPSrc.Is6() {
|
|
||||||
ipHeaderLen = ip6HeaderLength
|
|
||||||
}
|
|
||||||
return ipHeaderLen +
|
|
||||||
1 + // TSMPType byte
|
|
||||||
1 + // IPProto byte
|
1 + // IPProto byte
|
||||||
1 + // TailscaleRejectReason byte
|
1 + // TailscaleRejectReason byte
|
||||||
2*2 // 2 uint16 ports
|
2*2 // 2 uint16 ports
|
||||||
|
if h.IPSrc.Is4() {
|
||||||
|
v += ip4HeaderLength
|
||||||
|
} else if h.IPSrc.Is6() {
|
||||||
|
v += ip6HeaderLength
|
||||||
|
}
|
||||||
|
if h.hasFlags() {
|
||||||
|
v++
|
||||||
|
}
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
|
func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
|
||||||
|
@ -117,6 +160,14 @@ func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
|
||||||
buf[2] = byte(h.Reason)
|
buf[2] = byte(h.Reason)
|
||||||
binary.BigEndian.PutUint16(buf[3:5], h.Src.Port)
|
binary.BigEndian.PutUint16(buf[3:5], h.Src.Port)
|
||||||
binary.BigEndian.PutUint16(buf[5:7], h.Dst.Port)
|
binary.BigEndian.PutUint16(buf[5:7], h.Dst.Port)
|
||||||
|
|
||||||
|
if h.hasFlags() {
|
||||||
|
var flags byte
|
||||||
|
if h.MaybeBroken {
|
||||||
|
flags |= rejectFlagBitMaybeBroken
|
||||||
|
}
|
||||||
|
buf[7] = flags
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,12 +180,17 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo
|
||||||
if len(p) < 7 || p[0] != byte(TSMPTypeRejectedConn) {
|
if len(p) < 7 || p[0] != byte(TSMPTypeRejectedConn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return TailscaleRejectedHeader{
|
h = TailscaleRejectedHeader{
|
||||||
Proto: IPProto(p[1]),
|
Proto: IPProto(p[1]),
|
||||||
Reason: TailscaleRejectReason(p[2]),
|
Reason: TailscaleRejectReason(p[2]),
|
||||||
IPSrc: pp.Src.IP,
|
IPSrc: pp.Src.IP,
|
||||||
IPDst: pp.Dst.IP,
|
IPDst: pp.Dst.IP,
|
||||||
Src: netaddr.IPPort{IP: pp.Dst.IP, Port: binary.BigEndian.Uint16(p[3:5])},
|
Src: netaddr.IPPort{IP: pp.Dst.IP, Port: binary.BigEndian.Uint16(p[3:5])},
|
||||||
Dst: netaddr.IPPort{IP: pp.Src.IP, Port: binary.BigEndian.Uint16(p[5:7])},
|
Dst: netaddr.IPPort{IP: pp.Src.IP, Port: binary.BigEndian.Uint16(p[5:7])},
|
||||||
}, true
|
}
|
||||||
|
if len(p) > 7 {
|
||||||
|
flags := p[7]
|
||||||
|
h.MaybeBroken = (flags & rejectFlagBitMaybeBroken) != 0
|
||||||
|
}
|
||||||
|
return h, true
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,18 @@ func TestTailscaleRejectedHeader(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantStr: "TSMP-reject-flow{UDP [1::1]:567 > [2::2]:443}: shields",
|
wantStr: "TSMP-reject-flow{UDP [1::1]:567 > [2::2]:443}: shields",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
h: TailscaleRejectedHeader{
|
||||||
|
IPSrc: netaddr.MustParseIP("2::2"),
|
||||||
|
IPDst: netaddr.MustParseIP("1::1"),
|
||||||
|
Src: netaddr.MustParseIPPort("[1::1]:567"),
|
||||||
|
Dst: netaddr.MustParseIPPort("[2::2]:443"),
|
||||||
|
Proto: UDP,
|
||||||
|
Reason: RejectedDueToIPForwarding,
|
||||||
|
MaybeBroken: true,
|
||||||
|
},
|
||||||
|
wantStr: "TSMP-reject-flow{UDP [1::1]:567 > [2::2]:443}: host-ip-forwarding-unavailable",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
gotStr := tt.h.String()
|
gotStr := tt.h.String()
|
||||||
|
|
|
@ -406,6 +406,7 @@ type Hostinfo struct {
|
||||||
BackendLogID string `json:",omitempty"` // logtail ID of backend instance
|
BackendLogID string `json:",omitempty"` // logtail ID of backend instance
|
||||||
OS string // operating system the client runs on (a version.OS value)
|
OS string // operating system the client runs on (a version.OS value)
|
||||||
OSVersion string `json:",omitempty"` // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041")
|
OSVersion string `json:",omitempty"` // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041")
|
||||||
|
Package string `json:",omitempty"` // Tailscale package to disambiguate ("choco", "appstore", etc; "" for unknown)
|
||||||
DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone 11 Pro")
|
DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone 11 Pro")
|
||||||
Hostname string // name of the host the client runs on
|
Hostname string // name of the host the client runs on
|
||||||
ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections
|
ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections
|
||||||
|
@ -634,6 +635,10 @@ type MapRequest struct {
|
||||||
// Current DebugFlags values are:
|
// Current DebugFlags values are:
|
||||||
// * "warn-ip-forwarding-off": client is trying to be a subnet
|
// * "warn-ip-forwarding-off": client is trying to be a subnet
|
||||||
// router but their IP forwarding is broken.
|
// router but their IP forwarding is broken.
|
||||||
|
// * "warn-router-unhealthy": client's Router implementation is
|
||||||
|
// having problems.
|
||||||
|
// * "v6-overlay": IPv6 development flag to have control send
|
||||||
|
// v6 node addrs
|
||||||
// * "minimize-netmap": have control minimize the netmap, removing
|
// * "minimize-netmap": have control minimize the netmap, removing
|
||||||
// peers that are unreachable per ACLS.
|
// peers that are unreachable per ACLS.
|
||||||
DebugFlags []string `json:",omitempty"`
|
DebugFlags []string `json:",omitempty"`
|
||||||
|
|
|
@ -107,6 +107,7 @@ var _HostinfoNeedsRegeneration = Hostinfo(struct {
|
||||||
BackendLogID string
|
BackendLogID string
|
||||||
OS string
|
OS string
|
||||||
OSVersion string
|
OSVersion string
|
||||||
|
Package string
|
||||||
DeviceModel string
|
DeviceModel string
|
||||||
Hostname string
|
Hostname string
|
||||||
ShieldsUp bool
|
ShieldsUp bool
|
||||||
|
|
|
@ -25,7 +25,7 @@ func fieldsOf(t reflect.Type) (fields []string) {
|
||||||
func TestHostinfoEqual(t *testing.T) {
|
func TestHostinfoEqual(t *testing.T) {
|
||||||
hiHandles := []string{
|
hiHandles := []string{
|
||||||
"IPNVersion", "FrontendLogID", "BackendLogID",
|
"IPNVersion", "FrontendLogID", "BackendLogID",
|
||||||
"OS", "OSVersion", "DeviceModel", "Hostname",
|
"OS", "OSVersion", "Package", "DeviceModel", "Hostname",
|
||||||
"ShieldsUp", "ShareeNode",
|
"ShieldsUp", "ShareeNode",
|
||||||
"GoArch",
|
"GoArch",
|
||||||
"RoutableIPs", "RequestTags",
|
"RoutableIPs", "RequestTags",
|
||||||
|
|
|
@ -64,9 +64,9 @@ type limitData struct {
|
||||||
|
|
||||||
var disableRateLimit = os.Getenv("TS_DEBUG_LOG_RATE") == "all"
|
var disableRateLimit = os.Getenv("TS_DEBUG_LOG_RATE") == "all"
|
||||||
|
|
||||||
// rateFreePrefix are format string prefixes that are exempt from rate limiting.
|
// rateFree are format string substrings that are exempt from rate limiting.
|
||||||
// Things should not be added to this unless they're already limited otherwise.
|
// Things should not be added to this unless they're already limited otherwise.
|
||||||
var rateFreePrefix = []string{
|
var rateFree = []string{
|
||||||
"magicsock: disco: ",
|
"magicsock: disco: ",
|
||||||
"magicsock: CreateEndpoint:",
|
"magicsock: CreateEndpoint:",
|
||||||
}
|
}
|
||||||
|
@ -93,8 +93,8 @@ func RateLimitedFn(logf Logf, f time.Duration, burst int, maxCache int) Logf {
|
||||||
)
|
)
|
||||||
|
|
||||||
judge := func(format string) verdict {
|
judge := func(format string) verdict {
|
||||||
for _, pfx := range rateFreePrefix {
|
for _, sub := range rateFree {
|
||||||
if strings.HasPrefix(format, pfx) {
|
if strings.Contains(format, sub) {
|
||||||
return allow
|
return allow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"expvar"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"math"
|
"math"
|
||||||
|
@ -153,13 +154,10 @@ type Conn struct {
|
||||||
// derpRecvCh is used by ReceiveIPv4 to read DERP messages.
|
// derpRecvCh is used by ReceiveIPv4 to read DERP messages.
|
||||||
derpRecvCh chan derpReadResult
|
derpRecvCh chan derpReadResult
|
||||||
|
|
||||||
// derpRecvCountAtomic is atomically incremented by runDerpReader whenever
|
// derpRecvCountAtomic is how many derpRecvCh sends are pending.
|
||||||
// a DERP message arrives. It's incremented before runDerpReader is interrupted.
|
// It's incremented by runDerpReader whenever a DERP message
|
||||||
|
// arrives and decremented when they're read.
|
||||||
derpRecvCountAtomic int64
|
derpRecvCountAtomic int64
|
||||||
// derpRecvCountLast is used by ReceiveIPv4 to compare against
|
|
||||||
// its last read value of derpRecvCountAtomic to determine
|
|
||||||
// whether a DERP channel read should be done.
|
|
||||||
derpRecvCountLast int64 // owned by ReceiveIPv4
|
|
||||||
|
|
||||||
// ippEndpoint4 and ippEndpoint6 are owned by ReceiveIPv4 and
|
// ippEndpoint4 and ippEndpoint6 are owned by ReceiveIPv4 and
|
||||||
// ReceiveIPv6, respectively, to cache an IPPort->endpoint for
|
// ReceiveIPv6, respectively, to cache an IPPort->endpoint for
|
||||||
|
@ -304,6 +302,9 @@ type Conn struct {
|
||||||
// with IPv4 or IPv6). It's used to suppress log spam and prevent
|
// with IPv4 or IPv6). It's used to suppress log spam and prevent
|
||||||
// new connection that'll fail.
|
// new connection that'll fail.
|
||||||
networkUp syncs.AtomicBool
|
networkUp syncs.AtomicBool
|
||||||
|
|
||||||
|
// havePrivateKey is whether privateKey is non-zero.
|
||||||
|
havePrivateKey syncs.AtomicBool
|
||||||
}
|
}
|
||||||
|
|
||||||
// derpRoute is a route entry for a public key, saying that a certain
|
// derpRoute is a route entry for a public key, saying that a certain
|
||||||
|
@ -960,6 +961,13 @@ func (c *Conn) setNearestDERP(derpNum int) (wantDERP bool) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// startDerpHomeConnectLocked starts connecting to our DERP home, if any.
|
||||||
|
//
|
||||||
|
// c.mu must be held.
|
||||||
|
func (c *Conn) startDerpHomeConnectLocked() {
|
||||||
|
c.goDerpConnect(c.myDerp)
|
||||||
|
}
|
||||||
|
|
||||||
// goDerpConnect starts a goroutine to start connecting to the given
|
// goDerpConnect starts a goroutine to start connecting to the given
|
||||||
// DERP node.
|
// DERP node.
|
||||||
//
|
//
|
||||||
|
@ -1353,6 +1361,8 @@ type derpReadResult struct {
|
||||||
// copyBuf is called to copy the data to dst. It returns how
|
// copyBuf is called to copy the data to dst. It returns how
|
||||||
// much data was copied, which will be n if dst is large
|
// much data was copied, which will be n if dst is large
|
||||||
// enough. copyBuf can only be called once.
|
// enough. copyBuf can only be called once.
|
||||||
|
// If copyBuf is nil, that's a signal from the sender to ignore
|
||||||
|
// this message.
|
||||||
copyBuf func(dst []byte) int
|
copyBuf func(dst []byte) int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1440,28 +1450,62 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
// Before we wake up ReceiveIPv4 with SetReadDeadline,
|
|
||||||
// note that a DERP packet has arrived. ReceiveIPv4
|
|
||||||
// will read this field to note that its UDP read
|
|
||||||
// error is due to us.
|
|
||||||
atomic.AddInt64(&c.derpRecvCountAtomic, 1)
|
|
||||||
// Cancel the pconn read goroutine.
|
|
||||||
c.pconn4.SetReadDeadline(aLongTimeAgo)
|
|
||||||
|
|
||||||
|
if !c.sendDerpReadResult(ctx, res) {
|
||||||
|
return
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case c.derpRecvCh <- res:
|
case <-didCopy:
|
||||||
select {
|
continue
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-didCopy:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
testCounterZeroDerpReadResultSend expvar.Int
|
||||||
|
testCounterZeroDerpReadResultRecv expvar.Int
|
||||||
|
)
|
||||||
|
|
||||||
|
// sendDerpReadResult sends res to c.derpRecvCh and reports whether it
|
||||||
|
// was sent. (It reports false if ctx was done first.)
|
||||||
|
//
|
||||||
|
// This includes doing the whole wake-up dance to interrupt
|
||||||
|
// ReceiveIPv4's blocking UDP read.
|
||||||
|
func (c *Conn) sendDerpReadResult(ctx context.Context, res derpReadResult) (sent bool) {
|
||||||
|
// Before we wake up ReceiveIPv4 with SetReadDeadline,
|
||||||
|
// note that a DERP packet has arrived. ReceiveIPv4
|
||||||
|
// will read this field to note that its UDP read
|
||||||
|
// error is due to us.
|
||||||
|
atomic.AddInt64(&c.derpRecvCountAtomic, 1)
|
||||||
|
// Cancel the pconn read goroutine.
|
||||||
|
c.pconn4.SetReadDeadline(aLongTimeAgo)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
select {
|
||||||
|
case <-c.donec:
|
||||||
|
// The whole Conn shut down. The reader of
|
||||||
|
// c.derpRecvCh also selects on c.donec, so it's
|
||||||
|
// safe to abort now.
|
||||||
|
case c.derpRecvCh <- (derpReadResult{}):
|
||||||
|
// Just this DERP reader is closing (perhaps
|
||||||
|
// the user is logging out, or the DERP
|
||||||
|
// connection is too idle for sends). Since we
|
||||||
|
// already incremented c.derpRecvCountAtomic,
|
||||||
|
// we need to send on the channel (unless the
|
||||||
|
// conn is going down).
|
||||||
|
// The receiver treats a derpReadResult zero value
|
||||||
|
// message as a skip.
|
||||||
|
testCounterZeroDerpReadResultSend.Add(1)
|
||||||
|
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case c.derpRecvCh <- res:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type derpWriteRequest struct {
|
type derpWriteRequest struct {
|
||||||
addr netaddr.IPPort
|
addr netaddr.IPPort
|
||||||
pubKey key.Public
|
pubKey key.Public
|
||||||
|
@ -1551,20 +1595,20 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) derpPacketArrived() bool {
|
func (c *Conn) derpPacketArrived() bool {
|
||||||
rc := atomic.LoadInt64(&c.derpRecvCountAtomic)
|
return atomic.LoadInt64(&c.derpRecvCountAtomic) > 0
|
||||||
if rc != c.derpRecvCountLast {
|
|
||||||
c.derpRecvCountLast = rc
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReceiveIPv4 is called by wireguard-go to receive an IPv4 packet.
|
// ReceiveIPv4 is called by wireguard-go to receive an IPv4 packet.
|
||||||
// In Tailscale's case, that packet might also arrive via DERP. A DERP packet arrival
|
// In Tailscale's case, that packet might also arrive via DERP. A DERP packet arrival
|
||||||
// aborts the pconn4 read deadline to make it fail.
|
// aborts the pconn4 read deadline to make it fail.
|
||||||
func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) {
|
func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) {
|
||||||
|
var pAddr net.Addr
|
||||||
for {
|
for {
|
||||||
n, pAddr, err := c.pconn4.ReadFrom(b)
|
// Drain DERP queues before reading new UDP packets.
|
||||||
|
if c.derpPacketArrived() {
|
||||||
|
goto ReadDERP
|
||||||
|
}
|
||||||
|
n, pAddr, err = c.pconn4.ReadFrom(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If the pconn4 read failed, the likely reason is a DERP reader received
|
// If the pconn4 read failed, the likely reason is a DERP reader received
|
||||||
// a packet and interrupted us.
|
// a packet and interrupted us.
|
||||||
|
@ -1572,22 +1616,28 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) {
|
||||||
// and for there to have also had a DERP packet arrive, but that's fine:
|
// and for there to have also had a DERP packet arrive, but that's fine:
|
||||||
// we'll get the same error from ReadFrom later.
|
// we'll get the same error from ReadFrom later.
|
||||||
if c.derpPacketArrived() {
|
if c.derpPacketArrived() {
|
||||||
c.pconn4.SetReadDeadline(time.Time{}) // restore
|
goto ReadDERP
|
||||||
n, ep, err = c.receiveIPv4DERP(b)
|
|
||||||
if err == errLoopAgain {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return n, ep, err
|
|
||||||
}
|
}
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint4); ok {
|
if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint4); ok {
|
||||||
return n, ep, nil
|
return n, ep, nil
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
ReadDERP:
|
||||||
|
n, ep, err = c.receiveIPv4DERP(b)
|
||||||
|
if err == errLoopAgain {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return n, ep, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// receiveIP is the shared bits of ReceiveIPv4 and ReceiveIPv6.
|
// receiveIP is the shared bits of ReceiveIPv4 and ReceiveIPv6.
|
||||||
|
//
|
||||||
|
// ok is whether this read should be reported up to wireguard-go (our
|
||||||
|
// caller).
|
||||||
func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep conn.Endpoint, ok bool) {
|
func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep conn.Endpoint, ok bool) {
|
||||||
ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone)
|
ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -1600,6 +1650,13 @@ func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep
|
||||||
if c.handleDiscoMessage(b, ipp) {
|
if c.handleDiscoMessage(b, ipp) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
if !c.havePrivateKey.Get() {
|
||||||
|
// If we have no private key, we're logged out or
|
||||||
|
// stopped. Don't try to pass these wireguard packets
|
||||||
|
// up to wireguard-go; it'll just complain (Issue
|
||||||
|
// 1167).
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
if cache.ipp == ipp && cache.de != nil && cache.gen == cache.de.numStopAndReset() {
|
if cache.ipp == ipp && cache.de != nil && cache.gen == cache.de.numStopAndReset() {
|
||||||
ep = cache.de
|
ep = cache.de
|
||||||
} else {
|
} else {
|
||||||
|
@ -1641,6 +1698,13 @@ func (c *Conn) receiveIPv4DERP(b []byte) (n int, ep conn.Endpoint, err error) {
|
||||||
case dm = <-c.derpRecvCh:
|
case dm = <-c.derpRecvCh:
|
||||||
// Below.
|
// Below.
|
||||||
}
|
}
|
||||||
|
if atomic.AddInt64(&c.derpRecvCountAtomic, -1) == 0 {
|
||||||
|
c.pconn4.SetReadDeadline(time.Time{})
|
||||||
|
}
|
||||||
|
if dm.copyBuf == nil {
|
||||||
|
testCounterZeroDerpReadResultRecv.Add(1)
|
||||||
|
return 0, nil, errLoopAgain
|
||||||
|
}
|
||||||
|
|
||||||
var regionID int
|
var regionID int
|
||||||
n, regionID = dm.n, dm.regionID
|
n, regionID = dm.n, dm.regionID
|
||||||
|
@ -1750,8 +1814,8 @@ func (c *Conn) sendDiscoMessage(dst netaddr.IPPort, dstKey tailcfg.NodeKey, dstD
|
||||||
return sent, err
|
return sent, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleDiscoMessage reports whether msg was a Tailscale inter-node discovery message
|
// handleDiscoMessage handles a discovery message and reports whether
|
||||||
// that was handled.
|
// msg was a Tailscale inter-node discovery message.
|
||||||
//
|
//
|
||||||
// A discovery message has the form:
|
// A discovery message has the form:
|
||||||
//
|
//
|
||||||
|
@ -1762,11 +1826,18 @@ func (c *Conn) sendDiscoMessage(dst netaddr.IPPort, dstKey tailcfg.NodeKey, dstD
|
||||||
//
|
//
|
||||||
// For messages received over DERP, the addr will be derpMagicIP (with
|
// For messages received over DERP, the addr will be derpMagicIP (with
|
||||||
// port being the region)
|
// port being the region)
|
||||||
func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) (isDiscoMsg bool) {
|
||||||
const headerLen = len(disco.Magic) + len(tailcfg.DiscoKey{}) + disco.NonceLen
|
const headerLen = len(disco.Magic) + len(tailcfg.DiscoKey{}) + disco.NonceLen
|
||||||
if len(msg) < headerLen || string(msg[:len(disco.Magic)]) != disco.Magic {
|
if len(msg) < headerLen || string(msg[:len(disco.Magic)]) != disco.Magic {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the first four parts are the prefix of disco.Magic
|
||||||
|
// (0x5453f09f) then it's definitely not a valid Wireguard
|
||||||
|
// packet (which starts with little-endian uint32 1, 2, 3, 4).
|
||||||
|
// Use naked returns for all following paths.
|
||||||
|
isDiscoMsg = true
|
||||||
|
|
||||||
var sender tailcfg.DiscoKey
|
var sender tailcfg.DiscoKey
|
||||||
copy(sender[:], msg[len(disco.Magic):])
|
copy(sender[:], msg[len(disco.Magic):])
|
||||||
|
|
||||||
|
@ -1774,20 +1845,21 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if c.closed {
|
if c.closed {
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
if debugDisco {
|
if debugDisco {
|
||||||
c.logf("magicsock: disco: got disco-looking frame from %v", sender.ShortString())
|
c.logf("magicsock: disco: got disco-looking frame from %v", sender.ShortString())
|
||||||
}
|
}
|
||||||
if c.privateKey.IsZero() {
|
if c.privateKey.IsZero() {
|
||||||
// Ignore disco messages when we're stopped.
|
// Ignore disco messages when we're stopped.
|
||||||
return false
|
// Still return true, to not pass it down to wireguard.
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if c.discoPrivate.IsZero() {
|
if c.discoPrivate.IsZero() {
|
||||||
if debugDisco {
|
if debugDisco {
|
||||||
c.logf("magicsock: disco: ignoring disco-looking frame, no local key")
|
c.logf("magicsock: disco: ignoring disco-looking frame, no local key")
|
||||||
}
|
}
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
peerNode, ok := c.nodeOfDisco[sender]
|
peerNode, ok := c.nodeOfDisco[sender]
|
||||||
|
@ -1795,9 +1867,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
||||||
if debugDisco {
|
if debugDisco {
|
||||||
c.logf("magicsock: disco: ignoring disco-looking frame, don't know node for %v", sender.ShortString())
|
c.logf("magicsock: disco: ignoring disco-looking frame, don't know node for %v", sender.ShortString())
|
||||||
}
|
}
|
||||||
// Returning false keeps passing it down, to WireGuard.
|
return
|
||||||
// WireGuard will almost surely reject it, but give it a chance.
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
needsRecvActivityCall := false
|
needsRecvActivityCall := false
|
||||||
|
@ -1810,7 +1880,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
||||||
c.logf("magicsock: got disco message from idle peer, starting lazy conf for %v, %v", peerNode.Key.ShortString(), sender.ShortString())
|
c.logf("magicsock: got disco message from idle peer, starting lazy conf for %v, %v", peerNode.Key.ShortString(), sender.ShortString())
|
||||||
if c.noteRecvActivity == nil {
|
if c.noteRecvActivity == nil {
|
||||||
c.logf("magicsock: [unexpected] have node without endpoint, without c.noteRecvActivity hook")
|
c.logf("magicsock: [unexpected] have node without endpoint, without c.noteRecvActivity hook")
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
needsRecvActivityCall = true
|
needsRecvActivityCall = true
|
||||||
} else {
|
} else {
|
||||||
|
@ -1829,7 +1899,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
||||||
// Now, recheck invariants that might've changed while we'd
|
// Now, recheck invariants that might've changed while we'd
|
||||||
// released the lock, which isn't much:
|
// released the lock, which isn't much:
|
||||||
if c.closed || c.privateKey.IsZero() {
|
if c.closed || c.privateKey.IsZero() {
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
de, ok = c.endpointOfDisco[sender]
|
de, ok = c.endpointOfDisco[sender]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -1838,7 +1908,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
c.logf("magicsock: [unexpected] lazy endpoint not created for %v, %v", peerNode.Key.ShortString(), sender.ShortString())
|
c.logf("magicsock: [unexpected] lazy endpoint not created for %v, %v", peerNode.Key.ShortString(), sender.ShortString())
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
if !endpointFound0 {
|
if !endpointFound0 {
|
||||||
c.logf("magicsock: lazy endpoint created via disco message for %v, %v", peerNode.Key.ShortString(), sender.ShortString())
|
c.logf("magicsock: lazy endpoint created via disco message for %v, %v", peerNode.Key.ShortString(), sender.ShortString())
|
||||||
|
@ -1865,7 +1935,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
||||||
c.logf("magicsock: disco: failed to open naclbox from %v (wrong rcpt?)", sender)
|
c.logf("magicsock: disco: failed to open naclbox from %v (wrong rcpt?)", sender)
|
||||||
}
|
}
|
||||||
// TODO(bradfitz): add some counter for this that logs rarely
|
// TODO(bradfitz): add some counter for this that logs rarely
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dm, err := disco.Parse(payload)
|
dm, err := disco.Parse(payload)
|
||||||
|
@ -1879,7 +1949,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
||||||
// understand. Not even worth logging about, lest it
|
// understand. Not even worth logging about, lest it
|
||||||
// be too spammy for old clients.
|
// be too spammy for old clients.
|
||||||
// TODO(bradfitz): add some counter for this that logs rarely
|
// TODO(bradfitz): add some counter for this that logs rarely
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch dm := dm.(type) {
|
switch dm := dm.(type) {
|
||||||
|
@ -1887,14 +1957,14 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
||||||
c.handlePingLocked(dm, de, src, sender, peerNode)
|
c.handlePingLocked(dm, de, src, sender, peerNode)
|
||||||
case *disco.Pong:
|
case *disco.Pong:
|
||||||
if de == nil {
|
if de == nil {
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
de.handlePongConnLocked(dm, src)
|
de.handlePongConnLocked(dm, src)
|
||||||
case *disco.CallMeMaybe:
|
case *disco.CallMeMaybe:
|
||||||
if src.IP != derpMagicIPAddr {
|
if src.IP != derpMagicIPAddr {
|
||||||
// CallMeMaybe messages should only come via DERP.
|
// CallMeMaybe messages should only come via DERP.
|
||||||
c.logf("[unexpected] CallMeMaybe packets should only come via DERP")
|
c.logf("[unexpected] CallMeMaybe packets should only come via DERP")
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
if de != nil {
|
if de != nil {
|
||||||
c.logf("magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints",
|
c.logf("magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints",
|
||||||
|
@ -1904,8 +1974,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
|
||||||
go de.handleCallMeMaybe(dm)
|
go de.handleCallMeMaybe(dm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) handlePingLocked(dm *disco.Ping, de *discoEndpoint, src netaddr.IPPort, sender tailcfg.DiscoKey, peerNode *tailcfg.Node) {
|
func (c *Conn) handlePingLocked(dm *disco.Ping, de *discoEndpoint, src netaddr.IPPort, sender tailcfg.DiscoKey, peerNode *tailcfg.Node) {
|
||||||
|
@ -2061,7 +2130,9 @@ func (c *Conn) SetNetworkUp(up bool) {
|
||||||
c.logf("magicsock: SetNetworkUp(%v)", up)
|
c.logf("magicsock: SetNetworkUp(%v)", up)
|
||||||
c.networkUp.Set(up)
|
c.networkUp.Set(up)
|
||||||
|
|
||||||
if !up {
|
if up {
|
||||||
|
c.startDerpHomeConnectLocked()
|
||||||
|
} else {
|
||||||
c.closeAllDerpLocked("network-down")
|
c.closeAllDerpLocked("network-down")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2082,6 +2153,7 @@ func (c *Conn) SetPrivateKey(privateKey wgkey.Private) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
c.privateKey = newKey
|
c.privateKey = newKey
|
||||||
|
c.havePrivateKey.Set(!newKey.IsZero())
|
||||||
|
|
||||||
if oldKey.IsZero() {
|
if oldKey.IsZero() {
|
||||||
c.everHadKey = true
|
c.everHadKey = true
|
||||||
|
@ -2102,7 +2174,7 @@ func (c *Conn) SetPrivateKey(privateKey wgkey.Private) error {
|
||||||
// Key changed. Close existing DERP connections and reconnect to home.
|
// Key changed. Close existing DERP connections and reconnect to home.
|
||||||
if c.myDerp != 0 && !newKey.IsZero() {
|
if c.myDerp != 0 && !newKey.IsZero() {
|
||||||
c.logf("magicsock: private key changed, reconnecting to home derp-%d", c.myDerp)
|
c.logf("magicsock: private key changed, reconnecting to home derp-%d", c.myDerp)
|
||||||
c.goDerpConnect(c.myDerp)
|
c.startDerpHomeConnectLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
if newKey.IsZero() {
|
if newKey.IsZero() {
|
||||||
|
@ -2192,8 +2264,12 @@ func (c *Conn) SetNetworkMap(nm *controlclient.NetworkMap) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
numDisco++
|
numDisco++
|
||||||
if ep, ok := c.endpointOfDisco[n.DiscoKey]; ok {
|
if ep, ok := c.endpointOfDisco[n.DiscoKey]; ok && ep.publicKey == n.Key {
|
||||||
ep.updateFromNode(n)
|
ep.updateFromNode(n)
|
||||||
|
} else if ok {
|
||||||
|
c.logf("magicsock: disco key %v changed from node key %v to %v", n.DiscoKey, ep.publicKey.ShortString(), n.Key.ShortString())
|
||||||
|
ep.stopAndReset()
|
||||||
|
delete(c.endpointOfDisco, n.DiscoKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2565,12 +2641,11 @@ func (c *Conn) Rebind() {
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
c.closeAllDerpLocked("rebind")
|
c.closeAllDerpLocked("rebind")
|
||||||
haveKey := !c.privateKey.IsZero()
|
if !c.privateKey.IsZero() {
|
||||||
|
c.startDerpHomeConnectLocked()
|
||||||
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if haveKey {
|
|
||||||
c.goDerpConnect(c.myDerp)
|
|
||||||
}
|
|
||||||
c.resetEndpointStates()
|
c.resetEndpointStates()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -1410,19 +1411,136 @@ func Test32bitAlignment(t *testing.T) {
|
||||||
atomic.AddInt64(&c.derpRecvCountAtomic, 1)
|
atomic.AddInt64(&c.derpRecvCountAtomic, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkReceiveFrom(b *testing.B) {
|
// newNonLegacyTestConn returns a new Conn with DisableLegacyNetworking set true.
|
||||||
port := pickPort(b)
|
func newNonLegacyTestConn(t testing.TB) *Conn {
|
||||||
|
t.Helper()
|
||||||
|
port := pickPort(t)
|
||||||
conn, err := NewConn(Options{
|
conn, err := NewConn(Options{
|
||||||
Logf: b.Logf,
|
Logf: t.Logf,
|
||||||
Port: port,
|
Port: port,
|
||||||
EndpointsFunc: func(eps []string) {
|
EndpointsFunc: func(eps []string) {
|
||||||
b.Logf("endpoints: %q", eps)
|
t.Logf("endpoints: %q", eps)
|
||||||
},
|
},
|
||||||
DisableLegacyNetworking: true,
|
DisableLegacyNetworking: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests concurrent DERP readers pushing DERP data into ReceiveIPv4
|
||||||
|
// (which should blend all DERP reads into UDP reads).
|
||||||
|
func TestDerpReceiveFromIPv4(t *testing.T) {
|
||||||
|
conn := newNonLegacyTestConn(t)
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer sendConn.Close()
|
||||||
|
nodeKey, _ := addTestEndpoint(conn, sendConn)
|
||||||
|
|
||||||
|
var sends int = 250e3 // takes about a second
|
||||||
|
if testing.Short() {
|
||||||
|
sends /= 10
|
||||||
|
}
|
||||||
|
senders := runtime.NumCPU()
|
||||||
|
sends -= (sends % senders)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
defer wg.Wait()
|
||||||
|
t.Logf("doing %v sends over %d senders", sends, senders)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer conn.Close()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
doneCtx, cancelDoneCtx := context.WithCancel(context.Background())
|
||||||
|
cancelDoneCtx()
|
||||||
|
|
||||||
|
for i := 0; i < senders; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
regionID := i + 1
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < sends/senders; i++ {
|
||||||
|
res := derpReadResult{
|
||||||
|
regionID: regionID,
|
||||||
|
n: 123,
|
||||||
|
src: key.Public(nodeKey),
|
||||||
|
copyBuf: func(dst []byte) int { return 123 },
|
||||||
|
}
|
||||||
|
// First send with the closed context. ~50% of
|
||||||
|
// these should end up going through the
|
||||||
|
// send-a-zero-derpReadResult path, returning
|
||||||
|
// true, in which case we don't want to send again.
|
||||||
|
// We test later that we hit the other path.
|
||||||
|
if conn.sendDerpReadResult(doneCtx, res) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !conn.sendDerpReadResult(ctx, res) {
|
||||||
|
t.Error("unexpected false")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
zeroSendsStart := testCounterZeroDerpReadResultSend.Value()
|
||||||
|
|
||||||
|
buf := make([]byte, 1500)
|
||||||
|
for i := 0; i < sends; i++ {
|
||||||
|
n, ep, err := conn.ReceiveIPv4(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_ = n
|
||||||
|
_ = ep
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("did %d ReceiveIPv4 calls", sends)
|
||||||
|
|
||||||
|
zeroSends, zeroRecv := testCounterZeroDerpReadResultSend.Value(), testCounterZeroDerpReadResultRecv.Value()
|
||||||
|
if zeroSends != zeroRecv {
|
||||||
|
t.Errorf("did %d zero sends != %d corresponding receives", zeroSends, zeroRecv)
|
||||||
|
}
|
||||||
|
zeroSendDelta := zeroSends - zeroSendsStart
|
||||||
|
if zeroSendDelta == 0 {
|
||||||
|
t.Errorf("didn't see any sends of derpReadResult zero value")
|
||||||
|
}
|
||||||
|
if zeroSendDelta == int64(sends) {
|
||||||
|
t.Errorf("saw %v sends of the derpReadResult zero value which was unexpectedly high (100%% of our %v sends)", zeroSendDelta, sends)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// Give conn just enough state that it'll recognize sendConn as a
|
||||||
|
// valid peer and not fall through to the legacy magicsock
|
||||||
|
// codepath.
|
||||||
|
discoKey := tailcfg.DiscoKey{31: 1}
|
||||||
|
nodeKey := tailcfg.NodeKey{0: 'N', 1: 'K'}
|
||||||
|
conn.SetNetworkMap(&controlclient.NetworkMap{
|
||||||
|
Peers: []*tailcfg.Node{
|
||||||
|
{
|
||||||
|
Key: nodeKey,
|
||||||
|
DiscoKey: discoKey,
|
||||||
|
Endpoints: []string{sendConn.LocalAddr().String()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
conn.SetPrivateKey(wgkey.Private{0: 1})
|
||||||
|
conn.CreateEndpoint([32]byte(nodeKey), "0000000000000000000000000000000000000000000000000000000000000001.disco.tailscale:12345")
|
||||||
|
conn.addValidDiscoPathForTest(discoKey, netaddr.MustParseIPPort(sendConn.LocalAddr().String()))
|
||||||
|
return nodeKey, discoKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkReceiveFrom(b *testing.B) {
|
||||||
|
conn := newNonLegacyTestConn(b)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
||||||
|
@ -1431,20 +1549,7 @@ func BenchmarkReceiveFrom(b *testing.B) {
|
||||||
}
|
}
|
||||||
defer sendConn.Close()
|
defer sendConn.Close()
|
||||||
|
|
||||||
// Give conn just enough state that it'll recognize sendConn as a
|
addTestEndpoint(conn, sendConn)
|
||||||
// valid peer and not fall through to the legacy magicsock
|
|
||||||
// codepath.
|
|
||||||
discoKey := tailcfg.DiscoKey{31: 1}
|
|
||||||
conn.SetNetworkMap(&controlclient.NetworkMap{
|
|
||||||
Peers: []*tailcfg.Node{
|
|
||||||
{
|
|
||||||
DiscoKey: discoKey,
|
|
||||||
Endpoints: []string{sendConn.LocalAddr().String()},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
conn.CreateEndpoint([32]byte{1: 1}, "0000000000000000000000000000000000000000000000000000000000000001.disco.tailscale:12345")
|
|
||||||
conn.addValidDiscoPathForTest(discoKey, netaddr.MustParseIPPort(sendConn.LocalAddr().String()))
|
|
||||||
|
|
||||||
var dstAddr net.Addr = conn.pconn4.LocalAddr()
|
var dstAddr net.Addr = conn.pconn4.LocalAddr()
|
||||||
sendBuf := make([]byte, 1<<10)
|
sendBuf := make([]byte, 1<<10)
|
||||||
|
@ -1496,3 +1601,71 @@ func BenchmarkReceiveFrom_Native(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that a netmap update where node changes its node key but
|
||||||
|
// doesn't change its disco key doesn't result in a broken state.
|
||||||
|
//
|
||||||
|
// https://github.com/tailscale/tailscale/issues/1391
|
||||||
|
func TestSetNetworkMapChangingNodeKey(t *testing.T) {
|
||||||
|
conn := newNonLegacyTestConn(t)
|
||||||
|
t.Cleanup(func() { conn.Close() })
|
||||||
|
var logBuf bytes.Buffer
|
||||||
|
conn.logf = func(format string, a ...interface{}) {
|
||||||
|
fmt.Fprintf(&logBuf, format, a...)
|
||||||
|
if !bytes.HasSuffix(logBuf.Bytes(), []byte("\n")) {
|
||||||
|
logBuf.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetPrivateKey(wgkey.Private{0: 1})
|
||||||
|
|
||||||
|
discoKey := tailcfg.DiscoKey{31: 1}
|
||||||
|
nodeKey1 := tailcfg.NodeKey{0: 'N', 1: 'K', 2: '1'}
|
||||||
|
nodeKey2 := tailcfg.NodeKey{0: 'N', 1: 'K', 2: '2'}
|
||||||
|
|
||||||
|
conn.SetNetworkMap(&controlclient.NetworkMap{
|
||||||
|
Peers: []*tailcfg.Node{
|
||||||
|
{
|
||||||
|
Key: nodeKey1,
|
||||||
|
DiscoKey: discoKey,
|
||||||
|
Endpoints: []string{"192.168.1.2:345"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
_, err := conn.CreateEndpoint([32]byte(nodeKey1), "0000000000000000000000000000000000000000000000000000000000000001.disco.tailscale:12345")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
conn.SetNetworkMap(&controlclient.NetworkMap{
|
||||||
|
Peers: []*tailcfg.Node{
|
||||||
|
{
|
||||||
|
Key: nodeKey2,
|
||||||
|
DiscoKey: discoKey,
|
||||||
|
Endpoints: []string{"192.168.1.2:345"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
de := conn.endpointOfDisco[discoKey]
|
||||||
|
if de != nil && de.publicKey != nodeKey2 {
|
||||||
|
t.Fatalf("discoEndpoint public key = %q; want %q", de.publicKey[:], nodeKey2[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logBuf.String()
|
||||||
|
wantSub := map[string]int{
|
||||||
|
"magicsock: got updated network map; 1 peers (1 with discokey)": 2,
|
||||||
|
"magicsock: disco key discokey:0000000000000000000000000000000000000000000000000000000000000001 changed from node key [TksxA] to [TksyA]": 1,
|
||||||
|
}
|
||||||
|
for sub, want := range wantSub {
|
||||||
|
got := strings.Count(log, sub)
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("in log, count of substring %q = %v; want %v", sub, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.Failed() {
|
||||||
|
t.Logf("log output: %s", log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -39,12 +39,11 @@ type nlConn struct {
|
||||||
|
|
||||||
func newOSMon(logf logger.Logf) (osMon, error) {
|
func newOSMon(logf logger.Logf) (osMon, error) {
|
||||||
conn, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
|
conn, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
|
||||||
// IPv4 address and route changes. Routes get us most of the
|
// Routes get us most of the events of interest, but we need
|
||||||
// events of interest, but we need address as well to cover
|
// address as well to cover things like DHCP deciding to give
|
||||||
// things like DHCP deciding to give us a new address upon
|
// us a new address upon renewal - routing wouldn't change,
|
||||||
// renewal - routing wouldn't change, but all reachability
|
// but all reachability would.
|
||||||
// would.
|
Groups: unix.RTMGRP_IPV4_IFADDR | unix.RTMGRP_IPV6_IFADDR | unix.RTMGRP_IPV4_ROUTE | unix.RTMGRP_IPV6_ROUTE,
|
||||||
Groups: unix.RTMGRP_IPV4_IFADDR | unix.RTMGRP_IPV4_ROUTE,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dialing netlink socket: %v", err)
|
return nil, fmt.Errorf("dialing netlink socket: %v", err)
|
||||||
|
@ -98,7 +97,7 @@ func (c *nlConn) Receive() (message, error) {
|
||||||
dst := netaddrIPPrefix(rmsg.Attributes.Dst, rmsg.DstLength)
|
dst := netaddrIPPrefix(rmsg.Attributes.Dst, rmsg.DstLength)
|
||||||
gw := netaddrIP(rmsg.Attributes.Gateway)
|
gw := netaddrIP(rmsg.Attributes.Gateway)
|
||||||
|
|
||||||
if msg.Header.Type == unix.RTM_NEWROUTE && rmsg.Table == tsTable && rmsg.DstLength == 32 {
|
if msg.Header.Type == unix.RTM_NEWROUTE && rmsg.Table == tsTable && dst.IsSingleIP() {
|
||||||
// Don't log. Spammy and normal to see a bunch of these on start-up,
|
// Don't log. Spammy and normal to see a bunch of these on start-up,
|
||||||
// which we make ourselves.
|
// which we make ourselves.
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -30,6 +30,12 @@ func debugConnectFailures() bool {
|
||||||
|
|
||||||
type pendingOpenFlow struct {
|
type pendingOpenFlow struct {
|
||||||
timer *time.Timer // until giving up on the flow
|
timer *time.Timer // until giving up on the flow
|
||||||
|
|
||||||
|
// guarded by userspaceEngine.mu:
|
||||||
|
|
||||||
|
// problem is non-zero if we got a MaybeBroken (non-terminal)
|
||||||
|
// TSMP "reject" header.
|
||||||
|
problem packet.TailscaleRejectReason
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *userspaceEngine) removeFlow(f flowtrack.Tuple) (removed bool) {
|
func (e *userspaceEngine) removeFlow(f flowtrack.Tuple) (removed bool) {
|
||||||
|
@ -45,6 +51,17 @@ func (e *userspaceEngine) removeFlow(f flowtrack.Tuple) (removed bool) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *userspaceEngine) noteFlowProblemFromPeer(f flowtrack.Tuple, problem packet.TailscaleRejectReason) {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
of, ok := e.pendOpen[f]
|
||||||
|
if !ok {
|
||||||
|
// Not a tracked flow (likely already removed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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.TUN) (res filter.Response) {
|
||||||
res = filter.Accept // always
|
res = filter.Accept // always
|
||||||
|
|
||||||
|
@ -54,7 +71,9 @@ func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if f := rh.Flow(); e.removeFlow(f) {
|
if rh.MaybeBroken {
|
||||||
|
e.noteFlowProblemFromPeer(rh.Flow(), rh.Reason)
|
||||||
|
} else if f := rh.Flow(); e.removeFlow(f) {
|
||||||
e.logf("open-conn-track: flow %v %v > %v rejected due to %v", rh.Proto, rh.Src, rh.Dst, rh.Reason)
|
e.logf("open-conn-track: flow %v %v > %v rejected due to %v", rh.Proto, rh.Src, rh.Dst, rh.Reason)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -106,14 +125,20 @@ func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN
|
||||||
|
|
||||||
func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
|
func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
if _, ok := e.pendOpen[flow]; !ok {
|
of, ok := e.pendOpen[flow]
|
||||||
|
if !ok {
|
||||||
// Not a tracked flow, or already handled & deleted.
|
// Not a tracked flow, or already handled & deleted.
|
||||||
e.mu.Unlock()
|
e.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(e.pendOpen, flow)
|
delete(e.pendOpen, flow)
|
||||||
|
problem := of.problem
|
||||||
e.mu.Unlock()
|
e.mu.Unlock()
|
||||||
|
|
||||||
|
if !problem.IsZero() {
|
||||||
|
e.logf("open-conn-track: timeout opening %v; peer reported problem: %v", flow, problem)
|
||||||
|
}
|
||||||
|
|
||||||
// Diagnose why it might've timed out.
|
// Diagnose why it might've timed out.
|
||||||
n, ok := e.magicConn.PeerForIP(flow.Dst.IP)
|
n, ok := e.magicConn.PeerForIP(flow.Dst.IP)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -116,7 +116,7 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (
|
||||||
|
|
||||||
v6err := checkIPv6()
|
v6err := checkIPv6()
|
||||||
if v6err != nil {
|
if v6err != nil {
|
||||||
logf("disabling IPv6 due to system IPv6 config: %v", v6err)
|
logf("disabling tunneled IPv6 due to system IPv6 config: %v", v6err)
|
||||||
}
|
}
|
||||||
supportsV6 := v6err == nil
|
supportsV6 := v6err == nil
|
||||||
supportsV6NAT := supportsV6 && supportsV6NAT()
|
supportsV6NAT := supportsV6 && supportsV6NAT()
|
||||||
|
@ -366,7 +366,9 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error {
|
||||||
// address is already assigned to the interface, or if the addition
|
// address is already assigned to the interface, or if the addition
|
||||||
// fails.
|
// fails.
|
||||||
func (r *linuxRouter) addAddress(addr netaddr.IPPrefix) error {
|
func (r *linuxRouter) addAddress(addr netaddr.IPPrefix) error {
|
||||||
|
if !r.v6Available && addr.IP.Is6() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if err := r.cmd.run("ip", "addr", "add", addr.String(), "dev", r.tunname); err != nil {
|
if err := r.cmd.run("ip", "addr", "add", addr.String(), "dev", r.tunname); err != nil {
|
||||||
return fmt.Errorf("adding address %q to tunnel interface: %w", addr, err)
|
return fmt.Errorf("adding address %q to tunnel interface: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
@ -380,6 +382,9 @@ func (r *linuxRouter) addAddress(addr netaddr.IPPrefix) error {
|
||||||
// the address is not assigned to the interface, or if the removal
|
// the address is not assigned to the interface, or if the removal
|
||||||
// fails.
|
// fails.
|
||||||
func (r *linuxRouter) delAddress(addr netaddr.IPPrefix) error {
|
func (r *linuxRouter) delAddress(addr netaddr.IPPrefix) error {
|
||||||
|
if !r.v6Available && addr.IP.Is6() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if err := r.delLoopbackRule(addr.IP); err != nil {
|
if err := r.delLoopbackRule(addr.IP); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -437,6 +442,9 @@ func (r *linuxRouter) delLoopbackRule(addr netaddr.IP) error {
|
||||||
// interface. Fails if the route already exists, or if adding the
|
// interface. Fails if the route already exists, or if adding the
|
||||||
// route fails.
|
// route fails.
|
||||||
func (r *linuxRouter) addRoute(cidr netaddr.IPPrefix) error {
|
func (r *linuxRouter) addRoute(cidr netaddr.IPPrefix) error {
|
||||||
|
if !r.v6Available && cidr.IP.Is6() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
args := []string{
|
args := []string{
|
||||||
"ip", "route", "add",
|
"ip", "route", "add",
|
||||||
normalizeCIDR(cidr),
|
normalizeCIDR(cidr),
|
||||||
|
@ -452,6 +460,9 @@ func (r *linuxRouter) addRoute(cidr netaddr.IPPrefix) error {
|
||||||
// interface. Fails if the route doesn't exist, or if removing the
|
// interface. Fails if the route doesn't exist, or if removing the
|
||||||
// route fails.
|
// route fails.
|
||||||
func (r *linuxRouter) delRoute(cidr netaddr.IPPrefix) error {
|
func (r *linuxRouter) delRoute(cidr netaddr.IPPrefix) error {
|
||||||
|
if !r.v6Available && cidr.IP.Is6() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
args := []string{
|
args := []string{
|
||||||
"ip", "route", "del",
|
"ip", "route", "del",
|
||||||
normalizeCIDR(cidr),
|
normalizeCIDR(cidr),
|
||||||
|
@ -460,7 +471,42 @@ func (r *linuxRouter) delRoute(cidr netaddr.IPPrefix) error {
|
||||||
if r.ipRuleAvailable {
|
if r.ipRuleAvailable {
|
||||||
args = append(args, "table", tailscaleRouteTable)
|
args = append(args, "table", tailscaleRouteTable)
|
||||||
}
|
}
|
||||||
return r.cmd.run(args...)
|
err := r.cmd.run(args...)
|
||||||
|
if err != nil {
|
||||||
|
ok, err := r.hasRoute(cidr)
|
||||||
|
if err != nil {
|
||||||
|
r.logf("warning: error checking whether %v even exists after error deleting it: %v", err)
|
||||||
|
} else {
|
||||||
|
if !ok {
|
||||||
|
r.logf("warning: tried to delete route %v but it was already gone; ignoring error", cidr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func dashFam(ip netaddr.IP) string {
|
||||||
|
if ip.Is6() {
|
||||||
|
return "-6"
|
||||||
|
}
|
||||||
|
return "-4"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *linuxRouter) hasRoute(cidr netaddr.IPPrefix) (bool, error) {
|
||||||
|
args := []string{
|
||||||
|
"ip", dashFam(cidr.IP), "route", "show",
|
||||||
|
normalizeCIDR(cidr),
|
||||||
|
"dev", r.tunname,
|
||||||
|
}
|
||||||
|
if r.ipRuleAvailable {
|
||||||
|
args = append(args, "table", tailscaleRouteTable)
|
||||||
|
}
|
||||||
|
out, err := r.cmd.output(args...)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return len(out) > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// upInterface brings up the tunnel interface.
|
// upInterface brings up the tunnel interface.
|
||||||
|
@ -969,25 +1015,43 @@ func cidrDiff(kind string, old map[netaddr.IPPrefix]bool, new []netaddr.IPPrefix
|
||||||
ret[cidr] = true
|
ret[cidr] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var delFail []error
|
||||||
for cidr := range old {
|
for cidr := range old {
|
||||||
if newMap[cidr] {
|
if newMap[cidr] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := del(cidr); err != nil {
|
if err := del(cidr); err != nil {
|
||||||
logf("%s del failed: %v", kind, err)
|
logf("%s del failed: %v", kind, err)
|
||||||
return ret, err
|
delFail = append(delFail, err)
|
||||||
|
} else {
|
||||||
|
delete(ret, cidr)
|
||||||
}
|
}
|
||||||
delete(ret, cidr)
|
|
||||||
}
|
}
|
||||||
|
if len(delFail) == 1 {
|
||||||
|
return ret, delFail[0]
|
||||||
|
}
|
||||||
|
if len(delFail) > 0 {
|
||||||
|
return ret, fmt.Errorf("%d delete %s failures; first was: %w", len(delFail), kind, delFail[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
var addFail []error
|
||||||
for cidr := range newMap {
|
for cidr := range newMap {
|
||||||
if old[cidr] {
|
if old[cidr] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := add(cidr); err != nil {
|
if err := add(cidr); err != nil {
|
||||||
logf("%s add failed: %v", kind, err)
|
logf("%s add failed: %v", kind, err)
|
||||||
return ret, err
|
addFail = append(addFail, err)
|
||||||
|
} else {
|
||||||
|
ret[cidr] = true
|
||||||
}
|
}
|
||||||
ret[cidr] = true
|
}
|
||||||
|
|
||||||
|
if len(addFail) == 1 {
|
||||||
|
return ret, addFail[0]
|
||||||
|
}
|
||||||
|
if len(addFail) > 0 {
|
||||||
|
return ret, fmt.Errorf("%d add %s failures; first was: %w", len(addFail), kind, addFail[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
@ -1034,18 +1098,22 @@ func checkIPv6() error {
|
||||||
return errors.New("disable_ipv6 is set")
|
return errors.New("disable_ipv6 is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Older kernels don't support IPv6 policy routing.
|
// Older kernels don't support IPv6 policy routing. Some kernels
|
||||||
|
// support policy routing but don't have this knob, so absence of
|
||||||
|
// the knob is not fatal.
|
||||||
bs, err = ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
|
bs, err = ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
|
||||||
if err != nil {
|
if err == nil {
|
||||||
// Absent knob means policy routing is unsupported.
|
disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs)))
|
||||||
return err
|
if err != nil {
|
||||||
|
return errors.New("disable_policy has invalid bool")
|
||||||
|
}
|
||||||
|
if disabled {
|
||||||
|
return errors.New("disable_policy is set")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs)))
|
|
||||||
if err != nil {
|
if err := checkIPRuleSupportsV6(); err != nil {
|
||||||
return errors.New("disable_policy has invalid bool")
|
return fmt.Errorf("kernel doesn't support IPv6 policy routing: %w", err)
|
||||||
}
|
|
||||||
if disabled {
|
|
||||||
return errors.New("disable_policy is set")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some distros ship ip6tables separately from iptables.
|
// Some distros ship ip6tables separately from iptables.
|
||||||
|
@ -1053,10 +1121,6 @@ func checkIPv6() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := checkIPRuleSupportsV6(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1077,13 +1141,17 @@ func supportsV6NAT() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkIPRuleSupportsV6() error {
|
func checkIPRuleSupportsV6() error {
|
||||||
// First add a rule for "ip rule del" to delete.
|
add := []string{"-6", "rule", "add", "pref", "1234", "fwmark", tailscaleBypassMark, "table", tailscaleRouteTable}
|
||||||
// We ignore the "add" operation's error because it can also
|
del := []string{"-6", "rule", "del", "pref", "1234", "fwmark", tailscaleBypassMark, "table", tailscaleRouteTable}
|
||||||
// fail if the rule already exists.
|
|
||||||
exec.Command("ip", "-6", "rule", "add",
|
// First delete the rule unconditionally, and don't check for
|
||||||
"pref", "123", "fwmark", tailscaleBypassMark, "table", fmt.Sprint(tailscaleRouteTable)).Run()
|
// errors. This is just cleaning up anything that might be already
|
||||||
out, err := exec.Command("ip", "-6", "rule", "del",
|
// there.
|
||||||
"pref", "123", "fwmark", tailscaleBypassMark, "table", fmt.Sprint(tailscaleRouteTable)).CombinedOutput()
|
exec.Command("ip", del...).Run()
|
||||||
|
|
||||||
|
// Try adding the rule. This will fail on systems that support
|
||||||
|
// IPv6, but not IPv6 policy routing.
|
||||||
|
out, err := exec.Command("ip", add...).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out = bytes.TrimSpace(out)
|
out = bytes.TrimSpace(out)
|
||||||
var detail interface{} = out
|
var detail interface{} = out
|
||||||
|
@ -1092,5 +1160,8 @@ func checkIPRuleSupportsV6() error {
|
||||||
}
|
}
|
||||||
return fmt.Errorf("ip -6 rule failed: %s", detail)
|
return fmt.Errorf("ip -6 rule failed: %s", detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete again.
|
||||||
|
exec.Command("ip", del...).Run()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,18 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/tailscale/wireguard-go/tun"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -595,3 +599,69 @@ func (o *fakeOS) output(args ...string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return []byte(strings.Join(ret, "\n")), nil
|
return []byte(strings.Join(ret, "\n")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tunTestNum int64
|
||||||
|
|
||||||
|
func createTestTUN(t *testing.T) tun.Device {
|
||||||
|
const minimalMTU = 1280
|
||||||
|
tunName := fmt.Sprintf("tuntest%d", atomic.AddInt64(&tunTestNum, 1))
|
||||||
|
tun, err := tun.CreateTUN(tunName, minimalMTU)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateTUN(%q): %v", tunName, err)
|
||||||
|
}
|
||||||
|
return tun
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelRouteIdempotent(t *testing.T) {
|
||||||
|
if os.Getuid() != 0 {
|
||||||
|
t.Skip("test requires root")
|
||||||
|
}
|
||||||
|
tun := createTestTUN(t)
|
||||||
|
defer tun.Close()
|
||||||
|
|
||||||
|
var logOutput bytes.Buffer
|
||||||
|
logf := func(format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(&logOutput, format, args...)
|
||||||
|
if !bytes.HasSuffix(logOutput.Bytes(), []byte("\n")) {
|
||||||
|
logOutput.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := newUserspaceRouter(logf, nil, tun)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
lr := r.(*linuxRouter)
|
||||||
|
if err := lr.upInterface(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, s := range []string{
|
||||||
|
"192.0.2.0/24", // RFC 5737
|
||||||
|
"2001:DB8::/32", // RFC 3849
|
||||||
|
} {
|
||||||
|
cidr := netaddr.MustParseIPPrefix(s)
|
||||||
|
if err := lr.addRoute(cidr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
if err := lr.delRoute(cidr); err != nil {
|
||||||
|
t.Fatalf("delRoute(i=%d): %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wantSubs := map[string]int{
|
||||||
|
"warning: tried to delete route 192.0.2.0/24 but it was already gone; ignoring error": 1,
|
||||||
|
"warning: tried to delete route 2001:db8::/32 but it was already gone; ignoring error": 1,
|
||||||
|
}
|
||||||
|
out := logOutput.String()
|
||||||
|
for sub, want := range wantSubs {
|
||||||
|
got := strings.Count(out, sub)
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("log output substring %q occurred %d time; want %d", sub, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.Failed() {
|
||||||
|
t.Logf("Log output:\n%s", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ package router
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -121,11 +122,12 @@ func cleanup(logf logger.Logf, interfaceName string) {
|
||||||
type firewallTweaker struct {
|
type firewallTweaker struct {
|
||||||
logf logger.Logf
|
logf logger.Logf
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
running bool // doAsyncSet goroutine is running
|
didProcRule bool
|
||||||
known bool // firewall is in known state (in lastVal)
|
running bool // doAsyncSet goroutine is running
|
||||||
want []string // next value we want, or "" to delete the firewall rule
|
known bool // firewall is in known state (in lastVal)
|
||||||
lastVal []string // last set value, if known
|
want []string // next value we want, or "" to delete the firewall rule
|
||||||
|
lastVal []string // last set value, if known
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ft *firewallTweaker) clear() { ft.set(nil) }
|
func (ft *firewallTweaker) clear() { ft.set(nil) }
|
||||||
|
@ -177,6 +179,7 @@ func (ft *firewallTweaker) doAsyncSet() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
needClear := !ft.known || len(ft.lastVal) > 0 || len(val) == 0
|
needClear := !ft.known || len(ft.lastVal) > 0 || len(val) == 0
|
||||||
|
needProcRule := !ft.didProcRule
|
||||||
ft.mu.Unlock()
|
ft.mu.Unlock()
|
||||||
|
|
||||||
if needClear {
|
if needClear {
|
||||||
|
@ -189,6 +192,37 @@ func (ft *firewallTweaker) doAsyncSet() {
|
||||||
d, _ := ft.runFirewall("delete", "rule", "name=Tailscale-In", "dir=in")
|
d, _ := ft.runFirewall("delete", "rule", "name=Tailscale-In", "dir=in")
|
||||||
ft.logf("cleared Tailscale-In firewall rules in %v", d)
|
ft.logf("cleared Tailscale-In firewall rules in %v", d)
|
||||||
}
|
}
|
||||||
|
if needProcRule {
|
||||||
|
ft.logf("deleting any prior Tailscale-Process rule...")
|
||||||
|
d, err := ft.runFirewall("delete", "rule", "name=Tailscale-Process", "dir=in") // best effort
|
||||||
|
if err == nil {
|
||||||
|
ft.logf("removed old Tailscale-Process rule in %v", d)
|
||||||
|
}
|
||||||
|
var exe string
|
||||||
|
exe, err = os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
ft.logf("failed to find Executable for Tailscale-Process rule: %v", err)
|
||||||
|
} else {
|
||||||
|
ft.logf("adding Tailscale-Process rule to allow UDP for %q ...", exe)
|
||||||
|
d, err = ft.runFirewall("add", "rule", "name=Tailscale-Process",
|
||||||
|
"dir=in",
|
||||||
|
"action=allow",
|
||||||
|
"edge=yes",
|
||||||
|
"program="+exe,
|
||||||
|
"protocol=udp",
|
||||||
|
"profile=any",
|
||||||
|
"enable=yes",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
ft.logf("error adding Tailscale-Process rule: %v", err)
|
||||||
|
} else {
|
||||||
|
ft.mu.Lock()
|
||||||
|
ft.didProcRule = true
|
||||||
|
ft.mu.Unlock()
|
||||||
|
ft.logf("added Tailscale-Process rule in %v", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
for _, cidr := range val {
|
for _, cidr := range val {
|
||||||
ft.logf("adding Tailscale-In rule to allow %v ...", cidr)
|
ft.logf("adding Tailscale-In rule to allow %v ...", cidr)
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
package tsdns
|
package tsdns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
@ -71,7 +73,7 @@ func resolveToNXDOMAIN(w dns.ResponseWriter, req *dns.Msg) {
|
||||||
w.WriteMsg(m)
|
w.WriteMsg(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveDNS(addr string) (*dns.Server, chan error) {
|
func serveDNS(tb testing.TB, addr string) (*dns.Server, chan error) {
|
||||||
server := &dns.Server{Addr: addr, Net: "udp"}
|
server := &dns.Server{Addr: addr, Net: "udp"}
|
||||||
|
|
||||||
waitch := make(chan struct{})
|
waitch := make(chan struct{})
|
||||||
|
@ -79,7 +81,11 @@ func serveDNS(addr string) (*dns.Server, chan error) {
|
||||||
|
|
||||||
errch := make(chan error, 1)
|
errch := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
errch <- server.ListenAndServe()
|
err := server.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
tb.Logf("ListenAndServe(%q): %v", addr, err)
|
||||||
|
}
|
||||||
|
errch <- err
|
||||||
close(errch)
|
close(errch)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -274,15 +274,28 @@ func TestResolveReverse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ipv6Works() bool {
|
||||||
|
c, err := net.Listen("tcp", "[::1]:0")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func TestDelegate(t *testing.T) {
|
func TestDelegate(t *testing.T) {
|
||||||
rc := tstest.NewResourceCheck()
|
rc := tstest.NewResourceCheck()
|
||||||
defer rc.Assert(t)
|
defer rc.Assert(t)
|
||||||
|
|
||||||
|
if !ipv6Works() {
|
||||||
|
t.Skip("skipping test that requires localhost IPv6")
|
||||||
|
}
|
||||||
|
|
||||||
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
||||||
dnsHandleFunc("nxdomain.site.", resolveToNXDOMAIN)
|
dnsHandleFunc("nxdomain.site.", resolveToNXDOMAIN)
|
||||||
|
|
||||||
v4server, v4errch := serveDNS("127.0.0.1:0")
|
v4server, v4errch := serveDNS(t, "127.0.0.1:0")
|
||||||
v6server, v6errch := serveDNS("[::1]:0")
|
v6server, v6errch := serveDNS(t, "[::1]:0")
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := <-v4errch; err != nil {
|
if err := <-v4errch; err != nil {
|
||||||
|
@ -372,7 +385,7 @@ func TestDelegate(t *testing.T) {
|
||||||
func TestDelegateCollision(t *testing.T) {
|
func TestDelegateCollision(t *testing.T) {
|
||||||
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
||||||
|
|
||||||
server, errch := serveDNS("127.0.0.1:0")
|
server, errch := serveDNS(t, "127.0.0.1:0")
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := <-errch; err != nil {
|
if err := <-errch; err != nil {
|
||||||
t.Errorf("server error: %v", err)
|
t.Errorf("server error: %v", err)
|
||||||
|
@ -474,7 +487,7 @@ func TestConcurrentSetMap(t *testing.T) {
|
||||||
func TestConcurrentSetUpstreams(t *testing.T) {
|
func TestConcurrentSetUpstreams(t *testing.T) {
|
||||||
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
||||||
|
|
||||||
server, errch := serveDNS("127.0.0.1:0")
|
server, errch := serveDNS(t, "127.0.0.1:0")
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := <-errch; err != nil {
|
if err := <-errch; err != nil {
|
||||||
t.Errorf("server error: %v", err)
|
t.Errorf("server error: %v", err)
|
||||||
|
@ -753,7 +766,7 @@ func TestTrimRDNSBonjourPrefix(t *testing.T) {
|
||||||
func BenchmarkFull(b *testing.B) {
|
func BenchmarkFull(b *testing.B) {
|
||||||
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
||||||
|
|
||||||
server, errch := serveDNS("127.0.0.1:0")
|
server, errch := serveDNS(b, "127.0.0.1:0")
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := <-errch; err != nil {
|
if err := <-errch; err != nil {
|
||||||
b.Errorf("server error: %v", err)
|
b.Errorf("server error: %v", err)
|
||||||
|
|
|
@ -215,7 +215,17 @@ func (t *TUN) poll() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var magicDNSIPPort = netaddr.MustParseIPPort("100.100.100.100:0")
|
||||||
|
|
||||||
func (t *TUN) filterOut(p *packet.Parsed) filter.Response {
|
func (t *TUN) 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()
|
||||||
|
header.ToResponse()
|
||||||
|
outp := packet.Generate(&header, p.Payload())
|
||||||
|
t.InjectInboundCopy(outp)
|
||||||
|
return filter.DropSilently // don't pass on to OS; already handled
|
||||||
|
}
|
||||||
|
|
||||||
if t.PreFilterOut != nil {
|
if t.PreFilterOut != nil {
|
||||||
if res := t.PreFilterOut(p, t); res.IsDrop() {
|
if res := t.PreFilterOut(p, t); res.IsDrop() {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"go4.org/mem"
|
"go4.org/mem"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/control/controlclient"
|
"tailscale.com/control/controlclient"
|
||||||
|
"tailscale.com/health"
|
||||||
"tailscale.com/internal/deepprint"
|
"tailscale.com/internal/deepprint"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/flowtrack"
|
"tailscale.com/net/flowtrack"
|
||||||
|
@ -998,7 +999,9 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
|
||||||
routerCfg.DNS.Nameservers = []netaddr.IP{tsaddr.TailscaleServiceIP()}
|
routerCfg.DNS.Nameservers = []netaddr.IP{tsaddr.TailscaleServiceIP()}
|
||||||
}
|
}
|
||||||
e.logf("wgengine: Reconfig: configuring router")
|
e.logf("wgengine: Reconfig: configuring router")
|
||||||
if err := e.router.Set(routerCfg); err != nil {
|
err := e.router.Set(routerCfg)
|
||||||
|
health.SetRouterHealth(err)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,16 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build windows
|
||||||
|
|
||||||
package winnet
|
package winnet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/go-ole/go-ole"
|
"github.com/go-ole/go-ole"
|
||||||
"github.com/go-ole/go-ole/oleutil"
|
"github.com/go-ole/go-ole/oleutil"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const CLSID_NetworkListManager = "{DCB00C01-570F-4A9B-8D69-199FDBA5723B}"
|
const CLSID_NetworkListManager = "{DCB00C01-570F-4A9B-8D69-199FDBA5723B}"
|
||||||
|
|
Loading…
Reference in New Issue