Compare commits

...

34 Commits

Author SHA1 Message Date
Brad Fitzpatrick 31f2cd9e45 wgengine/tstun: reply to MagicDNS pings
Fixes #849

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 917307a90c)
2021-02-23 15:00:57 -08:00
Brad Fitzpatrick 90c249cf26 control/controlclient: note package type in Hostinfo
Fixes tailscale/corp#440

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 4ec01323c1)
2021-02-23 15:00:57 -08:00
Brad Fitzpatrick 8319b45be7 tailcfg: add Hostinfo.Package
Updates tailscale/corp#440

(cherry picked from commit 73552eb32e)
2021-02-23 15:00:57 -08:00
Brad Fitzpatrick 4afc189919 wgengine/router: make Linux delRoute idempotent, cidrDiff fail late as possible
This makes cidrDiff do as much as possible before failing, and makes a
delete of an already-deleted rule be a no-op. We should never do this
ourselves, but other things on the system can, and this should help us
recover a bit.

Also adds the start of root-requiring tests.

TODO: hook into wgengine/monitor and notice when routes are changed
behind our back, and invalidate our routes map and re-read from
kernel (via the ip command) at least on the next reconfig call.

Updates tailscale/corp#1338

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 2d96215d97)
2021-02-23 15:00:57 -08:00
Brad Fitzpatrick a660748272 health, control/controlclient, wgengine: report when router unhealthy
Updates tailscale/corp#1338

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit fd8e070d01)
2021-02-23 15:00:57 -08:00
Brad Fitzpatrick 1aeeeb7e45 net/netcheck: do PCP discovery without side effects
Manually cherry-picked subset of c64bd587ae
back into the 1.4 branch.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-02-23 15:00:57 -08:00
David Anderson 3b9fcc2b81 wgengine/monitor: don't log any single-IP routes added to the tailscale table.
Signed-off-by: David Anderson <danderson@tailscale.com>
(cherry picked from commit 6e42430ad8)
2021-02-23 15:00:57 -08:00
David Anderson d51ebe38d4 wgengine/monitor: on linux, also monitor for IPv6 changes.
Signed-off-by: David Anderson <danderson@tailscale.com>
(cherry picked from commit df5adb2e23)
2021-02-23 15:00:57 -08:00
Brad Fitzpatrick b8b83c8a3f wgengine/magicsock: fix discoEndpoint caching bug when a node key changes
Fixes #1391

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit e9e4f1063d)
2021-02-23 15:00:53 -08:00
Brad Fitzpatrick 2d0bd18a08 wgengine/tsdns: skip test that requires local IPv6 when IPv6 unavailable
Fixes #1292

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-02-16 15:07:35 -05:00
Brad Fitzpatrick 16c67870d3 wgengine/winnet: don't build on non-windows
It only affects 'go install ./...', etc, and only on darwin/arm64 (M1 Macs) where
the go-ole package doesn't compile.

No need to build it.

Updates #943

(cherry picked from commit cbd6224ca4)
2021-02-16 11:51:28 -08:00
David Anderson 64a9656c01 VERSION.txt: this is v1.4.4
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-02-10 13:28:13 -08:00
Brad Fitzpatrick b26876427c wgengine/router: add another Windows firewall rule to allow incoming UDP
Based on @sailorfrag's research.

Fixes #1312

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-02-10 13:19:26 -08:00
Brad Fitzpatrick cc2ec141fe wgengine/magicsock: reconnect to DERP home after network comes back up
Updates #1310
2021-02-10 13:19:26 -08:00
Brad Fitzpatrick d2c1ae7ed4 VERSION.txt: this is v1.4.3 2021-02-08 19:22:17 -08:00
Brad Fitzpatrick 121f5a00f7 wgengine/magicsock: fix DERP reader hang regression during concurrent reads
Fixes #1282

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 6b365b0239)
2021-02-08 14:39:02 -08:00
Brad Fitzpatrick d06ceffd02 wgengine/magicsock: add disabled failing (deadlocking) test for #1282
The fix can make this test run unconditionally.

This moves code from 5c619882bc for
testability but doesn't fix it yet. The #1282 problem remains (when I
wrote its wake-up mechanism, I forgot there were N DERP readers
funneling into 1 UDP reader, and the code just isn't correct at all
for that case).

Also factor out some test helper code from BenchmarkReceiveFrom.

The refactoring in magicsock.go for testability should have no
behavior change.

(cherry picked from commit 6d2b8df06d)
2021-02-08 14:38:37 -08:00
Brad Fitzpatrick 0cf60b5185 control/controlclient: don't call lite endpoint update path when logged out
This was the other half of the #1271 problem.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit e8d4afedd1)
2021-02-05 10:04:04 -08:00
Brad Fitzpatrick 910682c851 control/controlclient: avoid crash sending map request with zero node key
Fixes #1271

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 7529b74018)
2021-02-05 10:04:03 -08:00
Brad Fitzpatrick c027962893 wgengine: access flow pending problem with lock held
Missed review feedback from just-submitted d37058af72.

