Compare commits

...

25 Commits

Author SHA1 Message Date
Denton Gentry
b04815c9cd VERSION.txt: this is v1.18.2
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-12-15 18:29:57 -08:00
Brad Fitzpatrick
e67182d446 cmd/tailscaled: fix windows logtail integration
I broke it in 1.17.x sometime while rewiring some logs stuff,
mostly in 0653efb092 (but with a handful
of logs-related changes around that time)

Fixes tailscale/corp#3265

Change-Id: Icb5c07412dc6d55f1d9244c5d0b51dceca6a7e34
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 59f4f33f60)
2021-12-14 15:04:05 -08:00
Josh Bleecher Snyder
d4fd80c5d7 net/tshttpproxy: use correct size for Windows BOOL argument
The Windows BOOL type is an int32. We were using a bool,
which is a one byte wide. This could be responsible for the
ERROR_INVALID_PARAMETER errors we were seeing for calls to
WinHttpGetProxyForUrl.

We manually checked all other existing Windows syscalls
for similar mistakes and did not find any.

Updates #879

Co-authored-by: Aaron Klotz <aaron@tailscale.com>
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
(cherry picked from commit ca1b3fe235)
2021-12-14 11:11:36 -08:00
Josh Bleecher Snyder
71be839f03 net/portmapper: improve handling of UPnP parse errors
Without the continue, we might overwrite our current meta
with a zero meta.

Log the error, so that we can check for anything unexpected.

Change-Id: Ia0124e19b7df1191a7685aee949fd19336e41a76
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
(cherry picked from commit a8cc519c70)
2021-12-14 10:40:44 -08:00
Aaron Klotz
32210788a2 net/dns: fix checking for wrapped error when attempting to read wsl.conf for Windows WSL2
Fixes https://github.com/tailscale/tailscale/issues/3437

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
(cherry picked from commit f93cf6fa03)
2021-12-14 10:40:01 -08:00
Brad Fitzpatrick
3d9937513b cmd/tailscale: clarify which prefless flags don't need revert protection
Fixes #3482

Change-Id: Icb51476b0d78d3758cb04df3b565e372b8289a46
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit a8f60cf6e8)
2021-12-14 10:39:54 -08:00
Brad Fitzpatrick
972bcccc36 wgengine/filter: let unknown IPProto match if IP okay & match allows all ports
RELNOTE=yes

Change-Id: I96eaf3cf550cee7bb6cdb4ad81fc761e280a1b2a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 69de3bf7bf)
2021-12-14 10:39:39 -08:00
Brad Fitzpatrick
6c44133d8f ipn/{ipnserver,ipnlocal}: support incoming Taildrop on Synology
If the user has a "Taildrop" shared folder on startup and
the "tailscale" system user has read/write access to it,
then the user can "tailscale file cp" to their NAS.

Updates #2179 (would be fixes, but not super ideal/easy yet)

Change-Id: I68e59a99064b302abeb6d8cc84f7d2a09f764990
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit abc00e9c8d)
2021-12-14 10:38:07 -08:00
Brad Fitzpatrick
2d6404609d net/dns: bound how long we block looking for, restarting systemd-resolved
Fixes #3537

Change-Id: Iba6a3cde75983490d4072b5341f48dbfa2f997c0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit cab5c46481)
2021-12-14 10:36:16 -08:00
Maisem Ali
7d6407a632 wgengine/router{windows}: return the output from the firewallTweaker
on error.

While debugging a customer issue where the firewallTweaker was failing
the only message we have is `router: firewall: error adding
Tailscale-Process rule: exit status 1` which is not really helpful.
This will help diagnose firewall tweaking failures.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
(cherry picked from commit d24a8f7b5a)
2021-12-14 10:36:07 -08:00
Brad Fitzpatrick
7dd677043d cmd/tailscale/cli: don't complain about --accept-routes true->false on Synology
Fixes #3176

Change-Id: I844883e741dccfa5e7771c853180e9f65fb7f7a4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 105c545366)
2021-12-14 10:36:01 -08:00
Josh Bleecher Snyder
d79aad2b89 control/controlclient: stop logging about goal.url invariant
This isn't the ideal solution, but it's good enough for now.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
(cherry picked from commit d9c21936c3)
2021-12-14 10:35:53 -08:00
Josh Bleecher Snyder
e055d10f5b net/tstun: rate limit "self disco out packet" logging
When this happens, it is incredibly noisy in the logs.
It accounts for about a third of all remaining
"unexpected" log lines from a recent investigation.

It's not clear that we know how to fix this,
we have a functioning workaround,
and we now have a (cheap and efficient) metric for this
that we can use for measurements.

So reduce the logging to approximately once per minute.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
(cherry picked from commit 73beaaf360)
2021-12-14 10:35:45 -08:00
Josh Bleecher Snyder
e399b0bec8 net/dns: require space after nameserver/search parsing resolv.conf
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
(cherry picked from commit d10cefdb9b)
2021-12-14 10:35:35 -08:00
Josh Bleecher Snyder
21b1a44cd6 net/dns: handle comments in resolv.conf
Currently, comments in resolv.conf cause our parser to fail,
with error messages like:

ParseIP("192.168.0.100 # comment"): unexpected character (at " # comment")

Fix that.

Noticed while looking through logs.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
(cherry picked from commit 9f00510833)
2021-12-14 10:35:23 -08:00
David Anderson
47975d373f ipn/ipnlocal: resolve exit node IP to ID at EditPrefs time.
Without this, enabling an exit node immediately blackholes all traffic,
but doesn't correctly let it flow to the exit node until the next netmap
update.

