Compare commits

...

30 Commits
vm ... v1.20.4

Author SHA1 Message Date
Denton Gentry
8e32002cf3 VERSION.txt: this is v1.20.4
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-02-08 06:57:02 -08:00
Denton Gentry
2a482848d8 cli: remove RunSSHSet from 1.20 branch.
Field doesn't exist in 1.20 branch, causes tests to fail
(but no functional impact).

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-02-08 06:55:07 -08:00
Brad Fitzpatrick
9bb601ebe8 ipn/ipnlocal, wgengine/netstack: use netstack for peerapi server
We're finding a bunch of host operating systems/firewalls interact poorly
with peerapi. We either get ICMP errors from the host or users need to run
commands to allow the peerapi port:

https://github.com/tailscale/tailscale/issues/3842#issuecomment-1025133727

... even though the peerapi should be an internal implementation detail.

Rather than fight the host OS & firewalls, this change handles the
server side of peerapi entirely in netstack (except on iOS), so it
never makes its way to the host OS where it might be messed with. Two
main downsides are:

1) netstack isn't as fast, but we don't really need speed for peerapi.
   And actually, with fewer trips to/from the kernel, we might
   actually make up for some of the netstack performance loss by
   staying in userspace.

2) tcpdump / Wireshark etc packet captures will no longer see the peerapi
   traffic. Oh well. Crawshaw's been wanting to add packet capture server
   support to tailscaled, so we'll probably do that sooner now.

   A future change might also then use peerapi for the client-side
   (except on iOS).

Updates #3842 (probably fixes, as well as many exit node issues I bet)

Change-Id: Ibc25edbb895dc083d1f07bd3cab614134705aa39
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit bd90781b34) + edits
(and cherry picked part of commit f3c0023add)
2022-02-06 19:01:20 -08:00
Brad Fitzpatrick
a7e118052f cmd/tailscale: fix up --reset, again
Also fix a somewhat related printing bug in the process where
some paths would print "Success." inconsistently even
when there otherwise was no output (in the EditPrefs path)

Fixes #3830
Updates #3702 (which broke it once while trying to fix it)

Change-Id: Ic51e14526ad75be61ba00084670aa6a98221daa5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 02bdc654d5)
2022-02-06 19:01:20 -08:00
Aaron Bieber
df5d9aa4ef net/dns/resolvd: properly handle not having "search" entries
This prevents adding an empty "search" line when no search domains are set.

Signed-off-by: Aaron Bieber <aaron@bolddaemon.com>
(cherry picked from commit e5cd765e00)
2022-02-06 19:01:20 -08:00
Brad Fitzpatrick
6a949d39e0 net/interfaces: bound Linux /proc/net/route parsing
tailscaled was using 100% CPU on a machine with ~1M lines, 100MB+
of /proc/net/route data.

Two problems: in likelyHomeRouterIPLinux, we didn't stop reading the
file once we found the default route (which is on the first non-header
line when present). Which meant it was finding the answer and then
parsing 100MB over 1M lines unnecessarily. Second was that if the
default route isn't present, it'd read to the end of the file looking
for it. If it's not in the first 1,000 lines, it ain't coming, or at
least isn't worth having. (it's only used for discovering a potential
UPnP/PMP/PCP server, which is very unlikely to be present in the
environment of a machine with a ton of routes)

Change-Id: I2c4a291ab7f26aedc13885d79237b8f05c2fd8e4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 2a67beaacf)
2022-02-06 19:01:20 -08:00
Maisem Ali
d6fb860206 chirp: remove regex dependency
Signed-off-by: Maisem Ali <maisem@tailscale.com>
(cherry picked from commit e64cecac8e)
2022-02-03 11:25:41 -08:00
Maisem Ali
d8c78df2e6 chirp: handle multiline responses from BIRD
Also add tests to verify the parsing logic.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
(cherry picked from commit e3dccfd7ff)
2022-02-03 11:25:35 -08:00
Denton Gentry
ae33622c71 VERSION.txt: this is v1.20.3
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-01-25 20:35:24 -08:00
Brad Fitzpatrick
8e643357dc VERSION.txt: this is v1.20.2
Change-Id: I0d0905dd4f709591002e52a53846af8fba0aa660
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-01-20 08:26:03 -08:00
Aaron Bieber
59a1b849f0 net/dns/resolvd: store nameservers
Currently only search domains are stored. This was an oversight
(under?) on my part.

As things are now, when MagicDNS is on and "Override local DNS" is
off, the dns forwarder has to timeout before names resolve. This
introduces a pretty annoying lang that makes everything feel
extremely slow. You will also see an error: "upstream nameservers
not set".

I tested with "Override local DNS" on and off. In both situations
things seem to function as expected (and quickly).

Signed-off-by: Aaron Bieber <aaron@bolddaemon.com>
(cherry picked from commit 411c6c316c)
2022-01-20 08:12:22 -08:00
Brad Fitzpatrick
296d10a05d wgengine/netstack: clear TCP ECN bits before giving to gvisor
Updates #2642

Change-Id: Ic219442a2656dd9dc99ae1dd91e907fd3d924987
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit c64af5e676)
2022-01-20 08:12:19 -08:00
Josh Bleecher Snyder
f37cdaefa7 wgengine/magicsock: fix deadlock on shutdown
This fixes a deadlock on shutdown.
One goroutine is waiting to send on c.derpRecvCh before unlocking c.mu.
The other goroutine is waiting to lock c.mu before receiving from c.derpRecvCh.

#3736 has a more detailed explanation of the sequence of events.

Fixes #3736

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
(cherry picked from commit de4696da10)
2022-01-20 08:12:16 -08:00
Brad Fitzpatrick
1e6ca50e3b net/packet: fix typo in comment
Change-Id: Ia666609fde18db44bf38d4e656f490fc372ac3b6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 390490e7b1)
2022-01-20 08:12:13 -08:00
Brad Fitzpatrick
dbb6597381 wgengine/netstack: add a missing refcount decrement after packet injection
Fixes #3762
Updates #3745 (probably fixes?)

Change-Id: I1d3f0590fd5b8adfbc9110bc45ff717bb9e79aae
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 185825df11)
2022-01-20 08:12:05 -08:00
Brad Fitzpatrick
2e80227276 wgengine/netstack: add an Impl.Close method for tests
Change-Id: Idbb3fd6d749d3e4effdf96de77a1106584822fef
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 790e41645b)
2022-01-20 08:11:58 -08:00
Brad Fitzpatrick
96b76e4cc6 wgengine/netstack: add missing error logging in a RST case
Updates #2642

Change-Id: I9f2f8fd28fc980208b0739eb9caf9db7b0977c09
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 166fe3fb12)
2022-01-20 08:11:35 -08:00
Brad Fitzpatrick
ec04759c41 wgengine/netstack: fix netstack ping timeout on darwin
-W is milliseconds on darwin, not seconds, and empirically it's
milliseconds after a 1 second base.

Change-Id: I2520619e6699d9c505d9645ce4dfee4973555227
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 6be48dfcc6)
2022-01-20 08:11:29 -08:00
Denton Gentry
88c4bde778 VERSION.txt: this is v1.20.1
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-01-13 16:01:27 -08:00
Brad Fitzpatrick
7052c6fe25 wgengine/magicsock: fix lock ordering deadlock with derphttp
Fixes #3726

Change-Id: I32631a44dcc1da3ae47764728ec11ace1c78190d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit c6c39930cc)
2022-01-13 15:45:17 -08:00
David Anderson
e510abc8d0 net/dnscache: don't cancel the TLS context before writing to the result channel.
Cancelling the context makes the timeout goroutine race with the write that
reports a successful TLS handshake, so you can end up with a successful TLS
handshake that mysteriously reports that it timed out after ~0s in flight.

The context is always canceled and cleaned up as the function exits, which
happens mere microseconds later, so just let function exit clean up and
thereby avoid races.