(cherry picked from commit 70eb05fd47)
2021-02-04 11:19:54 -08:00
Brad Fitzpatrick acc50d6b67 net/packet: add some more TSMP packet reject reasons and MaybeBroken bit
Unused for now, but I want to backport this commit to 1.4 so 1.6 can
start sending these and then at least 1.4 logs will stringify nicely.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit d37058af72)
2021-02-04 10:59:17 -08:00
Brad Fitzpatrick a2ab23ba6c wgengine/magicsock: filter disco packets and packets when stopped from wireguard
Fixes #1167
Fixes tailscale/corp#219

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit f7eed25bb9)
2021-02-04 09:38:24 -08:00
Brad Fitzpatrick 71b13b5ac2 cmd/tailscale: fix IPN message reading stall in tailscale status -web
Fixes #1234
Updates #1254

(cherry picked from commit 9a70789853)
2021-02-02 14:58:47 -08:00
David Crawshaw 1c238cdce6 net/interfaces: use a uint32_t for ipv4 address
The code was using a C "int", which is a signed 32-bit integer.
That means some valid IP addresses were negative numbers.
(In particular, the default router address handed out by AT&T
fiber: 192.168.1.254. No I don't know why they do that.)
A negative number is < 255, and so was treated by the Go code
as an error.

This fixes the unit test failure:

	$ go test -v -run=TestLikelyHomeRouterIPSyscallExec ./net/interfaces
	=== RUN   TestLikelyHomeRouterIPSyscallExec
	    interfaces_darwin_cgo_test.go:15: syscall() = invalid IP, false, netstat = 192.168.1.254, true
	--- FAIL: TestLikelyHomeRouterIPSyscallExec (0.00s)

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
(cherry picked from commit d139fa9c92)
2021-02-02 14:58:45 -08:00
David Anderson a9f58fe822 VERSION.txt: this is v1.4.2
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-02-01 16:24:43 -08:00
David Anderson 5417ca69a7 wgengine/router: probe better for v6 policy routing support.
Previously we disabled v6 support if the disable_policy knob was
missing in /proc, but some kernels support policy routing without
exposing the toggle. So instead, treat disable_policy absence as a
"maybe", and make the direct `ip -6 rule` probing a bit more
elaborate to compensate.

Fixes #1241.

Signed-off-by: David Anderson <danderson@tailscale.com>
(cherry picked from commit 267531e4f8)
2021-02-01 16:13:32 -08:00
Brad Fitzpatrick 03e640e94d wgengine/router: clarify disabled IPv6 message on Linux
(cherry picked from commit 1f97037b94baf92bf26598c38987e4d69079efb7)
2021-02-01 14:09:50 -08:00
Brad Fitzpatrick 138bcae525 cmd/tailscale/cli: recommend sudo for 'tailscale up' on failure
Fixes #1220

(cherry picked from commit c7d4bf2333)
2021-02-01 13:54:48 -08:00
Brad Fitzpatrick bb0ef32dd2 cmd/tailscaled/tailscaled.service: revert recent hardening for now
It broke Debian Stretch. We'll try again later.

Updates #1245

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 2889fabaef)
2021-02-01 13:38:09 -08:00
David Anderson dde7ba4ecf VERSION.txt: this is v1.4.1
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-28 13:54:32 -08:00
Brad Fitzpatrick fc30cff688 wgengine/router: don't configure IPv6 on Linux when IPv6 is unavailable
Fixes #1214

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit c7fc4a06da)
2021-01-28 13:45:59 -08:00
David Anderson 775fe13e27 cmd/tailscaled: add /run to the allowed paths for iptables.
Signed-off-by: David Anderson <danderson@tailscale.com>
(cherry picked from commit de497358b8)
2021-01-28 13:45:08 -08:00
Josh Bleecher Snyder 2e33fdfe67 types/logger: fix rateFree interaction with verbosity prefixes
We log lines like this:

c.logf("[v1] magicsock: disco: %v->%v (%v, %v) sent %v", c.discoShort, dstDisco.ShortString(), dstKey.ShortString(), derpStr(dst.String()), disco.MessageSummary(m))

The leading [v1] causes it to get unintentionally rate limited.
Until we have a proper fix, work around it.

Fixes #1216

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
(cherry picked from commit 1e28207a15)
2021-01-28 10:22:09 -08:00
David Anderson 3d7cff91b3 VERSION.txt: this is v1.4.0
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-01-27 15:40:21 -08:00
30 changed files with 864 additions and 218 deletions

View File

@ -1 +1 @@
1.3.0
1.4.4

View File

@ -65,7 +65,17 @@ func runStatus(ctx context.Context, args []string) error {
log.Fatal(*n.ErrMessage)
}
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)

View File