Fixes #3447

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-12-08 19:45:01 -08:00
Maisem Ali
caf0da3628 build_docker.sh: prefix version strings with v
Signed-off-by: Maisem Ali <maisem@tailscale.com>
(cherry picked from commit 0a9932f3b2)
2021-12-03 13:56:50 -08:00
Maisem Ali
c005f67314 build_docker.sh: use github.com/tailscale/mkctr instead of docker
Signed-off-by: Maisem Ali <maisem@tailscale.com>
(cherry picked from commit 9feb483ad3)
2021-12-03 13:56:22 -08:00
Denton Gentry
aee2387d6e VERSION.txt: this is v1.18.1
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-11-22 12:06:00 -08:00
Brad Fitzpatrick
6f99ad1630 wgengine/router: demote TestDebugListRules fail to skip
Updates #3360

Change-Id: Ic5c98ea03f3171c13ab9293a0ae74d17fd04d149
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit e8db43e8fa)
2021-11-22 11:05:31 -08:00
Brad Fitzpatrick
c654662250 wgengine/router: fix checkIPRuleSupportsV6 to actually use IPv6
Updates #3358 (should fix it)
Updates #391

Change-Id: Ia62437dfa81247b0b5994d554cf279c3d540e4e7
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 946dfec98a)
2021-11-22 08:48:59 -08:00
Brad Fitzpatrick
bcb979f8bf wgengine/router: don't assume Linux was built with IP_MULTIPLE_TABLES
Updates #3351
Updates #391

Change-Id: I7e66b686e05f3c970846513679cc62556ebe322a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 9259377a7f)
2021-11-22 08:48:56 -08:00
Brad Fitzpatrick
6db09061bc wgengine{,/router}: annotate some more errors
Updates #3351

Change-Id: I8b4f957d2051b3e29401bb449dbadbdada3a7c46
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 0350cf0438)
2021-11-22 08:48:46 -08:00
Brad Fitzpatrick
1b9fbdc057 cmd/tailscaled: disambiguate some startup failure error messages
Updates #3351

Change-Id: I0afead4a084623567f56b19187574fa97b295b2a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 5294125e7a)
2021-11-22 08:48:42 -08:00
Denton Gentry
71f1dd5aa2 VERSION.txt: this is v1.18.0
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-11-18 12:03:11 -08:00
29 changed files with 395 additions and 86 deletions

View File