Signed-off-by: David Anderson <danderson@tailscale.com>
(cherry picked from commit a9da6b73a8)
2022-01-13 15:02:30 -08:00
Denton Gentry
958917dce8 VERSION.txt: this is v1.20.0
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-01-12 17:11:42 -08:00
Brad Fitzpatrick
7c1a1aa5d9 tailcfg: no-op bump of MapRequest.Version
So 1.18 and 1.20 don't have the same.

Change-Id: Ib2cac7c11eb37d9a0c2fcb66630f1cae619a97f4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit a7da236d3d)
2022-01-12 17:04:53 -08:00
Brad Fitzpatrick
90423bf3de wgengine/netstack: make userspace ping work when tailscaled has CAP_NET_RAW
Updates #3710

Change-Id: Ief56c7ac20f5f09a2f940a1906b9efbf1b0d6932
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit a93937abc3)
2022-01-12 14:24:04 -08:00
Maisem Ali
0028a8d4d5 cmd/tailscale/cli/web: fix typo where the html template data was being
replaced instead of being appended to.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
(cherry picked from commit 9e8a432146)
2022-01-12 12:45:32 -08:00
Brad Fitzpatrick
8519cab83d net/dns/resolver: handle tabs as whitespace when ExitDNS parses resolv.conf
On Synology, the /etc/resolv.conf has tabs in it, which this
resolv.conf parser (we have two, sigh) didn't handle.

Updates #3710

Change-Id: I86f8e09ad1867ee32fa211e85c382a27191418ea
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 24a04d07d1)
2022-01-12 12:45:31 -08:00
Brad Fitzpatrick
4cd062071c net/netns: remove a useless probe of the "ip" command
We stopped using it in 1.18.

Change-Id: If5adf1d99275286a89e2a05f0bce5193d9f6e5e3
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 51bc9a6d9d)
2022-01-11 20:27:24 -08:00
Brad Fitzpatrick
04a7f5066d cmd/tailscale: let 'tailscale up --reset' do a pref edit
The --reset shouldn't imply that a Backend.Start is necessary.  With
this, it can do a Backend.EditPrefs instead, which then doesn't do all
the heavy work that Start does. Also, Start on Windows behaves
slightly differently than Linux etc in some cases because of tailscaled
running in client mode on Windows (where the GUI supplies the prefs).

Fixes #3702

Change-Id: I75c9f08d5e0052bf623074030a3a7fcaa677abf6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit e6626366a2)
2022-01-11 13:02:22 -08:00
Brad Fitzpatrick
a14d445fc7 net/dns: make WSL network configuration opt-in for now
Tailscale seems to be breaking WSL configurations lately.  Until we
understand what changed, turn off Tailscale's involvement by default
and make it opt-in.

Updates #2815

Change-Id: I9977801f8debec7d489d97761f74000a4a33f71b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 8df3fa4638)
2022-01-11 10:11:02 -08:00
Aaron Bieber
1a4293c15c net/dns: teach OpenBSD's manager to talk to resolvd(8). (#2789)
OpenBSD 6.9 and up has a daemon which handles nameserver configuration. This PR
teaches the OpenBSD dns manager to check if resolvd is being used. If it is, it
will use the route(8) command to tell resolvd to add the Tailscale dns entries
to resolv.conf

Signed-off-by: Aaron Bieber <aaron@bolddaemon.com>
2022-01-11 08:59:18 -08:00
25 changed files with 908 additions and 102 deletions

View File

@@ -1 +1 @@
1.19.0
1.20.4

View File