@ -228,7 +228,16 @@ func runUp(ctx context.Context, args []string) error {
AuthKey: upArgs.authKey,
Notify: func(n ipn.Notify) {
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 {
switch *s {

View File

@ -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/derpmap from tailscale.com/cmd/tailscale/cli
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/ipn 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/portlist from tailscale.com/ipn
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+
W tailscale.com/tsconst from tailscale.com/net/interfaces
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
html from tailscale.com/ipn/ipnstate
io from bufio+
io/ioutil from crypto/tls+
io/fs from crypto/rand+
io/ioutil from github.com/godbus/dbus/v5+
log from expvar+
math from compress/flate+
math/big from crypto/dsa+

View File

@ -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/derphttp from tailscale.com/net/netcheck+
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/ipn from tailscale.com/ipn/ipnserver
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/safesocket 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+
W tailscale.com/tsconst from tailscale.com/net/interfaces
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/fnv from tailscale.com/wgengine/magicsock
hash/maphash from go4.org/mem
html from html/template+
html/template from net/http/pprof
html from net/http/pprof+
io from bufio+
io/ioutil from crypto/tls+
io/fs from crypto/rand+
io/ioutil from github.com/godbus/dbus/v5+
log from expvar+
math from compress/flate+
math/big from crypto/dsa+
@ -255,8 +256,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
sync/atomic from context+
syscall from crypto/rand+
text/tabwriter from runtime/pprof
text/template from html/template
text/template/parse from html/template+
time from compress/gzip+
unicode from bytes+
unicode/utf16 from encoding/asn1+

View File

@ -20,22 +20,5 @@ CacheDirectory=tailscale
CacheDirectoryMode=0750
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]
WantedBy=multi-user.target

View File

@ -18,6 +18,7 @@ import (
"time"
"golang.org/x/oauth2"
"tailscale.com/health"
"tailscale.com/logtail/backoff"
"tailscale.com/tailcfg"
"tailscale.com/types/empty"
@ -114,6 +115,8 @@ type Client struct {
closed bool
newMapCh chan struct{} // readable when we must restart a map request
unregisterHealthWatch func()
mu sync.Mutex // mutex guards the following fields
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.mapCtx, c.mapCancel = context.WithCancel(context.Background())
c.unregisterHealthWatch = health.RegisterWatcher(c.onHealthChange)
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.
@ -213,7 +223,7 @@ func (c *Client) sendNewMapRequest() {
// 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
// (which starts by sending a new map request)
if !c.inPollNetMap || c.inLiteMapUpdate {
if !c.inPollNetMap || c.inLiteMapUpdate || !c.loggedIn {
c.mu.Unlock()
c.cancelMapSafely()
return
@ -698,6 +708,7 @@ func (c *Client) Shutdown() {
c.logf("client.Shutdown: inSendStatus=%v", inSendStatus)
if !closed {
c.unregisterHealthWatch()
close(c.quit)
c.cancelAuth()
<-c.authDone

View File

@ -22,6 +22,7 @@ import (
"net/url"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"sort"
@ -34,6 +35,7 @@ import (
"golang.org/x/crypto/nacl/box"
"golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/health"
"tailscale.com/log/logheap"
"tailscale.com/net/dnscache"
"tailscale.com/net/netns"
@ -229,10 +231,25 @@ func NewHostinfo() *tailcfg.Hostinfo {
Hostname: hostname,
OS: version.OS(),
OSVersion: osv,
Package: packageType(),
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
// next update. It reports whether the Hostinfo has changed.
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
c.mu.Unlock()
if persist.PrivateNodeKey.IsZero() {
return errors.New("privateNodeKey is zero")
}
if backendLogID == "" {
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,
OmitPeers: cb == nil,
}
var extraDebugFlags []string
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
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 {
request.Compress = "zstd"

71
health/health.go 100644
View File

@ -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)
}
}

View File

@ -146,6 +146,10 @@ func (bs *BackendServer) GotFakeCommand(ctx context.Context, cmd *Command) error
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 {
if cmd.Version != version.Long && !cmd.AllowVersionSkew {
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) {
msg := "permission denied"
msg := ErrMsgPermissionDenied
bs.send(Notify{ErrMessage: &msg})
return nil
}

View File

@ -15,7 +15,7 @@ package interfaces
// privateGatewayIPFromRoute returns the private gateway ip address from rtm, if it exists.
// Otherwise, it returns 0.
int privateGatewayIPFromRoute(struct rt_msghdr2 *rtm)
uint32_t privateGatewayIPFromRoute(struct rt_msghdr2 *rtm)
{
// sockaddrs are after the message header
struct sockaddr* dst_sa = (struct sockaddr *)(rtm + 1);
@ -38,7 +38,7 @@ int privateGatewayIPFromRoute(struct rt_msghdr2 *rtm)
return 0; // gateway not IPv4
struct sockaddr_in* gateway_si= (struct sockaddr_in *)gateway_sa;
int ip;
uint32_t ip;
ip = gateway_si->sin_addr.s_addr;
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.
// On an error, it returns an error code in (0, 255].
// Any private gateway IP address is > 255.
int privateGatewayIP()
uint32_t privateGatewayIP()
{
size_t needed;
int mib[6];
@ -90,7 +90,7 @@ int privateGatewayIP()
struct rt_msghdr2 *rtm;
for (next = buf; next < lim; next += rtm->rtm_msglen) {
rtm = (struct rt_msghdr2 *)next;
int ip;
uint32_t ip;
ip = privateGatewayIPFromRoute(rtm);
if (ip) {
free(buf);

View File

@ -8,9 +8,7 @@ package netcheck
import (
"bufio"
"context"
"crypto/rand"
"crypto/tls"
"encoding/binary"
"errors"
"fmt"
"io"
@ -701,16 +699,14 @@ func (rs *reportState) probePortMapServices() {
return
}
defer uc.Close()
tempPort := uc.LocalAddr().(*net.UDPAddr).Port
uc.SetReadDeadline(time.Now().Add(portMapServiceProbeTimeout))
// Send request packets for all three protocols.
uc.WriteTo(uPnPPacket, port1900)
uc.WriteTo(pmpPacket, port5351)
uc.WriteTo(pcpPacket(myIP, tempPort, false), port5351)
uc.WriteTo(pcpAnnounceRequest(myIP), port5351)
res := make([]byte, 1500)
sentPCPDelete := false
for {
n, addr, err := uc.ReadFrom(res)
if err != nil {
@ -725,17 +721,8 @@ func (rs *reportState) probePortMapServices() {
if n == 12 && res[0] == 0x00 { // right length and version 0
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)
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")
// pcpPacket generates a PCP packet with a MAP opcode.
func pcpPacket(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte {
const udpProtoNumber = 17
lifetimeSeconds := uint32(1)
if delete {
lifetimeSeconds = 0
}
const opMap = 1
const (
pcpVersion = 2
pcpOpAnnounce = 0
)
// 24 byte header + 36 byte map opcode
pkt := make([]byte, (32+32+128)/8+(96+8+24+16+16+128)/8)
// The header (https://tools.ietf.org/html/rfc6887#section-7.1)
pkt[0] = 2 // version
pkt[1] = opMap
binary.BigEndian.PutUint32(pkt[4:8], lifetimeSeconds)
// pcpAnnounceRequest generates a PCP packet with an ANNOUNCE opcode.
func pcpAnnounceRequest(myIP netaddr.IP) []byte {
// See https://tools.ietf.org/html/rfc6887#section-7.1
pkt := make([]byte, 24)
pkt[0] = pcpVersion // version
pkt[1] = pcpOpAnnounce
myIP16 := myIP.As16()
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
}

View File

@ -23,12 +23,14 @@ import (
// Tailscale node has rejected the connection from another. Unlike a
// 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)
// * 'A' or 'S' (RejectedDueToACLs, RejectedDueToShieldsUp)
// * srcPort 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
// 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
Proto IPProto // proto that was rejected (TCP or UDP)
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 {
return flowtrack.Tuple{Src: rh.Src, Dst: rh.Dst}
}
@ -52,14 +67,32 @@ func (rh TailscaleRejectedHeader) String() string {
type TSMPType uint8
const (
// TSMPTypeRejectedConn is the type byte for a TailscaleRejectedHeader.
TSMPTypeRejectedConn TSMPType = '!'
)
type TailscaleRejectReason byte
// IsZero reports whether r is the zero value, representing no rejection.
func (r TailscaleRejectReason) IsZero() bool { return r == TailscaleRejectReasonNone }
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'
// 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 {
@ -68,22 +101,32 @@ func (r TailscaleRejectReason) String() string {
return "acl"
case RejectedDueToShieldsUp:
return "shields"
case RejectedDueToIPForwarding:
return "host-ip-forwarding-unavailable"
case RejectedDueToHostFirewall:
return "host-firewall"
}
return fmt.Sprintf("0x%02x", byte(r))
}
func (h TailscaleRejectedHeader) hasFlags() bool {
return h.MaybeBroken // the only one currently
}
func (h TailscaleRejectedHeader) Len() int {
var ipHeaderLen int
if h.IPSrc.Is4() {
ipHeaderLen = ip4HeaderLength
} else if h.IPSrc.Is6() {
ipHeaderLen = ip6HeaderLength
}
return ipHeaderLen +
1 + // TSMPType byte
v := 1 + // TSMPType byte
1 + // IPProto byte
1 + // TailscaleRejectReason byte
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 {
@ -117,6 +160,14 @@ func (h TailscaleRejectedHeader) Marshal(buf []byte) error {
buf[2] = byte(h.Reason)
binary.BigEndian.PutUint16(buf[3:5], h.Src.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
}
@ -129,12 +180,17 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo
if len(p) < 7 || p[0] != byte(TSMPTypeRejectedConn) {
return
}
return TailscaleRejectedHeader{
h = TailscaleRejectedHeader{
Proto: IPProto(p[1]),
Reason: TailscaleRejectReason(p[2]),
IPSrc: pp.Src.IP,
IPDst: pp.Dst.IP,
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])},
}, true
}
if len(p) > 7 {
flags := p[7]
h.MaybeBroken = (flags & rejectFlagBitMaybeBroken) != 0
}
return h, true
}

View File

@ -37,6 +37,18 @@ func TestTailscaleRejectedHeader(t *testing.T) {
},
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 {
gotStr := tt.h.String()

View File

@ -406,6 +406,7 @@ type Hostinfo struct {
BackendLogID string `json:",omitempty"` // logtail ID of backend instance
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")
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")
Hostname string // name of the host the client runs on
ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections
@ -634,6 +635,10 @@ type MapRequest struct {
// Current DebugFlags values are:
// * "warn-ip-forwarding-off": client is trying to be a subnet
// 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
// peers that are unreachable per ACLS.
DebugFlags []string `json:",omitempty"`

View File

@ -107,6 +107,7 @@ var _HostinfoNeedsRegeneration = Hostinfo(struct {
BackendLogID string
OS string
OSVersion string
Package string
DeviceModel string
Hostname string
ShieldsUp bool

View File

@ -25,7 +25,7 @@ func fieldsOf(t reflect.Type) (fields []string) {
func TestHostinfoEqual(t *testing.T) {
hiHandles := []string{
"IPNVersion", "FrontendLogID", "BackendLogID",
"OS", "OSVersion", "DeviceModel", "Hostname",
"OS", "OSVersion", "Package", "DeviceModel", "Hostname",
"ShieldsUp", "ShareeNode",
"GoArch",
"RoutableIPs", "RequestTags",

View File

@ -64,9 +64,9 @@ type limitData struct {
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.
var rateFreePrefix = []string{
var rateFree = []string{
"magicsock: disco: ",
"magicsock: CreateEndpoint:",
}
@ -93,8 +93,8 @@ func RateLimitedFn(logf Logf, f time.Duration, burst int, maxCache int) Logf {
)
judge := func(format string) verdict {
for _, pfx := range rateFreePrefix {
if strings.HasPrefix(format, pfx) {
for _, sub := range rateFree {
if strings.Contains(format, sub) {
return allow
}
}

View File

@ -12,6 +12,7 @@ import (
crand "crypto/rand"
"encoding/binary"
"errors"
"expvar"
"fmt"
"hash/fnv"
"math"
@ -153,13 +154,10 @@ type Conn struct {
// derpRecvCh is used by ReceiveIPv4 to read DERP messages.
derpRecvCh chan derpReadResult
// derpRecvCountAtomic is atomically incremented by runDerpReader whenever
// a DERP message arrives. It's incremented before runDerpReader is interrupted.
// derpRecvCountAtomic is how many derpRecvCh sends are pending.
// It's incremented by runDerpReader whenever a DERP message
// arrives and decremented when they're read.
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
// 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
// new connection that'll fail.
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
@ -960,6 +961,13 @@ func (c *Conn) setNearestDERP(derpNum int) (wantDERP bool) {
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
// DERP node.
//
@ -1353,6 +1361,8 @@ type derpReadResult struct {
// copyBuf is called to copy the data to dst. It returns how
// much data was copied, which will be n if dst is large
// 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
}
@ -1440,28 +1450,62 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d
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 {
case <-ctx.Done():
return
case c.derpRecvCh <- res:
select {
case <-ctx.Done():
return
case <-didCopy:
continue
}
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 {
addr netaddr.IPPort
pubKey key.Public
@ -1551,20 +1595,20 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, error) {
}
func (c *Conn) derpPacketArrived() bool {
rc := atomic.LoadInt64(&c.derpRecvCountAtomic)
if rc != c.derpRecvCountLast {
c.derpRecvCountLast = rc
return true
}
return false
return atomic.LoadInt64(&c.derpRecvCountAtomic) > 0
}
// 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
// aborts the pconn4 read deadline to make it fail.
func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) {
var pAddr net.Addr
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 the pconn4 read failed, the likely reason is a DERP reader received
// 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:
// we'll get the same error from ReadFrom later.
if c.derpPacketArrived() {
c.pconn4.SetReadDeadline(time.Time{}) // restore
n, ep, err = c.receiveIPv4DERP(b)
if err == errLoopAgain {
continue
}
return n, ep, err
goto ReadDERP
}
return 0, nil, err
}
if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint4); ok {
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.
//
// 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) {
ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone)
if !ok {
@ -1600,6 +1650,13 @@ func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep
if c.handleDiscoMessage(b, ipp) {
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() {
ep = cache.de
} else {
@ -1641,6 +1698,13 @@ func (c *Conn) receiveIPv4DERP(b []byte) (n int, ep conn.Endpoint, err error) {
case dm = <-c.derpRecvCh:
// 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
n, regionID = dm.n, dm.regionID
@ -1750,8 +1814,8 @@ func (c *Conn) sendDiscoMessage(dst netaddr.IPPort, dstKey tailcfg.NodeKey, dstD
return sent, err
}
// handleDiscoMessage reports whether msg was a Tailscale inter-node discovery message
// that was handled.
// handleDiscoMessage handles a discovery message and reports whether
// msg was a Tailscale inter-node discovery message.
//
// 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
// 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
if len(msg) < headerLen || string(msg[:len(disco.Magic)]) != disco.Magic {
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
copy(sender[:], msg[len(disco.Magic):])
@ -1774,20 +1845,21 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
defer c.mu.Unlock()
if c.closed {
return true
return
}
if debugDisco {
c.logf("magicsock: disco: got disco-looking frame from %v", sender.ShortString())
}
if c.privateKey.IsZero() {
// 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 debugDisco {
c.logf("magicsock: disco: ignoring disco-looking frame, no local key")
}
return false
return
}
peerNode, ok := c.nodeOfDisco[sender]
@ -1795,9 +1867,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
if debugDisco {
c.logf("magicsock: disco: ignoring disco-looking frame, don't know node for %v", sender.ShortString())
}
// Returning false keeps passing it down, to WireGuard.
// WireGuard will almost surely reject it, but give it a chance.
return false
return
}
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())
if c.noteRecvActivity == nil {
c.logf("magicsock: [unexpected] have node without endpoint, without c.noteRecvActivity hook")
return false
return
}
needsRecvActivityCall = true
} 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
// released the lock, which isn't much:
if c.closed || c.privateKey.IsZero() {
return true
return
}
de, ok = c.endpointOfDisco[sender]
if !ok {
@ -1838,7 +1908,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool {
return false
}
c.logf("magicsock: [unexpected] lazy endpoint not created for %v, %v", peerNode.Key.ShortString(), sender.ShortString())
return false
return
}
if !endpointFound0 {
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)
}
// TODO(bradfitz): add some counter for this that logs rarely
return false
return
}
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
// be too spammy for old clients.
// TODO(bradfitz): add some counter for this that logs rarely
return true
return
}
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)
case *disco.Pong:
if de == nil {
return true
return
}
de.handlePongConnLocked(dm, src)
case *disco.CallMeMaybe:
if src.IP != derpMagicIPAddr {
// CallMeMaybe messages should only come via DERP.
c.logf("[unexpected] CallMeMaybe packets should only come via DERP")
return true
return
}
if de != nil {
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)
}
}
return true
return
}
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.networkUp.Set(up)
if !up {
if up {
c.startDerpHomeConnectLocked()
} else {
c.closeAllDerpLocked("network-down")
}
}
@ -2082,6 +2153,7 @@ func (c *Conn) SetPrivateKey(privateKey wgkey.Private) error {
return nil
}
c.privateKey = newKey
c.havePrivateKey.Set(!newKey.IsZero())
if oldKey.IsZero() {
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.
if c.myDerp != 0 && !newKey.IsZero() {
c.logf("magicsock: private key changed, reconnecting to home derp-%d", c.myDerp)
c.goDerpConnect(c.myDerp)
c.startDerpHomeConnectLocked()
}
if newKey.IsZero() {
@ -2192,8 +2264,12 @@ func (c *Conn) SetNetworkMap(nm *controlclient.NetworkMap) {
continue
}
numDisco++
if ep, ok := c.endpointOfDisco[n.DiscoKey]; ok {
if ep, ok := c.endpointOfDisco[n.DiscoKey]; ok && ep.publicKey == n.Key {
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.closeAllDerpLocked("rebind")
haveKey := !c.privateKey.IsZero()
if !c.privateKey.IsZero() {
c.startDerpHomeConnectLocked()
}
c.mu.Unlock()
if haveKey {
c.goDerpConnect(c.myDerp)
}
c.resetEndpointStates()
}

View File

@ -17,6 +17,7 @@ import (
"net/http"
"net/http/httptest"
"os"
"runtime"
"strconv"
"strings"
"sync"
@ -1410,19 +1411,136 @@ func Test32bitAlignment(t *testing.T) {
atomic.AddInt64(&c.derpRecvCountAtomic, 1)
}
func BenchmarkReceiveFrom(b *testing.B) {
port := pickPort(b)
// newNonLegacyTestConn returns a new Conn with DisableLegacyNetworking set true.
func newNonLegacyTestConn(t testing.TB) *Conn {
t.Helper()
port := pickPort(t)
conn, err := NewConn(Options{
Logf: b.Logf,
Logf: t.Logf,
Port: port,
EndpointsFunc: func(eps []string) {
b.Logf("endpoints: %q", eps)
t.Logf("endpoints: %q", eps)
},
DisableLegacyNetworking: true,
})
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()
sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0")
@ -1431,20 +1549,7 @@ func BenchmarkReceiveFrom(b *testing.B) {
}
defer sendConn.Close()
// 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}
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()))
addTestEndpoint(conn, sendConn)
var dstAddr net.Addr = conn.pconn4.LocalAddr()
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)
}
}

View File

@ -39,12 +39,11 @@ type nlConn struct {
func newOSMon(logf logger.Logf) (osMon, error) {
conn, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
// IPv4 address and route changes. Routes get us most of the
// events of interest, but we need address as well to cover
// things like DHCP deciding to give us a new address upon
// renewal - routing wouldn't change, but all reachability
// would.
Groups: unix.RTMGRP_IPV4_IFADDR | unix.RTMGRP_IPV4_ROUTE,
// Routes get us most of the events of interest, but we need
// address as well to cover things like DHCP deciding to give
// us a new address upon renewal - routing wouldn't change,
// but all reachability would.
Groups: unix.RTMGRP_IPV4_IFADDR | unix.RTMGRP_IPV6_IFADDR | unix.RTMGRP_IPV4_ROUTE | unix.RTMGRP_IPV6_ROUTE,
})
if err != nil {
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)
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,
// which we make ourselves.
} else {

View File

@ -30,6 +30,12 @@ func debugConnectFailures() bool {
type pendingOpenFlow struct {
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) {
@ -45,6 +51,17 @@ func (e *userspaceEngine) removeFlow(f flowtrack.Tuple) (removed bool) {
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) {
res = filter.Accept // always
@ -54,7 +71,9 @@ func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN)
if !ok {
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)
}
return
@ -106,14 +125,20 @@ func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN
func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
e.mu.Lock()
if _, ok := e.pendOpen[flow]; !ok {
of, ok := e.pendOpen[flow]
if !ok {
// Not a tracked flow, or already handled & deleted.
e.mu.Unlock()
return
}
delete(e.pendOpen, flow)
problem := of.problem
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.
n, ok := e.magicConn.PeerForIP(flow.Dst.IP)
if !ok {

View File

@ -116,7 +116,7 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (
v6err := checkIPv6()
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
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
// fails.
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 {
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
// fails.
func (r *linuxRouter) delAddress(addr netaddr.IPPrefix) error {
if !r.v6Available && addr.IP.Is6() {
return nil
}
if err := r.delLoopbackRule(addr.IP); err != nil {
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
// route fails.
func (r *linuxRouter) addRoute(cidr netaddr.IPPrefix) error {
if !r.v6Available && cidr.IP.Is6() {
return nil
}
args := []string{
"ip", "route", "add",
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
// route fails.
func (r *linuxRouter) delRoute(cidr netaddr.IPPrefix) error {
if !r.v6Available && cidr.IP.Is6() {
return nil
}
args := []string{
"ip", "route", "del",
normalizeCIDR(cidr),
@ -460,7 +471,42 @@ func (r *linuxRouter) delRoute(cidr netaddr.IPPrefix) error {
if r.ipRuleAvailable {
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.
@ -969,25 +1015,43 @@ func cidrDiff(kind string, old map[netaddr.IPPrefix]bool, new []netaddr.IPPrefix
ret[cidr] = true
}
var delFail []error
for cidr := range old {
if newMap[cidr] {
continue
}
if err := del(cidr); err != nil {
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 {
if old[cidr] {
continue
}
if err := add(cidr); err != nil {
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
@ -1034,18 +1098,22 @@ func checkIPv6() error {
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")
if err != nil {
// Absent knob means policy routing is unsupported.
return err
if err == nil {
disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs)))
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 {
return errors.New("disable_policy has invalid bool")
}
if disabled {
return errors.New("disable_policy is set")
if err := checkIPRuleSupportsV6(); err != nil {
return fmt.Errorf("kernel doesn't support IPv6 policy routing: %w", err)
}
// Some distros ship ip6tables separately from iptables.
@ -1053,10 +1121,6 @@ func checkIPv6() error {
return err
}
if err := checkIPRuleSupportsV6(); err != nil {
return err
}
return nil
}
@ -1077,13 +1141,17 @@ func supportsV6NAT() bool {
}
func checkIPRuleSupportsV6() error {
// First add a rule for "ip rule del" to delete.
// We ignore the "add" operation's error because it can also
// fail if the rule already exists.
exec.Command("ip", "-6", "rule", "add",
"pref", "123", "fwmark", tailscaleBypassMark, "table", fmt.Sprint(tailscaleRouteTable)).Run()
out, err := exec.Command("ip", "-6", "rule", "del",
"pref", "123", "fwmark", tailscaleBypassMark, "table", fmt.Sprint(tailscaleRouteTable)).CombinedOutput()
add := []string{"-6", "rule", "add", "pref", "1234", "fwmark", tailscaleBypassMark, "table", tailscaleRouteTable}
del := []string{"-6", "rule", "del", "pref", "1234", "fwmark", tailscaleBypassMark, "table", tailscaleRouteTable}
// First delete the rule unconditionally, and don't check for
// errors. This is just cleaning up anything that might be already
// there.
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 {
out = bytes.TrimSpace(out)
var detail interface{} = out
@ -1092,5 +1160,8 @@ func checkIPRuleSupportsV6() error {
}
return fmt.Errorf("ip -6 rule failed: %s", detail)
}
// Delete again.
exec.Command("ip", del...).Run()
return nil
}

View File

@ -5,14 +5,18 @@
package router
import (
"bytes"
"errors"
"fmt"
"math/rand"
"os"
"sort"
"strings"
"sync/atomic"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/tailscale/wireguard-go/tun"
"inet.af/netaddr"
)
@ -595,3 +599,69 @@ func (o *fakeOS) output(args ...string) ([]byte, error) {
}
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)
}
}

View File

@ -7,6 +7,7 @@ package router
import (
"context"
"fmt"
"os"
"os/exec"
"sync"
"syscall"
@ -121,11 +122,12 @@ func cleanup(logf logger.Logf, interfaceName string) {
type firewallTweaker struct {
logf logger.Logf
mu sync.Mutex
running bool // doAsyncSet goroutine is running
known bool // firewall is in known state (in lastVal)
want []string // next value we want, or "" to delete the firewall rule
lastVal []string // last set value, if known
mu sync.Mutex
didProcRule bool
running bool // doAsyncSet goroutine is running
known bool // firewall is in known state (in lastVal)
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) }
@ -177,6 +179,7 @@ func (ft *firewallTweaker) doAsyncSet() {
return
}
needClear := !ft.known || len(ft.lastVal) > 0 || len(val) == 0
needProcRule := !ft.didProcRule
ft.mu.Unlock()
if needClear {
@ -189,6 +192,37 @@ func (ft *firewallTweaker) doAsyncSet() {
d, _ := ft.runFirewall("delete", "rule", "name=Tailscale-In", "dir=in")
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
for _, cidr := range val {
ft.logf("adding Tailscale-In rule to allow %v ...", cidr)

View File

@ -5,6 +5,8 @@
package tsdns
import (
"testing"
"github.com/miekg/dns"
"inet.af/netaddr"
)
@ -71,7 +73,7 @@ func resolveToNXDOMAIN(w dns.ResponseWriter, req *dns.Msg) {
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"}
waitch := make(chan struct{})
@ -79,7 +81,11 @@ func serveDNS(addr string) (*dns.Server, chan error) {
errch := make(chan error, 1)
go func() {
errch <- server.ListenAndServe()
err := server.ListenAndServe()
if err != nil {
tb.Logf("ListenAndServe(%q): %v", addr, err)
}
errch <- err
close(errch)
}()

View File

@ -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) {
rc := tstest.NewResourceCheck()
defer rc.Assert(t)
if !ipv6Works() {
t.Skip("skipping test that requires localhost IPv6")
}
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
dnsHandleFunc("nxdomain.site.", resolveToNXDOMAIN)
v4server, v4errch := serveDNS("127.0.0.1:0")
v6server, v6errch := serveDNS("[::1]:0")
v4server, v4errch := serveDNS(t, "127.0.0.1:0")
v6server, v6errch := serveDNS(t, "[::1]:0")
defer func() {
if err := <-v4errch; err != nil {
@ -372,7 +385,7 @@ func TestDelegate(t *testing.T) {
func TestDelegateCollision(t *testing.T) {
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() {
if err := <-errch; err != nil {
t.Errorf("server error: %v", err)
@ -474,7 +487,7 @@ func TestConcurrentSetMap(t *testing.T) {
func TestConcurrentSetUpstreams(t *testing.T) {
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() {
if err := <-errch; err != nil {
t.Errorf("server error: %v", err)
@ -753,7 +766,7 @@ func TestTrimRDNSBonjourPrefix(t *testing.T) {
func BenchmarkFull(b *testing.B) {
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() {
if err := <-errch; err != nil {
b.Errorf("server error: %v", err)

View File

@ -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 {
// 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 res := t.PreFilterOut(p, t); res.IsDrop() {

View File

@ -27,6 +27,7 @@ import (
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/health"
"tailscale.com/internal/deepprint"
"tailscale.com/ipn/ipnstate"
"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()}
}
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
}
}

View File

@ -2,13 +2,16 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package winnet
import (
"fmt"
"unsafe"
"github.com/go-ole/go-ole"
"github.com/go-ole/go-ole/oleutil"
"unsafe"
)
const CLSID_NetworkListManager = "{DCB00C01-570F-4A9B-8D69-199FDBA5723B}"