@@ -56,6 +56,5 @@ RUN GOARCH=$TARGETARCH go install -tags=xversion -ldflags="\
-X tailscale.com/version.GitCommit=$VERSION_GIT_HASH" \
-v ./cmd/tailscale ./cmd/tailscaled
FROM alpine:3.14
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables
FROM ghcr.io/tailscale/alpine-base:3.14
COPY --from=build-env /go/bin/* /usr/local/bin/

6
Dockerfile.base Normal file
View File

@@ -0,0 +1,6 @@
# 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.
FROM alpine:3.14
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables

View File

@@ -23,9 +23,8 @@ build386:
buildlinuxarm:
GOOS=linux GOARCH=arm go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
buildmultiarchimage:
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t ${IMAGE_REPO}:latest --push -f Dockerfile .
./build_docker.sh
check: staticcheck vet depaware buildwindows build386 buildlinuxarm

View File

@@ -1 +1 @@
1.17.0
1.18.2

View File

@@ -30,12 +30,14 @@ else
fi
long_suffix="$change_suffix-t$short_hash"
SHORT="$major.$minor.$patch"
MINOR="$major.$minor"
SHORT="$MINOR.$patch"
LONG="${SHORT}$long_suffix"
GIT_HASH="$git_hash"
if [ "$1" = "shellvars" ]; then
cat <<EOF
VERSION_MINOR="$MINOR"
VERSION_SHORT="$SHORT"
VERSION_LONG="$LONG"
VERSION_GIT_HASH="$GIT_HASH"

View File

@@ -21,8 +21,15 @@ set -eu
eval $(./build_dist.sh shellvars)
docker build \
--build-arg VERSION_LONG=$VERSION_LONG \
--build-arg VERSION_SHORT=$VERSION_SHORT \
--build-arg VERSION_GIT_HASH=$VERSION_GIT_HASH \
-t tailscale:$VERSION_SHORT -t tailscale:latest .
go run github.com/tailscale/mkctr@latest \
--base="ghcr.io/tailscale/alpine-base:3.14" \
--gopaths="\
tailscale.com/cmd/tailscale:/usr/local/bin/tailscale, \
tailscale.com/cmd/tailscaled:/usr/local/bin/tailscaled" \
--ldflags="\
-X tailscale.com/version.Long=${VERSION_LONG} \
-X tailscale.com/version.Short=${VERSION_SHORT} \
-X tailscale.com/version.GitCommit=${VERSION_GIT_HASH}" \
--tags="v${VERSION_SHORT},v${VERSION_MINOR}" \
--repos="tailscale/tailscale,ghcr.io/tailscale/tailscale" \
--push

View File

@@ -20,6 +20,7 @@ import (
"tailscale.com/tstest"
"tailscale.com/types/persist"
"tailscale.com/types/preftype"
"tailscale.com/version/distro"
)
// geese is a collection of gooses. It need not be complete.
@@ -57,6 +58,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
curExitNodeIP netaddr.IP
curUser string // os.Getenv("USER") on the client side
goos string // empty means "linux"
distro distro.Distro
want string
}{
@@ -427,6 +429,38 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
},
want: accidentalUpPrefix + " --netfilter-mode=off --accept-dns=false",
},
{
// Issue 3176: on Synology, don't require --accept-routes=false because user
// migth've had old an install, and we don't support --accept-routes anyway.
name: "synology_permit_omit_accept_routes",
flags: []string{"--hostname=foo"},
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
CorpDNS: true,
AllowSingleHosts: true,
RouteAll: true,
NetfilterMode: preftype.NetfilterOn,
},
goos: "linux",
distro: distro.Synology,
want: "",
},
{
// Same test case as "synology_permit_omit_accept_routes" above, but
// on non-Synology distro.
name: "not_synology_dont_permit_omit_accept_routes",
flags: []string{"--hostname=foo"},
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
CorpDNS: true,
AllowSingleHosts: true,
RouteAll: true,
NetfilterMode: preftype.NetfilterOn,
},
goos: "linux",
distro: "", // not Synology
want: accidentalUpPrefix + " --hostname=foo --accept-routes",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -447,6 +481,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
goos: goos,
flagSet: flagSet,
curExitNodeIP: tt.curExitNodeIP,
distro: tt.distro,
}); err != nil {
got = err.Error()
}

View File

@@ -46,8 +46,10 @@ down").
If flags are specified, the flags must be the complete set of desired
settings. An error is returned if any setting would be changed as a
result of an unspecified flag's default value, unless the --reset
flag is also used.
result of an unspecified flag's default value, unless the --reset flag
is also used. (The flags --authkey, --force-reauth, and --qr are not
considered settings that need to be re-specified when modifying
settings.)
`),
FlagSet: upFlagSet,
Exec: runUp,
@@ -380,6 +382,7 @@ func runUp(ctx context.Context, args []string) error {
env := upCheckEnv{
goos: effectiveGOOS(),
distro: distro.Get(),
user: os.Getenv("USER"),
flagSet: upFlagSet,
upArgs: upArgs,
@@ -622,6 +625,7 @@ type upCheckEnv struct {
upArgs upArgsT
backendState string
curExitNodeIP netaddr.IP
distro distro.Distro
}
// checkForAccidentalSettingReverts (the "up checker") checks for
@@ -672,6 +676,10 @@ func checkForAccidentalSettingReverts(newPrefs, curPrefs *ipn.Prefs, env upCheck
if flagName == "login-server" && ipn.IsLoginServerSynonym(valCur) && ipn.IsLoginServerSynonym(valNew) {
continue
}
if flagName == "accept-routes" && valNew == false && env.goos == "linux" && env.distro == distro.Synology {
// Issue 3176. Old prefs had 'RouteAll: true' on disk, so ignore that.
continue
}
missing = append(missing, fmtFlagValueArg(flagName, valCur))
}
if len(missing) == 0 {

View File

@@ -217,7 +217,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/netmap from tailscale.com/control/controlclient+
tailscale.com/types/nettype from tailscale.com/wgengine/magicsock
tailscale.com/types/opt from tailscale.com/control/controlclient+
tailscale.com/types/pad32 from tailscale.com/derp+
tailscale.com/types/pad32 from tailscale.com/derp
tailscale.com/types/persist from tailscale.com/control/controlclient+
tailscale.com/types/preftype from tailscale.com/ipn+
tailscale.com/types/structs from tailscale.com/control/controlclient+

View File

@@ -440,14 +440,14 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.
log.Printf("Connecting to BIRD at %s ...", args.birdSocketPath)
conf.BIRDClient, err = createBIRDClient(args.birdSocketPath)
if err != nil {
return nil, false, err
return nil, false, fmt.Errorf("createBIRDClient: %w", err)
}
}
if !useNetstack {
dev, devName, err := tstun.New(logf, name)
if err != nil {
tstun.Diagnose(logf, name)
return nil, false, err
return nil, false, fmt.Errorf("tstun.New(%q): %w", name, err)
}
conf.Tun = dev
if strings.HasPrefix(name, "tap:") {
@@ -459,11 +459,11 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.
r, err := router.New(logf, dev, linkMon)
if err != nil {
dev.Close()
return nil, false, err
return nil, false, fmt.Errorf("creating router: %w", err)
}
d, err := dns.NewOSConfigurator(logf, devName)
if err != nil {
return nil, false, err
return nil, false, fmt.Errorf("dns.NewOSConfigurator: %w", err)
}
conf.DNS = d
conf.Router = r

View File

@@ -78,7 +78,10 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch
// Make a logger without a date prefix, as filelogger
// and logtail both already add their own. All we really want
// from the log package is the automatic newline.
logger := log.New(os.Stderr, "", 0)
// We start with log.Default().Writer(), which is the logtail
// writer that logpolicy already installed as the global
// output.
logger := log.New(log.Default().Writer(), "", 0)
ipnserver.BabysitProc(ctx, args, logger.Printf)
}()
@@ -114,6 +117,9 @@ func beWindowsSubprocess() bool {
}
logid := os.Args[2]
// Remove the date/time prefix; the logtail + file logggers add it.
log.SetFlags(0)
log.Printf("Program starting: v%v: %#v", version.Long, os.Args)
log.Printf("subproc mode: logid=%v", logid)

View File

@@ -339,11 +339,9 @@ func (c *Auto) authRoutine() {
continue
}
if url != "" {
if goal.url != "" {
err = fmt.Errorf("[unexpected] server required a new URL?")
report(err, "WaitLoginURL")
}
// goal.url ought to be empty here.
// However, not all control servers get this right,
// and logging about it here just generates noise.
c.mu.Lock()
c.loginGoal = &LoginGoal{
wantLoggedIn: true,

View File

@@ -139,7 +139,11 @@ type LocalBackend struct {
// same as the Network Extension lifetime and we can thus avoid
// double-copying files by writing them to the right location
// immediately.
directFileRoot string
// It's also used on Synology, but in that case DoFinalRename is
// also set true, which moves the *.partial file to its final
// name on completion.
directFileRoot string
directFileDoFinalRename bool // false on macOS, true on Synology
// statusLock must be held before calling statusChanged.Wait() or
// statusChanged.Broadcast().
@@ -219,6 +223,17 @@ func (b *LocalBackend) SetDirectFileRoot(dir string) {
b.directFileRoot = dir
}
// SetDirectFileDoFinalRename sets whether the peerapi file server should rename
// a received "name.partial" file to "name" when the download is complete.
//
// This only applies when SetDirectFileRoot is non-empty.
// The default is false.
func (b *LocalBackend) SetDirectFileDoFinalRename(v bool) {
b.mu.Lock()
defer b.mu.Unlock()
b.directFileDoFinalRename = v
}
// b.mu must be held.
func (b *LocalBackend) maybePauseControlClientLocked() {
if b.cc == nil {
@@ -588,6 +603,11 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// findExitNodeIDLocked updates b.prefs to reference an exit node by ID,
// rather than by IP. It returns whether prefs was mutated.
func (b *LocalBackend) findExitNodeIDLocked(nm *netmap.NetworkMap) (prefsChanged bool) {
if nm == nil {
// No netmap, can't resolve anything.
return false
}
// If we have a desired IP on file, try to find the corresponding
// node.
if b.prefs.ExitNodeIP.IsZero() {
@@ -1651,7 +1671,7 @@ func (b *LocalBackend) SetPrefs(newp *ipn.Prefs) {
}
// setPrefsLockedOnEntry requires b.mu be held to call it, but it
// unlocks b.mu when done.
// unlocks b.mu when done. newp ownership passes to this function.
func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) {
netMap := b.netMap
stateKey := b.stateKey
@@ -1659,6 +1679,10 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) {
oldp := b.prefs
newp.Persist = oldp.Persist // caller isn't allowed to override this
b.prefs = newp
// findExitNodeIDLocked returns whether it updated b.prefs, but
// everything in this function treats b.prefs as completely new
// anyway. No-op if no exit node resolution is needed.
b.findExitNodeIDLocked(netMap)
b.inServerMode = newp.ForceDaemon
// We do this to avoid holding the lock while doing everything else.
newp = b.prefs.Clone()
@@ -2136,11 +2160,12 @@ func (b *LocalBackend) initPeerAPIListener() {
}
ps := &peerAPIServer{
b: b,
rootDir: fileRoot,
tunName: tunName,
selfNode: selfNode,
directFileMode: b.directFileRoot != "",
b: b,
rootDir: fileRoot,
selfNode: selfNode,
tunName: tunName,
directFileMode: b.directFileRoot != "",
directFileDoFinalRename: b.directFileDoFinalRename,
}
b.peerAPIServer = ps

View File

@@ -52,10 +52,17 @@ type peerAPIServer struct {
// directFileMode is whether we're writing files directly to a
// download directory (as *.partial files), rather than making
// the frontend retrieve it over localapi HTTP and write it
// somewhere itself. This is used on GUI macOS version.
// somewhere itself. This is used on the GUI macOS versions
// and on Synology.
// In directFileMode, the peerapi doesn't do the final rename
// from "foo.jpg.partial" to "foo.jpg".
// from "foo.jpg.partial" to "foo.jpg" unless
// directFileDoFinalRename is set.
directFileMode bool
// directFileDoFinalRename is whether in directFileMode we
// additionally move the *.direct file to its final name after
// it's received.
directFileDoFinalRename bool
}
const (
@@ -671,7 +678,7 @@ func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if h.ps.directFileMode {
if h.ps.directFileMode && !h.ps.directFileDoFinalRename {
if inFile != nil { // non-zero length; TODO: notify even for zero length
inFile.markAndNotifyDone()
}

View File

@@ -757,6 +757,18 @@ func New(logf logger.Logf, logid string, store ipn.StateStore, eng wgengine.Engi
b.SetDecompressor(func() (controlclient.Decompressor, error) {
return smallzstd.NewDecoder(nil)
})
if distro.Get() == distro.Synology {
// See if they have a "Taildrop" share.
// See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319
path, err := findSynologyTaildropDir()
if err != nil {
logf("Synology Taildrop support: %v", err)
} else {
logf("Synology Taildrop: using %v", path)
b.SetDirectFileRoot(path)
b.SetDirectFileDoFinalRename(true)
}
}
if opts.AutostartStateKey == "" {
autoStartKey, err := store.ReadState(ipn.ServerModeStartKey)
@@ -1112,3 +1124,17 @@ func (ln *listenerWithReadyConn) Accept() (net.Conn, error) {
}
return ln.Listener.Accept()
}
// findSynologyTaildropDir looks for the first volume containing a
// "Taildrop" directory. We'd run "synoshare --get Taildrop" command
// but on DSM7 at least, we lack permissions to run that.
func findSynologyTaildropDir() (dir string, err error) {
const name = "Taildrop"
for i := 1; i <= 16; i++ {
dir = fmt.Sprintf("/volume%v/%s", i, name)
if fi, err := os.Stat(dir); err == nil && fi.IsDir() {
return dir, nil
}
}
return "", fmt.Errorf("shared folder %q not found", name)
}

View File

@@ -7,6 +7,7 @@ package dns
import (
"bufio"
"bytes"
"context"
"crypto/rand"
"fmt"
"io"
@@ -16,6 +17,7 @@ import (
"path/filepath"
"runtime"
"strings"
"time"
"inet.af/netaddr"
"tailscale.com/types/logger"
@@ -50,10 +52,17 @@ func readResolv(r io.Reader) (config OSConfig, err error) {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
i := strings.IndexByte(line, '#')
if i >= 0 {
line = line[:i]
}
if strings.HasPrefix(line, "nameserver") {
nameserver := strings.TrimPrefix(line, "nameserver")
nameserver = strings.TrimSpace(nameserver)
s := strings.TrimPrefix(line, "nameserver")
nameserver := strings.TrimSpace(s)
if len(nameserver) == len(s) {
return OSConfig{}, fmt.Errorf("missing space after \"nameserver\" in %q", line)
}
ip, err := netaddr.ParseIP(nameserver)
if err != nil {
return OSConfig{}, err
@@ -63,8 +72,12 @@ func readResolv(r io.Reader) (config OSConfig, err error) {
}
if strings.HasPrefix(line, "search") {
domain := strings.TrimPrefix(line, "search")
domain = strings.TrimSpace(domain)
s := strings.TrimPrefix(line, "search")
domain := strings.TrimSpace(s)
if len(domain) == len(s) {
// No leading space?!
return OSConfig{}, fmt.Errorf("missing space after \"domain\" in %q", line)
}
fqdn, err := dnsname.ToFQDN(domain)
if err != nil {
return OSConfig{}, fmt.Errorf("parsing search domains %q: %w", line, err)
@@ -121,12 +134,20 @@ func isResolvedRunning() bool {
return false
}
// is-active exits with code 3 if the service is not active.
err = exec.Command("systemctl", "is-active", "systemd-resolved.service").Run()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
err = exec.CommandContext(ctx, "systemctl", "is-active", "systemd-resolved.service").Run()
// is-active exits with code 3 if the service is not active.
return err == nil
}
func restartResolved() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return exec.CommandContext(ctx, "systemctl", "restart", "systemd-resolved.service").Run()
}
// directManager is an OSConfigurator which replaces /etc/resolv.conf with a file
// generated from the given configuration, creating a backup of its old state.
//
@@ -383,7 +404,12 @@ func (m *directManager) Close() error {
}
if isResolvedRunning() && !runningAsGUIDesktopUser() {
exec.Command("systemctl", "restart", "systemd-resolved.service").Run() // Best-effort.
m.logf("restarting systemd-resolved...")
if err := restartResolved(); err != nil {
m.logf("restart of systemd-resolved failed: %v", err)
} else {
m.logf("restarted systemd-resolved")
}
}
return nil

View File

@@ -14,6 +14,7 @@ import (
"syscall"
"testing"
qt "github.com/frankban/quicktest"
"inet.af/netaddr"
"tailscale.com/util/dnsname"
)
@@ -138,3 +139,61 @@ func TestDirectBrokenRemove(t *testing.T) {
}
testDirect(t, brokenRemoveFS{directFS{prefix: tmp}})
}
func TestReadResolve(t *testing.T) {
c := qt.New(t)
tests := []struct {
in string
want OSConfig
wantErr bool
}{
{in: `nameserver 192.168.0.100`,
want: OSConfig{
Nameservers: []netaddr.IP{
netaddr.MustParseIP("192.168.0.100"),
},
},
},
{in: `nameserver 192.168.0.100 # comment`,
want: OSConfig{
Nameservers: []netaddr.IP{
netaddr.MustParseIP("192.168.0.100"),
},
},
},
{in: `nameserver 192.168.0.100#`,
want: OSConfig{
Nameservers: []netaddr.IP{
netaddr.MustParseIP("192.168.0.100"),
},
},
},
{in: `nameserver #192.168.0.100`, wantErr: true},
{in: `nameserver`, wantErr: true},
{in: `# nameserver 192.168.0.100`, want: OSConfig{}},
{in: `nameserver192.168.0.100`, wantErr: true},
{in: `search tailsacle.com`,
want: OSConfig{
SearchDomains: []dnsname.FQDN{"tailsacle.com."},
},
},
{in: `search tailsacle.com # typo`,
want: OSConfig{
SearchDomains: []dnsname.FQDN{"tailsacle.com."},
},
},
{in: `searchtailsacle.com`, wantErr: true},
{in: `search`, wantErr: true},
}
for _, test := range tests {
cfg, err := readResolv(strings.NewReader(test.in))
if test.wantErr {
c.Assert(err, qt.IsNotNil)
} else {
c.Assert(err, qt.IsNil)
}
c.Assert(cfg, qt.DeepEquals, test.want)
}
}

View File

@@ -6,6 +6,7 @@ package dns
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
@@ -193,7 +194,8 @@ func (fs wslFS) Truncate(name string) error { return fs.WriteFile(name, nil, 064
func (fs wslFS) ReadFile(name string) ([]byte, error) {
b, err := wslCombinedOutput(fs.cmd("cat", "--", name))
if ee, _ := err.(*exec.ExitError); ee != nil && ee.ExitCode() == 1 {
var ee *exec.ExitError
if errors.As(err, &ee) && ee.ExitCode() == 1 {
return nil, os.ErrNotExist
}
return b, err

View File

@@ -763,6 +763,7 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
meta, err := parseUPnPDiscoResponse(buf[:n])
if err != nil {
c.logf("unrecognized UPnP discovery response; ignoring")
continue
}
c.logf("[v1] UPnP reply %+v, %q", meta, buf[:n])
res.UPnP = true

View File

@@ -190,7 +190,7 @@ type autoProxyOptions struct {
AutoConfigUrl *uint16
_ uintptr
_ uint32
FAutoLogonIfChallenged bool
FAutoLogonIfChallenged int32 // BOOL
}
// WINHTTP_PROXY_INFO

View File

@@ -26,7 +26,6 @@ import (
"tailscale.com/types/ipproto"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/pad32"
"tailscale.com/util/clientmetric"
"tailscale.com/wgengine/filter"
)
@@ -68,14 +67,17 @@ type FilterFunc func(*packet.Parsed, *Wrapper) filter.Response
// Wrapper augments a tun.Device with packet filtering and injection.
type Wrapper struct {
logf logger.Logf
logf logger.Logf
limitedLogf logger.Logf // aggressively rate-limited logf used for potentially high volume errors
// tdev is the underlying Wrapper device.
tdev tun.Device
isTAP bool // whether tdev is a TAP device
closeOnce sync.Once
_ pad32.Four
// lastActivityAtomic is read/written atomically.
// On 32 bit systems, if the fields above change,
// you might need to add a pad32.Four field here.
lastActivityAtomic mono.Time // time of last send or receive
destIPActivity atomic.Value // of map[netaddr.IP]func()
@@ -168,10 +170,12 @@ func Wrap(logf logger.Logf, tdev tun.Device) *Wrapper {
}
func wrap(logf logger.Logf, tdev tun.Device, isTAP bool) *Wrapper {
logf = logger.WithPrefix(logf, "tstun: ")
tun := &Wrapper{
logf: logger.WithPrefix(logf, "tstun: "),
isTAP: isTAP,
tdev: tdev,
logf: logf,
limitedLogf: logger.RateLimitedFn(logf, 1*time.Minute, 2, 10),
isTAP: isTAP,
tdev: tdev,
// bufferConsumed is conceptually a condition variable:
// a goroutine should not block when setting it, even with no listeners.
bufferConsumed: make(chan struct{}, 1),
@@ -421,7 +425,7 @@ func (t *Wrapper) filterOut(p *packet.Parsed) filter.Response {
// macOS in Network Extension mode might be.
if p.IPProto == ipproto.UDP && // disco is over UDP; avoid isSelfDisco call for TCP/etc
t.isSelfDisco(p) {
t.logf("[unexpected] received self disco out packet over tstun; dropping")
t.limitedLogf("[unexpected] received self disco out packet over tstun; dropping")
metricPacketOutDropSelfDisco.Add(1)
return filter.DropSilently
}
@@ -535,7 +539,7 @@ func (t *Wrapper) filterIn(buf []byte) filter.Response {
// macOS in Network Extension mode might be.
if p.IPProto == ipproto.UDP && // disco is over UDP; avoid isSelfDisco call for TCP/etc
t.isSelfDisco(p) {
t.logf("[unexpected] received self disco in packet over tstun; dropping")
t.limitedLogf("[unexpected] received self disco in packet over tstun; dropping")
metricPacketInDropSelfDisco.Add(1)
return filter.DropSilently
}

View File

@@ -495,7 +495,7 @@ func TestPeerAPIBypass(t *testing.T) {
func TestFilterDiscoLoop(t *testing.T) {
var memLog tstest.MemLogger
discoPub := key.DiscoPublicFromRaw32(mem.B([]byte{1: 1, 2: 2, 31: 0}))
tw := &Wrapper{logf: memLog.Logf}
tw := &Wrapper{logf: memLog.Logf, limitedLogf: memLog.Logf}
tw.SetDiscoKey(discoPub)
uh := packet.UDP4Header{
IP4Header: packet.IP4Header{

View File

@@ -382,6 +382,9 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
case ipproto.TSMP:
return Accept, "tsmp ok"
default:
if f.matches4.matchProtoAndIPsOnlyIfAllPorts(q) {
return Accept, "otherproto ok"
}
return Drop, "Unknown proto"
}
return Drop, "no rules matched"
@@ -439,6 +442,9 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
case ipproto.TSMP:
return Accept, "tsmp ok"
default:
if f.matches6.matchProtoAndIPsOnlyIfAllPorts(q) {
return Accept, "otherproto ok"
}
return Drop, "Unknown proto"
}
return Drop, "no rules matched"

View File

@@ -23,17 +23,25 @@ import (
"tailscale.com/types/logger"
)
func newFilter(logf logger.Logf) *Filter {
m := func(srcs []netaddr.IPPrefix, dsts []NetPortRange, protos ...ipproto.Proto) Match {
if protos == nil {
protos = defaultProtos
}
return Match{
IPProto: protos,
Srcs: srcs,
Dsts: dsts,
}
// testAllowedProto is an IP protocol number we treat as allowed for
// these tests.
const (
testAllowedProto ipproto.Proto = 116
testDeniedProto ipproto.Proto = 127 // CRUDP, appropriately cruddy
)
func m(srcs []netaddr.IPPrefix, dsts []NetPortRange, protos ...ipproto.Proto) Match {
if protos == nil {
protos = defaultProtos
}
return Match{
IPProto: protos,
Srcs: srcs,
Dsts: dsts,
}
}
func newFilter(logf logger.Logf) *Filter {
matches := []Match{
m(nets("8.1.1.1", "8.2.2.2"), netports("1.2.3.4:22", "5.6.7.8:23-24")),
m(nets("9.1.1.1", "9.2.2.2"), netports("1.2.3.4:22", "5.6.7.8:23-24"), ipproto.SCTP),
@@ -44,6 +52,8 @@ func newFilter(logf logger.Logf) *Filter {
m(nets("153.1.1.1", "153.1.1.2", "153.3.3.3"), netports("1.2.3.4:999")),
m(nets("::1", "::2"), netports("2001::1:22", "2001::2:22")),
m(nets("::/0"), netports("::/0:443")),
m(nets("0.0.0.0/0"), netports("0.0.0.0/0:*"), testAllowedProto),
m(nets("::/0"), netports("::/0:*"), testAllowedProto),
}
// Expects traffic to 100.122.98.50, 1.2.3.4, 5.6.7.8,
@@ -112,6 +122,12 @@ func TestFilter(t *testing.T) {
{Drop, parsed(ipproto.SCTP, "8.1.1.1", "1.2.3.4", 999, 22)},
// But SCTP is allowed for 9.1.1.1
{Accept, parsed(ipproto.SCTP, "9.1.1.1", "1.2.3.4", 999, 22)},
// Unknown protocol is allowed if all its ports are allowed.
{Accept, parsed(testAllowedProto, "1.2.3.4", "5.6.7.8", 0, 0)},
{Accept, parsed(testAllowedProto, "2001::1", "2001::2", 0, 0)},
{Drop, parsed(testDeniedProto, "1.2.3.4", "5.6.7.8", 0, 0)},
{Drop, parsed(testDeniedProto, "2001::1", "2001::2", 0, 0)},
}
for i, test := range tests {
aclFunc := acl.runIn4
@@ -534,13 +550,7 @@ func TestLoggingPrivacy(t *testing.T) {
}
}
func mustIP(s string) netaddr.IP {
ip, err := netaddr.ParseIP(s)
if err != nil {
panic(err)
}
return ip
}
var mustIP = netaddr.MustParseIP
func parsed(proto ipproto.Proto, src, dst string, sport, dport uint16) packet.Parsed {
sip, dip := mustIP(src), mustIP(dst)
@@ -689,7 +699,7 @@ func nets(nets ...string) (ret []netaddr.IPPrefix) {
func ports(s string) PortRange {
if s == "*" {
return PortRange{First: 0, Last: 65535}
return allPorts
}
var fs, ls string
@@ -815,3 +825,40 @@ func TestMatchesFromFilterRules(t *testing.T) {
})
}
}
func TestMatchesMatchProtoAndIPsOnlyIfAllPorts(t *testing.T) {
tests := []struct {
name string
m Match
p packet.Parsed
want bool
}{
{
name: "all_ports_okay",
m: m(nets("0.0.0.0/0"), netports("0.0.0.0/0:*"), testAllowedProto),
p: parsed(testAllowedProto, "1.2.3.4", "5.6.7.8", 0, 0),
want: true,
},
{
name: "all_ports_match_but_packet_wrong_proto",
m: m(nets("0.0.0.0/0"), netports("0.0.0.0/0:*"), testAllowedProto),
p: parsed(testDeniedProto, "1.2.3.4", "5.6.7.8", 0, 0),
want: false,
},
{
name: "ports_requirements_dont_match_unknown_proto",
m: m(nets("0.0.0.0/0"), netports("0.0.0.0/0:12345"), testAllowedProto),
p: parsed(testAllowedProto, "1.2.3.4", "5.6.7.8", 0, 0),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matches := matches{tt.m}
got := matches.matchProtoAndIPsOnlyIfAllPorts(&tt.p)
if got != tt.want {
t.Errorf("got = %v; want %v", got, tt.want)
}
})
}
}

View File

@@ -20,6 +20,8 @@ type PortRange struct {
First, Last uint16 // inclusive
}
var allPorts = PortRange{0, 0xffff}
func (pr PortRange) String() string {
if pr.First == 0 && pr.Last == 65535 {
return "*"
@@ -115,6 +117,29 @@ func (ms matches) matchIPsOnly(q *packet.Parsed) bool {
return false
}
// matchProtoAndIPsOnlyIfAllPorts reports q matches any Match in ms where the
// Match if for the right IP Protocol and IP address, but ports are
// ignored, as long as the match is for the entire uint16 port range.
func (ms matches) matchProtoAndIPsOnlyIfAllPorts(q *packet.Parsed) bool {
for _, m := range ms {
if !protoInList(q.IPProto, m.IPProto) {
continue
}
if !ipInList(q.Src.IP(), m.Srcs) {
continue
}
for _, dst := range m.Dsts {
if dst.Ports != allPorts {
continue
}
if dst.Net.Contains(q.Dst.IP()) {
return true
}
}
}
return false
}
func ipInList(ip netaddr.IP, netlist []netaddr.IPPrefix) bool {
for _, net := range netlist {
if net.Contains(ip) {

View File

@@ -99,7 +99,7 @@ type linuxRouter struct {
ipRuleFixLimiter *rate.Limiter
// Various feature checks for the network stack.
ipRuleAvailable bool
ipRuleAvailable bool // whether kernel was built with IP_MULTIPLE_TABLES
v6Available bool
v6NATAvailable bool
@@ -119,7 +119,7 @@ func newUserspaceRouter(logf logger.Logf, tunDev tun.Device, linkMon *monitor.Mo
return nil, err
}
v6err := checkIPv6()
v6err := checkIPv6(logf)
if v6err != nil {
logf("disabling tunneled IPv6 due to system IPv6 config: %v", v6err)
}
@@ -165,8 +165,13 @@ func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, linkMon *monit
if r.useIPCommand() {
r.ipRuleAvailable = (cmd.run("ip", "rule") == nil)
} else {
// Pretend it is.
r.ipRuleAvailable = true
if rules, err := netlink.RuleList(netlink.FAMILY_V4); err != nil {
r.logf("error querying IP rules (does kernel have IP_MULTIPLE_TABLES?): %v", err)
r.logf("warning: running without policy routing")
} else {
r.logf("[v1] policy routing available; found %d rules", len(rules))
r.ipRuleAvailable = true
}
}
return r, nil
@@ -245,13 +250,13 @@ func (r *linuxRouter) Up() error {
return err
}
if err := r.addIPRules(); err != nil {
return err
return fmt.Errorf("adding IP rules: %w", err)
}
if err := r.setNetfilterMode(netfilterOff); err != nil {
return err
return fmt.Errorf("setting netfilter mode: %w", err)
}
if err := r.upInterface(); err != nil {
return err
return fmt.Errorf("bringing interface up: %w", err)
}
return nil
@@ -1487,7 +1492,7 @@ func cleanup(logf logger.Logf, interfaceName string) {
// missing. It does not check that IPv6 is currently functional or
// that there's a global address, just that the system would support
// IPv6 if it were on an IPv6 network.
func checkIPv6() error {
func checkIPv6(logf logger.Logf) error {
_, err := os.Stat("/proc/sys/net/ipv6")
if os.IsNotExist(err) {
return err
@@ -1519,7 +1524,7 @@ func checkIPv6() error {
}
}
if err := checkIPRuleSupportsV6(); err != nil {
if err := checkIPRuleSupportsV6(logf); err != nil {
return fmt.Errorf("kernel doesn't support IPv6 policy routing: %w", err)
}
@@ -1547,11 +1552,24 @@ func supportsV6NAT() bool {
return bytes.Contains(bs, []byte("nat\n"))
}
func checkIPRuleSupportsV6() error {
func checkIPRuleSupportsV6(logf logger.Logf) error {
// First try just a read-only operation to ideally avoid
// having to modify any state.
if rules, err := netlink.RuleList(netlink.FAMILY_V6); err != nil {
return fmt.Errorf("querying IPv6 policy routing rules: %w", err)
} else {
if len(rules) > 0 {
logf("[v1] kernel supports IPv6 policy routing (found %d rules)", len(rules))
return nil
}
}
// Try to actually create & delete one as a test.
rule := netlink.NewRule()
rule.Priority = 1234
rule.Mark = tailscaleBypassMarkNum
rule.Table = tailscaleRouteTable.num
rule.Family = netlink.FAMILY_V6
// First delete the rule unconditionally, and don't check for
// errors. This is just cleaning up anything that might be already
// there.

View File

@@ -793,7 +793,7 @@ func TestDebugListRules(t *testing.T) {
t.Run(famName[fam], func(t *testing.T) {
rules, err := netlink.RuleList(fam)
if err != nil {
t.Fatal(err)
t.Skipf("skip; RuleList fails with: %v", err)
}
for _, r := range rules {
t.Logf("Rule: %+v", r)
@@ -803,7 +803,7 @@ func TestDebugListRules(t *testing.T) {
}
func TestCheckIPRuleSupportsV6(t *testing.T) {
err := checkIPRuleSupportsV6()
err := checkIPRuleSupportsV6(t.Logf)
if err != nil && os.Getuid() != 0 {
t.Skipf("skipping, error when not root: %v", err)
}

View File

@@ -183,7 +183,10 @@ func (ft *firewallTweaker) runFirewall(args ...string) (time.Duration, error) {
args = append([]string{"advfirewall", "firewall"}, args...)
cmd := exec.Command("netsh", args...)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
err := cmd.Run()
b, err := cmd.CombinedOutput()
if err != nil {
err = fmt.Errorf("%w: %v", err, string(b))
}
return time.Since(t0).Round(time.Millisecond), err
}

View File

@@ -389,11 +389,11 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
e.logf("Bringing wireguard device up...")
if err := e.wgdev.Up(); err != nil {
return nil, fmt.Errorf("wgdev.Up(): %w", err)
return nil, fmt.Errorf("wgdev.Up: %w", err)
}
e.logf("Bringing router up...")
if err := e.router.Up(); err != nil {
return nil, err
return nil, fmt.Errorf("router.Up: %w", err)
}
// It's a little pointless to apply no-op settings here (they
@@ -401,7 +401,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
// router implementation early on.
e.logf("Clearing router settings...")
if err := e.router.Set(nil); err != nil {
return nil, err
return nil, fmt.Errorf("router.Set(nil): %w", err)
}
e.logf("Starting link monitor...")
e.linkMon.Start()