@@ -19,9 +19,9 @@ func New(socket string) (*BIRDClient, error) {
if err != nil {
return nil, fmt.Errorf("failed to connect to BIRD: %w", err)
}
b := &BIRDClient{socket: socket, conn: conn, bs: bufio.NewScanner(conn)}
b := &BIRDClient{socket: socket, conn: conn, scanner: bufio.NewScanner(conn)}
// Read and discard the first line as that is the welcome message.
if _, err := b.readLine(); err != nil {
if _, err := b.readResponse(); err != nil {
return nil, err
}
return b, nil
@@ -29,9 +29,9 @@ func New(socket string) (*BIRDClient, error) {
// BIRDClient handles communication with the BIRD Internet Routing Daemon.
type BIRDClient struct {
socket string
conn net.Conn
bs *bufio.Scanner
socket string
conn net.Conn
scanner *bufio.Scanner
}
// Close closes the underlying connection to BIRD.
@@ -39,7 +39,7 @@ func (b *BIRDClient) Close() error { return b.conn.Close() }
// DisableProtocol disables the provided protocol.
func (b *BIRDClient) DisableProtocol(protocol string) error {
out, err := b.exec("disable %s\n", protocol)
out, err := b.exec("disable %s", protocol)
if err != nil {
return err
}
@@ -53,7 +53,7 @@ func (b *BIRDClient) DisableProtocol(protocol string) error {
// EnableProtocol enables the provided protocol.
func (b *BIRDClient) EnableProtocol(protocol string) error {
out, err := b.exec("enable %s\n", protocol)
out, err := b.exec("enable %s", protocol)
if err != nil {
return err
}
@@ -65,19 +65,65 @@ func (b *BIRDClient) EnableProtocol(protocol string) error {
return fmt.Errorf("failed to enable %s: %v", protocol, out)
}
// BIRD CLI docs from https://bird.network.cz/?get_doc&v=20&f=prog-2.html#ss2.9
// Each session of the CLI consists of a sequence of request and replies,
// slightly resembling the FTP and SMTP protocols.
// Requests are commands encoded as a single line of text,
// replies are sequences of lines starting with a four-digit code
// followed by either a space (if it's the last line of the reply) or
// a minus sign (when the reply is going to continue with the next line),
// the rest of the line contains a textual message semantics of which depends on the numeric code.
// If a reply line has the same code as the previous one and it's a continuation line,
// the whole prefix can be replaced by a single white space character.
//
// Reply codes starting with 0 stand for action successfully completed messages,
// 1 means table entry, 8 runtime error and 9 syntax error.
func (b *BIRDClient) exec(cmd string, args ...interface{}) (string, error) {
if _, err := fmt.Fprintf(b.conn, cmd, args...); err != nil {
return "", err
}
return b.readLine()
fmt.Fprintln(b.conn)
return b.readResponse()
}
func (b *BIRDClient) readLine() (string, error) {
if !b.bs.Scan() {
return "", fmt.Errorf("reading response from bird failed")
// hasResponseCode reports whether the provided byte slice is
// prefixed with a BIRD response code.
// Equivalent regex: `^\d{4}[ -]`.
func hasResponseCode(s []byte) bool {
if len(s) < 5 {
return false
}
if err := b.bs.Err(); err != nil {
return "", err
for _, b := range s[:4] {
if '0' <= b && b <= '9' {
continue
}
return false
}
return b.bs.Text(), nil
return s[4] == ' ' || s[4] == '-'
}
func (b *BIRDClient) readResponse() (string, error) {
var resp strings.Builder
var done bool
for !done {
if !b.scanner.Scan() {
return "", fmt.Errorf("reading response from bird failed: %q", resp.String())
}
if err := b.scanner.Err(); err != nil {
return "", err
}
out := b.scanner.Bytes()
if _, err := resp.Write(out); err != nil {
return "", err
}
if hasResponseCode(out) {
done = out[4] == ' '
}
if !done {
resp.WriteRune('\n')
}
}
return resp.String(), nil
}

111
chirp/chirp_test.go Normal file
View File

@@ -0,0 +1,111 @@
// Copyright (c) 2022 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 chirp
import (
"bufio"
"errors"
"fmt"
"net"
"path/filepath"
"strings"
"testing"
)
type fakeBIRD struct {
net.Listener
protocolsEnabled map[string]bool
sock string
}
func newFakeBIRD(t *testing.T, protocols ...string) *fakeBIRD {
sock := filepath.Join(t.TempDir(), "sock")
l, err := net.Listen("unix", sock)
if err != nil {
t.Fatal(err)
}
pe := make(map[string]bool)
for _, p := range protocols {
pe[p] = false
}
return &fakeBIRD{
Listener: l,
protocolsEnabled: pe,
sock: sock,
}
}
func (fb *fakeBIRD) listen() error {
for {
c, err := fb.Accept()
if err != nil {
if errors.Is(err, net.ErrClosed) {
return nil
}
return err
}
go fb.handle(c)
}
}
func (fb *fakeBIRD) handle(c net.Conn) {
fmt.Fprintln(c, "0001 BIRD 2.0.8 ready.")
sc := bufio.NewScanner(c)
for sc.Scan() {
cmd := sc.Text()
args := strings.Split(cmd, " ")
switch args[0] {
case "enable":
en, ok := fb.protocolsEnabled[args[1]]
if !ok {
fmt.Fprintln(c, "9001 syntax error, unexpected CF_SYM_UNDEFINED, expecting CF_SYM_KNOWN or TEXT or ALL")
} else if en {
fmt.Fprintf(c, "0010-%s: already enabled\n", args[1])
} else {
fmt.Fprintf(c, "0011-%s: enabled\n", args[1])
}
fmt.Fprintln(c, "0000 ")
fb.protocolsEnabled[args[1]] = true
case "disable":
en, ok := fb.protocolsEnabled[args[1]]
if !ok {
fmt.Fprintln(c, "9001 syntax error, unexpected CF_SYM_UNDEFINED, expecting CF_SYM_KNOWN or TEXT or ALL")
} else if !en {
fmt.Fprintf(c, "0008-%s: already disabled\n", args[1])
} else {
fmt.Fprintf(c, "0009-%s: disabled\n", args[1])
}
fmt.Fprintln(c, "0000 ")
fb.protocolsEnabled[args[1]] = false
}
}
}
func TestChirp(t *testing.T) {
fb := newFakeBIRD(t, "tailscale")
defer fb.Close()
go fb.listen()
c, err := New(fb.sock)
if err != nil {
t.Fatal(err)
}
if err := c.EnableProtocol("tailscale"); err != nil {
t.Fatal(err)
}
if err := c.EnableProtocol("tailscale"); err != nil {
t.Fatal(err)
}
if err := c.DisableProtocol("tailscale"); err != nil {
t.Fatal(err)
}
if err := c.DisableProtocol("tailscale"); err != nil {
t.Fatal(err)
}
if err := c.EnableProtocol("rando"); err == nil {
t.Fatalf("enabling %q succeded", "rando")
}
if err := c.DisableProtocol("rando"); err == nil {
t.Fatalf("disabling %q succeded", "rando")
}
}

View File

@@ -786,6 +786,32 @@ func TestUpdatePrefs(t *testing.T) {
wantSimpleUp: true,
wantJustEditMP: &ipn.MaskedPrefs{WantRunningSet: true},
},
{
name: "just_edit_reset",
flags: []string{"--reset"},
curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL,
Persist: &persist.Persist{LoginName: "crawshaw.github"},
},
env: upCheckEnv{backendState: "Running"},
wantJustEditMP: &ipn.MaskedPrefs{
AdvertiseRoutesSet: true,
AdvertiseTagsSet: true,
AllowSingleHostsSet: true,
ControlURLSet: true,
CorpDNSSet: true,
ExitNodeAllowLANAccessSet: true,
ExitNodeIDSet: true,
ExitNodeIPSet: true,
HostnameSet: true,
NetfilterModeSet: true,
NoSNATSet: true,
OperatorUserSet: true,
RouteAllSet: true,
ShieldsUpSet: true,
WantRunningSet: true,
},
},
{
name: "control_synonym",
flags: []string{},
@@ -850,16 +876,23 @@ func TestUpdatePrefs(t *testing.T) {
if simpleUp != tt.wantSimpleUp {
t.Fatalf("simpleUp=%v, want %v", simpleUp, tt.wantSimpleUp)
}
var oldEditPrefs ipn.Prefs
if justEditMP != nil {
oldEditPrefs = justEditMP.Prefs
justEditMP.Prefs = ipn.Prefs{} // uninteresting
}
if !reflect.DeepEqual(justEditMP, tt.wantJustEditMP) {
t.Fatalf("justEditMP: %v", cmp.Diff(justEditMP, tt.wantJustEditMP))
t.Logf("justEditMP != wantJustEditMP; following diff omits the Prefs field, which was %+v", oldEditPrefs)
t.Fatalf("justEditMP: %v\n\n: ", cmp.Diff(justEditMP, tt.wantJustEditMP, cmpIP))
}
})
}
}
var cmpIP = cmp.Comparer(func(a, b netaddr.IP) bool {
return a == b
})
func TestExitNodeIPOfArg(t *testing.T) {
mustIP := netaddr.MustParseIP
tests := []struct {

View File

@@ -379,7 +379,8 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
return prefs, nil
}
// updatePrefs updates prefs based on curPrefs
// updatePrefs returns how to edit preferences based on the
// flag-provided 'prefs' and the currently active 'curPrefs'.
//
// It returns a non-nil justEditMP if we're already running and none of
// the flags require a restart, so we can just do an EditPrefs call and
@@ -413,15 +414,19 @@ func updatePrefs(prefs, curPrefs *ipn.Prefs, env upCheckEnv) (simpleUp bool, jus
justEdit := env.backendState == ipn.Running.String() &&
!env.upArgs.forceReauth &&
!env.upArgs.reset &&
env.upArgs.authKeyOrFile == "" &&
!controlURLChanged &&
!tagsChanged
if justEdit {
justEditMP = new(ipn.MaskedPrefs)
justEditMP.WantRunningSet = true
justEditMP.Prefs = *prefs
env.flagSet.Visit(func(f *flag.Flag) {
visitFlags := env.flagSet.Visit
if env.upArgs.reset {
visitFlags = env.flagSet.VisitAll
}
visitFlags(func(f *flag.Flag) {
updateMaskedPrefsFromUpFlag(justEditMP, f.Name)
})
}
@@ -513,7 +518,7 @@ func runUp(ctx context.Context, args []string) error {
pumpErr := make(chan error, 1)
go func() { pumpErr <- pump(pumpCtx, bc, c) }()
printed := !simpleUp
var printed bool // whether we've yet printed anything to stdout or stderr
var loginOnce sync.Once
startLoginInteractive := func() { loginOnce.Do(func() { bc.StartLoginInteractive() }) }
@@ -539,7 +544,6 @@ func runUp(ctx context.Context, args []string) error {
if s := n.State; s != nil {
switch *s {
case ipn.NeedsLogin:
printed = true
startLoginInteractive()
case ipn.NeedsMachineAuth:
printed = true

View File

@@ -375,7 +375,7 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
data.AdvertiseExitNode = true
} else {
if data.AdvertiseRoutes != "" {
data.AdvertiseRoutes = ","
data.AdvertiseRoutes += ","
}
data.AdvertiseRoutes += r.String()
}

View File

@@ -328,9 +328,6 @@ func run() error {
}
ns.ProcessLocalIPs = useNetstack
ns.ProcessSubnets = useNetstack || wrapNetstack
if err := ns.Start(); err != nil {
return fmt.Errorf("failed to start netstack: %w", err)
}
if useNetstack {
dialer.UseNetstackForIP = func(ip netaddr.IP) bool {
@@ -391,6 +388,10 @@ func run() error {
if err != nil {
return fmt.Errorf("ipnserver.New: %w", err)
}
ns.SetLocalBackend(srv.LocalBackend())
if err := ns.Start(); err != nil {
return fmt.Errorf("failed to start netstack: %w", err)
}
if debugMux != nil {
debugMux.HandleFunc("/debug/ipn", srv.ServeHTMLStatus)

View File

@@ -212,7 +212,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, diale
wiredPeerAPIPort := false
if ig, ok := e.(wgengine.InternalsGetter); ok {
if tunWrap, _, ok := ig.GetInternals(); ok {
tunWrap.PeerAPIPort = b.getPeerAPIPortForTSMPPing
tunWrap.PeerAPIPort = b.GetPeerAPIPort
wiredPeerAPIPort = true
}
}
@@ -1779,7 +1779,9 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) {
b.send(ipn.Notify{Prefs: newp})
}
func (b *LocalBackend) getPeerAPIPortForTSMPPing(ip netaddr.IP) (port uint16, ok bool) {
// GetPeerAPIPort returns the port number for the peerapi server
// running on the provided IP.
func (b *LocalBackend) GetPeerAPIPort(ip netaddr.IP) (port uint16, ok bool) {
b.mu.Lock()
defer b.mu.Unlock()
for _, pln := range b.peerAPIListeners {
@@ -1790,6 +1792,27 @@ func (b *LocalBackend) getPeerAPIPortForTSMPPing(ip netaddr.IP) (port uint16, ok
return 0, false
}
// ServePeerAPIConnection serves an already-accepted connection c.
//
// The remote parameter is the remote address.
// The local paramater is the local address (either a Tailscale IPv4
// or IPv6 IP and the peerapi port for that address).
//
// The connection will be closed by ServePeerAPIConnection.
func (b *LocalBackend) ServePeerAPIConnection(remote, local netaddr.IPPort, c net.Conn) {
b.mu.Lock()
defer b.mu.Unlock()
for _, pln := range b.peerAPIListeners {
if pln.ip == local.IP() {
go pln.ServeConn(remote, c)
return
}
}
b.logf("[unexpected] no peerAPI listener found for %v", local)
c.Close()
return
}
func (b *LocalBackend) peerAPIServicesLocked() (ret []tailcfg.Service) {
for _, pln := range b.peerAPIListeners {
proto := tailcfg.PeerAPI4

View File

@@ -481,29 +481,34 @@ func (pln *peerAPIListener) serve() {
c.Close()
continue
}
peerNode, peerUser, ok := pln.lb.WhoIs(ipp)
if !ok {
logf("peerapi: unknown peer %v", ipp)
c.Close()
continue
}
h := &peerAPIHandler{
ps: pln.ps,
isSelf: pln.ps.selfNode.User == peerNode.User,
remoteAddr: ipp,
peerNode: peerNode,
peerUser: peerUser,
}
httpServer := &http.Server{
Handler: h,
}
if addH2C != nil {
addH2C(httpServer)
}
go httpServer.Serve(&oneConnListener{Listener: pln.ln, conn: c})
pln.ServeConn(ipp, c)
}
}
func (pln *peerAPIListener) ServeConn(src netaddr.IPPort, c net.Conn) {
logf := pln.lb.logf
peerNode, peerUser, ok := pln.lb.WhoIs(src)
if !ok {
logf("peerapi: unknown peer %v", src)
c.Close()
return
}
h := &peerAPIHandler{
ps: pln.ps,
isSelf: pln.ps.selfNode.User == peerNode.User,
remoteAddr: src,
peerNode: peerNode,
peerUser: peerUser,
}
httpServer := &http.Server{
Handler: h,
}
if addH2C != nil {
addH2C(httpServer)
}
go httpServer.Serve(&oneConnListener{Listener: pln.ln, conn: c})
}
type oneConnListener struct {
net.Listener
conn net.Conn

View File

@@ -4,8 +4,71 @@
package dns
import "tailscale.com/types/logger"
import (
"bytes"
"fmt"
"os"
func NewOSConfigurator(logf logger.Logf, _ string) (OSConfigurator, error) {
"tailscale.com/types/logger"
)
type kv struct {
k, v string
}
func (kv kv) String() string {
return fmt.Sprintf("%s=%s", kv.k, kv.v)
}
func NewOSConfigurator(logf logger.Logf, interfaceName string) (OSConfigurator, error) {
return newOSConfigurator(logf, interfaceName,
newOSConfigEnv{
rcIsResolvd: rcIsResolvd,
fs: directFS{},
})
}
// newOSConfigEnv are the funcs newOSConfigurator needs, pulled out for testing.
type newOSConfigEnv struct {
fs directFS
rcIsResolvd func(resolvConfContents []byte) bool
}
func newOSConfigurator(logf logger.Logf, interfaceName string, env newOSConfigEnv) (ret OSConfigurator, err error) {
var debug []kv
dbg := func(k, v string) {
debug = append(debug, kv{k, v})
}
defer func() {
if ret != nil {
dbg("ret", fmt.Sprintf("%T", ret))
}
logf("dns: %v", debug)
}()
bs, err := env.fs.ReadFile(resolvConf)
if os.IsNotExist(err) {
dbg("rc", "missing")
return newDirectManager(logf), nil
}
if err != nil {
return nil, fmt.Errorf("reading /etc/resolv.conf: %w", err)
}
if env.rcIsResolvd(bs) {
dbg("resolvd", "yes")
return newResolvdManager(logf, interfaceName)
}
dbg("resolvd", "missing")
return newDirectManager(logf), nil
}
func rcIsResolvd(resolvConfContents []byte) bool {
// If we have the string "# resolvd:" in resolv.conf resolvd(8) is
// managing things.
if bytes.Contains(resolvConfContents, []byte("# resolvd:")) {
return true
}
return false
}

View File

@@ -7,8 +7,10 @@ package dns
import (
"errors"
"fmt"
"os"
"os/exec"
"sort"
"strconv"
"strings"
"syscall"
"time"
@@ -34,6 +36,8 @@ const (
versionKey = `SOFTWARE\Microsoft\Windows NT\CurrentVersion`
)
var configureWSL, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_CONFIGURE_WSL"))
type windowsManager struct {
logf logger.Logf
guid string
@@ -307,13 +311,15 @@ func (m windowsManager) SetDNS(cfg OSConfig) error {
// On initial setup of WSL, the restart caused by --shutdown is slow,
// so we do it out-of-line.
go func() {
if err := m.wslManager.SetDNS(cfg); err != nil {
m.logf("WSL SetDNS: %v", err) // continue
} else {
m.logf("WSL SetDNS: success")
}
}()
if configureWSL {
go func() {
if err := m.wslManager.SetDNS(cfg); err != nil {
m.logf("WSL SetDNS: %v", err) // continue
} else {
m.logf("WSL SetDNS: success")
}
}()
}
return nil
}

163
net/dns/resolvd.go Normal file
View File

@@ -0,0 +1,163 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build openbsd
// +build openbsd
package dns
import (
"bufio"
"bytes"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"inet.af/netaddr"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname"
)
func newResolvdManager(logf logger.Logf, interfaceName string) (*resolvdManager, error) {
return &resolvdManager{
logf: logf,
ifName: interfaceName,
fs: directFS{},
}, nil
}
// resolvdManager is an OSConfigurator which uses route(1) to teach OpenBSD's
// resolvd(8) about DNS servers.
type resolvdManager struct {
logf logger.Logf
ifName string
fs directFS
}
func (m *resolvdManager) SetDNS(config OSConfig) error {
args := []string{
"nameserver",
m.ifName,
}
origResolv, err := m.readAndCopy(resolvConf, backupConf, 0644)
if err != nil {
return err
}
newResolvConf := removeSearchLines(origResolv)
for _, ns := range config.Nameservers {
args = append(args, ns.String())
}
var newSearch = []string{
"search",
}
for _, s := range config.SearchDomains {
newSearch = append(newSearch, s.WithoutTrailingDot())
}
if len(newSearch) > 1 {
newResolvConf = append(newResolvConf, []byte(strings.Join(newSearch, " "))...)
}
err = m.fs.WriteFile(resolvConf, newResolvConf, 0644)
if err != nil {
return err
}
cmd := exec.Command("/sbin/route", args...)
return cmd.Run()
}
func (m *resolvdManager) SupportsSplitDNS() bool {
return false
}
func (m *resolvdManager) GetBaseConfig() (OSConfig, error) {
cfg, err := m.readResolvConf()
if err != nil {
return OSConfig{}, err
}
return cfg, nil
}
func (m *resolvdManager) Close() error {
// resolvd handles teardown of nameservers so we only need to write back the original
// config and be done.
_, err := m.readAndCopy(backupConf, resolvConf, 0644)
if err != nil {
return err
}
return m.fs.Remove(backupConf)
}
func (m *resolvdManager) readAndCopy(a, b string, mode os.FileMode) ([]byte, error) {
orig, err := m.fs.ReadFile(a)
if err != nil {
return nil, err
}
err = m.fs.WriteFile(b, orig, mode)
if err != nil {
return nil, err
}
return orig, nil
}
func (m resolvdManager) readResolvConf() (config OSConfig, err error) {
b, err := m.fs.ReadFile(resolvConf)
if err != nil {
return OSConfig{}, err
}
scanner := bufio.NewScanner(bytes.NewReader(b))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// resolvd manages "nameserver" lines, we only need to handle
// "search".
if strings.HasPrefix(line, "search") {
domain := strings.TrimPrefix(line, "search")
domain = strings.TrimSpace(domain)
fqdn, err := dnsname.ToFQDN(domain)
if err != nil {
return OSConfig{}, fmt.Errorf("parsing search domains %q: %w", line, err)
}
config.SearchDomains = append(config.SearchDomains, fqdn)
continue
}
if strings.HasPrefix(line, "nameserver") {
s := strings.TrimPrefix(line, "nameserver")
parts := strings.Split(s, " # ")
if len(parts) == 0 {
return OSConfig{}, err
}
nameserver := strings.TrimSpace(parts[0])
ip, err := netaddr.ParseIP(nameserver)
if err != nil {
return OSConfig{}, err
}
config.Nameservers = append(config.Nameservers, ip)
continue
}
}
if err = scanner.Err(); err != nil {
return OSConfig{}, err
}
return config, nil
}
func removeSearchLines(orig []byte) []byte {
re := regexp.MustCompile(`(?m)^search\s+.+$`)
return re.ReplaceAll(orig, []byte(""))
}

View File

@@ -529,6 +529,10 @@ func stubResolverForOS() (ip netaddr.IP, err error) {
if c, ok := resolvConfCacheValue.Load().(resolvConfCache); ok && c.mod == cur.mod && c.size == cur.size {
return c.ip, nil
}
// TODO(bradfitz): unify this /etc/resolv.conf parsing code with readResolv
// in net/dns, which we can't use due to circular dependency reasons.
// Move it to a leaf, including the OSConfig type (perhaps in its own dnstype
// package?)
err = lineread.File("/etc/resolv.conf", func(line []byte) error {
if !ip.IsZero() {
return nil
@@ -537,6 +541,12 @@ func stubResolverForOS() (ip netaddr.IP, err error) {
if len(line) == 0 || line[0] == '#' {
return nil
}
// Normalize tabs to spaces to simplify parsing code later.
for i, b := range line {
if b == '\t' {
line[i] = ' '
}
}
if mem.HasPrefix(mem.B(line), mem.S("nameserver ")) {
s := strings.TrimSpace(strings.TrimPrefix(string(line), "nameserver "))
ip, err = netaddr.ParseIP(s)

View File

@@ -457,9 +457,7 @@ func TLSDialer(fwd DialContextFunc, dnsCache *Resolver, tlsConfigBase *tls.Confi
}
}()
go func() {
err := tlsConn.Handshake()
handshakeTimeoutCancel()
errc <- err
errc <- tlsConn.Handshake()
}()
if err := <-errc; err != nil {
tcpConn.Close()

View File

@@ -28,6 +28,11 @@ func init() {
var procNetRouteErr syncs.AtomicBool
// errStopReading is a sentinel error value used internally by
// lineread.File callers to stop reading. It doesn't escape to
// callers/users.
var errStopReading = errors.New("stop reading")
/*
Parse 10.0.0.1 out of:
@@ -47,12 +52,15 @@ func likelyHomeRouterIPLinux() (ret netaddr.IP, ok bool) {
}
lineNum := 0
var f []mem.RO
err := lineread.File("/proc/net/route", func(line []byte) error {
err := lineread.File(procNetRoutePath, func(line []byte) error {
lineNum++
if lineNum == 1 {
// Skip header line.
return nil
}
if lineNum > maxProcNetRouteRead {
return errStopReading
}
f = mem.AppendFields(f[:0], mem.B(line))
if len(f) < 4 {
return nil
@@ -74,9 +82,13 @@ func likelyHomeRouterIPLinux() (ret netaddr.IP, ok bool) {
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
if ip.IsPrivate() {
ret = ip
return errStopReading
}
return nil
})
if errors.Is(err, errStopReading) {
err = nil
}
if err != nil {
procNetRouteErr.Set(true)
if runtime.GOOS == "android" {
@@ -139,6 +151,10 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
var zeroRouteBytes = []byte("00000000")
var procNetRoutePath = "/proc/net/route"
// maxProcNetRouteRead is the max number of lines to read from
// /proc/net/route looking for a default route.
const maxProcNetRouteRead = 1000
func defaultRouteInterfaceProcNetInternal(bufsize int) (string, error) {
f, err := os.Open(procNetRoutePath)
if err != nil {
@@ -147,9 +163,11 @@ func defaultRouteInterfaceProcNetInternal(bufsize int) (string, error) {
defer f.Close()
br := bufio.NewReaderSize(f, bufsize)
lineNum := 0
for {
lineNum++
line, err := br.ReadSlice('\n')
if err == io.EOF {
if err == io.EOF || lineNum > maxProcNetRouteRead {
return "", fmt.Errorf("no default routes found: %w", err)
}
if err != nil {

View File

@@ -51,7 +51,7 @@ func TestExtremelyLongProcNetRoute(t *testing.T) {
t.Fatal(err)
}
for n := 0; n <= 1000; n++ {
for n := 0; n <= 900; n++ {
line := fmt.Sprintf("eth%d\t8008FEA9\t00000000\t0001\t0\t0\t0\t01FFFFFF\t0\t0\t0\n", n)
_, err := f.Write([]byte(line))
if err != nil {

View File

@@ -11,7 +11,6 @@ import (
"fmt"
"net"
"os"
"os/exec"
"sync"
"syscall"
@@ -67,8 +66,7 @@ func socketMarkWorks() bool {
// If it doesn't, we have to use SO_BINDTODEVICE on our sockets instead.
func useSocketMark() bool {
socketMarkWorksOnce.Do(func() {
ipRuleWorks := exec.Command("ip", "rule").Run() == nil
socketMarkWorksOnce.v = ipRuleWorks && socketMarkWorks()
socketMarkWorksOnce.v = socketMarkWorks()
})
return socketMarkWorksOnce.v
}

View File

@@ -22,12 +22,16 @@ const minFrag = 60 + 20 // max IPv4 header + basic TCP header
type TCPFlag uint8
const (
TCPFin TCPFlag = 0x01
TCPSyn TCPFlag = 0x02
TCPRst TCPFlag = 0x04
TCPPsh TCPFlag = 0x08
TCPAck TCPFlag = 0x10
TCPSynAck TCPFlag = TCPSyn | TCPAck
TCPFin TCPFlag = 0x01
TCPSyn TCPFlag = 0x02
TCPRst TCPFlag = 0x04
TCPPsh TCPFlag = 0x08
TCPAck TCPFlag = 0x10
TCPUrg TCPFlag = 0x20
TCPECNEcho TCPFlag = 0x40
TCPCWR TCPFlag = 0x80
TCPSynAck TCPFlag = TCPSyn | TCPAck
TCPECNBits TCPFlag = TCPECNEcho | TCPCWR
)
// Parsed is a minimal decoding of a packet suitable for use in filters.
@@ -52,7 +56,7 @@ type Parsed struct {
Src netaddr.IPPort
// DstIP4 is the destination address. Family matches IPVersion.
Dst netaddr.IPPort
// TCPFlags is the packet's TCP flag bigs. Valid iff IPProto == TCP.
// TCPFlags is the packet's TCP flag bits. Valid iff IPProto == TCP.
TCPFlags TCPFlag
}
@@ -180,7 +184,7 @@ func (q *Parsed) decode4(b []byte) {
}
q.Src = q.Src.WithPort(binary.BigEndian.Uint16(sub[0:2]))
q.Dst = q.Dst.WithPort(binary.BigEndian.Uint16(sub[2:4]))
q.TCPFlags = TCPFlag(sub[13]) & 0x3F
q.TCPFlags = TCPFlag(sub[13])
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
@@ -282,7 +286,7 @@ func (q *Parsed) decode6(b []byte) {
}
q.Src = q.Src.WithPort(binary.BigEndian.Uint16(sub[0:2]))
q.Dst = q.Dst.WithPort(binary.BigEndian.Uint16(sub[2:4]))
q.TCPFlags = TCPFlag(sub[13]) & 0x3F
q.TCPFlags = TCPFlag(sub[13])
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
return
@@ -374,8 +378,14 @@ func (q *Parsed) Payload() []byte {
return q.b[q.dataofs:q.length]
}
// IsTCPSyn reports whether q is a TCP SYN packet
// (i.e. the first packet in a new connection).
// Transport returns the transport header and payload (IP subprotocol, such as TCP or UDP).
// This is a read-only view; that is, p retains the ownership of the buffer.
func (p *Parsed) Transport() []byte {
return p.b[p.subofs:]
}
// IsTCPSyn reports whether q is a TCP SYN packet,
// without ACK set. (i.e. the first packet in a new connection)
func (q *Parsed) IsTCPSyn() bool {
return (q.TCPFlags & TCPSynAck) == TCPSyn
}
@@ -424,6 +434,40 @@ func (q *Parsed) IsEchoResponse() bool {
}
}
// RemoveECNBits modifies p and its underlying memory buffer to remove
// ECN bits, if any. It reports whether it did so.
//
// It currently only does the TCP flags.
func (p *Parsed) RemoveECNBits() bool {
if p.IPVersion == 0 {
return false
}
if p.IPProto != ipproto.TCP {
// TODO(bradfitz): handle non-TCP too? for now only trying to
// fix the Issue 2642 problem.
return false
}
if p.TCPFlags&TCPECNBits == 0 {
// Nothing to do.
return false
}
// Clear flags.
// First in the parsed output.
p.TCPFlags = p.TCPFlags & ^TCPECNBits
// Then in the underlying memory.
tcp := p.Transport()
old := binary.BigEndian.Uint16(tcp[12:14])
tcp[13] = byte(p.TCPFlags)
new := binary.BigEndian.Uint16(tcp[12:14])
oldSum := binary.BigEndian.Uint16(tcp[16:18])
newSum := ^checksumUpdate2ByteAlignedUint16(^oldSum, old, new)
binary.BigEndian.PutUint16(tcp[16:18], newSum)
return true
}
func Hexdump(b []byte) string {
out := new(strings.Builder)
for i := 0; i < len(b); i += 16 {
@@ -455,3 +499,26 @@ func Hexdump(b []byte) string {
}
return out.String()
}
// From gVisor's unexported API:
// checksumUpdate2ByteAlignedUint16 updates a uint16 value in a calculated
// checksum.
//
// The value MUST begin at a 2-byte boundary in the original buffer.
func checksumUpdate2ByteAlignedUint16(xsum, old, new uint16) uint16 {
// As per RFC 1071 page 4,
//(4) Incremental Update
//
// ...
//
// To update the checksum, simply add the differences of the
// sixteen bit integers that have been changed. To see why this
// works, observe that every 16-bit integer has an additive inverse
// and that addition is associative. From this it follows that
// given the original value m, the new value m', and the old
// checksum C, the new checksum C' is:
//
// C' = C + (-m) + m' = C + (m' - m)
return checksumCombine(xsum, checksumCombine(new, ^old))
}

View File

@@ -6,7 +6,9 @@ package packet
import (
"bytes"
"encoding/hex"
"reflect"
"regexp"
"testing"
"inet.af/netaddr"
@@ -561,3 +563,57 @@ func BenchmarkString(b *testing.B) {
})
}
}
func TestRemoveECNBits(t *testing.T) {
// withECNHex is a TCP SYN packet with ECN bits set in the TCP
// header as captured by Wireshark on macOS against the
// Tailscale interface. In this packet (because it's a SYN
// control packet), the ECN bits are not set in the IP header.
const withECNHex = `45 00 00 40 00 00 40 00
40 06 0c 66 64 7b 65 28 64 7f 00 30 f1 ab 00 16
5a 7a 63 e8 00 00 00 00 b0 c2 ff ff 97 76 00 00
02 04 04 d8 01 03 03 06 01 01 08 0a 03 e1 bd 49
00 00 00 00 04 02 00 00`
// Generated by hand-editing a pcap file in hexl-mode to set
// the TCP flags to just SYN (0x02), then loading that pcap
// file in wireshark to get the expected checksum value, then
// putting that checksum value (0x9836) in the file.
const wantStrippedHex = `45 00 00 40 00 00 40 00
40 06 0c 66 64 7b 65 28 64 7f 00 30 f1 ab 00 16
5a 7a 63 e8 00 00 00 00 b0 02 ff ff 98 36 00 00
02 04 04 d8 01 03 03 06 01 01 08 0a 03 e1 bd 49
00 00 00 00 04 02 00 00`
var p Parsed
pktBuf := bytesOfHex(withECNHex)
p.Decode(pktBuf)
if want := TCPCWR | TCPECNEcho | TCPSyn; p.TCPFlags != want {
t.Fatalf("pre flags = %v; want %v", p.TCPFlags, want)
}
if !p.RemoveECNBits() {
t.Fatal("didn't remove bits")
}
if want := TCPSyn; p.TCPFlags != want {
t.Fatalf("post flags = %v; want %v", p.TCPFlags, want)
}
wantPkt := bytesOfHex(wantStrippedHex)
if !bytes.Equal(pktBuf, wantPkt) {
t.Fatalf("wrong result.\n got: % 2x\nwant: % 2x\n", pktBuf, wantPkt)
}
if p.RemoveECNBits() {
t.Fatal("unexpected true return value on second call")
}
}
var nonHex = regexp.MustCompile(`[^0-9a-fA-F]+`)
func bytesOfHex(s string) []byte {
b, err := hex.DecodeString(nonHex.ReplaceAllString(s, ""))
if err != nil {
panic(err)
}
return b
}

View File

@@ -48,7 +48,8 @@ import (
// 23: 2021-08-25: DNSConfig.Routes values may be empty (for ExtraRecords support in 1.14.1+)
// 24: 2021-09-18: MapResponse.Health from control to node; node shows in "tailscale status"
// 25: 2021-11-01: MapResponse.Debug.Exit
const CurrentMapRequestVersion = 25
// 26: 2022-01-12: (nothing, just bumping for 1.20.0)
const CurrentMapRequestVersion = 26
type StableID string

View File

@@ -376,6 +376,7 @@ func (h *Harness) testDistro(t *testing.T, d Distro, ipm ipMapping) {
t.Run("login", func(t *testing.T) {
runTestCommands(t, timeout, cli, []expect.Batcher{
&expect.BSnd{S: fmt.Sprintf("tailscale up --login-server=%s\n", loginServer)},
&expect.BSnd{S: "echo Success.\n"},
&expect.BExp{R: `Success.`},
})
})

View File

@@ -265,6 +265,7 @@ type Conn struct {
stunReceiveFunc atomic.Value // of func(p []byte, fromAddr *net.UDPAddr)
// derpRecvCh is used by receiveDERP to read DERP messages.
// It must have buffer size > 0; see issue 3736.
derpRecvCh chan derpReadResult
// bind is the wireguard-go conn.Bind for Conn.
@@ -299,11 +300,18 @@ type Conn struct {
havePrivateKey syncs.AtomicBool
publicKeyAtomic atomic.Value // of key.NodePublic (or NodeKey zero value if !havePrivateKey)
// derpMapAtomic is the same as derpMap, but without requiring
// sync.Mutex. For use with NewRegionClient's callback, to avoid
// lock ordering deadlocks. See issue 3726 and mu field docs.
derpMapAtomic atomic.Value // of *tailcfg.DERPMap
// port is the preferred port from opts.Port; 0 means auto.
port syncs.AtomicUint32
// ============================================================
// mu guards all following fields; see userspaceEngine lock ordering rules
// mu guards all following fields; see userspaceEngine lock
// ordering rules against the engine. For derphttp, mu must
// be held before derphttp.Client.mu.
mu sync.Mutex
muCond *sync.Cond
@@ -522,7 +530,7 @@ func (o *Options) derpActiveFunc() func() {
// of NewConn. Mostly for tests.
func newConn() *Conn {
c := &Conn{
derpRecvCh: make(chan derpReadResult),
derpRecvCh: make(chan derpReadResult, 1), // must be buffered, see issue 3736
derpStarted: make(chan struct{}),
peerLastDerp: make(map[key.NodePublic]int),
peerMap: newPeerMap(),
@@ -1351,19 +1359,23 @@ func (c *Conn) derpWriteChanOfAddr(addr netaddr.IPPort, peer key.NodePublic) cha
}
// Note that derphttp.NewRegionClient does not dial the server
// so it is safe to do under the mu lock.
// (it doesn't block) so it is safe to do under the c.mu lock.
dc := derphttp.NewRegionClient(c.privateKey, c.logf, func() *tailcfg.DERPRegion {
// Warning: it is not legal to acquire
// magicsock.Conn.mu from this callback.
// It's run from derphttp.Client.connect (via Send, etc)
// and the lock ordering rules are that magicsock.Conn.mu
// must be acquired before derphttp.Client.mu.
// See https://github.com/tailscale/tailscale/issues/3726
if c.connCtx.Err() != nil {
// If we're closing, don't try to acquire the lock.
// We might already be in Conn.Close and the Lock would deadlock.
// We're closing anyway; return nil to stop dialing.
return nil
}
c.mu.Lock()
defer c.mu.Unlock()
if c.derpMap == nil {
derpMap, _ := c.derpMapAtomic.Load().(*tailcfg.DERPMap)
if derpMap == nil {
return nil
}
return c.derpMap.Regions[regionID]
return derpMap.Regions[regionID]
})
dc.SetCanAckPings(true)
@@ -2252,6 +2264,7 @@ func (c *Conn) SetDERPMap(dm *tailcfg.DERPMap) {
return
}
c.derpMapAtomic.Store(dm)
old := c.derpMap
c.derpMap = dm
if dm == nil {
@@ -2629,6 +2642,7 @@ func (c *connBind) Close() error {
}
// Send an empty read result to unblock receiveDERP,
// which will then check connBind.Closed.
// connBind.Closed takes c.mu, but c.derpRecvCh is buffered.
c.derpRecvCh <- derpReadResult{}
return nil
}

View File

@@ -34,13 +34,16 @@ import (
"inet.af/netstack/tcpip/transport/tcp"
"inet.af/netstack/tcpip/transport/udp"
"inet.af/netstack/waiter"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tsdial"
"tailscale.com/net/tstun"
"tailscale.com/syncs"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/version/distro"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
@@ -71,13 +74,19 @@ type Impl struct {
// It can only be set before calling Start.
ProcessSubnets bool
ipstack *stack.Stack
linkEP *channel.Endpoint
tundev *tstun.Wrapper
e wgengine.Engine
mc *magicsock.Conn
logf logger.Logf
dialer *tsdial.Dialer
ipstack *stack.Stack
linkEP *channel.Endpoint
tundev *tstun.Wrapper
e wgengine.Engine
mc *magicsock.Conn
logf logger.Logf
dialer *tsdial.Dialer
ctx context.Context // alive until Close
ctxCancel context.CancelFunc // called on Close
lb *ipnlocal.LocalBackend // or nil
peerapiPort4Atomic uint32 // uint16 port number for IPv4 peerapi
peerapiPort6Atomic uint32 // uint16 port number for IPv6 peerapi
// atomicIsLocalIPFunc holds a func that reports whether an IP
// is a local (non-subnet) Tailscale IP address of this
@@ -151,10 +160,22 @@ func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magi
dialer: dialer,
connsOpenBySubnetIP: make(map[netaddr.IP]int),
}
ns.ctx, ns.ctxCancel = context.WithCancel(context.Background())
ns.atomicIsLocalIPFunc.Store(tsaddr.NewContainsIPFunc(nil))
return ns, nil
}
func (ns *Impl) Close() error {
ns.ctxCancel()
return nil
}
// SetLocalBackend sets the LocalBackend; it should only be run before
// the Start method is called.
func (ns *Impl) SetLocalBackend(lb *ipnlocal.LocalBackend) {
ns.lb = lb
}
// wrapProtoHandler returns protocol handler h wrapped in a version
// that dynamically reconfigures ns's subnet addresses as needed for
// outbound traffic.
@@ -242,8 +263,9 @@ func (ns *Impl) updateIPs(nm *netmap.NetworkMap) {
ap := protocolAddr.AddressWithPrefix
ip := netaddrIPFromNetstackIP(ap.Address)
if ip == v4broadcast && ap.PrefixLen == 32 {
// Don't delete this one later. It seems to be important.
// Related to Issue 2642? Likely.
// Don't add 255.255.255.255/32 to oldIPs so we don't
// delete it later. We didn't install it, so it's not
// ours to delete.
continue
}
oldIPs[ap] = true
@@ -254,10 +276,10 @@ func (ns *Impl) updateIPs(nm *netmap.NetworkMap) {
if nm.SelfNode != nil {
for _, ipp := range nm.SelfNode.Addresses {
isAddr[ipp] = true
newIPs[ipPrefixToAddressWithPrefix(ipp)] = true
}
for _, ipp := range nm.SelfNode.AllowedIPs {
local := isAddr[ipp]
if local && ns.ProcessLocalIPs || !local && ns.ProcessSubnets {
if !isAddr[ipp] && ns.ProcessSubnets {
newIPs[ipPrefixToAddressWithPrefix(ipp)] = true
}
}
@@ -346,8 +368,12 @@ func (ns *Impl) DialContextUDP(ctx context.Context, ipp netaddr.IPPort) (*gonet.
func (ns *Impl) injectOutbound() {
for {
packetInfo, ok := ns.linkEP.ReadContext(context.Background())
packetInfo, ok := ns.linkEP.ReadContext(ns.ctx)
if !ok {
if ns.ctx.Err() != nil {
// Return without logging.
return
}
ns.logf("[v2] ReadContext-for-write = ok=false")
continue
}
@@ -376,9 +402,33 @@ func (ns *Impl) isLocalIP(ip netaddr.IP) bool {
return ns.atomicIsLocalIPFunc.Load().(func(netaddr.IP) bool)(ip)
}
func (ns *Impl) peerAPIPortAtomic(ip netaddr.IP) *uint32 {
if ip.Is4() {
return &ns.peerapiPort4Atomic
} else {
return &ns.peerapiPort6Atomic
}
}
// shouldProcessInbound reports whether an inbound packet should be
// handled by netstack.
func (ns *Impl) shouldProcessInbound(p *packet.Parsed, t *tstun.Wrapper) bool {
// Handle incoming peerapi connections in netstack.
if ns.lb != nil && p.IPProto == ipproto.TCP {
var peerAPIPort uint16
dstIP := p.Dst.IP()
if p.TCPFlags&packet.TCPSynAck == packet.TCPSyn && ns.isLocalIP(dstIP) {
if port, ok := ns.lb.GetPeerAPIPort(p.Dst.IP()); ok {
peerAPIPort = port
atomic.StoreUint32(ns.peerAPIPortAtomic(dstIP), uint32(port))
}
} else {
peerAPIPort = uint16(atomic.LoadUint32(ns.peerAPIPortAtomic(dstIP)))
}
if p.IPProto == ipproto.TCP && p.Dst.Port() == peerAPIPort {
return true
}
}
if !ns.ProcessLocalIPs && !ns.ProcessSubnets {
// Fast path for common case (e.g. Linux server in TUN mode) where
// netstack isn't used at all; don't even do an isLocalIP lookup.
@@ -394,8 +444,14 @@ func (ns *Impl) shouldProcessInbound(p *packet.Parsed, t *tstun.Wrapper) bool {
return false
}
// setAmbientCapsRaw is non-nil on Linux for Synology, to run ping with
// CAP_NET_RAW from tailscaled's binary.
var setAmbientCapsRaw func(*exec.Cmd)
var userPingSem = syncs.NewSemaphore(20) // 20 child ping processes at once
var isSynology = runtime.GOOS == "linux" && distro.Get() == distro.Synology
// userPing tried to ping dstIP and if it succeeds, injects pingResPkt
// into the tundev.
//
@@ -419,6 +475,11 @@ func (ns *Impl) userPing(dstIP netaddr.IP, pingResPkt []byte) {
switch runtime.GOOS {
case "windows":
err = exec.Command("ping", "-n", "1", "-w", "3000", dstIP.String()).Run()
case "darwin":
// Note: 2000 ms is actually 1 second + 2,000
// milliseconds extra for 3 seconds total.
// See https://github.com/tailscale/tailscale/pull/3753 for details.
err = exec.Command("ping", "-c", "1", "-W", "2000", dstIP.String()).Run()
case "android":
ping := "/system/bin/ping"
if dstIP.Is6() {
@@ -426,11 +487,29 @@ func (ns *Impl) userPing(dstIP netaddr.IP, pingResPkt []byte) {
}
err = exec.Command(ping, "-c", "1", "-w", "3", dstIP.String()).Run()
default:
err = exec.Command("ping", "-c", "1", "-W", "3", dstIP.String()).Run()
ping := "ping"
if isSynology {
ping = "/bin/ping"
}
cmd := exec.Command(ping, "-c", "1", "-W", "3", dstIP.String())
if isSynology && os.Getuid() != 0 {
// On DSM7 we run as non-root and need to pass
// CAP_NET_RAW if our binary has it.
setAmbientCapsRaw(cmd)
}
err = cmd.Run()
}
d := time.Since(t0)
if err != nil {
ns.logf("exec ping of %v failed in %v", dstIP, d)
if d < time.Second/2 {
// If it failed quicker than the 3 second
// timeout we gave above (500 ms is a
// reasonable threshold), then assume the ping
// failed for problems finding/running
// ping. We don't want to log if the host is
// just down.
ns.logf("exec ping of %v failed in %v: %v", dstIP, d, err)
}
return
}
if debugNetstack {
@@ -470,6 +549,7 @@ func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Respons
case 6:
pn = header.IPv6ProtocolNumber
}
p.RemoveECNBits() // Issue 2642
if debugPackets {
ns.logf("[v2] packet in (from %v): % x", p.Src, p.Buffer())
}
@@ -478,6 +558,7 @@ func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Respons
Data: vv,
})
ns.linkEP.InjectInbound(pn, packetBuf)
packetBuf.DecRef()
// We've now delivered this to netstack, so we're done.
// Instead of returning a filter.Accept here (which would also
@@ -508,7 +589,7 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
clientRemoteIP := netaddrIPFromNetstackIP(reqDetails.RemoteAddress)
if !clientRemoteIP.IsValid() {
ns.logf("invalid RemoteAddress in TCP ForwarderRequest: %s", stringifyTEI(reqDetails))
r.Complete(true)
r.Complete(true) // sends a RST
return
}
@@ -524,7 +605,8 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
var wq waiter.Queue
ep, err := r.CreateEndpoint(&wq)
if err != nil {
r.Complete(true)
ns.logf("CreateEndpoint error for %s: %v", stringifyTEI(reqDetails), err)
r.Complete(true) // sends a RST
return
}
r.Complete(false)
@@ -539,6 +621,16 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
// block until the TCP handshake is complete.
c := gonet.NewTCPConn(&wq, ep)
if ns.lb != nil {
if port, ok := ns.lb.GetPeerAPIPort(dialIP); ok {
if reqDetails.LocalPort == port && ns.isLocalIP(dialIP) {
src := netaddr.IPPortFrom(clientRemoteIP, reqDetails.RemotePort)
dst := netaddr.IPPortFrom(dialIP, port)
ns.lb.ServePeerAPIConnection(src, dst, c)
return
}
}
}
if ns.ForwardTCPIn != nil {
ns.ForwardTCPIn(c, reqDetails.LocalPort)
return

View File

@@ -0,0 +1,20 @@
// 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 netstack
import (
"os/exec"
"syscall"
"golang.org/x/sys/unix"
)
func init() {
setAmbientCapsRaw = func(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
AmbientCaps: []uintptr{unix.CAP_NET_RAW},
}
}
}

View File

@@ -0,0 +1,76 @@
// 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 netstack
import (
"runtime"
"testing"
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/net/tsdial"
"tailscale.com/net/tstun"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
)
// TestInjectInboundLeak tests that injectInbound doesn't leak memory.
// See https://github.com/tailscale/tailscale/issues/3762
func TestInjectInboundLeak(t *testing.T) {
tunDev := tstun.NewFake()
dialer := new(tsdial.Dialer)
logf := func(format string, args ...interface{}) {
if !t.Failed() {
t.Logf(format, args...)
}
}
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
Tun: tunDev,
Dialer: dialer,
})
if err != nil {
t.Fatal(err)
}
defer eng.Close()
ig, ok := eng.(wgengine.InternalsGetter)
if !ok {
t.Fatal("not an InternalsGetter")
}
tunWrap, magicSock, ok := ig.GetInternals()
if !ok {
t.Fatal("failed to get internals")
}
ns, err := Create(logf, tunWrap, eng, magicSock, dialer)
if err != nil {
t.Fatal(err)
}
defer ns.Close()
ns.ProcessLocalIPs = true
if err := ns.Start(); err != nil {
t.Fatalf("Start: %v", err)
}
ns.atomicIsLocalIPFunc.Store(func(netaddr.IP) bool { return true })
pkt := &packet.Parsed{}
const N = 10_000
ms0 := getMemStats()
for i := 0; i < N; i++ {
outcome := ns.injectInbound(pkt, tunWrap)
if outcome != filter.DropSilently {
t.Fatalf("got outcome %v; want DropSilently", outcome)
}
}
ms1 := getMemStats()
if grew := int64(ms1.HeapObjects) - int64(ms0.HeapObjects); grew >= N {
t.Fatalf("grew by %v (which is too much and >= the %v packets we sent)", grew, N)
}
}
func getMemStats() (ms runtime.MemStats) {
runtime.GC()
runtime.ReadMemStats(&ms)
return
}