Compare commits

...

134 Commits

Author SHA1 Message Date
David Anderson
ff16e58d23 tailcfg: move NodeKey type to types/key.
This leaves behind a type alias and associated constructor, to allow
for gradual switchover.

Updates #3206.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 16:04:45 -07:00
David Anderson
15d329b4fa tailcfg: add marshaling round-tripping test.
Temporary until #3206 goes away, but having changed the marshal/unmarshal
implementation I got nervous about the new one doing the correct thing.
Thankfully, the test says it does.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 15:21:41 -07:00
David Anderson
27e83402a8 cmd/tailscaled: fix depaware. 2021-10-29 15:07:13 -07:00
David Anderson
b43362852c types/key: delete legacy undifferentiated key types.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 15:01:12 -07:00
David Anderson
eeb97fd89f various: remove remaining uses of key.NewPrivate.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 15:01:12 -07:00
David Anderson
ccd36cb5b1 wgengine: remove use of legacy key parsing helper.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 14:57:32 -07:00
David Anderson
743293d473 types/key: remove node key AsPublic/AsPrivate compat shims.
Updates #3206.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 14:48:36 -07:00
David Anderson
2486d7cb9b tailcfg: remove use of legacy key parsing helper.
Updates #3206.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 14:48:07 -07:00
David Anderson
ef241f782e wgengine/magicsock: remove uses of tailcfg.DiscoKey.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 14:31:44 -07:00
David Anderson
073a3ec416 types/key: correct ShortString representation of DiscoPublic.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 14:31:44 -07:00
Brad Fitzpatrick
cb87b7aa5b version: only prefix VERSION.txt to version if not link-stamped
(Fix to 31e4f60047)

The 31e4f60047 change accidentally
made it always prepend the VERSION.txt, even when it was already
link-stamped properly.

Updates #81

Change-Id: I6cdcff096c25d92d566ad3ac1de5771c7384daea
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-29 14:05:03 -07:00
David Anderson
06dccea416 types/key: fix license header on disco files.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 13:45:49 -07:00
David Anderson
05cc2f510b types/key: new types for disco keys.
Needed for #3206 to remove final uses of key.{Public,Private}.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 13:44:02 -07:00
Maisem Ali
05e55f4a0b logtail/filch: limit buffer file size to 50MB
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-29 13:31:30 -07:00
David Anderson
55b6753c11 wgengine/magicsock: remove use of key.{Public,Private}.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 13:20:13 -07:00
Brad Fitzpatrick
429632d32c ipn/ipnlocal: treat js/wasm interative logins as ephemeral for now
At least until js/wasm starts using browser LocalStorage or something.
But for the foreseeable future, any login from a browser should
be considered ephemeral as the tab can close at any time and lose
the wireguard key, never to be seen again.

Updates #3157

Change-Id: I6c410d86dc7f9f233c3edd623313d9dee2085aac
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-29 11:57:12 -07:00
David Anderson
c1d009b9e9 ipn/ipnstate: use key.NodePublic instead of the generic key.Public.
Updates #3206.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-29 10:00:59 -07:00
David Anderson
ebae0d95d0 Revert "Revert "tailcfg: remove reference to types/key.Public.""
Updates #3206

This reverts commit ef14663934.
2021-10-29 09:38:44 -07:00
David Anderson
ef14663934 Revert "tailcfg: remove reference to types/key.Public."
Breaks corp unit tests.

Updates #3206

This reverts commit 94f6257fde.
2021-10-28 19:00:29 -07:00
David Anderson
94f6257fde tailcfg: remove reference to types/key.Public.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 16:16:38 -07:00
David Anderson
1f06f77dcb derp: remove package shadowing of types/key.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 16:13:28 -07:00
David Anderson
37c150aee1 derp: use new node key type.
Update #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 16:02:11 -07:00
David Anderson
15376f975b types/wgkey: delete, no longer used.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 14:53:38 -07:00
Brad Fitzpatrick
19189d7018 wgengine/router: add a addrFamily type [linux]
In prep for more netlink-ification.

Change-Id: I7c34a04001988107dc2583597aa4f26ddb887e91
2021-10-28 14:52:29 -07:00
David Anderson
c41fe182f0 cmd/tailscaled: update depaware.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 14:28:59 -07:00
David Anderson
4d38194c21 control/controlclient: stop using wgkey.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 14:22:51 -07:00
David Anderson
e03fda7ae6 wgengine/magicsock: remove test uses of wgkey.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 14:17:25 -07:00
Brad Fitzpatrick
7c40a5d440 wgengine/router: refactor in prep for Linux netlink-ification
Pull out the list of policy routing rules to a data structure
now shared between the add & delete paths, but to also be shared
by the netlink paths in a future change.

Updates #391

Change-Id: I119ab1c246f141d639006c808b61c585c3d67924
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-28 13:56:46 -07:00
Brad Fitzpatrick
ada8cd99af control/controlclient: add a LoginEphemeral LoginFlags bit
Change-Id: Ib9029ea0c49aa2ee1b6aac6e464ab1f16aef92e8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-28 13:21:35 -07:00
Josh Bleecher Snyder
94fb42d4b2 all: use testingutil.MinAllocsPerRun
There are a few remaining uses of testing.AllocsPerRun:
Two in which we only log the number of allocations,
and one in which dynamically calculate the allocations
target based on a different AllocsPerRun run.

This also allows us to tighten the "no allocs"
test in wgengine/filter.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-10-28 12:48:37 -07:00
Josh Bleecher Snyder
1df865a580 wgengine/magicsock: allow even fewer allocs per UDP receive
We improved things again for Go 1.18. Lock that in.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-10-28 12:48:37 -07:00
Josh Bleecher Snyder
c1d377078d wgengine/magicsock: use testingutil.MinAllocsPerRun
This speeds up and deflakes the test.

Fixes #2826 (again)

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-10-28 12:48:37 -07:00
Josh Bleecher Snyder
4bb2c6980d util/testingutil: new package with MinAllocsPerRun
testing.AllocsPerRun measures the total allocations performed
by the entire program while repeatedly executing a function f.
If some unrelated part of the rest of the program happens to
allocate a lot during that period, you end up with a test failure.

Ideally, the rest of the program would be silent while
testing.AllocsPerRun executes.

Realistically, that is often unachievable.

AllocsPerRun attempts to mitigate this by setting GOMAXPROCS to 1,
but that doesn't prevent other code from running;
it only makes it less likely.

You can also mitigate this by passing a large iteration count to
AllocsPerRun, but that is unreliable and needlessly expensive.

Unlike most of package testing, AllocsPerRun doesn't use any
toolchain magic, so we can just write a replacement.

One wild idea is to change how we count mallocs.
Instead of using runtime.MemStats, turn on memory profiling with a
memprofilerate of 1. Discard all samples from the profile whose stack
does not contain testing.AllocsPerRun. Count the remaining samples to
determine the number of mallocs.

That's fun, but overkill.

Instead, this change adds a simple API that attempts to get f to
run at least once with a target number of allocations.
This is useful when you know that f should allocate consistently.
We can then assume that any iterations with too many allocations
are probably due to one-time costs or background noise.

This suits most uses of AllocsPerRun.

Ratcheting tests tend to be significantly less flaky,
because they are biased towards success.
They can also be faster, because they can exit early,
once success has been reached.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-10-28 12:48:37 -07:00
Josh Bleecher Snyder
640de1921f depaware: update
To fix build broken by c9bf773312.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-10-28 12:35:14 -07:00
Brad Fitzpatrick
aad46bd9ff wgengine/router: stop cleaning up old dev rules on Linux
Anybody using that one old, unreleased version of Tailscale from over
a year ago should've rebooted their machine by now to get various
non-Tailscale security updates. :)

Change-Id: If9e043cb008b20fcd6ddfd03756b3b23a9d7aeb5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-28 12:29:54 -07:00
David Anderson
c9bf773312 wgengine/magicsock: replace use of wgkey with new node key type.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 11:21:52 -07:00
Brad Fitzpatrick
d36c0d3566 wgengine/router: add debug test to enumerate rules
No non-test changes.

Updates #391

Change-Id: Ia88610c08e07a119d002e58250463cb4659b9f54
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-28 11:12:16 -07:00
David Anderson
6e5175373e types/netmap: use new node key type.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 10:44:34 -07:00
David Anderson
96ad68c5d6 ipn: remove mention of wgkey in comment.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 10:40:44 -07:00
David Anderson
bab2d92c42 tailcfg: remove use of wgkey.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 10:39:56 -07:00
David Anderson
3164c7410e wgengine/wgcfg: remove unused helper function.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 10:38:13 -07:00
David Anderson
0c546a28ba types/persist: use new node key type.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 10:29:43 -07:00
Denton Gentry
5302e4be96 net/portmapper: only print PCP/PMP if VerboseLogs
Make UPnP, NAT-PMP, and PCP packet reception logs be [v1] so
they will never appear on stdout and instead only go to logtail.

```
$ tailscale netcheck
2021/10/15 22:50:31 portmap: Got PMP response; IP: w.x.y.z, epoch: 1012707
2021/10/15 22:50:31 portmap: Got PCP response: epoch: 1012707

Report:
        * UDP: true
        * IPv4: yes, w.x.y.z:1511
        * IPv6: no
        * MappingVariesByDestIP: true
        * HairPinning: false
        * PortMapping: NAT-PMP, PCP
        * Nearest DERP: San Francisco
        * DERP latency:
                - sfo: 5.9ms   (San Francisco)
                - sea: 24ms    (Seattle)
                - dfw: 45ms    (Dallas)
                - ord: 53.7ms  (Chicago)
                - nyc: 74.1ms  (New York City)
                - tok: 111.1ms (Tokyo)
                - lhr: 139.4ms (London)
                - syd: 152.7ms (Sydney)
                - fra: 153.1ms (Frankfurt)
                - sin: 182.1ms (Singapore)
                - sao: 190.1ms (S_o Paulo)
                - blr: 218.6ms (Bangalore)
```

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-10-28 10:18:51 -07:00
Brad Fitzpatrick
dc2fbf5877 wgengine/router: start using netlink instead of 'ip' on Linux
Converts up, down, add/del addresses, add/del routes.

Not yet done: rules.

Updates #391

Change-Id: I02554ca07046d18f838e04a626ba99bbd35266fb
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-28 10:16:26 -07:00
Brad Fitzpatrick
7b87c04861 tailcfg: add RegisterRequest.Ephemeral to request new ephemeral node
So js/wasm clients can log in for a bit using regular Gmail/GitHub auth
without using an ephemeral key but still have their node cleaned up
when they're done.

Updates #3157

Change-Id: I49e3d14e9d355a9b8bff0ea810b0016bfe8d47f2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-28 10:05:36 -07:00
Michael Stapelberg
3ad11f6b8c Dockerfile: build/run instructions need a /, not :
The image is pulled using tailscale/tailscale:latest, and can be run using tailscale/tailscale

Signed-off-by: Michael Stapelberg <michael@stapelberg.de>
2021-10-28 09:51:50 -07:00
Brad Fitzpatrick
31e4f60047 version: embed VERSION.txt in unstamped version
Temporary measure until we switch to Go 1.18.

    $ go run ./cmd/tailscale version
    1.17.0-date.20211022
      go version: go1.17

Updates #81

Change-Id: Ic82ebffa5f46789089e5fb9810b3f29e36a47f1a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-28 09:48:24 -07:00
David Anderson
a9c78910bd wgengine/wgcfg: convert to use new node key type.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 09:39:23 -07:00
David Anderson
a47158e14d cmd/derper: use new node key type.
Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 09:39:23 -07:00
David Anderson
bc89a796ec types/key: add a dedicated type for node keys.
Complete with converters to all the other types that represent a
node key today, so the new type can gradually subsume old ones.

Updates #3206

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-28 09:16:39 -07:00
Brad Fitzpatrick
22dbaa0894 ipn/ipnserver: add New, Server.Server
Change-Id: I1dc85a5131b9a628d7fc780fde7103492cd3f1f8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-28 09:01:01 -07:00
Brad Fitzpatrick
d381bc2b6c ipn/ipnserver: move the unserved connection logic to a Listener
So future refactors can only deal with a net.Listener and
be unconcerned with their caller's (Windows-specific) struggles.

Change-Id: I0af588b9a769ab65c59b0bd21f8a0c99abfa1784
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-28 09:01:01 -07:00
Brad Fitzpatrick
c23a378f63 ipn/ipnserver: start refactoring ipnserver.Run into smaller pieces
I'll keep ipnserver.Run for compatibility, but it'll be a wrapper
around several smaller pieces. (more testable too)

For now, start untangling some things in preparation.

Plan is to have to have a constructor for the just-exported
ipnserver.Server type that takes a LocalBackend and can
accept (in a new method) on a provided listener.

Change-Id: Ide73aadaac1a82605c97a2af1321d0d8f60b2a8c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-28 09:01:01 -07:00
Brad Fitzpatrick
e4d2ef2b67 go.sum: tidy
Change-Id: I198755a3a94d89d838ff817573fbdd198412b2f3
2021-10-27 21:36:49 -07:00
Josh Bleecher Snyder
cf8fcc1254 syncs: mark as safe for Go 1.18
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-10-27 18:10:09 -07:00
Brad Fitzpatrick
869999955d ipn/ipnserver: export server type as Server
It's all opaque, there's no constructor, and no exported
methods, so it's useless at this point, but this is one
small refactoring step.

Change-Id: Id961e8880cf0c84f1a0a989eefff48ecb3735add
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-27 15:48:28 -07:00
Josh Bleecher Snyder
f27950e97f go.mod: upgrade netaddr, netstack
For Go 1.18 support.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2021-10-27 15:47:15 -07:00
David Anderson
060ba86baa net/portmapper: ignore IGD SSDP responses from !defaultgw
Now that we multicast the SSDP query, we can get IGD offers from
devices other than the current device's default gateway. We don't want
to accidentally bind ourselves to those.

Updates #3197

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-27 15:34:27 -07:00
Brad Fitzpatrick
675f9cd199 cmd/tailscale/cli: add, use log.Fatalf indirection for js/wasm
Updates #3157

Change-Id: I97a4962a44bd36313ff68388e3de0d852a8fa869
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-27 15:19:52 -07:00
David Anderson
4a65b07e34 net/portmapper: also send UPnP SSDP query to the SSDP multicast address.
Fixes #3197

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-27 15:02:03 -07:00
Brad Fitzpatrick
5df7ac70d6 cmd/tailscale/cli: add Stdout, Stderr and output through them
So js/wasm can override where those go, without implementing
an *os.File pipe pair, etc.

Updates #3157

Change-Id: I14ba954d9f2349ff15b58796d95ecb1367e8ba3a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-27 14:53:46 -07:00
Brad Fitzpatrick
2ce5fc7b0a safesocket: fail early on js/wasm
Updates #3157

Change-Id: Ib78efb3b1ba34ca4fb34296033b95327188774a7
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-27 14:53:28 -07:00
Brad Fitzpatrick
3b5ada1fd8 cmd/tailscale/cli: use errors.Is to check ff's wrapped flag errors
And also check from its Parse method.

Change-Id: I18754920575254cb6858a16b7954e74aa16483a1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-27 14:06:22 -07:00
Brad Fitzpatrick
75de4e9cc2 cmd/tailscale/cli: don't ExitOnError on js/wasm
An os.Exit brings down the whole wasm module.

Updates #3157

Change-Id: I3daa97fd854715b901f3dbb04b57d841576b60b1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-27 13:59:12 -07:00
Brad Fitzpatrick
b0b0a80318 net/netcheck: implement netcheck for js/wasm clients
And the derper change to add a CORS endpoint for latency measurement.

And a little magicsock change to cut down some log spam on js/wasm.

Updates #3157

Change-Id: I5fd9e6f5098c815116ddc8ac90cbcd0602098a48
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-27 09:59:31 -07:00
Brad Fitzpatrick
eebe7afad7 derp/derphttp: only log about a weird upgrade if any was specified
Otherwise random browser requests to /derp cause log spam.

Change-Id: I7bdf991d2106f0323868e651156c788a877a90d5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-27 09:41:25 -07:00
Maisem Ali
81cabf48ec control/controlclient,tailcfg: propagate registration errors to the frontend
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-27 06:57:26 -07:00
Denton Gentry
139a6c4c9c net/dns: detect when resolvconf points to systemd-resolved.
There are /etc/resolv.conf files out there where resolvconf wrote
the file but pointed to systemd-resolved as the nameserver.
We're better off handling those as systemd-resolved.

> # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
> #     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
> # 127.0.0.53 is the systemd-resolved stub resolver.
> # run "systemd-resolve --status" to see details about the actual nameservers.

Fixes https://github.com/tailscale/tailscale/issues/3026
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-10-26 18:00:31 -07:00
David Anderson
a320d70614 net/dns: fall back to copy+delete/truncate if moving to/from /etc/resolv.conf fails.
In some containers, /etc/resolv.conf is a bind-mount from outside the container.
This prevents renaming to or from /etc/resolv.conf, because it's on a different
filesystem from linux's perspective. It also prevents removing /etc/resolv.conf,
because doing so would break the bind-mount.

If we find ourselves within this environment, fall back to using copy+delete when
renaming to /etc/resolv.conf, and copy+truncate when renaming from /etc/resolv.conf.

Fixes #3000

Co-authored-by: Denton Gentry <dgentry@tailscale.com>
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-26 09:03:37 -07:00
David Anderson
04d24d3a38 net/dns: move directManager function below directManager's definition.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-26 09:03:37 -07:00
David Anderson
422ea4980f net/dns: remove a tiny wrapper function that isn't contributing anything.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-26 09:03:37 -07:00
Maisem Ali
10745c099a tailcfg: add Node.Tags
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-25 22:04:45 -07:00
Maisem Ali
85fa1b0d61 wgengine: fail NewUserspaceEngine if wireguard device doesn't come up
Just something I ran across while debugging an unrelated failure. This
is not in response to any bug/issue.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-25 12:34:14 -07:00
Maya Kaczorowski
59a906df47 Merge pull request #3179 from tailscale/bugreport
.github: Add Synology as an OS
2021-10-25 09:59:39 -07:00
Denton Gentry
c1293b3858 .github: Add Synology as an OS
Sufficiently different from Linux to split it out separately.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-10-24 06:01:29 -07:00
Brad Fitzpatrick
505f844a43 cmd/derper, derp/derphttp: add websocket support
Updates #3157

Change-Id: I337a919a3b350bc7bd9af567b49c4d5d6616abdd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-22 12:51:30 -07:00
David Crawshaw
0b62f26349 magicsock: remove test data race
Speculative, I haven't been able to replicate it locally.

Fixes #3156

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2021-10-22 11:19:07 -07:00
Brad Fitzpatrick
09e692e318 health: don't look for UDP goroutines in js/wasm health check
Updates #3157

Change-Id: I43d97e6876eeb2d1936fc567835134568bb8615c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-22 09:12:00 -07:00
Brad Fitzpatrick
ed3fb197ad wgengine/magicsock: fix/disable a few misc things to get js/wasm working
Updates #3157

Change-Id: Ie9e3a772bb9878584080bb257b32150492e26eaf
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-22 09:09:37 -07:00
Brad Fitzpatrick
a8e2cceefd net/netcheck: hard-code preferred DERP region 900 on js/wasm for now
See TODO in code.

Updates #3157

Change-Id: I3a14dd2cf51d3c21336bb357af5abc362a079ff4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-22 09:08:15 -07:00
Brad Fitzpatrick
c209278a9b go.mod: bump wireguard-go to pick up upstreamed js/wasm build fixes
Updates #3157

Change-Id: I727cb5f77110c87850061aa3b9f03c15dbda70d3
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-21 10:37:07 -07:00
Brad Fitzpatrick
9b101bd6af net/tstun: don't compile the code New constructor on js/wasm
Updates #3157

Change-Id: I81603edf3e69e6f1517b0074eef6b648f2981c50
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-21 10:36:30 -07:00
David Anderson
c60806b557 scripts/installer.sh: use .asc suffix for armored debian gpg key URL.
Fixes #2512

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-20 18:58:50 -07:00
Maxim Merzhanov
9f954628e5 net/dns: ignore UnknownMethod error in SetLinkDefaultRoute for resolved manager
Signed-off-by: Maxim Merzhanov <maksimmerzh@gmail.com>
2021-10-20 16:31:24 -07:00
Brad Fitzpatrick
e25afc6656 wgengine/magicsock: don't try to determine endpoints on js/wasm
Avoid netcheck, LocalAddr, etc.

Updates #3157

Change-Id: Ibc875c787c0e101b8076e64833f4fcc809372815
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-20 12:57:45 -07:00
David Anderson
8e3b8dbb50 scripts/installer.sh: Correct support for Oracle Linux.
Co-Authored-By: Jonathan Hult <jhult@mythics.com>
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-20 12:47:38 -07:00
Brad Fitzpatrick
6cb2705833 wgengine/magicsock: don't run UDP listeners on js/wasm
Be DERP-only for now. (WebRTC can come later :))

Updates #3157

Change-Id: I56ebb3d914e37e8f4ab651306fd705b817ca381c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-20 12:23:22 -07:00
Brad Fitzpatrick
8efc306e4f net/interfaces: assume the network's up on js/wasm
Updates #3157

Change-Id: If4acd33598ad5e8ef7fb5960964c9ac32bc8f68b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-20 12:23:22 -07:00
Brad Fitzpatrick
9310713bfb all: fix some js/wasm compilation issues
Change-Id: I05a3a4835e225a1e413ec3540a7c7e4a2d477084
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-20 10:06:16 -07:00
Maisem Ali
0bf515e780 cmd/tailscale: changes to --advertise-tags should wait for possible
reauth.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-20 10:31:40 -04:00
David Anderson
1b4e007425 scripts/installer.sh: use expr for regex matches.
=~ doesn't work in posix shell, only in bash, and we don't use bash.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-19 19:33:52 -07:00
David Anderson
7ce9c7ce84 scripts/installer.sh: use the appropriate apt key wrangling for the distro.
Updates #1937

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-19 19:15:07 -07:00
David Anderson
118fe105f5 scripts/installer.sh: add a few more supported distro versions.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-19 19:15:07 -07:00
Brad Fitzpatrick
c30fa5903d wgengine/magicsock: remove peerMap.byDiscoKey map
No longer used.

Updates #3088

Change-Id: I0ced3f87baa4053d3838d3c4a828ed0293923825
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-19 12:22:11 -07:00
David Crawshaw
3552d86525 wgengine/magicsock: turn down timeouts in tests
Before:

	--- PASS: TestActiveDiscovery (11.78s)
	    --- PASS: TestActiveDiscovery/facing_easy_firewalls (5.89s)
	    --- PASS: TestActiveDiscovery/facing_nats (5.89s)
	    --- PASS: TestActiveDiscovery/simple_internet (0.89s)

After:

	--- PASS: TestActiveDiscovery (1.98s)
	    --- PASS: TestActiveDiscovery/facing_easy_firewalls (0.99s)
	    --- PASS: TestActiveDiscovery/facing_nats (0.99s)
	    --- PASS: TestActiveDiscovery/simple_internet (0.89s)

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2021-10-19 09:22:50 -07:00
dependabot[bot]
eaa0aef934 go.mod: bump github.com/creack/pty from 1.1.16 to 1.1.17
Bumps [github.com/creack/pty](https://github.com/creack/pty) from 1.1.16 to 1.1.17.
- [Release notes](https://github.com/creack/pty/releases)
- [Commits](https://github.com/creack/pty/compare/v1.1.16...v1.1.17)

---
updated-dependencies:
- dependency-name: github.com/creack/pty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-18 15:34:09 -07:00
David Anderson
b956139b0c wgengine/magicsock: track IP<>node mappings without relying on discokeys.
Updates #3088.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-18 14:58:21 -07:00
Brad Fitzpatrick
7a243ae5b1 wgengine/magicsock: finish TODO to speed up peerMap.forEachEndpointWithDiscoKey
Now that peerMap tracks the set of nodes for a DiscoKey.

Updates #3088

Change-Id: I927bf2bdfd2b8126475f6b6acc44bc799fcb489f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-18 14:50:28 -07:00
Aaron Klotz
c6ea282b3f utils/winutil utils/winutil/vss: add utility function for extracting data from Windows System Restore Point backups.
utils/winutil/vss contains just enough COM wrapping to query the Volume Shadow Copy service for snapshots.
WalkSnapshotsForLegacyStateDir is the friendlier interface that adds awareness of our actual use case,
mapping the snapshots and locating our legacy state directory.

Updates #3011

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2021-10-18 15:48:42 -06:00
Aaron Klotz
6425f497b1 ipn/ipnserver paths: add paths.LegacyStateFilePath
Moving this information into a centralized place so that it is accessible to
code in subsequent commits.

Updates #3011

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2021-10-18 15:48:42 -06:00
Brad Fitzpatrick
11fdb14c53 wgengine/magicsock: don't check always-non-nil endpoint for nil-ness
Continuation of 2aa5df7ac1, remove nil
check because it can never be nil. (It previously was able to be nil.)

Change-Id: I59cd9ad611dbdcbfba680ed9b22e841b00c9d5e6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-18 14:37:59 -07:00
David Anderson
e7eb46bced wgengine/magicsock: add an explicit else branch to peerMap update.
Clarifies that the replace+delete of peerinfo data is only when peerInfo
already exists.

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-18 13:05:52 -07:00
David Anderson
1c56643136 disco: amplify comment that disco ping's NodeKey shouldn't be trusted by itself.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-18 13:05:52 -07:00
Robert
cb030a0bb4 docs/k8s: add example about setting up a subnet router
Signed-off-by: Robert <rspier@pobox.com>
Co-authored-by: Maisem Ali <3953239+maisem@users.noreply.github.com>
2021-10-18 14:54:00 -04:00
Maisem Ali
53199738fb wgengine: don't try to delete legacy netfilter rules on synology.
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-18 14:51:25 -04:00
David Anderson
2aa5df7ac1 wgengine/magicsock: document and enforce that peerInfo.ep is non-nil.
Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-18 10:49:24 -07:00
David Anderson
521b44e653 wgengine/magicsock: move discoKey fields to the mutex-protected section.
Fixes #3106

Signed-off-by: David Anderson <danderson@tailscale.com>
2021-10-18 10:49:24 -07:00
Maisem Ali
27799a1a96 wgengine: only use AmbientCaps on DSM7+
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-18 13:39:51 -04:00
Brad Fitzpatrick
a6d02dc122 wgengine/magicsock: track which NodeKey each DiscoKey was last for
This adds new fields (currently unused) to discoInfo to track what the
last verified (unambiguous) NodeKey a DiscoKey last mapped to, and
when.

Then on CallMeMaybe, Pong and on most Pings, we update the mapping
from DiscoKey to the current NodeKey for that DiscoKey.

Updates #3088

Change-Id: Idc4261972084dec71cf8ec7f9861fb9178eb0a4d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-18 09:55:02 -07:00
Brad Fitzpatrick
c759fcc7d3 wgengine/magicsock: fix data race with sync.Pool in error+logging path
Fixes #3122

Change-Id: Ib52e84f9bd5813d6cf2e80ce5b2296912a48e064
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-17 17:27:57 -07:00
Brad Fitzpatrick
75a7779b42 disco, wgengine/magicsock: send self node key in disco pings
This lets clients quickly (sub-millisecond within a local LAN) map
from an ambiguous disco key to a node key without waiting for a
CallMeMaybe (over relatively high latency DERP).

Updates #3088

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-17 10:24:07 -07:00
Joe Tsai
9af27ba829 cmd/cloner: mangle "go:generate" in cloner.go
The "go generate" command blindly looks for "//go:generate" anywhere
in the file regardless of whether it is truly a comment.
Prevent this false positive in cloner.go by mangling the string
to look less like "//go:generate".

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2021-10-16 17:53:43 -07:00
Denton Gentry
def650b3e8 wgengine/magicsock: don't Rebind after STUN error if closed.
https://github.com/tailscale/tailscale/pull/3014 added a
rebind on STUN failure, which means there can now be a
tailscale.com/wgengine/magicsock.(*RebindingUDPConn).ReadFromNetaddr
in progress at the end of the test waiting for a STUN
response which will never arrive.

This causes a test flake due to the resource leak in those
cases where the Conn decided to rebind. For whatever reason,
it mostly flakes with Windows.

If the Conn is closed, don't Rebind after a send error.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-10-16 17:22:13 -07:00
Brad Fitzpatrick
f55c2bccf5 wgengine/magicsock: don't call setAddrToDiscoLocked on DERP ping
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-16 07:43:48 -07:00
Brad Fitzpatrick
569f70abfd wgengine/magicsock: finish some renamings of discoEndpoint to endpoint
Renames only; continuation of earlier 8049063d35

These kept confusing me while working on #3088

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-15 22:26:07 -07:00
Brad Fitzpatrick
695df497ba wgengine/magicsock: delete peerMap.endpointForDiscoKey, remove remaining caller
The one remaining caller of peerMap.endpointForDiscoKey was making the
improper assumption that there's exactly 1 node with a given DiscoKey
in the network. That was the cause of #3088.

Now that all the other callers have been updated to not use
endpointForDiscoKey, there's no need to try to keep maintaining that
prone-to-misuse index.

Updates #3088

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-15 22:19:27 -07:00
Brad Fitzpatrick
04fd94acd6 wgengine/magicsock: remove endpointForDiscoKey call from handleDiscoMessage
A DiscoKey maps 1:n to endpoints. When we get a disco pong, we don't
necessarily know which endpoint sent it to us. Ask them all. There
will only usually be 1 (and in rare circumstances 2). So it's easier
to ask all two rather than building new maps from the random ping TxID
to its endpoint.

Updates #3088

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-15 21:59:15 -07:00
Brad Fitzpatrick
151b4415ca wgengine/magicsock: remove endpoint parameter from handlePingLocked
We can reply to a ping without knowing which exact node it's from.  As
long as it's in our netmap, it's safe to reply. If there's more than
one node with that discokey, it doesn't matter who we're relpying to.

Updates #3088

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-15 21:44:52 -07:00
Brad Fitzpatrick
d86081f353 wgengine/magicsock: add new discoInfo type for DiscoKey state, move some fields
As more prep for removing the false assumption that you're able to
map from DiscoKey to a single peer, move the lastPingFrom and lastPingTime
fields from the endpoint type to a new discoInfo type, effectively upgrading
the old sharedDiscoKey map (which only held a *[32]byte nacl precomputed key
as its value) to discoInfo which then includes that naclbox key.

Then start plumbing it into handlePing in prep for removing the need
for handlePing to take an endpoint parameter.

Updates #3088

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-15 20:48:44 -07:00
Brad Fitzpatrick
e5779f019e wgengine/magicsock: move temporary endpoint lookup later, add TODO to remove
Updates #3088

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-15 19:22:30 -07:00
Brad Fitzpatrick
36a07089ee wgengine/magicsock: remove redundant/wrong sharedDiscoKey delete
The pass just after in this method handles cleaning up sharedDiscoKey.
No need to do it wrong (assuming DiscoKey => 1 node) earlier.

Updates #3088

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-15 16:57:59 -07:00
Brad Fitzpatrick
3e80806804 wgengine/magicsock: pass src NodeKey to handleDiscoMessage for DERP disco msgs
And then use it to avoid another lookup-by-DiscoKey.

Updates #3088
2021-10-15 16:52:42 -07:00
Brad Fitzpatrick
82fa15fa3b wgengine/magicsock: start removing endpointForDiscoKey
It's not valid to assume that a discokey is globally unique.

This removes the first two of the four callers.

Updates #3088

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-15 16:44:02 -07:00
Maisem Ali
7817ab6b20 net/dns/resolver: set maxDoHInFlight to 1000 on iOS 15+.
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-14 23:29:23 -04:00
Maisem Ali
2662a1c98c hostinfo: add EnvType for Kubernetes
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-14 23:02:26 -04:00
Felipe Cruz Martinez
47ace13ac8 Fix k8s README
Use the correct KUBE_SECRET value
2021-10-14 19:12:48 -04:00
Maisem Ali
c6d3f622e9 ipn/ipnlocal: use netaddr.IPSetBuilder when constructing list of interface IPPrefixes.
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-14 18:53:54 -04:00
Maisem Ali
e538d47bd5 docs/k8s: update run.sh to use the correct socket path
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2021-10-14 18:09:12 -04:00
Brad Fitzpatrick
4a3e2842d9 net/interfaces: add List, GetList
And start moving funcs to methods on List.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-14 15:06:12 -07:00
Brad Fitzpatrick
14f9c75293 wgengine/router: ignore Linux ip route error adding dup route
Updates #3060
Updates #391

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-14 14:00:45 -07:00
Brad Fitzpatrick
ddf3394b40 ipn/ipnlocal: don't try to block localhost traffic when using exit nodes
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-10-14 14:00:45 -07:00
David Crawshaw
77696579f5 net/dns/resolver: drop dropping log
Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2021-10-14 13:58:24 -07:00
Brad Fitzpatrick
7742caef0a .github/workflows: always ignore go:generate dnsfallback check
Keep the now-redundant github.ref branch check for
the future, in case we want to change the policy for main vs
release-branch again later. Save somebody the YAML debugging
time.
2021-10-14 13:57:02 -07:00
Joe Tsai
2fa004a2a0 cmd/cloner: emit go:generate pragmas (#3082)
Emit a go:generate pragma with the full set of flags passed to cloner.
This allows the user to simply run "go generate" at the location
of the generate file to reproduce the file.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2021-10-14 12:25:55 -07:00
145 changed files with 4434 additions and 1990 deletions

View File

@@ -41,6 +41,7 @@ body:
- Windows
- iOS
- Android
- Synology
- Other
validations:
required: false
@@ -49,7 +50,7 @@ body:
attributes:
label: OS version
description: What OS version are you using?
placeholder: e.g., Debian 11.0, macOS Big Sur 11.6
placeholder: e.g., Debian 11.0, macOS Big Sur 11.6, Synology DSM 7
validations:
required: false
- type: input

View File

@@ -30,7 +30,7 @@ jobs:
then
pkgs=$(go list ./... | grep -v dnsfallback)
else
pkgs=$(go list ./...)
pkgs=$(go list ./... | grep -v dnsfallback)
fi
go generate $pkgs
echo

View File

@@ -17,11 +17,11 @@
#
# To build the Dockerfile:
#
# $ docker build -t tailscale:tailscale .
# $ docker build -t tailscale/tailscale .
#
# To run the tailscaled agent:
#
# $ docker run -d --name=tailscaled -v /var/lib:/var/lib -v /dev/net/tun:/dev/net/tun --network=host --privileged tailscale:tailscale tailscaled
# $ docker run -d --name=tailscaled -v /var/lib:/var/lib -v /dev/net/tun:/dev/net/tun --network=host --privileged tailscale/tailscale tailscaled
#
# To then log in:
#

View File

@@ -95,7 +95,20 @@ func main() {
}
contents := new(bytes.Buffer)
fmt.Fprintf(contents, header, *flagTypes, pkg.Name)
var flagArgs []string
if *flagTypes != "" {
flagArgs = append(flagArgs, "-type="+*flagTypes)
}
if *flagOutput != "" {
flagArgs = append(flagArgs, "-output="+*flagOutput)
}
if *flagBuildTags != "" {
flagArgs = append(flagArgs, "-tags="+*flagBuildTags)
}
if *flagCloneFunc {
flagArgs = append(flagArgs, "-clonefunc")
}
fmt.Fprintf(contents, header, strings.Join(flagArgs, " "), pkg.Name)
fmt.Fprintf(contents, "import (\n")
for s := range imports {
fmt.Fprintf(contents, "\t%q\n", s)
@@ -117,8 +130,8 @@ const header = `// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserve
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by the following command; DO NOT EDIT.
// tailscale.com/cmd/cloner -type %s
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
//` + `go:generate` + ` go run tailscale.com/cmd/cloner %s
package %s

View File

@@ -31,7 +31,6 @@ import (
"tailscale.com/net/stun"
"tailscale.com/tsweb"
"tailscale.com/types/key"
"tailscale.com/types/wgkey"
)
var (
@@ -70,12 +69,12 @@ func init() {
}
type config struct {
PrivateKey wgkey.Private
PrivateKey key.NodePrivate
}
func loadConfig() config {
if *dev {
return config{PrivateKey: mustNewKey()}
return config{PrivateKey: key.NewNode()}
}
if *configPath == "" {
if os.Getuid() == 0 {
@@ -101,21 +100,13 @@ func loadConfig() config {
}
}
func mustNewKey() wgkey.Private {
key, err := wgkey.NewPrivate()
if err != nil {
log.Fatal(err)
}
return key
}
func writeNewConfig() config {
key := mustNewKey()
k := key.NewNode()
if err := os.MkdirAll(filepath.Dir(*configPath), 0777); err != nil {
log.Fatal(err)
}
cfg := config{
PrivateKey: key,
PrivateKey: k,
}
b, err := json.MarshalIndent(cfg, "", "\t")
if err != nil {
@@ -152,7 +143,7 @@ func main() {
serveTLS := tsweb.IsProd443(*addr)
s := derp.NewServer(key.Private(cfg.PrivateKey), log.Printf)
s := derp.NewServer(cfg.PrivateKey, log.Printf)
s.SetVerifyClient(*verifyClients)
if *meshPSKFile != "" {
@@ -173,7 +164,10 @@ func main() {
expvar.Publish("derp", s.ExpVar())
mux := http.NewServeMux()
mux.Handle("/derp", derphttp.Handler(s))
derpHandler := derphttp.Handler(s)
derpHandler = addWebSocketSupport(s, derpHandler)
mux.Handle("/derp", derpHandler)
mux.HandleFunc("/derp/probe", probeHandler)
go refreshBootstrapDNSLoop()
mux.HandleFunc("/bootstrap-dns", handleBootstrapDNS)
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -269,8 +263,18 @@ func main() {
}
}
func serveSTUN(host string) {
// probeHandler is the endpoint that js/wasm clients hit to measure
// DERP latency, since they can't do UDP STUN queries.
func probeHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "HEAD", "GET":
w.Header().Set("Access-Control-Allow-Origin", "*")
default:
http.Error(w, "bogus probe method", http.StatusMethodNotAllowed)
}
}
func serveSTUN(host string) {
pc, err := net.ListenPacket("udp", net.JoinHostPort(host, "3478"))
if err != nil {
log.Fatalf("failed to open STUN listener: %v", err)

View File

@@ -69,8 +69,8 @@ func startMeshWithHost(s *derp.Server, host string) error {
return d.DialContext(ctx, network, addr)
})
add := func(k key.Public) { s.AddPacketForwarder(k, c) }
remove := func(k key.Public) { s.RemovePacketForwarder(k, c) }
add := func(k key.NodePublic) { s.AddPacketForwarder(k, c) }
remove := func(k key.NodePublic) { s.RemovePacketForwarder(k, c) }
go c.RunWatchConnectionLoop(context.Background(), s.PublicKey(), logf, add, remove)
return nil
}

52
cmd/derper/websocket.go Normal file
View File

@@ -0,0 +1,52 @@
// 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 main
import (
"bufio"
"expvar"
"log"
"net/http"
"strings"
"nhooyr.io/websocket"
"tailscale.com/derp"
"tailscale.com/derp/wsconn"
)
var counterWebSocketAccepts = expvar.NewInt("derp_websocket_accepts")
// addWebSocketSupport returns a Handle wrapping base that adds WebSocket server support.
func addWebSocketSupport(s *derp.Server, base http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
up := strings.ToLower(r.Header.Get("Upgrade"))
// Very early versions of Tailscale set "Upgrade: WebSocket" but didn't actually
// speak WebSockets (they still assumed DERP's binary framining). So to distinguish
// clients that actually want WebSockets, look for an explicit "derp" subprotocol.
if up != "websocket" || !strings.Contains(r.Header.Get("Sec-Websocket-Protocol"), "derp") {
base.ServeHTTP(w, r)
return
}
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
Subprotocols: []string{"derp"},
OriginPatterns: []string{"*"},
})
if err != nil {
log.Printf("websocket.Accept: %v", err)
return
}
defer c.Close(websocket.StatusInternalError, "closing")
if c.Subprotocol() != "derp" {
c.Close(websocket.StatusPolicyViolation, "client must speak the derp subprotocol")
return
}
counterWebSocketAccepts.Add(1)
wc := wsconn.New(c)
brw := bufio.NewReadWriter(bufio.NewReader(wc), bufio.NewWriter(wc))
s.Accept(wc, brw, r.RemoteAddr)
})
}

View File

@@ -344,7 +344,7 @@ func probeNodePair(ctx context.Context, dm *tailcfg.DERPMap, from, to *tailcfg.D
}
func newConn(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode) (*derphttp.Client, error) {
priv := key.NewPrivate()
priv := key.NewNode()
dc := derphttp.NewRegionClient(priv, log.Printf, func() *tailcfg.DERPRegion {
rid := n.RegionID
return &tailcfg.DERPRegion{

View File

@@ -7,7 +7,6 @@ package cli
import (
"context"
"errors"
"fmt"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/client/tailscale"
@@ -33,6 +32,6 @@ func runBugReport(ctx context.Context, args []string) error {
if err != nil {
return err
}
fmt.Println(logMarker)
outln(logMarker)
return nil
}

View File

@@ -29,7 +29,7 @@ var certCmd = &ffcli.Command{
ShortHelp: "get TLS certs",
ShortUsage: "cert [flags] <domain>",
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("cert", flag.ExitOnError)
fs := newFlagSet("cert")
fs.StringVar(&certArgs.certFile, "cert-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.crt if --cert-file and --key-file are both unset")
fs.StringVar(&certArgs.keyFile, "key-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.key if --cert-file and --key-file are both unset")
fs.BoolVar(&certArgs.serve, "serve-demo", false, "if true, serve on port :443 using the cert as a demo, instead of writing out the files to disk")
@@ -81,7 +81,7 @@ func runCert(ctx context.Context, args []string) error {
domain := args[0]
printf := func(format string, a ...interface{}) {
fmt.Printf(format, a...)
printf(format, a...)
}
if certArgs.certFile == "-" || certArgs.keyFile == "-" {
printf = log.Printf
@@ -143,7 +143,7 @@ func runCert(ctx context.Context, args []string) error {
func writeIfChanged(filename string, contents []byte, mode os.FileMode) (changed bool, err error) {
if filename == "-" {
os.Stdout.Write(contents)
Stdout.Write(contents)
return false, nil
}
if old, err := os.ReadFile(filename); err == nil && bytes.Equal(contents, old) {

View File

@@ -31,6 +31,22 @@ import (
"tailscale.com/syncs"
)
var Stderr io.Writer = os.Stderr
var Stdout io.Writer = os.Stdout
func printf(format string, a ...interface{}) {
fmt.Fprintf(Stdout, format, a...)
}
// outln is like fmt.Println in the common case, except when Stdout is
// changed (as in js/wasm).
//
// It's not named println because that looks like the Go built-in
// which goes to stderr and formats slightly differently.
func outln(a ...interface{}) {
fmt.Fprintln(Stdout, a...)
}
// ActLikeCLI reports whether a GUI application should act like the
// CLI based on os.Args, GOOS, the context the process is running in
// (pty, parent PID), etc.
@@ -77,6 +93,16 @@ func ActLikeCLI() bool {
return false
}
func newFlagSet(name string) *flag.FlagSet {
onError := flag.ExitOnError
if runtime.GOOS == "js" {
onError = flag.ContinueOnError
}
fs := flag.NewFlagSet(name, onError)
fs.SetOutput(Stderr)
return fs
}
// Run runs the CLI. The args do not include the binary name.
func Run(args []string) error {
if len(args) == 1 && (args[0] == "-V" || args[0] == "--version") {
@@ -86,11 +112,11 @@ func Run(args []string) error {
var warnOnce sync.Once
tailscale.SetVersionMismatchHandler(func(clientVer, serverVer string) {
warnOnce.Do(func() {
fmt.Fprintf(os.Stderr, "Warning: client version %q != tailscaled server version %q\n", clientVer, serverVer)
fmt.Fprintf(Stderr, "Warning: client version %q != tailscaled server version %q\n", clientVer, serverVer)
})
})
rootfs := flag.NewFlagSet("tailscale", flag.ExitOnError)
rootfs := newFlagSet("tailscale")
rootfs.StringVar(&rootArgs.socket, "socket", paths.DefaultTailscaledSocket(), "path to tailscaled's unix socket")
rootCmd := &ffcli.Command{
@@ -131,23 +157,33 @@ change in the future.
}
if err := rootCmd.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
return nil
}
return err
}
tailscale.TailscaledSocket = rootArgs.socket
err := rootCmd.Run(context.Background())
if err == flag.ErrHelp {
if errors.Is(err, flag.ErrHelp) {
return nil
}
return err
}
func fatalf(format string, a ...interface{}) {
if Fatalf != nil {
Fatalf(format, a...)
return
}
log.SetFlags(0)
log.Fatalf(format, a...)
}
// Fatalf, if non-nil, is used instead of log.Fatalf.
var Fatalf func(format string, a ...interface{})
var rootArgs struct {
socket string
}

View File

@@ -759,6 +759,18 @@ func TestUpdatePrefs(t *testing.T) {
wantJustEditMP: &ipn.MaskedPrefs{WantRunningSet: true},
wantErrSubtr: "can't change --login-server without --force-reauth",
},
{
name: "change_tags",
flags: []string{"--advertise-tags=tag:foo"},
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{LoginName: "crawshaw.github"},
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
},
env: upCheckEnv{backendState: "Running"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@@ -27,7 +27,7 @@ var debugCmd = &ffcli.Command{
Name: "debug",
Exec: runDebug,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("debug", flag.ExitOnError)
fs := newFlagSet("debug")
fs.BoolVar(&debugArgs.goroutines, "daemon-goroutines", false, "If true, dump the tailscaled daemon's goroutines")
fs.BoolVar(&debugArgs.ipn, "ipn", false, "If true, subscribe to IPN notifications")
fs.BoolVar(&debugArgs.prefs, "prefs", false, "If true, dump active prefs")
@@ -61,7 +61,7 @@ var debugArgs struct {
func writeProfile(dst string, v []byte) error {
if dst == "-" {
_, err := os.Stdout.Write(v)
_, err := Stdout.Write(v)
return err
}
return os.WriteFile(dst, v, 0600)
@@ -83,21 +83,21 @@ func runDebug(ctx context.Context, args []string) error {
}
if debugArgs.env {
for _, e := range os.Environ() {
fmt.Println(e)
outln(e)
}
return nil
}
if debugArgs.localCreds {
port, token, err := safesocket.LocalTCPPortAndToken()
if err == nil {
fmt.Printf("curl -u:%s http://localhost:%d/localapi/v0/status\n", token, port)
printf("curl -u:%s http://localhost:%d/localapi/v0/status\n", token, port)
return nil
}
if runtime.GOOS == "windows" {
fmt.Printf("curl http://localhost:41112/localapi/v0/status\n")
printf("curl http://localhost:41112/localapi/v0/status\n")
return nil
}
fmt.Printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket())
printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket())
return nil
}
if out := debugArgs.cpuFile; out != "" {
@@ -128,10 +128,10 @@ func runDebug(ctx context.Context, args []string) error {
return err
}
if debugArgs.pretty {
fmt.Println(prefs.Pretty())
outln(prefs.Pretty())
} else {
j, _ := json.MarshalIndent(prefs, "", "\t")
fmt.Println(string(j))
outln(string(j))
}
return nil
}
@@ -140,7 +140,7 @@ func runDebug(ctx context.Context, args []string) error {
if err != nil {
return err
}
os.Stdout.Write(goroutines)
Stdout.Write(goroutines)
return nil
}
if debugArgs.derpMap {
@@ -150,7 +150,7 @@ func runDebug(ctx context.Context, args []string) error {
"failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err,
)
}
enc := json.NewEncoder(os.Stdout)
enc := json.NewEncoder(Stdout)
enc.SetIndent("", "\t")
enc.Encode(dm)
return nil
@@ -164,7 +164,7 @@ func runDebug(ctx context.Context, args []string) error {
n.NetMap = nil
}
j, _ := json.MarshalIndent(n, "", "\t")
fmt.Printf("%s\n", j)
printf("%s\n", j)
})
bc.RequestEngineStatus()
pump(ctx, bc, c)
@@ -174,9 +174,9 @@ func runDebug(ctx context.Context, args []string) error {
if debugArgs.file == "get" {
wfs, err := tailscale.WaitingFiles(ctx)
if err != nil {
log.Fatal(err)
fatalf("%v\n", err)
}
e := json.NewEncoder(os.Stdout)
e := json.NewEncoder(Stdout)
e.SetIndent("", "\t")
e.Encode(wfs)
return nil
@@ -190,7 +190,7 @@ func runDebug(ctx context.Context, args []string) error {
return err
}
log.Printf("Size: %v\n", size)
io.Copy(os.Stdout, rc)
io.Copy(Stdout, rc)
return nil
}
return nil

View File

@@ -7,8 +7,6 @@ package cli
import (
"context"
"fmt"
"log"
"os"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/client/tailscale"
@@ -25,7 +23,7 @@ var downCmd = &ffcli.Command{
func runDown(ctx context.Context, args []string) error {
if len(args) > 0 {
log.Fatalf("too many non-flag arguments: %q", args)
return fmt.Errorf("too many non-flag arguments: %q", args)
}
st, err := tailscale.Status(ctx)
@@ -33,7 +31,7 @@ func runDown(ctx context.Context, args []string) error {
return fmt.Errorf("error fetching current status: %w", err)
}
if st.BackendState == "Stopped" {
fmt.Fprintf(os.Stderr, "Tailscale was already stopped.\n")
fmt.Fprintf(Stderr, "Tailscale was already stopped.\n")
return nil
}
_, err = tailscale.EditPrefs(ctx, &ipn.MaskedPrefs{

View File

@@ -55,7 +55,7 @@ var fileCpCmd = &ffcli.Command{
ShortHelp: "Copy file(s) to a host",
Exec: runCp,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("cp", flag.ExitOnError)
fs := newFlagSet("cp")
fs.StringVar(&cpArgs.name, "name", "", "alternate filename to use, especially useful when <file> is \"-\" (stdin)")
fs.BoolVar(&cpArgs.verbose, "verbose", false, "verbose output")
fs.BoolVar(&cpArgs.targets, "targets", false, "list possible file cp targets")
@@ -101,7 +101,7 @@ func runCp(ctx context.Context, args []string) error {
return fmt.Errorf("can't send to %s: %v", target, err)
}
if isOffline {
fmt.Fprintf(os.Stderr, "# warning: %s is offline\n", target)
fmt.Fprintf(Stderr, "# warning: %s is offline\n", target)
}
if len(files) > 1 {
@@ -172,7 +172,7 @@ func runCp(ctx context.Context, args []string) error {
res.Body.Close()
continue
}
io.Copy(os.Stdout, res.Body)
io.Copy(Stdout, res.Body)
res.Body.Close()
return errors.New(res.Status)
}
@@ -293,7 +293,7 @@ func runCpTargets(ctx context.Context, args []string) error {
if detail != "" {
detail = "\t" + detail
}
fmt.Printf("%s\t%s%s\n", n.Addresses[0].IP(), n.ComputedName, detail)
printf("%s\t%s%s\n", n.Addresses[0].IP(), n.ComputedName, detail)
}
return nil
}
@@ -304,7 +304,7 @@ var fileGetCmd = &ffcli.Command{
ShortHelp: "Move files out of the Tailscale file inbox",
Exec: runFileGet,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("get", flag.ExitOnError)
fs := newFlagSet("get")
fs.BoolVar(&getArgs.wait, "wait", false, "wait for a file to arrive if inbox is empty")
fs.BoolVar(&getArgs.verbose, "verbose", false, "verbose output")
return fs
@@ -415,7 +415,7 @@ func waitForFile(ctx context.Context) error {
fileWaiting := make(chan bool, 1)
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
fatalf("Notify.ErrMessage: %v\n", *n.ErrMessage)
}
if n.FilesWaiting != nil {
select {

View File

@@ -23,7 +23,7 @@ var ipCmd = &ffcli.Command{
LongHelp: "Shows the Tailscale IP address of the current machine without an argument. With an argument, it shows the IP of a named peer.",
Exec: runIP,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("ip", flag.ExitOnError)
fs := newFlagSet("ip")
fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address")
fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address")
return fs
@@ -75,7 +75,7 @@ func runIP(ctx context.Context, args []string) error {
for _, ip := range ips {
if ip.Is4() && v4 || ip.Is6() && v6 {
match = true
fmt.Println(ip)
outln(ip)
}
}
if !match {

View File

@@ -6,7 +6,7 @@ package cli
import (
"context"
"log"
"fmt"
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
@@ -28,7 +28,7 @@ a reauthentication.
func runLogout(ctx context.Context, args []string) error {
if len(args) > 0 {
log.Fatalf("too many non-flag arguments: %q", args)
return fmt.Errorf("too many non-flag arguments: %q", args)
}
return tailscale.Logout(ctx)
}

View File

@@ -33,7 +33,7 @@ var netcheckCmd = &ffcli.Command{
ShortHelp: "Print an analysis of local network conditions",
Exec: runNetcheck,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("netcheck", flag.ExitOnError)
fs := newFlagSet("netcheck")
fs.StringVar(&netcheckArgs.format, "format", "", `output format; empty (for human-readable), "json" or "json-line"`)
fs.DurationVar(&netcheckArgs.every, "every", 0, "if non-zero, do an incremental report with the given frequency")
fs.BoolVar(&netcheckArgs.verbose, "verbose", false, "verbose logs")
@@ -60,7 +60,7 @@ func runNetcheck(ctx context.Context, args []string) error {
}
if strings.HasPrefix(netcheckArgs.format, "json") {
fmt.Fprintln(os.Stderr, "# Warning: this JSON format is not yet considered a stable interface")
fmt.Fprintln(Stderr, "# Warning: this JSON format is not yet considered a stable interface")
}
dm, err := tailscale.CurrentDERPMap(ctx)
@@ -82,7 +82,7 @@ func runNetcheck(ctx context.Context, args []string) error {
c.Logf("GetReport took %v; err=%v", d.Round(time.Millisecond), err)
}
if err != nil {
log.Fatalf("netcheck: %v", err)
return fmt.Errorf("netcheck: %w", err)
}
if err := printReport(dm, report); err != nil {
return err
@@ -112,36 +112,36 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
}
if j != nil {
j = append(j, '\n')
os.Stdout.Write(j)
Stdout.Write(j)
return nil
}
fmt.Printf("\nReport:\n")
fmt.Printf("\t* UDP: %v\n", report.UDP)
printf("\nReport:\n")
printf("\t* UDP: %v\n", report.UDP)
if report.GlobalV4 != "" {
fmt.Printf("\t* IPv4: yes, %v\n", report.GlobalV4)
printf("\t* IPv4: yes, %v\n", report.GlobalV4)
} else {
fmt.Printf("\t* IPv4: (no addr found)\n")
printf("\t* IPv4: (no addr found)\n")
}
if report.GlobalV6 != "" {
fmt.Printf("\t* IPv6: yes, %v\n", report.GlobalV6)
printf("\t* IPv6: yes, %v\n", report.GlobalV6)
} else if report.IPv6 {
fmt.Printf("\t* IPv6: (no addr found)\n")
printf("\t* IPv6: (no addr found)\n")
} else {
fmt.Printf("\t* IPv6: no\n")
printf("\t* IPv6: no\n")
}
fmt.Printf("\t* MappingVariesByDestIP: %v\n", report.MappingVariesByDestIP)
fmt.Printf("\t* HairPinning: %v\n", report.HairPinning)
fmt.Printf("\t* PortMapping: %v\n", portMapping(report))
printf("\t* MappingVariesByDestIP: %v\n", report.MappingVariesByDestIP)
printf("\t* HairPinning: %v\n", report.HairPinning)
printf("\t* PortMapping: %v\n", portMapping(report))
// When DERP latency checking failed,
// magicsock will try to pick the DERP server that
// most of your other nodes are also using
if len(report.RegionLatency) == 0 {
fmt.Printf("\t* Nearest DERP: unknown (no response to latency probes)\n")
printf("\t* Nearest DERP: unknown (no response to latency probes)\n")
} else {
fmt.Printf("\t* Nearest DERP: %v\n", dm.Regions[report.PreferredDERP].RegionName)
fmt.Printf("\t* DERP latency:\n")
printf("\t* Nearest DERP: %v\n", dm.Regions[report.PreferredDERP].RegionName)
printf("\t* DERP latency:\n")
var rids []int
for rid := range dm.Regions {
rids = append(rids, rid)
@@ -168,7 +168,7 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
if netcheckArgs.verbose {
derpNum = fmt.Sprintf("derp%d, ", rid)
}
fmt.Printf("\t\t- %3s: %-7s (%s%s)\n", r.RegionCode, latency, derpNum, r.RegionName)
printf("\t\t- %3s: %-7s (%s%s)\n", r.RegionCode, latency, derpNum, r.RegionName)
}
}
return nil

View File

@@ -45,7 +45,7 @@ relay node.
`),
Exec: runPing,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("ping", flag.ExitOnError)
fs := newFlagSet("ping")
fs.BoolVar(&pingArgs.verbose, "verbose", false, "verbose output")
fs.BoolVar(&pingArgs.untilDirect, "until-direct", true, "stop once a direct path is established")
fs.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through IP + wireguard, but not involving host OS stack)")
@@ -74,7 +74,7 @@ func runPing(ctx context.Context, args []string) error {
prc := make(chan *ipnstate.PingResult, 1)
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
fatalf("Notify.ErrMessage: %v", *n.ErrMessage)
}
if pr := n.PingResult; pr != nil && pr.IP == ip {
prc <- pr
@@ -89,7 +89,7 @@ func runPing(ctx context.Context, args []string) error {
return err
}
if self {
fmt.Printf("%v is local Tailscale IP\n", ip)
printf("%v is local Tailscale IP\n", ip)
return nil
}
@@ -105,14 +105,14 @@ func runPing(ctx context.Context, args []string) error {
timer := time.NewTimer(pingArgs.timeout)
select {
case <-timer.C:
fmt.Printf("timeout waiting for ping reply\n")
printf("timeout waiting for ping reply\n")
case err := <-pumpErr:
return err
case pr := <-prc:
timer.Stop()
if pr.Err != "" {
if pr.IsLocalIP {
fmt.Println(pr.Err)
outln(pr.Err)
return nil
}
return errors.New(pr.Err)
@@ -132,7 +132,7 @@ func runPing(ctx context.Context, args []string) error {
if pr.PeerAPIPort != 0 {
extra = fmt.Sprintf(", %d", pr.PeerAPIPort)
}
fmt.Printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency)
printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency)
if pingArgs.tsmp {
return nil
}

View File

@@ -31,7 +31,7 @@ var statusCmd = &ffcli.Command{
ShortHelp: "Show state of tailscaled and its connections",
Exec: runStatus,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("status", flag.ExitOnError)
fs := newFlagSet("status")
fs.BoolVar(&statusArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
fs.BoolVar(&statusArgs.web, "web", false, "run webserver with HTML showing status")
fs.BoolVar(&statusArgs.active, "active", false, "filter output to only peers with active sessions (not applicable to web mode)")
@@ -70,7 +70,7 @@ func runStatus(ctx context.Context, args []string) error {
if err != nil {
return err
}
fmt.Printf("%s", j)
printf("%s", j)
return nil
}
if statusArgs.web {
@@ -79,7 +79,7 @@ func runStatus(ctx context.Context, args []string) error {
return err
}
statusURL := interfaces.HTTPOfListener(ln)
fmt.Printf("Serving Tailscale status at %v ...\n", statusURL)
printf("Serving Tailscale status at %v ...\n", statusURL)
go func() {
<-ctx.Done()
ln.Close()
@@ -108,30 +108,30 @@ func runStatus(ctx context.Context, args []string) error {
switch st.BackendState {
default:
fmt.Fprintf(os.Stderr, "unexpected state: %s\n", st.BackendState)
fmt.Fprintf(Stderr, "unexpected state: %s\n", st.BackendState)
os.Exit(1)
case ipn.Stopped.String():
fmt.Println("Tailscale is stopped.")
outln("Tailscale is stopped.")
os.Exit(1)
case ipn.NeedsLogin.String():
fmt.Println("Logged out.")
outln("Logged out.")
if st.AuthURL != "" {
fmt.Printf("\nLog in at: %s\n", st.AuthURL)
printf("\nLog in at: %s\n", st.AuthURL)
}
os.Exit(1)
case ipn.NeedsMachineAuth.String():
fmt.Println("Machine is not yet authorized by tailnet admin.")
outln("Machine is not yet authorized by tailnet admin.")
os.Exit(1)
case ipn.Running.String(), ipn.Starting.String():
// Run below.
}
if len(st.Health) > 0 {
fmt.Printf("# Health check:\n")
printf("# Health check:\n")
for _, m := range st.Health {
fmt.Printf("# - %s\n", m)
printf("# - %s\n", m)
}
fmt.Println()
outln()
}
var buf bytes.Buffer
@@ -190,7 +190,7 @@ func runStatus(ctx context.Context, args []string) error {
printPS(ps)
}
}
os.Stdout.Write(buf.Bytes())
Stdout.Write(buf.Bytes())
return nil
}

View File

@@ -63,7 +63,7 @@ func effectiveGOOS() string {
var upFlagSet = newUpFlagSet(effectiveGOOS(), &upArgs)
func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet {
upf := flag.NewFlagSet("up", flag.ExitOnError)
upf := newFlagSet("up")
upf.BoolVar(&upArgs.qr, "qr", false, "show QR code for login URLs")
upf.BoolVar(&upArgs.forceReauth, "force-reauth", false, "force reauthentication")
@@ -139,7 +139,7 @@ func (a upArgsT) getAuthKey() (string, error) {
var upArgs upArgsT
func warnf(format string, args ...interface{}) {
fmt.Printf("Warning: "+format+"\n", args...)
printf("Warning: "+format+"\n", args...)
}
var (
@@ -277,7 +277,7 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
// 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
// change the prefs at runtime (e.g. changing hostname, changing
// advertised tags, routes, etc).
// advertised routes, etc).
//
// It returns simpleUp if we're running a simple "tailscale up" to
// transition to running from a previously-logged-in but down state,
@@ -297,6 +297,8 @@ func updatePrefs(prefs, curPrefs *ipn.Prefs, env upCheckEnv) (simpleUp bool, jus
return false, nil, fmt.Errorf("can't change --login-server without --force-reauth")
}
tagsChanged := !reflect.DeepEqual(curPrefs.AdvertiseTags, prefs.AdvertiseTags)
simpleUp = env.flagSet.NFlag() == 0 &&
curPrefs.Persist != nil &&
curPrefs.Persist.LoginName != "" &&
@@ -306,7 +308,8 @@ func updatePrefs(prefs, curPrefs *ipn.Prefs, env upCheckEnv) (simpleUp bool, jus
!env.upArgs.forceReauth &&
!env.upArgs.reset &&
env.upArgs.authKeyOrFile == "" &&
!controlURLChanged
!controlURLChanged &&
!tagsChanged
if justEdit {
justEditMP = new(ipn.MaskedPrefs)
justEditMP.WantRunningSet = true
@@ -432,12 +435,12 @@ func runUp(ctx context.Context, args []string) error {
startLoginInteractive()
case ipn.NeedsMachineAuth:
printed = true
fmt.Fprintf(os.Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
fmt.Fprintf(Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
case ipn.Running:
// Done full authentication process
if printed {
// Only need to print an update if we printed the "please click" message earlier.
fmt.Fprintf(os.Stderr, "Success.\n")
fmt.Fprintf(Stderr, "Success.\n")
}
select {
case running <- true:
@@ -448,13 +451,13 @@ func runUp(ctx context.Context, args []string) error {
}
if url := n.BrowseToURL; url != nil && printAuthURL(*url) {
printed = true
fmt.Fprintf(os.Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
fmt.Fprintf(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
if upArgs.qr {
q, err := qrcode.New(*url, qrcode.Medium)
if err != nil {
log.Printf("QR code error: %v", err)
} else {
fmt.Fprintf(os.Stderr, "%s\n", q.ToString(false))
fmt.Fprintf(Stderr, "%s\n", q.ToString(false))
}
}

View File

@@ -8,7 +8,6 @@ import (
"context"
"flag"
"fmt"
"log"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/client/tailscale"
@@ -20,7 +19,7 @@ var versionCmd = &ffcli.Command{
ShortUsage: "version [flags]",
ShortHelp: "Print Tailscale version",
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("version", flag.ExitOnError)
fs := newFlagSet("version")
fs.BoolVar(&versionArgs.daemon, "daemon", false, "also print local node's daemon version")
return fs
})(),
@@ -33,19 +32,19 @@ var versionArgs struct {
func runVersion(ctx context.Context, args []string) error {
if len(args) > 0 {
log.Fatalf("too many non-flag arguments: %q", args)
return fmt.Errorf("too many non-flag arguments: %q", args)
}
if !versionArgs.daemon {
fmt.Println(version.String())
outln(version.String())
return nil
}
fmt.Printf("Client: %s\n", version.String())
printf("Client: %s\n", version.String())
st, err := tailscale.StatusWithoutPeers(ctx)
if err != nil {
return err
}
fmt.Printf("Daemon: %s\n", st.Version)
printf("Daemon: %s\n", st.Version)
return nil
}

View File

@@ -76,7 +76,7 @@ Tailscale, as opposed to a CLI or a native app.
`),
FlagSet: (func() *flag.FlagSet {
webf := flag.NewFlagSet("web", flag.ExitOnError)
webf := newFlagSet("web")
webf.StringVar(&webArgs.listen, "listen", "localhost:8088", "listen address; use port 0 for automatic")
webf.BoolVar(&webArgs.cgi, "cgi", false, "run as CGI script")
return webf
@@ -114,7 +114,7 @@ func tlsConfigFromEnvironment() *tls.Config {
func runWeb(ctx context.Context, args []string) error {
if len(args) > 0 {
log.Fatalf("too many non-flag arguments: %q", args)
return fmt.Errorf("too many non-flag arguments: %q", args)
}
if webArgs.cgi {

View File

@@ -4,6 +4,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
L github.com/klauspost/compress/flate from nhooyr.io/websocket
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
github.com/peterbourgon/ff/v3 from github.com/peterbourgon/ff/v3/ffcli
github.com/peterbourgon/ff/v3/ffcli from tailscale.com/cmd/tailscale/cli
@@ -23,6 +24,10 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
go4.org/unsafe/assume-no-moving-gc from go4.org/intern
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
inet.af/netaddr from tailscale.com/cmd/tailscale/cli+
L nhooyr.io/websocket from tailscale.com/derp/derphttp+
L nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
L nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
tailscale.com from tailscale.com/version
tailscale.com/atomicfile from tailscale.com/ipn+
tailscale.com/client/tailscale from tailscale.com/cmd/tailscale/cli+
tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale+
@@ -30,6 +35,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/control/controlknobs from tailscale.com/net/portmapper
tailscale.com/derp from tailscale.com/derp/derphttp
tailscale.com/derp/derphttp from tailscale.com/net/netcheck
L tailscale.com/derp/wsconn from tailscale.com/derp/derphttp
tailscale.com/disco from tailscale.com/derp
tailscale.com/hostinfo from tailscale.com/net/interfaces
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli+
@@ -66,7 +72,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/types/persist from tailscale.com/ipn
tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+
tailscale.com/types/structs from tailscale.com/ipn+
tailscale.com/types/wgkey from tailscale.com/types/netmap+
tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+
W tailscale.com/util/endian from tailscale.com/net/netns
tailscale.com/util/groupmember from tailscale.com/cmd/tailscale/cli
@@ -76,12 +81,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/wgengine/filter from tailscale.com/types/netmap
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
golang.org/x/crypto/chacha20poly1305 from crypto/tls
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
golang.org/x/crypto/curve25519 from crypto/tls+
golang.org/x/crypto/hkdf from crypto/tls
golang.org/x/crypto/nacl/box from tailscale.com/derp+
golang.org/x/crypto/nacl/box from tailscale.com/types/key
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
@@ -130,7 +135,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
crypto/tls from github.com/tcnksm/go-httpstat+
crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+
embed from tailscale.com/cmd/tailscale/cli
embed from tailscale.com/cmd/tailscale/cli+
encoding from encoding/json+
encoding/asn1 from crypto/x509+
encoding/base64 from encoding/json+

View File

@@ -193,8 +193,8 @@ func checkDerp(ctx context.Context, derpRegion string) error {
panic("unreachable")
}
priv1 := key.NewPrivate()
priv2 := key.NewPrivate()
priv1 := key.NewNode()
priv2 := key.NewNode()
c1 := derphttp.NewRegionClient(priv1, log.Printf, getRegion)
c2 := derphttp.NewRegionClient(priv2, log.Printf, getRegion)

View File

@@ -73,6 +73,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress from github.com/klauspost/compress/zstd
L github.com/klauspost/compress/flate from nhooyr.io/websocket
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0
github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd
github.com/klauspost/compress/internal/snapref from github.com/klauspost/compress/zstd
@@ -95,6 +96,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/u-root/uio/rand from github.com/insomniacslk/dhcp/dhcpv4
L github.com/u-root/uio/ubinary from github.com/u-root/uio/uio
L github.com/u-root/uio/uio from github.com/insomniacslk/dhcp/dhcpv4+
L 💣 github.com/vishvananda/netlink from tailscale.com/wgengine/router
L 💣 github.com/vishvananda/netlink/nl from github.com/vishvananda/netlink
L github.com/vishvananda/netns from github.com/vishvananda/netlink+
💣 go4.org/intern from inet.af/netaddr
💣 go4.org/mem from tailscale.com/client/tailscale+
go4.org/unsafe/assume-no-moving-gc from go4.org/intern
@@ -145,6 +149,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
inet.af/netstack/waiter from inet.af/netstack/tcpip+
inet.af/peercred from tailscale.com/ipn/ipnserver
W 💣 inet.af/wf from tailscale.com/wf
L nhooyr.io/websocket from tailscale.com/derp/derphttp+
L nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
L nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
tailscale.com from tailscale.com/version
tailscale.com/atomicfile from tailscale.com/ipn+
LD tailscale.com/chirp from tailscale.com/cmd/tailscaled
tailscale.com/client/tailscale from tailscale.com/derp
@@ -153,6 +161,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
tailscale.com/derp from tailscale.com/derp/derphttp+
tailscale.com/derp/derphttp from tailscale.com/cmd/tailscaled+
L tailscale.com/derp/wsconn from tailscale.com/derp/derphttp
tailscale.com/disco from tailscale.com/derp+
tailscale.com/health from tailscale.com/control/controlclient+
tailscale.com/hostinfo from tailscale.com/control/controlclient+
@@ -213,7 +222,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
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+
tailscale.com/types/wgkey from tailscale.com/control/controlclient+
L tailscale.com/util/cmpver from tailscale.com/net/dns
💣 tailscale.com/util/deephash from tailscale.com/ipn/ipnlocal+
tailscale.com/util/dnsname from tailscale.com/hostinfo+
@@ -226,6 +234,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/util/systemd from tailscale.com/control/controlclient+
tailscale.com/util/uniq from tailscale.com/wgengine/magicsock
tailscale.com/util/winutil from tailscale.com/cmd/tailscaled+
W 💣 tailscale.com/util/winutil/vss from tailscale.com/util/winutil
tailscale.com/version from tailscale.com/client/tailscale+
tailscale.com/version/distro from tailscale.com/cmd/tailscaled+
W tailscale.com/wf from tailscale.com/cmd/tailscaled
@@ -248,7 +257,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
golang.org/x/crypto/curve25519 from crypto/tls+
golang.org/x/crypto/hkdf from crypto/tls
golang.org/x/crypto/nacl/box from tailscale.com/derp+
golang.org/x/crypto/nacl/box from tailscale.com/types/key
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
@@ -278,7 +287,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/time/rate from inet.af/netstack/tcpip/stack+
bufio from compress/flate+
bytes from bufio+
compress/flate from compress/gzip
compress/flate from compress/gzip+
compress/gzip from internal/profile+
container/heap from inet.af/netstack/tcpip/transport/tcp
container/list from crypto/tls+

View File

@@ -14,11 +14,11 @@ import (
"tailscale.com/logtail/backoff"
"tailscale.com/tailcfg"
"tailscale.com/types/empty"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/types/persist"
"tailscale.com/types/structs"
"tailscale.com/types/wgkey"
)
type LoginGoal struct {
@@ -281,7 +281,6 @@ func (c *Auto) authRoutine() {
report := func(err error, msg string) {
c.logf("[v1] %s: %v", msg, err)
err = fmt.Errorf("%s: %v", msg, err)
// don't send status updates for context errors,
// since context cancelation is always on purpose.
if ctx.Err() == nil {
@@ -431,7 +430,7 @@ func (c *Auto) mapRoutine() {
report := func(err error, msg string) {
c.logf("[v1] %s: %v", msg, err)
err = fmt.Errorf("%s: %v", msg, err)
err = fmt.Errorf("%s: %w", msg, err)
// don't send status updates for context errors,
// since context cancelation is always on purpose.
if ctx.Err() == nil {
@@ -599,9 +598,7 @@ func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkM
NetMap: nm,
Hostinfo: hi,
State: state,
}
if err != nil {
new.Err = err.Error()
Err: err,
}
if statusFunc != nil {
statusFunc(new)
@@ -702,7 +699,7 @@ func (c *Auto) Shutdown() {
// NodePublicKey returns the node public key currently in use. This is
// used exclusively in tests.
func (c *Auto) TestOnlyNodePublicKey() wgkey.Key {
func (c *Auto) TestOnlyNodePublicKey() key.NodePublic {
priv := c.direct.GetPersist()
return priv.PrivateNodeKey.Public()
}

View File

@@ -20,6 +20,7 @@ type LoginFlags int
const (
LoginDefault = LoginFlags(0)
LoginInteractive = LoginFlags(1 << iota) // force user login and key refresh
LoginEphemeral // set RegisterRequest.Ephemeral
)
// Client represents a client connection to the control server.

View File

@@ -46,7 +46,6 @@ import (
"tailscale.com/types/netmap"
"tailscale.com/types/opt"
"tailscale.com/types/persist"
"tailscale.com/types/wgkey"
"tailscale.com/util/systemd"
"tailscale.com/wgengine/monitor"
)
@@ -72,7 +71,7 @@ type Direct struct {
serverKey key.MachinePublic
persist persist.Persist
authKey string
tryingNewKey wgkey.Private
tryingNewKey key.NodePrivate
expiry *time.Time
// hostinfo is mutated in-place while mu is held.
hostinfo *tailcfg.Hostinfo // always non-nil
@@ -327,7 +326,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
c.mu.Unlock()
}
var oldNodeKey wgkey.Key
var oldNodeKey key.NodePublic
switch {
case opt.Logout:
tryingNewKey = persist.PrivateNodeKey
@@ -336,12 +335,7 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
case regen || persist.PrivateNodeKey.IsZero():
c.logf("Generating a new nodekey.")
persist.OldPrivateNodeKey = persist.PrivateNodeKey
key, err := wgkey.NewPrivate()
if err != nil {
c.logf("login keygen: %v", err)
return regen, opt.URL, err
}
tryingNewKey = key
tryingNewKey = key.NewNode()
default:
// Try refreshing the current key first
tryingNewKey = persist.PrivateNodeKey
@@ -363,11 +357,12 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
now := time.Now().Round(time.Second)
request := tailcfg.RegisterRequest{
Version: 1,
OldNodeKey: tailcfg.NodeKey(oldNodeKey),
NodeKey: tailcfg.NodeKey(tryingNewKey.Public()),
OldNodeKey: tailcfg.NodeKeyFromNodePublic(oldNodeKey),
NodeKey: tailcfg.NodeKeyFromNodePublic(tryingNewKey.Public()),
Hostinfo: hostinfo,
Followup: opt.URL,
Timestamp: &now,
Ephemeral: (opt.Flags & LoginEphemeral) != 0,
}
if opt.Logout {
request.Expiry = time.Unix(123, 0) // far in the past
@@ -435,6 +430,9 @@ func (c *Direct) doLogin(ctx context.Context, opt loginOpt) (mustRegen bool, new
c.logf("RegisterReq: got response; nodeKeyExpired=%v, machineAuthorized=%v; authURL=%v",
resp.NodeKeyExpired, resp.MachineAuthorized, resp.AuthURL != "")
if resp.Error != "" {
return false, "", errors.New(resp.Error)
}
if resp.NodeKeyExpired {
if regen {
return true, "", fmt.Errorf("weird: regen=true but server says NodeKeyExpired: %v", request.NodeKey)
@@ -597,7 +595,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
request := &tailcfg.MapRequest{
Version: tailcfg.CurrentMapRequestVersion,
KeepAlive: c.keepAlive,
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
NodeKey: tailcfg.NodeKeyFromNodePublic(persist.PrivateNodeKey.Public()),
DiscoKey: c.discoPubKey,
Endpoints: epStrs,
EndpointTypes: epTypes,

View File

@@ -15,7 +15,6 @@ import (
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/types/wgkey"
"tailscale.com/wgengine/filter"
)
@@ -29,7 +28,7 @@ import (
// one MapRequest).
type mapSession struct {
// Immutable fields.
privateNodeKey wgkey.Private
privateNodeKey key.NodePrivate
logf logger.Logf
vlogf logger.Logf
machinePubKey key.MachinePublic
@@ -51,7 +50,7 @@ type mapSession struct {
netMapBuilding *netmap.NetworkMap
}
func newMapSession(privateNodeKey wgkey.Private) *mapSession {
func newMapSession(privateNodeKey key.NodePrivate) *mapSession {
ms := &mapSession{
privateNodeKey: privateNodeKey,
logf: logger.Discard,
@@ -111,7 +110,7 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
}
nm := &netmap.NetworkMap{
NodeKey: tailcfg.NodeKey(ms.privateNodeKey.Public()),
NodeKey: tailcfg.NodeKeyFromNodePublic(ms.privateNodeKey.Public()),
PrivateKey: ms.privateNodeKey,
MachineKey: ms.machinePubKey,
Peers: resp.Peers,

View File

@@ -13,8 +13,8 @@ import (
"time"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/netmap"
"tailscale.com/types/wgkey"
)
func TestUndeltaPeers(t *testing.T) {
@@ -170,11 +170,7 @@ func formatNodes(nodes []*tailcfg.Node) string {
}
func newTestMapSession(t *testing.T) *mapSession {
k, err := wgkey.NewPrivate()
if err != nil {
t.Fatal(err)
}
return newMapSession(k)
return newMapSession(key.NewNode())
}
func TestNetmapForResponse(t *testing.T) {

View File

@@ -67,7 +67,7 @@ type Status struct {
_ structs.Incomparable
LoginFinished *empty.Message // nonempty when login finishes
LogoutFinished *empty.Message // nonempty when logout finishes
Err string
Err error
URL string // interactive URL to visit to finish logging in
NetMap *netmap.NetworkMap // server-pushed configuration

View File

@@ -6,7 +6,6 @@ package derp
import (
"bufio"
crand "crypto/rand"
"encoding/binary"
"encoding/json"
"errors"
@@ -15,7 +14,7 @@ import (
"sync"
"time"
"golang.org/x/crypto/nacl/box"
"go4.org/mem"
"golang.org/x/time/rate"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@@ -23,9 +22,9 @@ import (
// Client is a DERP client.
type Client struct {
serverKey key.Public // of the DERP server; not a machine or node key
privateKey key.Private
publicKey key.Public // of privateKey
serverKey key.NodePublic // of the DERP server; not a machine or node key
privateKey key.NodePrivate
publicKey key.NodePublic // of privateKey
logf logger.Logf
nc Conn
br *bufio.Reader
@@ -54,7 +53,7 @@ func (f clientOptFunc) update(o *clientOpt) { f(o) }
// clientOpt are the options passed to newClient.
type clientOpt struct {
MeshKey string
ServerPub key.Public
ServerPub key.NodePublic
CanAckPings bool
IsProber bool
}
@@ -71,7 +70,7 @@ func IsProber(v bool) ClientOpt { return clientOptFunc(func(o *clientOpt) { o.Is
// ServerPublicKey returns a ClientOpt to declare that the server's DERP public key is known.
// If key is the zero value, the returned ClientOpt is a no-op.
func ServerPublicKey(key key.Public) ClientOpt {
func ServerPublicKey(key key.NodePublic) ClientOpt {
return clientOptFunc(func(o *clientOpt) { o.ServerPub = key })
}
@@ -81,7 +80,7 @@ func CanAckPings(v bool) ClientOpt {
return clientOptFunc(func(o *clientOpt) { o.CanAckPings = v })
}
func NewClient(privateKey key.Private, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opts ...ClientOpt) (*Client, error) {
func NewClient(privateKey key.NodePrivate, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opts ...ClientOpt) (*Client, error) {
var opt clientOpt
for _, o := range opts {
if o == nil {
@@ -92,7 +91,7 @@ func NewClient(privateKey key.Private, nc Conn, brw *bufio.ReadWriter, logf logg
return newClient(privateKey, nc, brw, logf, opt)
}
func newClient(privateKey key.Private, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opt clientOpt) (*Client, error) {
func newClient(privateKey key.NodePrivate, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opt clientOpt) (*Client, error) {
c := &Client{
privateKey: privateKey,
publicKey: privateKey.Public(),
@@ -130,7 +129,7 @@ func (c *Client) recvServerKey() error {
if flen < uint32(len(buf)) || t != frameServerKey || string(buf[:len(magic)]) != magic {
return errors.New("invalid server greeting")
}
copy(c.serverKey[:], buf[len(magic):])
c.serverKey = key.NodePublicFromRaw32(mem.B(buf[len(magic):]))
return nil
}
@@ -143,13 +142,9 @@ func (c *Client) parseServerInfo(b []byte) (*serverInfo, error) {
if fl > maxLength {
return nil, fmt.Errorf("long serverInfo frame")
}
// TODO: add a read-nonce-and-box helper
var nonce [nonceLen]byte
copy(nonce[:], b)
msgbox := b[nonceLen:]
msg, ok := box.Open(nil, msgbox, &nonce, c.serverKey.B32(), c.privateKey.B32())
msg, ok := c.privateKey.OpenFrom(c.serverKey, b)
if !ok {
return nil, fmt.Errorf("failed to open naclbox from server key %x", c.serverKey[:])
return nil, fmt.Errorf("failed to open naclbox from server key %s", c.serverKey)
}
info := new(serverInfo)
if err := json.Unmarshal(msg, info); err != nil {
@@ -176,10 +171,6 @@ type clientInfo struct {
}
func (c *Client) sendClientKey() error {
var nonce [nonceLen]byte
if _, err := crand.Read(nonce[:]); err != nil {
return err
}
msg, err := json.Marshal(clientInfo{
Version: ProtocolVersion,
MeshKey: c.meshKey,
@@ -189,24 +180,23 @@ func (c *Client) sendClientKey() error {
if err != nil {
return err
}
msgbox := box.Seal(nil, msg, &nonce, c.serverKey.B32(), c.privateKey.B32())
msgbox := c.privateKey.SealTo(c.serverKey, msg)
buf := make([]byte, 0, nonceLen+keyLen+len(msgbox))
buf = append(buf, c.publicKey[:]...)
buf = append(buf, nonce[:]...)
buf := make([]byte, 0, keyLen+len(msgbox))
buf = c.publicKey.AppendTo(buf)
buf = append(buf, msgbox...)
return writeFrame(c.bw, frameClientInfo, buf)
}
// ServerPublicKey returns the server's public key.
func (c *Client) ServerPublicKey() key.Public { return c.serverKey }
func (c *Client) ServerPublicKey() key.NodePublic { return c.serverKey }
// Send sends a packet to the Tailscale node identified by dstKey.
//
// It is an error if the packet is larger than 64KB.
func (c *Client) Send(dstKey key.Public, pkt []byte) error { return c.send(dstKey, pkt) }
func (c *Client) Send(dstKey key.NodePublic, pkt []byte) error { return c.send(dstKey, pkt) }
func (c *Client) send(dstKey key.Public, pkt []byte) (ret error) {
func (c *Client) send(dstKey key.NodePublic, pkt []byte) (ret error) {
defer func() {
if ret != nil {
ret = fmt.Errorf("derp.Send: %w", ret)
@@ -220,15 +210,15 @@ func (c *Client) send(dstKey key.Public, pkt []byte) (ret error) {
c.wmu.Lock()
defer c.wmu.Unlock()
if c.rate != nil {
pktLen := frameHeaderLen + len(dstKey) + len(pkt)
pktLen := frameHeaderLen + dstKey.RawLen() + len(pkt)
if !c.rate.AllowN(time.Now(), pktLen) {
return nil // drop
}
}
if err := writeFrameHeader(c.bw, frameSendPacket, uint32(len(dstKey)+len(pkt))); err != nil {
if err := writeFrameHeader(c.bw, frameSendPacket, uint32(dstKey.RawLen()+len(pkt))); err != nil {
return err
}
if _, err := c.bw.Write(dstKey[:]); err != nil {
if _, err := c.bw.Write(dstKey.AppendTo(nil)); err != nil {
return err
}
if _, err := c.bw.Write(pkt); err != nil {
@@ -237,7 +227,7 @@ func (c *Client) send(dstKey key.Public, pkt []byte) (ret error) {
return c.bw.Flush()
}
func (c *Client) ForwardPacket(srcKey, dstKey key.Public, pkt []byte) (err error) {
func (c *Client) ForwardPacket(srcKey, dstKey key.NodePublic, pkt []byte) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("derp.ForwardPacket: %w", err)
@@ -257,10 +247,10 @@ func (c *Client) ForwardPacket(srcKey, dstKey key.Public, pkt []byte) (err error
if err := writeFrameHeader(c.bw, frameForwardPacket, uint32(keyLen*2+len(pkt))); err != nil {
return err
}
if _, err := c.bw.Write(srcKey[:]); err != nil {
if _, err := c.bw.Write(srcKey.AppendTo(nil)); err != nil {
return err
}
if _, err := c.bw.Write(dstKey[:]); err != nil {
if _, err := c.bw.Write(dstKey.AppendTo(nil)); err != nil {
return err
}
if _, err := c.bw.Write(pkt); err != nil {
@@ -322,10 +312,10 @@ func (c *Client) WatchConnectionChanges() error {
// ClosePeer asks the server to close target's TCP connection.
// It's a fatal error if the client wasn't created using MeshKey.
func (c *Client) ClosePeer(target key.Public) error {
func (c *Client) ClosePeer(target key.NodePublic) error {
c.wmu.Lock()
defer c.wmu.Unlock()
return writeFrame(c.bw, frameClosePeer, target[:])
return writeFrame(c.bw, frameClosePeer, target.AppendTo(nil))
}
// ReceivedMessage represents a type returned by Client.Recv. Unless
@@ -338,7 +328,7 @@ type ReceivedMessage interface {
// ReceivedPacket is a ReceivedMessage representing an incoming packet.
type ReceivedPacket struct {
Source key.Public
Source key.NodePublic
// Data is the received packet bytes. It aliases the memory
// passed to Client.Recv.
Data []byte
@@ -349,13 +339,13 @@ func (ReceivedPacket) msg() {}
// PeerGoneMessage is a ReceivedMessage that indicates that the client
// identified by the underlying public key had previously sent you a
// packet but has now disconnected from the server.
type PeerGoneMessage key.Public
type PeerGoneMessage key.NodePublic
func (PeerGoneMessage) msg() {}
// PeerPresentMessage is a ReceivedMessage that indicates that the client
// is connected to the server. (Only used by trusted mesh clients)
type PeerPresentMessage key.Public
type PeerPresentMessage key.NodePublic
func (PeerPresentMessage) msg() {}
@@ -516,8 +506,7 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
c.logf("[unexpected] dropping short peerGone frame from DERP server")
continue
}
var pg PeerGoneMessage
copy(pg[:], b[:keyLen])
pg := PeerGoneMessage(key.NodePublicFromRaw32(mem.B(b[:keyLen])))
return pg, nil
case framePeerPresent:
@@ -525,8 +514,7 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
c.logf("[unexpected] dropping short peerPresent frame from DERP server")
continue
}
var pg PeerPresentMessage
copy(pg[:], b[:keyLen])
pg := PeerPresentMessage(key.NodePublicFromRaw32(mem.B(b[:keyLen])))
return pg, nil
case frameRecvPacket:
@@ -535,7 +523,7 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
c.logf("[unexpected] dropping short packet from DERP server")
continue
}
copy(rp.Source[:], b[:keyLen])
rp.Source = key.NodePublicFromRaw32(mem.B(b[:keyLen]))
rp.Data = b[keyLen:n]
return rp, nil

View File

@@ -34,7 +34,6 @@ import (
"time"
"go4.org/mem"
"golang.org/x/crypto/nacl/box"
"golang.org/x/sync/errgroup"
"golang.org/x/time/rate"
"inet.af/netaddr"
@@ -52,7 +51,7 @@ var debug, _ = strconv.ParseBool(os.Getenv("DERP_DEBUG_LOGS"))
// verboseDropKeys is the set of destination public keys that should
// verbosely log whenever DERP drops a packet.
var verboseDropKeys = map[key.Public]bool{}
var verboseDropKeys = map[key.NodePublic]bool{}
func init() {
keys := os.Getenv("TS_DEBUG_VERBOSE_DROPS")
@@ -60,7 +59,7 @@ func init() {
return
}
for _, keyStr := range strings.Split(keys, ",") {
k, err := key.NewPublicFromHexMem(mem.S(keyStr))
k, err := key.ParseNodePublicUntyped(mem.S(keyStr))
if err != nil {
log.Printf("ignoring invalid debug key %q: %v", keyStr, err)
} else {
@@ -99,8 +98,8 @@ type Server struct {
// before failing when writing to a client.
WriteTimeout time.Duration
privateKey key.Private
publicKey key.Public
privateKey key.NodePrivate
publicKey key.NodePublic
logf logger.Logf
memSys0 uint64 // runtime.MemStats.Sys at start (or early-ish)
meshKey string
@@ -146,22 +145,22 @@ type Server struct {
mu sync.Mutex
closed bool
netConns map[Conn]chan struct{} // chan is closed when conn closes
clients map[key.Public]clientSet
clients map[key.NodePublic]clientSet
watchers map[*sclient]bool // mesh peer -> true
// clientsMesh tracks all clients in the cluster, both locally
// and to mesh peers. If the value is nil, that means the
// peer is only local (and thus in the clients Map, but not
// remote). If the value is non-nil, it's remote (+ maybe also
// local).
clientsMesh map[key.Public]PacketForwarder
clientsMesh map[key.NodePublic]PacketForwarder
// sentTo tracks which peers have sent to which other peers,
// and at which connection number. This isn't on sclient
// because it includes intra-region forwarded packets as the
// src.
sentTo map[key.Public]map[key.Public]int64 // src => dst => dst's latest sclient.connNum
sentTo map[key.NodePublic]map[key.NodePublic]int64 // src => dst => dst's latest sclient.connNum
// maps from netaddr.IPPort to a client's public key
keyOfAddr map[netaddr.IPPort]key.Public
keyOfAddr map[netaddr.IPPort]key.NodePublic
}
// clientSet represents 1 or more *sclients.
@@ -277,7 +276,7 @@ func (s *dupClientSet) removeClient(c *sclient) bool {
// is a multiForwarder, which this package creates as needed if a
// public key gets more than one PacketForwarder registered for it.
type PacketForwarder interface {
ForwardPacket(src, dst key.Public, payload []byte) error
ForwardPacket(src, dst key.NodePublic, payload []byte) error
}
// Conn is the subset of the underlying net.Conn the DERP Server needs.
@@ -294,7 +293,7 @@ type Conn interface {
// NewServer returns a new DERP server. It doesn't listen on its own.
// Connections are given to it via Server.Accept.
func NewServer(privateKey key.Private, logf logger.Logf) *Server {
func NewServer(privateKey key.NodePrivate, logf logger.Logf) *Server {
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
@@ -306,14 +305,14 @@ func NewServer(privateKey key.Private, logf logger.Logf) *Server {
packetsRecvByKind: metrics.LabelMap{Label: "kind"},
packetsDroppedReason: metrics.LabelMap{Label: "reason"},
packetsDroppedType: metrics.LabelMap{Label: "type"},
clients: map[key.Public]clientSet{},
clientsMesh: map[key.Public]PacketForwarder{},
clients: map[key.NodePublic]clientSet{},
clientsMesh: map[key.NodePublic]PacketForwarder{},
netConns: map[Conn]chan struct{}{},
memSys0: ms.Sys,
watchers: map[*sclient]bool{},
sentTo: map[key.Public]map[key.Public]int64{},
sentTo: map[key.NodePublic]map[key.NodePublic]int64{},
avgQueueDuration: new(uint64),
keyOfAddr: map[netaddr.IPPort]key.Public{},
keyOfAddr: map[netaddr.IPPort]key.NodePublic{},
}
s.initMetacert()
s.packetsRecvDisco = s.packetsRecvByKind.Get("disco")
@@ -353,10 +352,10 @@ func (s *Server) HasMeshKey() bool { return s.meshKey != "" }
func (s *Server) MeshKey() string { return s.meshKey }
// PrivateKey returns the server's private key.
func (s *Server) PrivateKey() key.Private { return s.privateKey }
func (s *Server) PrivateKey() key.NodePrivate { return s.privateKey }
// PublicKey returns the server's public key.
func (s *Server) PublicKey() key.Public { return s.publicKey }
func (s *Server) PublicKey() key.NodePublic { return s.publicKey }
// Close closes the server and waits for the connections to disconnect.
func (s *Server) Close() error {
@@ -447,7 +446,7 @@ func (s *Server) initMetacert() {
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(ProtocolVersion),
Subject: pkix.Name{
CommonName: fmt.Sprintf("derpkey%x", s.publicKey[:]),
CommonName: fmt.Sprintf("derpkey%s", s.publicKey.UntypedHexString()),
},
// Windows requires NotAfter and NotBefore set:
NotAfter: time.Now().Add(30 * 24 * time.Hour),
@@ -515,7 +514,7 @@ func (s *Server) registerClient(c *sclient) {
// presence changed.
//
// s.mu must be held.
func (s *Server) broadcastPeerStateChangeLocked(peer key.Public, present bool) {
func (s *Server) broadcastPeerStateChangeLocked(peer key.NodePublic, present bool) {
for w := range s.watchers {
w.peerStateChange = append(w.peerStateChange, peerConnState{peer: peer, present: present})
go w.requestMeshUpdate()
@@ -577,7 +576,7 @@ func (s *Server) unregisterClient(c *sclient) {
// key has sent to previously (whether those sends were from a local
// client or forwarded). It must only be called after the key has
// been removed from clientsMesh.
func (s *Server) notePeerGoneFromRegionLocked(key key.Public) {
func (s *Server) notePeerGoneFromRegionLocked(key key.NodePublic) {
if _, ok := s.clientsMesh[key]; ok {
panic("usage")
}
@@ -663,7 +662,7 @@ func (s *Server) accept(nc Conn, brw *bufio.ReadWriter, remoteAddr string, connN
connectedAt: time.Now(),
sendQueue: make(chan pkt, perClientSendQueueDepth),
discoSendQueue: make(chan pkt, perClientSendQueueDepth),
peerGone: make(chan key.Public),
peerGone: make(chan key.NodePublic),
canMesh: clientInfo.MeshKey != "" && clientInfo.MeshKey == s.meshKey,
}
@@ -774,8 +773,8 @@ func (c *sclient) handleFrameClosePeer(ft frameType, fl uint32) error {
if !c.canMesh {
return fmt.Errorf("insufficient permissions")
}
var targetKey key.Public
if _, err := io.ReadFull(c.br, targetKey[:]); err != nil {
var targetKey key.NodePublic
if err := targetKey.ReadRawWithoutAllocating(c.br); err != nil {
return err
}
s := c.s
@@ -845,10 +844,10 @@ func (c *sclient) handleFrameForwardPacket(ft frameType, fl uint32) error {
// notePeerSendLocked records that src sent to dst. We keep track of
// that so when src disconnects, we can tell dst (if it's still
// around) that src is gone (a peerGone frame).
func (s *Server) notePeerSendLocked(src key.Public, dst *sclient) {
func (s *Server) notePeerSendLocked(src key.NodePublic, dst *sclient) {
m, ok := s.sentTo[src]
if !ok {
m = map[key.Public]int64{}
m = map[key.NodePublic]int64{}
s.sentTo[src] = m
}
m[dst.key] = dst.connNum
@@ -919,7 +918,7 @@ const (
dropReasonDupClient // the public key is connected 2+ times (active/active, fighting)
)
func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.Public, reason dropReason) {
func (s *Server) recordDrop(packetBytes []byte, srcKey, dstKey key.NodePublic, reason dropReason) {
s.packetsDropped.Add(1)
s.packetsDroppedReasonCounters[reason].Add(1)
if disco.LooksLikeDiscoWrapper(packetBytes) {
@@ -982,7 +981,7 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
// requestPeerGoneWrite sends a request to write a "peer gone" frame
// that the provided peer has disconnected. It blocks until either the
// write request is scheduled, or the client has closed.
func (c *sclient) requestPeerGoneWrite(peer key.Public) {
func (c *sclient) requestPeerGoneWrite(peer key.NodePublic) {
select {
case c.peerGone <- peer:
case <-c.done:
@@ -999,7 +998,7 @@ func (c *sclient) requestMeshUpdate() {
}
}
func (s *Server) verifyClient(clientKey key.Public, info *clientInfo) error {
func (s *Server) verifyClient(clientKey key.NodePublic, info *clientInfo) error {
if !s.verifyClients {
return nil
}
@@ -1018,9 +1017,9 @@ func (s *Server) verifyClient(clientKey key.Public, info *clientInfo) error {
}
func (s *Server) sendServerKey(lw *lazyBufioWriter) error {
buf := make([]byte, 0, len(magic)+len(s.publicKey))
buf := make([]byte, 0, len(magic)+s.publicKey.RawLen())
buf = append(buf, magic...)
buf = append(buf, s.publicKey[:]...)
buf = s.publicKey.AppendTo(buf)
err := writeFrame(lw.bw(), frameServerKey, buf)
lw.Flush() // redundant (no-op) flush to release bufio.Writer
return err
@@ -1084,21 +1083,14 @@ type serverInfo struct {
TokenBucketBytesBurst int `json:",omitempty"`
}
func (s *Server) sendServerInfo(bw *lazyBufioWriter, clientKey key.Public) error {
var nonce [24]byte
if _, err := crand.Read(nonce[:]); err != nil {
return err
}
func (s *Server) sendServerInfo(bw *lazyBufioWriter, clientKey key.NodePublic) error {
msg, err := json.Marshal(serverInfo{Version: ProtocolVersion})
if err != nil {
return err
}
msgbox := box.Seal(nil, msg, &nonce, clientKey.B32(), s.privateKey.B32())
if err := writeFrameHeader(bw.bw(), frameServerInfo, nonceLen+uint32(len(msgbox))); err != nil {
return err
}
if _, err := bw.Write(nonce[:]); err != nil {
msgbox := s.privateKey.SealTo(clientKey, msg)
if err := writeFrameHeader(bw.bw(), frameServerInfo, uint32(len(msgbox))); err != nil {
return err
}
if _, err := bw.Write(msgbox); err != nil {
@@ -1110,7 +1102,7 @@ func (s *Server) sendServerInfo(bw *lazyBufioWriter, clientKey key.Public) error
// recvClientKey reads the frameClientInfo frame from the client (its
// proof of identity) upon its initial connection. It should be
// considered especially untrusted at this point.
func (s *Server) recvClientKey(br *bufio.Reader) (clientKey key.Public, info *clientInfo, err error) {
func (s *Server) recvClientKey(br *bufio.Reader) (clientKey key.NodePublic, info *clientInfo, err error) {
fl, err := readFrameTypeHeader(br, frameClientInfo)
if err != nil {
return zpub, nil, err
@@ -1124,21 +1116,17 @@ func (s *Server) recvClientKey(br *bufio.Reader) (clientKey key.Public, info *cl
if fl > 256<<10 {
return zpub, nil, errors.New("long client info")
}
if _, err := io.ReadFull(br, clientKey[:]); err != nil {
if err := clientKey.ReadRawWithoutAllocating(br); err != nil {
return zpub, nil, err
}
var nonce [24]byte
if _, err := io.ReadFull(br, nonce[:]); err != nil {
return zpub, nil, fmt.Errorf("nonce: %v", err)
}
msgLen := int(fl - minLen)
msgLen := int(fl - keyLen)
msgbox := make([]byte, msgLen)
if _, err := io.ReadFull(br, msgbox); err != nil {
return zpub, nil, fmt.Errorf("msgbox: %v", err)
}
msg, ok := box.Open(nil, msgbox, &nonce, (*[32]byte)(&clientKey), s.privateKey.B32())
msg, ok := s.privateKey.OpenFrom(clientKey, msgbox)
if !ok {
return zpub, nil, fmt.Errorf("msgbox: cannot open len=%d with client key %x", msgLen, clientKey[:])
return zpub, nil, fmt.Errorf("msgbox: cannot open len=%d with client key %s", msgLen, clientKey)
}
info = new(clientInfo)
if err := json.Unmarshal(msg, info); err != nil {
@@ -1147,11 +1135,11 @@ func (s *Server) recvClientKey(br *bufio.Reader) (clientKey key.Public, info *cl
return clientKey, info, nil
}
func (s *Server) recvPacket(br *bufio.Reader, frameLen uint32) (dstKey key.Public, contents []byte, err error) {
func (s *Server) recvPacket(br *bufio.Reader, frameLen uint32) (dstKey key.NodePublic, contents []byte, err error) {
if frameLen < keyLen {
return zpub, nil, errors.New("short send packet frame")
}
if err := readPublicKey(br, &dstKey); err != nil {
if err := dstKey.ReadRawWithoutAllocating(br); err != nil {
return zpub, nil, err
}
packetLen := frameLen - keyLen
@@ -1172,17 +1160,17 @@ func (s *Server) recvPacket(br *bufio.Reader, frameLen uint32) (dstKey key.Publi
return dstKey, contents, nil
}
// zpub is the key.Public zero value.
var zpub key.Public
// zpub is the key.NodePublic zero value.
var zpub key.NodePublic
func (s *Server) recvForwardPacket(br *bufio.Reader, frameLen uint32) (srcKey, dstKey key.Public, contents []byte, err error) {
func (s *Server) recvForwardPacket(br *bufio.Reader, frameLen uint32) (srcKey, dstKey key.NodePublic, contents []byte, err error) {
if frameLen < keyLen*2 {
return zpub, zpub, nil, errors.New("short send packet frame")
}
if _, err := io.ReadFull(br, srcKey[:]); err != nil {
if err := srcKey.ReadRawWithoutAllocating(br); err != nil {
return zpub, zpub, nil, err
}
if _, err := io.ReadFull(br, dstKey[:]); err != nil {
if err := dstKey.ReadRawWithoutAllocating(br); err != nil {
return zpub, zpub, nil, err
}
packetLen := frameLen - keyLen*2
@@ -1206,19 +1194,19 @@ type sclient struct {
connNum int64 // process-wide unique counter, incremented each Accept
s *Server
nc Conn
key key.Public
key key.NodePublic
info clientInfo
logf logger.Logf
done <-chan struct{} // closed when connection closes
remoteAddr string // usually ip:port from net.Conn.RemoteAddr().String()
remoteIPPort netaddr.IPPort // zero if remoteAddr is not ip:port.
sendQueue chan pkt // packets queued to this client; never closed
discoSendQueue chan pkt // important packets queued to this client; never closed
peerGone chan key.Public // write request that a previous sender has disconnected (not used by mesh peers)
meshUpdate chan struct{} // write request to write peerStateChange
canMesh bool // clientInfo had correct mesh token for inter-region routing
isDup syncs.AtomicBool // whether more than 1 sclient for key is connected
isDisabled syncs.AtomicBool // whether sends to this peer are disabled due to active/active dups
done <-chan struct{} // closed when connection closes
remoteAddr string // usually ip:port from net.Conn.RemoteAddr().String()
remoteIPPort netaddr.IPPort // zero if remoteAddr is not ip:port.
sendQueue chan pkt // packets queued to this client; never closed
discoSendQueue chan pkt // important packets queued to this client; never closed
peerGone chan key.NodePublic // write request that a previous sender has disconnected (not used by mesh peers)
meshUpdate chan struct{} // write request to write peerStateChange
canMesh bool // clientInfo had correct mesh token for inter-region routing
isDup syncs.AtomicBool // whether more than 1 sclient for key is connected
isDisabled syncs.AtomicBool // whether sends to this peer are disabled due to active/active dups
// replaceLimiter controls how quickly two connections with
// the same client key can kick each other off the server by
@@ -1245,14 +1233,14 @@ type sclient struct {
// peerConnState represents whether a peer is connected to the server
// or not.
type peerConnState struct {
peer key.Public
peer key.NodePublic
present bool
}
// pkt is a request to write a data frame to an sclient.
type pkt struct {
// src is the who's the sender of the packet.
src key.Public
src key.NodePublic
// enqueuedAt is when a packet was put onto a queue before it was sent,
// and is used for reporting metrics on the duration of packets in the queue.
@@ -1397,23 +1385,23 @@ func (c *sclient) sendKeepAlive() error {
}
// sendPeerGone sends a peerGone frame, without flushing.
func (c *sclient) sendPeerGone(peer key.Public) error {
func (c *sclient) sendPeerGone(peer key.NodePublic) error {
c.s.peerGoneFrames.Add(1)
c.setWriteDeadline()
if err := writeFrameHeader(c.bw.bw(), framePeerGone, keyLen); err != nil {
return err
}
_, err := c.bw.Write(peer[:])
_, err := c.bw.Write(peer.AppendTo(nil))
return err
}
// sendPeerPresent sends a peerPresent frame, without flushing.
func (c *sclient) sendPeerPresent(peer key.Public) error {
func (c *sclient) sendPeerPresent(peer key.NodePublic) error {
c.setWriteDeadline()
if err := writeFrameHeader(c.bw.bw(), framePeerPresent, keyLen); err != nil {
return err
}
_, err := c.bw.Write(peer[:])
_, err := c.bw.Write(peer.AppendTo(nil))
return err
}
@@ -1465,7 +1453,7 @@ func (c *sclient) sendMeshUpdates() error {
// DERPv2. The bytes of contents are only valid until this function
// returns, do not retain slices.
// It does not flush its bufio.Writer.
func (c *sclient) sendPacket(srcKey key.Public, contents []byte) (err error) {
func (c *sclient) sendPacket(srcKey key.NodePublic, contents []byte) (err error) {
defer func() {
// Stats update.
if err != nil {
@@ -1481,14 +1469,13 @@ func (c *sclient) sendPacket(srcKey key.Public, contents []byte) (err error) {
withKey := !srcKey.IsZero()
pktLen := len(contents)
if withKey {
pktLen += len(srcKey)
pktLen += srcKey.RawLen()
}
if err = writeFrameHeader(c.bw.bw(), frameRecvPacket, uint32(pktLen)); err != nil {
return err
}
if withKey {
err := writePublicKey(c.bw.bw(), &srcKey)
if err != nil {
if err := srcKey.WriteRawWithoutAllocating(c.bw.bw()); err != nil {
return err
}
}
@@ -1498,7 +1485,7 @@ func (c *sclient) sendPacket(srcKey key.Public, contents []byte) (err error) {
// AddPacketForwarder registers fwd as a packet forwarder for dst.
// fwd must be comparable.
func (s *Server) AddPacketForwarder(dst key.Public, fwd PacketForwarder) {
func (s *Server) AddPacketForwarder(dst key.NodePublic, fwd PacketForwarder) {
s.mu.Lock()
defer s.mu.Unlock()
if prev, ok := s.clientsMesh[dst]; ok {
@@ -1530,7 +1517,7 @@ func (s *Server) AddPacketForwarder(dst key.Public, fwd PacketForwarder) {
// RemovePacketForwarder removes fwd as a packet forwarder for dst.
// fwd must be comparable.
func (s *Server) RemovePacketForwarder(dst key.Public, fwd PacketForwarder) {
func (s *Server) RemovePacketForwarder(dst key.NodePublic, fwd PacketForwarder) {
s.mu.Lock()
defer s.mu.Unlock()
v, ok := s.clientsMesh[dst]
@@ -1592,7 +1579,7 @@ func (m multiForwarder) maxVal() (max uint8) {
return
}
func (m multiForwarder) ForwardPacket(src, dst key.Public, payload []byte) error {
func (m multiForwarder) ForwardPacket(src, dst key.NodePublic, payload []byte) error {
var fwd PacketForwarder
var lowest uint8
for k, v := range m {
@@ -1692,37 +1679,6 @@ func (s *Server) ConsistencyCheck() error {
return errors.New(strings.Join(errs, ", "))
}
// readPublicKey reads key from br.
// It is ~4x slower than io.ReadFull(br, key),
// but it prevents key from escaping and thus being allocated.
// If io.ReadFull(br, key) does not cause key to escape, use that instead.
func readPublicKey(br *bufio.Reader, key *key.Public) error {
// Do io.ReadFull(br, key), but one byte at a time, to avoid allocation.
for i := range key {
b, err := br.ReadByte()
if err != nil {
return err
}
key[i] = b
}
return nil
}
// writePublicKey writes key to bw.
// It is ~3x slower than bw.Write(key[:]),
// but it prevents key from escaping and thus being allocated.
// If bw.Write(key[:]) does not cause key to escape, use that instead.
func writePublicKey(bw *bufio.Writer, key *key.Public) error {
// Do bw.Write(key[:]), but one byte at a time to avoid allocation.
for _, b := range key {
err := bw.WriteByte(b)
if err != nil {
return err
}
}
return nil
}
const minTimeBetweenLogs = 2 * time.Second
// BytesSentRecv records the number of bytes that have been sent since the last traffic check
@@ -1731,7 +1687,7 @@ type BytesSentRecv struct {
Sent uint64
Recv uint64
// Key is the public key of the client which sent/received these bytes.
Key key.Public
Key key.NodePublic
}
// parseSSOutput parses the output from the specific call to ss in ServeDebugTraffic.

View File

@@ -8,7 +8,6 @@ import (
"bufio"
"bytes"
"context"
crand "crypto/rand"
"crypto/x509"
"encoding/json"
"errors"
@@ -23,20 +22,13 @@ import (
"testing"
"time"
"go4.org/mem"
"golang.org/x/time/rate"
"tailscale.com/net/nettest"
"tailscale.com/types/key"
"tailscale.com/types/logger"
)
func newPrivateKey(tb testing.TB) (k key.Private) {
tb.Helper()
if _, err := crand.Read(k[:]); err != nil {
tb.Fatal(err)
}
return
}
func TestClientInfoUnmarshal(t *testing.T) {
for i, in := range []string{
`{"Version":5,"MeshKey":"abc"}`,
@@ -54,15 +46,15 @@ func TestClientInfoUnmarshal(t *testing.T) {
}
func TestSendRecv(t *testing.T) {
serverPrivateKey := newPrivateKey(t)
serverPrivateKey := key.NewNode()
s := NewServer(serverPrivateKey, t.Logf)
defer s.Close()
const numClients = 3
var clientPrivateKeys []key.Private
var clientKeys []key.Public
var clientPrivateKeys []key.NodePrivate
var clientKeys []key.NodePublic
for i := 0; i < numClients; i++ {
priv := newPrivateKey(t)
priv := key.NewNode()
clientPrivateKeys = append(clientPrivateKeys, priv)
clientKeys = append(clientKeys, priv.Public())
}
@@ -225,7 +217,7 @@ func TestSendRecv(t *testing.T) {
}
func TestSendFreeze(t *testing.T) {
serverPrivateKey := newPrivateKey(t)
serverPrivateKey := key.NewNode()
s := NewServer(serverPrivateKey, t.Logf)
defer s.Close()
s.WriteTimeout = 100 * time.Millisecond
@@ -238,7 +230,7 @@ func TestSendFreeze(t *testing.T) {
// Then cathy stops processing messsages.
// That should not interfere with alice talking to bob.
newClient := func(name string, k key.Private) (c *Client, clientConn nettest.Conn) {
newClient := func(name string, k key.NodePrivate) (c *Client, clientConn nettest.Conn) {
t.Helper()
c1, c2 := nettest.NewConn(name, 1024)
go s.Accept(c1, bufio.NewReadWriter(bufio.NewReader(c1), bufio.NewWriter(c1)), name)
@@ -252,13 +244,13 @@ func TestSendFreeze(t *testing.T) {
return c, c2
}
aliceKey := newPrivateKey(t)
aliceKey := key.NewNode()
aliceClient, aliceConn := newClient("alice", aliceKey)
bobKey := newPrivateKey(t)
bobKey := key.NewNode()
bobClient, bobConn := newClient("bob", bobKey)
cathyKey := newPrivateKey(t)
cathyKey := key.NewNode()
cathyClient, cathyConn := newClient("cathy", cathyKey)
var (
@@ -427,7 +419,7 @@ type testServer struct {
logf logger.Logf
mu sync.Mutex
pubName map[key.Public]string
pubName map[key.NodePublic]string
clients map[*testClient]bool
}
@@ -437,14 +429,14 @@ func (ts *testServer) addTestClient(c *testClient) {
ts.clients[c] = true
}
func (ts *testServer) addKeyName(k key.Public, name string) {
func (ts *testServer) addKeyName(k key.NodePublic, name string) {
ts.mu.Lock()
defer ts.mu.Unlock()
ts.pubName[k] = name
ts.logf("test adding named key %q for %x", name, k)
}
func (ts *testServer) keyName(k key.Public) string {
func (ts *testServer) keyName(k key.NodePublic) string {
ts.mu.Lock()
defer ts.mu.Unlock()
if name, ok := ts.pubName[k]; ok {
@@ -465,7 +457,7 @@ func (ts *testServer) close(t *testing.T) error {
func newTestServer(t *testing.T) *testServer {
t.Helper()
logf := logger.WithPrefix(t.Logf, "derp-server: ")
s := NewServer(newPrivateKey(t), logf)
s := NewServer(key.NewNode(), logf)
s.SetMeshKey("mesh-key")
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
@@ -491,7 +483,7 @@ func newTestServer(t *testing.T) *testServer {
ln: ln,
logf: logf,
clients: map[*testClient]bool{},
pubName: map[key.Public]string{},
pubName: map[key.NodePublic]string{},
}
}
@@ -499,20 +491,20 @@ type testClient struct {
name string
c *Client
nc net.Conn
pub key.Public
pub key.NodePublic
ts *testServer
closed bool
}
func newTestClient(t *testing.T, ts *testServer, name string, newClient func(net.Conn, key.Private, logger.Logf) (*Client, error)) *testClient {
func newTestClient(t *testing.T, ts *testServer, name string, newClient func(net.Conn, key.NodePrivate, logger.Logf) (*Client, error)) *testClient {
t.Helper()
nc, err := net.Dial("tcp", ts.ln.Addr().String())
if err != nil {
t.Fatal(err)
}
key := newPrivateKey(t)
ts.addKeyName(key.Public(), name)
c, err := newClient(nc, key, logger.WithPrefix(t.Logf, "client-"+name+": "))
k := key.NewNode()
ts.addKeyName(k.Public(), name)
c, err := newClient(nc, k, logger.WithPrefix(t.Logf, "client-"+name+": "))
if err != nil {
t.Fatal(err)
}
@@ -521,14 +513,14 @@ func newTestClient(t *testing.T, ts *testServer, name string, newClient func(net
nc: nc,
c: c,
ts: ts,
pub: key.Public(),
pub: k.Public(),
}
ts.addTestClient(tc)
return tc
}
func newRegularClient(t *testing.T, ts *testServer, name string) *testClient {
return newTestClient(t, ts, name, func(nc net.Conn, priv key.Private, logf logger.Logf) (*Client, error) {
return newTestClient(t, ts, name, func(nc net.Conn, priv key.NodePrivate, logf logger.Logf) (*Client, error) {
brw := bufio.NewReadWriter(bufio.NewReader(nc), bufio.NewWriter(nc))
c, err := NewClient(priv, nc, brw, logf)
if err != nil {
@@ -541,7 +533,7 @@ func newRegularClient(t *testing.T, ts *testServer, name string) *testClient {
}
func newTestWatcher(t *testing.T, ts *testServer, name string) *testClient {
return newTestClient(t, ts, name, func(nc net.Conn, priv key.Private, logf logger.Logf) (*Client, error) {
return newTestClient(t, ts, name, func(nc net.Conn, priv key.NodePrivate, logf logger.Logf) (*Client, error) {
brw := bufio.NewReadWriter(bufio.NewReader(nc), bufio.NewWriter(nc))
c, err := NewClient(priv, nc, brw, logf, MeshKey("mesh-key"))
if err != nil {
@@ -555,9 +547,9 @@ func newTestWatcher(t *testing.T, ts *testServer, name string) *testClient {
})
}
func (tc *testClient) wantPresent(t *testing.T, peers ...key.Public) {
func (tc *testClient) wantPresent(t *testing.T, peers ...key.NodePublic) {
t.Helper()
want := map[key.Public]bool{}
want := map[key.NodePublic]bool{}
for _, k := range peers {
want[k] = true
}
@@ -569,7 +561,7 @@ func (tc *testClient) wantPresent(t *testing.T, peers ...key.Public) {
}
switch m := m.(type) {
case PeerPresentMessage:
got := key.Public(m)
got := key.NodePublic(m)
if !want[got] {
t.Fatalf("got peer present for %v; want present for %v", tc.ts.keyName(got), logger.ArgWriter(func(bw *bufio.Writer) {
for _, pub := range peers {
@@ -587,7 +579,7 @@ func (tc *testClient) wantPresent(t *testing.T, peers ...key.Public) {
}
}
func (tc *testClient) wantGone(t *testing.T, peer key.Public) {
func (tc *testClient) wantGone(t *testing.T, peer key.NodePublic) {
t.Helper()
m, err := tc.c.recvTimeout(time.Second)
if err != nil {
@@ -595,7 +587,7 @@ func (tc *testClient) wantGone(t *testing.T, peer key.Public) {
}
switch m := m.(type) {
case PeerGoneMessage:
got := key.Public(m)
got := key.NodePublic(m)
if peer != got {
t.Errorf("got gone message for %v; want gone for %v", tc.ts.keyName(got), tc.ts.keyName(peer))
}
@@ -654,21 +646,24 @@ func TestWatch(t *testing.T) {
type testFwd int
func (testFwd) ForwardPacket(key.Public, key.Public, []byte) error { panic("not called in tests") }
func (testFwd) ForwardPacket(key.NodePublic, key.NodePublic, []byte) error {
panic("not called in tests")
}
func pubAll(b byte) (ret key.Public) {
for i := range ret {
ret[i] = b
func pubAll(b byte) (ret key.NodePublic) {
var bs [32]byte
for i := range bs {
bs[i] = b
}
return
return key.NodePublicFromRaw32(mem.B(bs[:]))
}
func TestForwarderRegistration(t *testing.T) {
s := &Server{
clients: make(map[key.Public]clientSet),
clientsMesh: map[key.Public]PacketForwarder{},
clients: make(map[key.NodePublic]clientSet),
clientsMesh: map[key.NodePublic]PacketForwarder{},
}
want := func(want map[key.Public]PacketForwarder) {
want := func(want map[key.NodePublic]PacketForwarder) {
t.Helper()
if got := s.clientsMesh; !reflect.DeepEqual(got, want) {
t.Fatalf("mismatch\n got: %v\nwant: %v\n", got, want)
@@ -687,28 +682,28 @@ func TestForwarderRegistration(t *testing.T) {
s.AddPacketForwarder(u1, testFwd(1))
s.AddPacketForwarder(u2, testFwd(2))
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: testFwd(1),
u2: testFwd(2),
})
// Verify a remove of non-registered forwarder is no-op.
s.RemovePacketForwarder(u2, testFwd(999))
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: testFwd(1),
u2: testFwd(2),
})
// Verify a remove of non-registered user is no-op.
s.RemovePacketForwarder(u3, testFwd(1))
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: testFwd(1),
u2: testFwd(2),
})
// Actual removal.
s.RemovePacketForwarder(u2, testFwd(2))
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: testFwd(1),
})
@@ -716,7 +711,7 @@ func TestForwarderRegistration(t *testing.T) {
wantCounter(&s.multiForwarderCreated, 0)
s.AddPacketForwarder(u1, testFwd(100))
s.AddPacketForwarder(u1, testFwd(100)) // dup to trigger dup path
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: multiForwarder{
testFwd(1): 1,
testFwd(100): 2,
@@ -726,7 +721,7 @@ func TestForwarderRegistration(t *testing.T) {
// Removing a forwarder in a multi set that doesn't exist; does nothing.
s.RemovePacketForwarder(u1, testFwd(55))
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: multiForwarder{
testFwd(1): 1,
testFwd(100): 2,
@@ -737,7 +732,7 @@ func TestForwarderRegistration(t *testing.T) {
// from being a multiForwarder.
wantCounter(&s.multiForwarderDeleted, 0)
s.RemovePacketForwarder(u1, testFwd(1))
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: testFwd(100),
})
wantCounter(&s.multiForwarderDeleted, 1)
@@ -750,18 +745,18 @@ func TestForwarderRegistration(t *testing.T) {
}
s.clients[u1] = singleClient{u1c}
s.RemovePacketForwarder(u1, testFwd(100))
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: nil,
})
// But once that client disconnects, it should go away.
s.unregisterClient(u1c)
want(map[key.Public]PacketForwarder{})
want(map[key.NodePublic]PacketForwarder{})
// But if it already has a forwarder, it's not removed.
s.AddPacketForwarder(u1, testFwd(2))
s.unregisterClient(u1c)
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: testFwd(2),
})
@@ -770,17 +765,17 @@ func TestForwarderRegistration(t *testing.T) {
// from nil to the new one, not a multiForwarder.
s.clients[u1] = singleClient{u1c}
s.clientsMesh[u1] = nil
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: nil,
})
s.AddPacketForwarder(u1, testFwd(3))
want(map[key.Public]PacketForwarder{
want(map[key.NodePublic]PacketForwarder{
u1: testFwd(3),
})
}
func TestMetaCert(t *testing.T) {
priv := newPrivateKey(t)
priv := key.NewNode()
pub := priv.Public()
s := NewServer(priv, t.Logf)
@@ -792,7 +787,7 @@ func TestMetaCert(t *testing.T) {
if fmt.Sprint(cert.SerialNumber) != fmt.Sprint(ProtocolVersion) {
t.Errorf("serial = %v; want %v", cert.SerialNumber, ProtocolVersion)
}
if g, w := cert.Subject.CommonName, fmt.Sprintf("derpkey%x", pub[:]); g != w {
if g, w := cert.Subject.CommonName, fmt.Sprintf("derpkey%s", pub.UntypedHexString()); g != w {
t.Errorf("CommonName = %q; want %q", g, w)
}
}
@@ -882,10 +877,10 @@ func TestClientSendPong(t *testing.T) {
}
func TestServerDupClients(t *testing.T) {
serverPriv := newPrivateKey(t)
serverPriv := key.NewNode()
var s *Server
clientPriv := newPrivateKey(t)
clientPriv := key.NewNode()
clientPub := clientPriv.Public()
var c1, c2, c3 *sclient
@@ -1141,12 +1136,12 @@ func BenchmarkSendRecv(b *testing.B) {
}
func benchmarkSendRecvSize(b *testing.B, packetSize int) {
serverPrivateKey := newPrivateKey(b)
serverPrivateKey := key.NewNode()
s := NewServer(serverPrivateKey, logger.Discard)
defer s.Close()
key := newPrivateKey(b)
clientKey := key.Public()
k := key.NewNode()
clientKey := k.Public()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
@@ -1170,7 +1165,7 @@ func benchmarkSendRecvSize(b *testing.B, packetSize int) {
go s.Accept(connIn, brwServer, "test-client")
brw := bufio.NewReadWriter(bufio.NewReader(connOut), bufio.NewWriter(connOut))
client, err := NewClient(key, connOut, brw, logger.Discard)
client, err := NewClient(k, connOut, brw, logger.Discard)
if err != nil {
b.Fatalf("client: %v", err)
}
@@ -1279,7 +1274,7 @@ func TestClientSendRateLimiting(t *testing.T) {
c.setSendRateLimiter(ServerInfoMessage{})
pkt := make([]byte, 1000)
if err := c.send(key.Public{}, pkt); err != nil {
if err := c.send(key.NodePublic{}, pkt); err != nil {
t.Fatal(err)
}
writes1, bytes1 := cw.Stats()
@@ -1290,7 +1285,7 @@ func TestClientSendRateLimiting(t *testing.T) {
// Flood should all succeed.
cw.ResetStats()
for i := 0; i < 1000; i++ {
if err := c.send(key.Public{}, pkt); err != nil {
if err := c.send(key.NodePublic{}, pkt); err != nil {
t.Fatal(err)
}
}
@@ -1309,7 +1304,7 @@ func TestClientSendRateLimiting(t *testing.T) {
TokenBucketBytesBurst: int(bytes1 * 2),
})
for i := 0; i < 1000; i++ {
if err := c.send(key.Public{}, pkt); err != nil {
if err := c.send(key.NodePublic{}, pkt); err != nil {
t.Fatal(err)
}
}

View File

@@ -22,6 +22,9 @@ import (
"net"
"net/http"
"net/url"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
@@ -50,7 +53,7 @@ type Client struct {
MeshKey string // optional; for trusted clients
IsProber bool // optional; for probers to optional declare themselves as such
privateKey key.Private
privateKey key.NodePrivate
logf logger.Logf
dialer func(ctx context.Context, network, addr string) (net.Conn, error)
@@ -68,12 +71,12 @@ type Client struct {
netConn io.Closer
client *derp.Client
connGen int // incremented once per new connection; valid values are >0
serverPubKey key.Public
serverPubKey key.NodePublic
}
// NewRegionClient returns a new DERP-over-HTTP client. It connects lazily.
// To trigger a connection, use Connect.
func NewRegionClient(privateKey key.Private, logf logger.Logf, getRegion func() *tailcfg.DERPRegion) *Client {
func NewRegionClient(privateKey key.NodePrivate, logf logger.Logf, getRegion func() *tailcfg.DERPRegion) *Client {
ctx, cancel := context.WithCancel(context.Background())
c := &Client{
privateKey: privateKey,
@@ -93,7 +96,7 @@ func NewNetcheckClient(logf logger.Logf) *Client {
// NewClient returns a new DERP-over-HTTP client. It connects lazily.
// To trigger a connection, use Connect.
func NewClient(privateKey key.Private, serverURL string, logf logger.Logf) (*Client, error) {
func NewClient(privateKey key.NodePrivate, serverURL string, logf logger.Logf) (*Client, error) {
u, err := url.Parse(serverURL)
if err != nil {
return nil, fmt.Errorf("derphttp.NewClient: %v", err)
@@ -124,14 +127,14 @@ func (c *Client) Connect(ctx context.Context) error {
//
// It only returns a non-zero value once a connection has succeeded
// from an earlier call.
func (c *Client) ServerPublicKey() key.Public {
func (c *Client) ServerPublicKey() key.NodePublic {
c.mu.Lock()
defer c.mu.Unlock()
return c.serverPubKey
}
// SelfPublicKey returns our own public key.
func (c *Client) SelfPublicKey() key.Public {
func (c *Client) SelfPublicKey() key.NodePublic {
return c.privateKey.Public()
}
@@ -177,6 +180,20 @@ func (c *Client) urlString(node *tailcfg.DERPNode) string {
return fmt.Sprintf("https://%s/derp", node.HostName)
}
// dialWebsocketFunc is non-nil (set by websocket.go's init) when compiled in.
var dialWebsocketFunc func(ctx context.Context, urlStr string) (net.Conn, error)
func useWebsockets() bool {
if runtime.GOOS == "js" {
return true
}
if dialWebsocketFunc != nil {
v, _ := strconv.ParseBool(os.Getenv("TS_DEBUG_DERP_WS_CLIENT"))
return v
}
return false
}
func (c *Client) connect(ctx context.Context, caller string) (client *derp.Client, connGen int, err error) {
c.mu.Lock()
defer c.mu.Unlock()
@@ -229,10 +246,44 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
}()
var node *tailcfg.DERPNode // nil when using c.url to dial
if c.url != nil {
switch {
case useWebsockets():
var urlStr string
if c.url != nil {
urlStr = c.url.String()
} else {
urlStr = c.urlString(reg.Nodes[0])
}
c.logf("%s: connecting websocket to %v", caller, urlStr)
conn, err := dialWebsocketFunc(ctx, urlStr)
if err != nil {
c.logf("%s: websocket to %v error: %v", caller, urlStr, err)
return nil, 0, err
}
brw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
derpClient, err := derp.NewClient(c.privateKey, conn, brw, c.logf,
derp.MeshKey(c.MeshKey),
derp.CanAckPings(c.canAckPings),
derp.IsProber(c.IsProber),
)
if err != nil {
return nil, 0, err
}
if c.preferred {
if err := derpClient.NotePreferred(true); err != nil {
go conn.Close()
return nil, 0, err
}
}
c.serverPubKey = derpClient.ServerPublicKey()
c.client = derpClient
c.netConn = tcpConn
c.connGen++
return c.client, c.connGen, nil
case c.url != nil:
c.logf("%s: connecting to %v", caller, c.url)
tcpConn, err = c.dialURL(ctx)
} else {
default:
c.logf("%s: connecting to derp-%d (%v)", caller, reg.RegionID, reg.RegionCode)
tcpConn, node, err = c.dialRegion(ctx, reg)
}
@@ -264,8 +315,8 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
}
}()
var httpConn net.Conn // a TCP conn or a TLS conn; what we speak HTTP to
var serverPub key.Public // or zero if unknown (if not using TLS or TLS middlebox eats it)
var httpConn net.Conn // a TCP conn or a TLS conn; what we speak HTTP to
var serverPub key.NodePublic // or zero if unknown (if not using TLS or TLS middlebox eats it)
var serverProtoVersion int
if c.useHTTPS() {
tlsConn := c.tlsClient(tcpConn, node)
@@ -636,7 +687,7 @@ func (c *Client) dialNodeUsingProxy(ctx context.Context, n *tailcfg.DERPNode, pr
return proxyConn, nil
}
func (c *Client) Send(dstKey key.Public, b []byte) error {
func (c *Client) Send(dstKey key.NodePublic, b []byte) error {
client, _, err := c.connect(context.TODO(), "derphttp.Client.Send")
if err != nil {
return err
@@ -647,7 +698,7 @@ func (c *Client) Send(dstKey key.Public, b []byte) error {
return err
}
func (c *Client) ForwardPacket(from, to key.Public, b []byte) error {
func (c *Client) ForwardPacket(from, to key.NodePublic, b []byte) error {
client, _, err := c.connect(context.TODO(), "derphttp.Client.ForwardPacket")
if err != nil {
return err
@@ -728,7 +779,7 @@ func (c *Client) WatchConnectionChanges() error {
// ClosePeer asks the server to close target's TCP connection.
//
// Only trusted connections (using MeshKey) are allowed to use this.
func (c *Client) ClosePeer(target key.Public) error {
func (c *Client) ClosePeer(target key.NodePublic) error {
client, _, err := c.connect(context.TODO(), "derphttp.Client.ClosePeer")
if err != nil {
return err
@@ -812,15 +863,15 @@ func (c *Client) closeForReconnect(brokenClient *derp.Client) {
var ErrClientClosed = errors.New("derphttp.Client closed")
func parseMetaCert(certs []*x509.Certificate) (serverPub key.Public, serverProtoVersion int) {
func parseMetaCert(certs []*x509.Certificate) (serverPub key.NodePublic, serverProtoVersion int) {
for _, cert := range certs {
if cn := cert.Subject.CommonName; strings.HasPrefix(cn, "derpkey") {
var err error
serverPub, err = key.NewPublicFromHexMem(mem.S(strings.TrimPrefix(cn, "derpkey")))
serverPub, err = key.ParseNodePublicUntyped(mem.S(strings.TrimPrefix(cn, "derpkey")))
if err == nil && cert.SerialNumber.BitLen() <= 8 { // supports up to version 255
return serverPub, int(cert.SerialNumber.Int64())
}
}
}
return key.Public{}, 0
return key.NodePublic{}, 0
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"log"
"net/http"
"strings"
"tailscale.com/derp"
)
@@ -20,10 +21,15 @@ const fastStartHeader = "Derp-Fast-Start"
func Handler(s *derp.Server) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if p := r.Header.Get("Upgrade"); p != "WebSocket" && p != "DERP" {
up := strings.ToLower(r.Header.Get("Upgrade"))
if up != "websocket" && up != "derp" {
if up != "" {
log.Printf("Weird upgrade: %q", up)
}
http.Error(w, "DERP requires connection upgrade", http.StatusUpgradeRequired)
return
}
fastStart := r.Header.Get(fastStartHeader) == "1"
h, ok := w.(http.Hijacker)
@@ -45,9 +51,9 @@ func Handler(s *derp.Server) http.Handler {
"Upgrade: DERP\r\n"+
"Connection: Upgrade\r\n"+
"Derp-Version: %v\r\n"+
"Derp-Public-Key: %x\r\n\r\n",
"Derp-Public-Key: %s\r\n\r\n",
derp.ProtocolVersion,
pubKey[:])
pubKey.UntypedHexString())
}
s.Accept(netConn, conn, netConn.RemoteAddr().String())

View File

@@ -18,13 +18,13 @@ import (
)
func TestSendRecv(t *testing.T) {
serverPrivateKey := key.NewPrivate()
serverPrivateKey := key.NewNode()
const numClients = 3
var clientPrivateKeys []key.Private
var clientKeys []key.Public
var clientPrivateKeys []key.NodePrivate
var clientKeys []key.NodePublic
for i := 0; i < numClients; i++ {
priv := key.NewPrivate()
priv := key.NewNode()
clientPrivateKeys = append(clientPrivateKeys, priv)
clientKeys = append(clientKeys, priv.Public())
}

View File

@@ -27,7 +27,7 @@ import (
//
// To force RunWatchConnectionLoop to return quickly, its ctx needs to
// be closed, and c itself needs to be closed.
func (c *Client) RunWatchConnectionLoop(ctx context.Context, ignoreServerKey key.Public, infoLogf logger.Logf, add, remove func(key.Public)) {
func (c *Client) RunWatchConnectionLoop(ctx context.Context, ignoreServerKey key.NodePublic, infoLogf logger.Logf, add, remove func(key.NodePublic)) {
if infoLogf == nil {
infoLogf = logger.Discard
}
@@ -36,7 +36,7 @@ func (c *Client) RunWatchConnectionLoop(ctx context.Context, ignoreServerKey key
const statusInterval = 10 * time.Second
var (
mu sync.Mutex
present = map[key.Public]bool{}
present = map[key.NodePublic]bool{}
loggedConnected = false
)
clear := func() {
@@ -49,7 +49,7 @@ func (c *Client) RunWatchConnectionLoop(ctx context.Context, ignoreServerKey key
for k := range present {
remove(k)
}
present = map[key.Public]bool{}
present = map[key.NodePublic]bool{}
}
lastConnGen := 0
lastStatus := time.Now()
@@ -69,7 +69,7 @@ func (c *Client) RunWatchConnectionLoop(ctx context.Context, ignoreServerKey key
})
defer timer.Stop()
updatePeer := func(k key.Public, isPresent bool) {
updatePeer := func(k key.NodePublic, isPresent bool) {
if isPresent {
add(k)
} else {
@@ -127,9 +127,9 @@ func (c *Client) RunWatchConnectionLoop(ctx context.Context, ignoreServerKey key
}
switch m := m.(type) {
case derp.PeerPresentMessage:
updatePeer(key.Public(m), true)
updatePeer(key.NodePublic(m), true)
case derp.PeerGoneMessage:
updatePeer(key.Public(m), false)
updatePeer(key.NodePublic(m), false)
default:
continue
}

View File

@@ -0,0 +1,33 @@
// 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.
//go:build linux || js
// +build linux js
package derphttp
import (
"context"
"log"
"net"
"nhooyr.io/websocket"
"tailscale.com/derp/wsconn"
)
func init() {
dialWebsocketFunc = dialWebsocket
}
func dialWebsocket(ctx context.Context, urlStr string) (net.Conn, error) {
c, res, err := websocket.Dial(ctx, urlStr, &websocket.DialOptions{
Subprotocols: []string{"derp"},
})
if err != nil {
log.Printf("websocket Dial: %v, %+v", err, res)
return nil, err
}
log.Printf("websocket: connected to %v", urlStr)
return wsconn.New(c), nil
}

104
derp/wsconn/wsconn.go Normal file
View File

@@ -0,0 +1,104 @@
// 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 wsconn contains an adapter type that turns
// a websocket connection into a net.Conn.
package wsconn
import (
"context"
"net"
"sync"
"time"
"nhooyr.io/websocket"
)
// New returns a net.Conn wrapper around c,
// using c to send and receive binary messages with
// chunks of bytes with no defined framing, effectively
// discarding all WebSocket-level message framing.
func New(c *websocket.Conn) net.Conn {
return &websocketConn{c: c}
}
// websocketConn implements derp.Conn around a *websocket.Conn,
// treating a websocket.Conn as a byte stream, ignoring the WebSocket
// frame/message boundaries.
type websocketConn struct {
c *websocket.Conn
// rextra are extra bytes owned by the reader.
rextra []byte
mu sync.Mutex
rdeadline time.Time
cancelRead context.CancelFunc
}
func (wc *websocketConn) LocalAddr() net.Addr { return addr{} }
func (wc *websocketConn) RemoteAddr() net.Addr { return addr{} }
type addr struct{}
func (addr) Network() string { return "websocket" }
func (addr) String() string { return "websocket" }
func (wc *websocketConn) Read(p []byte) (n int, err error) {
// Drain any leftover from previously.
n = copy(p, wc.rextra)
if n > 0 {
wc.rextra = wc.rextra[n:]
return n, nil
}
var ctx context.Context
var cancel context.CancelFunc
wc.mu.Lock()
if dl := wc.rdeadline; !dl.IsZero() {
ctx, cancel = context.WithDeadline(context.Background(), wc.rdeadline)
} else {
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(30*24*time.Hour))
wc.rdeadline = time.Time{}
}
wc.cancelRead = cancel
wc.mu.Unlock()
defer cancel()
_, buf, err := wc.c.Read(ctx)
n = copy(p, buf)
wc.rextra = buf[n:]
return n, err
}
func (wc *websocketConn) Write(p []byte) (n int, err error) {
err = wc.c.Write(context.Background(), websocket.MessageBinary, p)
if err != nil {
return 0, err
}
return len(p), nil
}
func (wc *websocketConn) Close() error { return wc.c.Close(websocket.StatusNormalClosure, "close") }
func (wc *websocketConn) SetDeadline(t time.Time) error {
wc.SetReadDeadline(t)
wc.SetWriteDeadline(t)
return nil
}
func (wc *websocketConn) SetReadDeadline(t time.Time) error {
wc.mu.Lock()
defer wc.mu.Unlock()
if !t.IsZero() && (wc.rdeadline.IsZero() || t.Before(wc.rdeadline)) && wc.cancelRead != nil {
wc.cancelRead()
}
wc.rdeadline = t
return nil
}
func (wc *websocketConn) SetWriteDeadline(t time.Time) error {
return nil
}

View File

@@ -26,6 +26,7 @@ import (
"net"
"inet.af/netaddr"
"tailscale.com/tailcfg"
)
// Magic is the 6 byte header of all discovery messages.
@@ -106,12 +107,28 @@ func appendMsgHeader(b []byte, t MessageType, ver uint8, dataLen int) (all, data
}
type Ping struct {
// TxID is a random client-generated per-ping transaction ID.
TxID [12]byte
// NodeKey is allegedly the ping sender's wireguard public key.
// Old clients (~1.16.0 and earlier) don't send this field.
// It shouldn't be trusted by itself, but can be combined with
// netmap data to reduce the discokey:nodekey relation from 1:N to
// 1:1.
NodeKey tailcfg.NodeKey
}
func (m *Ping) AppendMarshal(b []byte) []byte {
ret, d := appendMsgHeader(b, TypePing, v0, 12)
copy(d, m.TxID[:])
dataLen := 12
hasKey := !m.NodeKey.IsZero()
if hasKey {
dataLen += len(m.NodeKey)
}
ret, d := appendMsgHeader(b, TypePing, v0, dataLen)
n := copy(d, m.TxID[:])
if hasKey {
copy(d[n:], m.NodeKey[:])
}
return ret
}
@@ -120,7 +137,10 @@ func parsePing(ver uint8, p []byte) (m *Ping, err error) {
return nil, errShort
}
m = new(Ping)
copy(m.TxID[:], p)
p = p[copy(m.TxID[:], p):]
if len(p) >= len(m.NodeKey) {
copy(m.NodeKey[:], p)
}
return m, nil
}

View File

@@ -11,6 +11,7 @@ import (
"testing"
"inet.af/netaddr"
"tailscale.com/tailcfg"
)
func TestMarshalAndParse(t *testing.T) {
@@ -26,6 +27,19 @@ func TestMarshalAndParse(t *testing.T) {
},
want: "01 00 01 02 03 04 05 06 07 08 09 0a 0b 0c",
},
{
name: "ping_with_nodekey_src",
m: &Ping{
TxID: [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
NodeKey: tailcfg.NodeKey{
1: 1,
2: 2,
30: 30,
31: 31,
},
},
want: "01 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 00 01 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1e 1f",
},
{
name: "pong",
m: &Pong{

View File

@@ -32,3 +32,7 @@ userspace-sidecar:
proxy:
@kubectl delete -f proxy.yaml --ignore-not-found --grace-period=0
@sed -e "s;{{KUBE_SECRET}};$(KUBE_SECRET);g" proxy.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | sed -e "s;{{IMAGE_TAG}};$(IMAGE_TAG);g" | sed -e "s;{{DEST_IP}};$(DEST_IP);g" | kubectl create -f-
subnet-router:
@kubectl delete -f subnet.yaml --ignore-not-found --grace-period=0
@sed -e "s;{{KUBE_SECRET}};$(KUBE_SECRET);g" subnet.yaml | sed -e "s;{{SA_NAME}};$(SA_NAME);g" | sed -e "s;{{IMAGE_TAG}};$(IMAGE_TAG);g" | sed -e "s;{{ROUTES}};$(ROUTES);g" | kubectl create -f-

View File

@@ -27,7 +27,7 @@ There are quite a few ways of running Tailscale inside a Kubernetes Cluster, som
Configure RBAC to allow the Tailscale pod to read/write the `tailscale` secret.
```bash
export SA_NAME=tailscale
export KUBE_SECRET=tailscale
export KUBE_SECRET=tailscale-auth
make rbac
```
@@ -107,4 +107,41 @@ Running a Tailscale proxy allows you to provide inbound connectivity to a Kubern
```bash
curl "http://$(tailscale ip -4 proxy)"
```
### Subnet Router
Running a Tailscale [subnet router](https://tailscale.com/kb/1019/subnets/) allows you to access
the entire Kubernetes cluster network (assuming NetworkPolicies allow) over Tailscale.
1. Identify the Pod/Service CIDRs that cover your Kubernetes cluster. These will vary depending on [which CNI](https://kubernetes.io/docs/concepts/cluster-administration/networking/) you are using and on the Cloud Provider you are using. Add these to the `ROUTES` variable as comma-separated values.
```bash
SERVICE_CIDR=10.20.0.0/16
POD_CIDR=10.42.0.0/15
export ROUTES=$SERVICE_CIDR,$POD_CIDR
```
1. Deploy the subnet-router pod.
```bash
make subnet-router
# If not using an auth key, authenticate by grabbing the Login URL here:
kubectl logs subnet-router
```
1. In the [Tailscale admin console](https://login.tailscale.com/admin/machines), ensure that the
routes for the subnet-router are enabled.
1. Make sure that any client you want to connect from has `--accept-routes` enabled.
1. Check if you can connect to a `ClusterIP` or a `PodIP` over Tailscale:
```bash
# Get the Service IP
INTERNAL_IP="$(kubectl get svc <SVC_NAME> -o=jsonpath='{.spec.clusterIP}')"
# or, the Pod IP
# INTERNAL_IP="$(kubectl get po <POD_NAME> -o=jsonpath='{.status.podIP}')"
INTERNAL_PORT=8080
curl http://$INTERNAL_IP:$INTERNAL_PORT
```

View File

@@ -53,7 +53,7 @@ tailscale --socket=/tmp/tailscaled.sock up ${UP_ARGS}
if [[ ! -z "${DEST_IP}" ]]; then
echo "Adding iptables rule for DNAT"
iptables -t nat -I PREROUTING -d "$(tailscale ip -4)" -j DNAT --to-destination "${DEST_IP}"
iptables -t nat -I PREROUTING -d "$(tailscale --socket=/tmp/tailscaled.sock ip -4)" -j DNAT --to-destination "${DEST_IP}"
fi
wait ${PID}
wait ${PID}

32
docs/k8s/subnet.yaml Normal file
View File

@@ -0,0 +1,32 @@
# 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.
apiVersion: v1
kind: Pod
metadata:
name: subnet-router
labels:
app: tailscale
spec:
serviceAccountName: "{{SA_NAME}}"
containers:
- name: tailscale
imagePullPolicy: Always
image: "{{IMAGE_TAG}}"
env:
# Store the state in a k8s secret
- name: KUBE_SECRET
value: "{{KUBE_SECRET}}"
- name: USERSPACE
value: "true"
- name: AUTH_KEY
valueFrom:
secretKeyRef:
name: tailscale-auth
key: AUTH_KEY
optional: true
- name: ROUTES
value: "{{ROUTES}}"
securityContext:
runAsUser: 1000
runAsGroup: 1000

19
go.mod
View File

@@ -11,7 +11,7 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.8.3
github.com/aws/aws-sdk-go-v2/service/ssm v1.12.0
github.com/coreos/go-iptables v0.6.0
github.com/creack/pty v1.1.16
github.com/creack/pty v1.1.17
github.com/dave/jennifer v1.4.1
github.com/frankban/quicktest v1.13.1
github.com/gliderlabs/ssh v0.3.3
@@ -42,21 +42,23 @@ require (
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/vishvananda/netlink v1.1.0
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/net v0.0.0-20210927181540-4e4d966f7476
golang.org/x/net v0.0.0-20211020060615-d418f374d309
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6
golang.org/x/sys v0.0.0-20211020174200-9d6173849985
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
golang.org/x/tools v0.1.7
golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62
golang.zx2c4.com/wireguard v0.0.0-20211020205005-82e0b734e5d2
golang.zx2c4.com/wireguard/windows v0.4.10
honnef.co/go/tools v0.2.1
inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1
inet.af/netstack v0.0.0-20210622165351-29b14ebc044e
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
inet.af/netstack v0.0.0-20211027215559-ec21145de76b
inet.af/peercred v0.0.0-20210318190834-4259e17bb763
inet.af/wf v0.0.0-20210516214145-a5343001b756
nhooyr.io/websocket v1.8.7
)
require (
@@ -190,9 +192,10 @@ require (
github.com/ultraware/funlen v0.0.3 // indirect
github.com/ultraware/whitespace v0.0.4 // indirect
github.com/uudashr/gocognit v1.0.1 // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect

75
go.sum
View File

@@ -103,8 +103,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.16 h1:vfetlOf3A+9YKggibynnX9mnFjuSVvkRj+IWpcTSLEQ=
github.com/creack/pty v1.1.16/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/daixiang0/gci v0.2.4/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4=
github.com/daixiang0/gci v0.2.7 h1:bosLNficubzJZICsVzxuyNc6oAbdz0zcqLG2G/RxtY4=
github.com/daixiang0/gci v0.2.7/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc=
@@ -133,6 +133,10 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
@@ -157,6 +161,13 @@ github.com/go-multierror/multierror v1.0.2/go.mod h1:U7SZR/D9jHgt2nkSj8XcbCWdmVM
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-ole/go-ole v1.2.6-0.20210915003542-8b1f7f90f6b1 h1:4dntyT+x6QTOSCIrgczbQ+ockAEha0cfxD5Wi0iCzjY=
github.com/go-ole/go-ole v1.2.6-0.20210915003542-8b1f7f90f6b1/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g=
@@ -181,6 +192,12 @@ github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/godbus/dbus/v5 v5.0.5 h1:9Eg0XUhQxtkV8ykTMKtMMYY72g4NgxtRq4jgh4Ih5YM=
github.com/godbus/dbus/v5 v5.0.5/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY=
@@ -199,11 +216,14 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0=
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
@@ -251,6 +271,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@@ -274,6 +295,8 @@ github.com/goreleaser/fileglob v0.3.1 h1:OTFDWqUUHjQazk2N5GdUqEbqT/grBnRARaAXsV0
github.com/goreleaser/fileglob v0.3.1/go.mod h1:kNcPrPzjCp+Ox3jmXLU5QEsjhqrtLBm6OnXAif8KRl8=
github.com/goreleaser/nfpm v1.10.3 h1:NzpWKKzSFr7JOn55XN0SskyFOjP6BkvRt3JujoX8fws=
github.com/goreleaser/nfpm v1.10.3/go.mod h1:EEC7YD5wi+ol0MiAshpgPANBOkjXDl7wqTLVk68OBsk=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
@@ -350,6 +373,8 @@ github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190 h1:iycCSDo8EKVueI9sfVBBJmtNn9DnXV/K1YWwEJO+uOs=
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@@ -362,6 +387,7 @@ github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
@@ -383,6 +409,8 @@ github.com/kunwardeep/paralleltest v1.0.2 h1:/jJRv0TiqPoEy/Y8dQxCFJhD56uS/pnvtat
github.com/kunwardeep/paralleltest v1.0.2/go.mod h1:ZPqNm1fVHPllh5LPVujzbVz1JN2GhLxSfY+oqUsvG30=
github.com/kyoh86/exportloopref v0.1.8 h1:5Ry/at+eFdkX9Vsdw3qU4YkvGtzuVfzT4X7S77LoN/M=
github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
@@ -453,7 +481,11 @@ github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EHf4=
github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k=
@@ -632,6 +664,10 @@ github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9r
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
@@ -645,6 +681,10 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
@@ -663,13 +703,15 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 h1:vSug/WNOi2+4jrKdivxayTN/zd8EA1UrStjpWvvo1jk=
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -756,8 +798,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210927181540-4e4d966f7476 h1:s5hu7bTnLKswvidgtqc4GwsW83m9LZu8UAqzmWOZtI4=
golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -794,6 +836,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -834,8 +877,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985 h1:LOlKVhfDyahgmqa97awczplwkjzNaELFg3zRIJ13RYo=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
@@ -850,6 +893,7 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -915,8 +959,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.0-20210905140043-2ef39d47540c/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62 h1:c39XZipaMOiSSqTCpqJmYgnzscTBGLFPgMmGvubmZ6E=
golang.zx2c4.com/wireguard v0.0.0-20211012062646-82d2aa87aa62/go.mod h1:id8Oh3eCCmpj9uVGWVjsUAl6UPX5ysMLzu6QxJU2UOU=
golang.zx2c4.com/wireguard v0.0.0-20211020205005-82e0b734e5d2 h1:mHJssZsxXvTmTP+sxkGZCItVGhaOWo0UnFqrM2lMqOk=
golang.zx2c4.com/wireguard v0.0.0-20211020205005-82e0b734e5d2/go.mod h1:RTjaYEQboNk7+2qfPGBotaMEh/5HIvmPZ6DIe10lTqI=
golang.zx2c4.com/wireguard/windows v0.4.10 h1:HmjzJnb+G4NCdX+sfjsQlsxGPuYaThxRbZUZFLyR0/s=
golang.zx2c4.com/wireguard/windows v0.4.10/go.mod h1:v7w/8FC48tTBm1IzScDVPEEb0/GjLta+T0ybpP9UWRg=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -950,6 +994,7 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -989,10 +1034,10 @@ honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
inet.af/netaddr v0.0.0-20210515010201-ad03edc7c841/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1 h1:mxmfTV6kjXTlFqqFETnG9FQZzNFc6AKunZVAgQ3b7WA=
inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netstack v0.0.0-20210622165351-29b14ebc044e h1:z11NK94NQcI3DA+a3pUC/2dRYTph1kPX6B0FnCaMDzk=
inet.af/netstack v0.0.0-20210622165351-29b14ebc044e/go.mod h1:fG3G1dekmK8oDX3iVzt8c0zICLMLSN8SjdxbXVt0WjU=
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw=
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8=
inet.af/netstack v0.0.0-20211027215559-ec21145de76b h1:5aGmvztDCPhMYl/pByQYY0eaqbDzqTtX28180pffKqw=
inet.af/netstack v0.0.0-20211027215559-ec21145de76b/go.mod h1:fG3G1dekmK8oDX3iVzt8c0zICLMLSN8SjdxbXVt0WjU=
inet.af/peercred v0.0.0-20210318190834-4259e17bb763 h1:gPSJmmVzmdy4kHhlCMx912GdiUz3k/RzJGg0ADqy1dg=
inet.af/peercred v0.0.0-20210318190834-4259e17bb763/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
inet.af/wf v0.0.0-20210516214145-a5343001b756 h1:muIT3C1rH3/xpvIH8blKkMvhctV7F+OtZqs7kcwHDBQ=
@@ -1006,6 +1051,8 @@ mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphD
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY=
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc=
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237 h1:iAEkCBPbRaflBgZ7o9gjVUuWuvWeV4sytFWg9o+Pj2k=
software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237/go.mod h1:/xvNRWUqm0+/ZMiF4EX00vrSCMsE4/NHb+Pt3freEeQ=

View File

@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"os"
"runtime"
"sort"
"sync"
"sync/atomic"
@@ -347,6 +348,12 @@ var (
receiveFuncs = []*ReceiveFuncStats{&ReceiveIPv4, &ReceiveIPv6, &ReceiveDERP}
)
func init() {
if runtime.GOOS == "js" {
receiveFuncs = receiveFuncs[2:] // ignore IPv4 and IPv6
}
}
// ReceiveFuncStats tracks the calls made to a wireguard-go receive func.
type ReceiveFuncStats struct {
// name is the name of the receive func.

View File

@@ -28,7 +28,7 @@ func New() *tailcfg.Hostinfo {
IPNVersion: version.Long,
Hostname: hostname,
OS: version.OS(),
OSVersion: getOSVersion(),
OSVersion: GetOSVersion(),
Package: packageType(),
GoArch: runtime.GOARCH,
DeviceModel: deviceModel(),
@@ -37,7 +37,8 @@ func New() *tailcfg.Hostinfo {
var osVersion func() string // non-nil on some platforms
func getOSVersion() string {
// GetOSVersion returns the OSVersion of current host if available.
func GetOSVersion() string {
if s, _ := osVersionAtomic.Load().(string); s != "" {
return s
}
@@ -82,6 +83,7 @@ const (
AzureAppService = EnvType("az")
AWSFargate = EnvType("fg")
FlyDotIo = EnvType("fly")
Kubernetes = EnvType("k8s")
)
var envType atomic.Value // of EnvType
@@ -136,6 +138,9 @@ func getEnvType() EnvType {
if inFlyDotIo() {
return FlyDotIo
}
if inKubernetes() {
return Kubernetes
}
return ""
}
@@ -212,3 +217,10 @@ func inFlyDotIo() bool {
}
return false
}
func inKubernetes() bool {
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" {
return true
}
return false
}

View File

@@ -26,6 +26,7 @@ import (
"time"
"github.com/go-multierror/multierror"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/control/controlclient"
@@ -47,7 +48,6 @@ import (
"tailscale.com/types/netmap"
"tailscale.com/types/persist"
"tailscale.com/types/preftype"
"tailscale.com/types/wgkey"
"tailscale.com/util/deephash"
"tailscale.com/util/dnsname"
"tailscale.com/util/osshare"
@@ -294,8 +294,8 @@ func (b *LocalBackend) Prefs() *ipn.Prefs {
p := b.prefs.Clone()
if p != nil && p.Persist != nil {
p.Persist.LegacyFrontendPrivateMachineKey = key.MachinePrivate{}
p.Persist.PrivateNodeKey = wgkey.Private{}
p.Persist.OldPrivateNodeKey = wgkey.Private{}
p.Persist.PrivateNodeKey = key.NodePrivate{}
p.Persist.OldPrivateNodeKey = key.NodePrivate{}
}
return p
}
@@ -389,7 +389,7 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
tailscaleIPs = append(tailscaleIPs, addr.IP())
}
}
sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{
sb.AddPeer(key.NodePublicFromRaw32(mem.B(p.Key[:])), &ipnstate.PeerStatus{
InNetworkMap: true,
ID: p.StableID,
UserID: p.User,
@@ -448,12 +448,14 @@ func (b *LocalBackend) SetDecompressor(fn func() (controlclient.Decompressor, er
// Among other things, this is where we update the netmap, packet filters, DNS and DERP maps.
func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// The following do not depend on any data for which we need to lock b.
if st.Err != "" {
if st.Err != nil {
// TODO(crawshaw): display in the UI.
if st.Err == "EOF" {
if errors.Is(st.Err, io.EOF) {
b.logf("[v1] Received error: EOF")
} else {
b.logf("Received error: %v", st.Err)
e := st.Err.Error()
b.send(ipn.Notify{ErrMessage: &e})
}
return
}
@@ -1022,14 +1024,29 @@ var removeFromDefaultRoute = []netaddr.IPPrefix{
// Given that "internal" routes don't leave the device, we choose to
// trust them more, allowing access to them when an Exit Node is enabled.
func internalAndExternalInterfaces() (internal, external []netaddr.IPPrefix, err error) {
if err := interfaces.ForeachInterfaceAddress(func(iface interfaces.Interface, pfx netaddr.IPPrefix) {
il, err := interfaces.GetList()
if err != nil {
return nil, nil, err
}
return internalAndExternalInterfacesFrom(il, runtime.GOOS)
}
func internalAndExternalInterfacesFrom(il interfaces.List, goos string) (internal, external []netaddr.IPPrefix, err error) {
// We use an IPSetBuilder here to canonicalize the prefixes
// and to remove any duplicate entries.
var internalBuilder, externalBuilder netaddr.IPSetBuilder
if err := il.ForeachInterfaceAddress(func(iface interfaces.Interface, pfx netaddr.IPPrefix) {
if tsaddr.IsTailscaleIP(pfx.IP()) {
return
}
if pfx.IsSingleIP() {
return
}
if runtime.GOOS == "windows" {
if iface.IsLoopback() {
internalBuilder.AddPrefix(pfx)
return
}
if goos == "windows" {
// Windows Hyper-V prefixes all MAC addresses with 00:15:5d.
// https://docs.microsoft.com/en-us/troubleshoot/windows-server/virtualization/default-limit-256-dynamic-mac-addresses
//
@@ -1040,16 +1057,24 @@ func internalAndExternalInterfaces() (internal, external []netaddr.IPPrefix, err
// configuration breaks WSL2 DNS without this.
mac := iface.Interface.HardwareAddr
if len(mac) == 6 && mac[0] == 0x00 && mac[1] == 0x15 && mac[2] == 0x5d {
internal = append(internal, pfx)
internalBuilder.AddPrefix(pfx)
return
}
}
external = append(external, pfx)
externalBuilder.AddPrefix(pfx)
}); err != nil {
return nil, nil, err
}
iSet, err := internalBuilder.IPSet()
if err != nil {
return nil, nil, err
}
eSet, err := externalBuilder.IPSet()
if err != nil {
return nil, nil, err
}
return internal, external, nil
return iSet.Prefixes(), eSet.Prefixes(), nil
}
func interfaceRoutes() (ips *netaddr.IPSet, hostIPs []netaddr.IP, err error) {
@@ -1483,7 +1508,15 @@ func (b *LocalBackend) StartLoginInteractive() {
if url != "" {
b.popBrowserAuthNow()
} else {
cc.Login(nil, controlclient.LoginInteractive)
flags := controlclient.LoginInteractive
if runtime.GOOS == "js" {
// The js/wasm client has no state storage so for now
// treat all interactive logins as ephemeral.
// TODO(bradfitz): if we start using browser LocalStorage
// or something, then rethink this.
flags |= controlclient.LoginEphemeral
}
cc.Login(nil, flags)
}
}
@@ -2659,7 +2692,7 @@ func (b *LocalBackend) TestOnlyPublicKeys() (machineKey key.MachinePublic, nodeK
mk := machinePrivKey.Public()
nk := prefs.Persist.PrivateNodeKey.Public()
return mk, tailcfg.NodeKey(nk)
return mk, tailcfg.NodeKeyFromNodePublic(nk)
}
func (b *LocalBackend) WaitingFiles() ([]apitype.WaitingFile, error) {
@@ -2749,7 +2782,7 @@ func (b *LocalBackend) SetDNS(ctx context.Context, name, value string) error {
b.mu.Lock()
cc := b.cc
if prefs := b.prefs; prefs != nil {
req.NodeKey = tailcfg.NodeKey(prefs.Persist.PrivateNodeKey.Public())
req.NodeKey = tailcfg.NodeKeyFromNodePublic(prefs.Persist.PrivateNodeKey.Public())
}
b.mu.Unlock()
if cc == nil {

View File

@@ -6,6 +6,7 @@ package ipnlocal
import (
"fmt"
"net"
"net/http"
"reflect"
"testing"
@@ -494,3 +495,103 @@ func TestFileTargets(t *testing.T) {
}
// (other cases handled by TestPeerAPIBase above)
}
func TestInternalAndExternalInterfaces(t *testing.T) {
type interfacePrefix struct {
i interfaces.Interface
pfx netaddr.IPPrefix
}
masked := func(ips ...interfacePrefix) (pfxs []netaddr.IPPrefix) {
for _, ip := range ips {
pfxs = append(pfxs, ip.pfx.Masked())
}
return pfxs
}
iList := func(ips ...interfacePrefix) (il interfaces.List) {
for _, ip := range ips {
il = append(il, ip.i)
}
return il
}
newInterface := func(name, pfx string, wsl2, loopback bool) interfacePrefix {
ippfx := netaddr.MustParseIPPrefix(pfx)
ip := interfaces.Interface{
Interface: &net.Interface{},
AltAddrs: []net.Addr{
ippfx.IPNet(),
},
}
if loopback {
ip.Flags = net.FlagLoopback
}
if wsl2 {
ip.HardwareAddr = []byte{0x00, 0x15, 0x5d, 0x00, 0x00, 0x00}
}
return interfacePrefix{i: ip, pfx: ippfx}
}
var (
en0 = newInterface("en0", "10.20.2.5/16", false, false)
en1 = newInterface("en1", "192.168.1.237/24", false, false)
wsl = newInterface("wsl", "192.168.5.34/24", true, false)
loopback = newInterface("lo0", "127.0.0.1/8", false, true)
)
tests := []struct {
name string
goos string
il interfaces.List
wantInt []netaddr.IPPrefix
wantExt []netaddr.IPPrefix
}{
{
name: "single-interface",
goos: "linux",
il: iList(
en0,
loopback,
),
wantInt: masked(loopback),
wantExt: masked(en0),
},
{
name: "multiple-interfaces",
goos: "linux",
il: iList(
en0,
en1,
wsl,
loopback,
),
wantInt: masked(loopback),
wantExt: masked(en0, en1, wsl),
},
{
name: "wsl2",
goos: "windows",
il: iList(
en0,
en1,
wsl,
loopback,
),
wantInt: masked(loopback, wsl),
wantExt: masked(en0, en1),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
gotInt, gotExt, err := internalAndExternalInterfacesFrom(tc.il, tc.goos)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(gotInt, tc.wantInt) {
t.Errorf("unexpected internal prefixes\ngot %v\nwant %v", gotInt, tc.wantInt)
}
if !reflect.DeepEqual(gotExt, tc.wantExt) {
t.Errorf("unexpected external prefixes\ngot %v\nwant %v", gotExt, tc.wantExt)
}
})
}
}

View File

@@ -90,7 +90,7 @@ func TestLocalLogLines(t *testing.T) {
TxBytes: 10,
RxBytes: 10,
LastHandshake: time.Now(),
NodeKey: tailcfg.NodeKey(key.NewPrivate()),
NodeKey: tailcfg.NodeKeyFromNodePublic(key.NewNode().Public()),
}},
})
lb.mu.Unlock()
@@ -105,7 +105,7 @@ func TestLocalLogLines(t *testing.T) {
TxBytes: 11,
RxBytes: 12,
LastHandshake: time.Now(),
NodeKey: tailcfg.NodeKey(key.NewPrivate()),
NodeKey: tailcfg.NodeKeyFromNodePublic(key.NewNode().Public()),
}},
})
lb.mu.Unlock()

View File

@@ -21,7 +21,6 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/types/persist"
"tailscale.com/types/wgkey"
"tailscale.com/wgengine"
)
@@ -122,7 +121,7 @@ func (cc *mockControl) populateKeys() (newKeys bool) {
if cc.persist.PrivateNodeKey.IsZero() {
cc.logf("Generating a new nodekey.")
cc.persist.OldPrivateNodeKey = cc.persist.PrivateNodeKey
cc.persist.PrivateNodeKey, _ = wgkey.NewPrivate()
cc.persist.PrivateNodeKey = key.NewNode()
newKeys = true
}
@@ -137,9 +136,7 @@ func (cc *mockControl) send(err error, url string, loginFinished bool, nm *netma
URL: url,
NetMap: nm,
Persist: &cc.persist,
}
if err != nil {
s.Err = err.Error()
Err: err,
}
if loginFinished {
s.LoginFinished = &empty.Message{}

View File

@@ -20,7 +20,6 @@ import (
"os/exec"
"os/signal"
"os/user"
"path/filepath"
"runtime"
"strconv"
"strings"
@@ -90,9 +89,9 @@ type Options struct {
DebugMux *http.ServeMux
}
// server is an IPN backend and its set of 0 or more active connections
// talking to an IPN backend.
type server struct {
// Server is an IPN backend and its set of 0 or more active localhost
// TCP or unix socket connections talking to that backend.
type Server struct {
b *ipnlocal.LocalBackend
logf logger.Logf
backendLogID string
@@ -102,6 +101,7 @@ type server struct {
// connection (such as on Windows by default). Even if this
// is true, the ForceDaemon pref can override this.
resetOnZero bool
opts Options
bsMu sync.Mutex // lock order: bsMu, then mu
bs *ipn.BackendServer
@@ -135,7 +135,7 @@ type connIdentity struct {
// (pid, userid, user). If it's not Windows (for now), it returns a nil error
// and a ConnIdentity with NotWindows set true. It's only an error if we expected
// to be able to map it and couldn't.
func (s *server) getConnIdentity(c net.Conn) (ci connIdentity, err error) {
func (s *Server) getConnIdentity(c net.Conn) (ci connIdentity, err error) {
ci = connIdentity{Conn: c}
if runtime.GOOS != "windows" { // for now; TODO: expand to other OSes
ci.NotWindows = true
@@ -172,7 +172,7 @@ func (s *server) getConnIdentity(c net.Conn) (ci connIdentity, err error) {
return ci, fmt.Errorf("failed to map connection's pid to a user%s: %w", hint, err)
}
ci.UserID = uid
u, err := s.lookupUserFromID(uid)
u, err := lookupUserFromID(s.logf, uid)
if err != nil {
return ci, fmt.Errorf("failed to look up user from userid: %w", err)
}
@@ -180,10 +180,10 @@ func (s *server) getConnIdentity(c net.Conn) (ci connIdentity, err error) {
return ci, nil
}
func (s *server) lookupUserFromID(uid string) (*user.User, error) {
func lookupUserFromID(logf logger.Logf, uid string) (*user.User, error) {
u, err := user.LookupId(uid)
if err != nil && runtime.GOOS == "windows" && errors.Is(err, syscall.Errno(0x534)) {
s.logf("[warning] issue 869: os/user.LookupId failed; ignoring")
logf("[warning] issue 869: os/user.LookupId failed; ignoring")
// Work around https://github.com/tailscale/tailscale/issues/869 for
// now. We don't strictly need the username. It's just a nice-to-have.
// So make up a *user.User if their machine is broken in this way.
@@ -199,7 +199,7 @@ func (s *server) lookupUserFromID(uid string) (*user.User, error) {
// blockWhileInUse blocks while until either a Read from conn fails
// (i.e. it's closed) or until the server is able to accept ci as a
// user.
func (s *server) blockWhileInUse(conn io.Reader, ci connIdentity) {
func (s *Server) blockWhileInUse(conn io.Reader, ci connIdentity) {
s.logf("blocking client while server in use; connIdentity=%v", ci)
connDone := make(chan struct{})
go func() {
@@ -241,7 +241,7 @@ func bufferHasHTTPRequest(br *bufio.Reader) bool {
mem.Contains(mem.B(peek), mem.S(" HTTP/"))
}
func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
func (s *Server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
// First see if it's an HTTP request.
br := bufio.NewReader(c)
c.SetReadDeadline(time.Now().Add(time.Second))
@@ -391,7 +391,7 @@ func (e inUseOtherUserError) Unwrap() error { return e.error }
// The returned error, when non-nil, will be of type inUseOtherUserError.
//
// s.mu must be held.
func (s *server) checkConnIdentityLocked(ci connIdentity) error {
func (s *Server) checkConnIdentityLocked(ci connIdentity) error {
// If clients are already connected, verify they're the same user.
// This mostly matters on Windows at the moment.
if len(s.allClients) > 0 {
@@ -413,7 +413,7 @@ func (s *server) checkConnIdentityLocked(ci connIdentity) error {
// the Tailscale local daemon API.
//
// s.mu must not be held.
func (s *server) localAPIPermissions(ci connIdentity) (read, write bool) {
func (s *Server) localAPIPermissions(ci connIdentity) (read, write bool) {
if runtime.GOOS == "windows" {
s.mu.Lock()
defer s.mu.Unlock()
@@ -430,7 +430,7 @@ func (s *server) localAPIPermissions(ci connIdentity) (read, write bool) {
// registerDisconnectSub adds ch as a subscribe to connection disconnect
// events. If add is false, the subscriber is removed.
func (s *server) registerDisconnectSub(ch chan<- struct{}, add bool) {
func (s *Server) registerDisconnectSub(ch chan<- struct{}, add bool) {
s.mu.Lock()
defer s.mu.Unlock()
if add {
@@ -448,7 +448,7 @@ func (s *server) registerDisconnectSub(ch chan<- struct{}, add bool) {
//
// If the returned error is of type inUseOtherUserError then the
// returned connIdentity is also valid.
func (s *server) addConn(c net.Conn, isHTTP bool) (ci connIdentity, err error) {
func (s *Server) addConn(c net.Conn, isHTTP bool) (ci connIdentity, err error) {
ci, err = s.getConnIdentity(c)
if err != nil {
return
@@ -492,7 +492,7 @@ func (s *server) addConn(c net.Conn, isHTTP bool) (ci connIdentity, err error) {
return ci, nil
}
func (s *server) removeAndCloseConn(c net.Conn) {
func (s *Server) removeAndCloseConn(c net.Conn) {
s.mu.Lock()
delete(s.clients, c)
delete(s.allClients, c)
@@ -516,7 +516,7 @@ func (s *server) removeAndCloseConn(c net.Conn) {
c.Close()
}
func (s *server) stopAll() {
func (s *Server) stopAll() {
s.mu.Lock()
defer s.mu.Unlock()
for c := range s.clients {
@@ -529,7 +529,7 @@ func (s *server) stopAll() {
// setServerModeUserLocked is called when we're in server mode but our s.serverModeUser is nil.
//
// s.mu must be held
func (s *server) setServerModeUserLocked() {
func (s *Server) setServerModeUserLocked() {
var ci connIdentity
var ok bool
for _, ci = range s.allClients {
@@ -553,7 +553,7 @@ func (s *server) setServerModeUserLocked() {
var jsonEscapedZero = []byte(`\u0000`)
func (s *server) writeToClients(n ipn.Notify) {
func (s *Server) writeToClients(n ipn.Notify) {
inServerMode := s.b.InServerMode()
s.mu.Lock()
@@ -602,7 +602,7 @@ func tryWindowsAppDataMigration(logf logger.Logf, path string) string {
// what they are doing.
return path
}
oldFile := filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "server-state.conf")
oldFile := paths.LegacyStateFilePath()
return paths.TryConfigFileMigration(logf, oldFile, path)
}
@@ -618,11 +618,8 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
return fmt.Errorf("safesocket.Listen: %v", err)
}
server := &server{
backendLogID: logid,
logf: logf,
resetOnZero: !opts.SurviveDisconnects,
}
var serverMu sync.Mutex
var serverOrNil *Server
// When the context is closed or when we return, whichever is first, close our listener
// and all open connections.
@@ -631,11 +628,16 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
case <-ctx.Done():
case <-runDone:
}
server.stopAll()
serverMu.Lock()
if s := serverOrNil; s != nil {
s.stopAll()
}
serverMu.Unlock()
listen.Close()
}()
logf("Listening on %v", listen.Addr())
var serverModeUser *user.User
var store ipn.StateStore
if opts.StatePath != "" {
const kubePrefix = "kube:"
@@ -670,12 +672,12 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
key := string(autoStartKey)
if strings.HasPrefix(key, "user-") {
uid := strings.TrimPrefix(key, "user-")
u, err := server.lookupUserFromID(uid)
u, err := lookupUserFromID(logf, uid)
if err != nil {
logf("ipnserver: found server mode auto-start key %q; failed to load user: %v", key, err)
} else {
logf("ipnserver: found server mode auto-start key %q (user %s)", key, u.Username)
server.serverModeUser = u
serverModeUser = u
}
opts.AutostartStateKey = ipn.StateKey(key)
}
@@ -717,12 +719,31 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
return err
}
}
if unservedConn != nil {
listen = &listenerWithReadyConn{
Listener: listen,
c: unservedConn,
}
}
server, err := New(logf, logid, store, eng, serverModeUser, opts)
if err != nil {
return err
}
serverMu.Lock()
serverOrNil = server
serverMu.Unlock()
return server.Serve(ctx, listen)
}
// New returns a new Server.
//
// The opts.StatePath option is ignored; it's only used by Run.
func New(logf logger.Logf, logid string, store ipn.StateStore, eng wgengine.Engine, serverModeUser *user.User, opts Options) (*Server, error) {
b, err := ipnlocal.NewLocalBackend(logf, logid, store, eng)
if err != nil {
return fmt.Errorf("NewLocalBackend: %v", err)
return nil, fmt.Errorf("NewLocalBackend: %v", err)
}
defer b.Shutdown()
b.SetDecompressor(func() (controlclient.Decompressor, error) {
return smallzstd.NewDecoder(nil)
})
@@ -733,38 +754,51 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
})
}
server.b = b
server := &Server{
b: b,
backendLogID: logid,
logf: logf,
resetOnZero: !opts.SurviveDisconnects,
serverModeUser: serverModeUser,
opts: opts,
}
server.bs = ipn.NewBackendServer(logf, b, server.writeToClients)
return server, nil
}
if opts.AutostartStateKey != "" {
server.bs.GotCommand(context.TODO(), &ipn.Command{
// Serve accepts connections from ln forever.
//
// The context is only used to suppress errors
func (s *Server) Serve(ctx context.Context, ln net.Listener) error {
defer s.b.Shutdown()
if s.opts.AutostartStateKey != "" {
s.bs.GotCommand(ctx, &ipn.Command{
Version: version.Long,
Start: &ipn.StartArgs{
Opts: ipn.Options{StateKey: opts.AutostartStateKey},
Opts: ipn.Options{StateKey: s.opts.AutostartStateKey},
},
})
}
systemd.Ready()
for i := 1; ctx.Err() == nil; i++ {
var c net.Conn
var err error
if unservedConn != nil {
c = unservedConn
unservedConn = nil
} else {
c, err = listen.Accept()
bo := backoff.NewBackoff("ipnserver", s.logf, 30*time.Second)
var connNum int
for {
if ctx.Err() != nil {
return ctx.Err()
}
c, err := ln.Accept()
if err != nil {
if ctx.Err() == nil {
logf("ipnserver: Accept: %v", err)
bo.BackOff(ctx, err)
if ctx.Err() != nil {
return ctx.Err()
}
s.logf("ipnserver: Accept: %v", err)
bo.BackOff(ctx, err)
continue
}
go server.serveConn(ctx, c, logger.WithPrefix(logf, fmt.Sprintf("ipnserver: conn%d: ", i)))
connNum++
go s.serveConn(ctx, c, logger.WithPrefix(s.logf, fmt.Sprintf("ipnserver: conn%d: ", connNum)))
}
return ctx.Err()
}
// BabysitProc runs the current executable as a child process with the
@@ -954,7 +988,7 @@ func (a dummyAddr) String() string { return string(a) }
// HTTP. So we Read from its bufio.Reader. On Close, we we tell the
// server it's closed, so the server can account the who's connected.
type protoSwitchConn struct {
s *server
s *Server
net.Conn
br *bufio.Reader
closeOnce sync.Once
@@ -966,7 +1000,7 @@ func (psc *protoSwitchConn) Close() error {
return nil
}
func (s *server) localhostHandler(ci connIdentity) http.Handler {
func (s *Server) localhostHandler(ci connIdentity) http.Handler {
lah := localapi.NewHandler(s.b, s.logf, s.backendLogID)
lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci)
@@ -1020,3 +1054,23 @@ func marshalNotify(n ipn.Notify, logf logger.Logf) (b []byte, ok bool) {
}
return b, true
}
// listenerWithReadyConn is a net.Listener wrapper that has
// one net.Conn ready to be accepted already.
type listenerWithReadyConn struct {
net.Listener
mu sync.Mutex
c net.Conn // if non-nil, ready to be Accepted
}
func (ln *listenerWithReadyConn) Accept() (net.Conn, error) {
ln.mu.Lock()
c := ln.c
ln.c = nil
ln.mu.Unlock()
if c != nil {
return c, nil
}
return ln.Listener.Accept()
}

View File

@@ -8,7 +8,6 @@
package ipnstate
import (
"bytes"
"fmt"
"html"
"io"
@@ -57,16 +56,16 @@ type Status struct {
// trailing periods, and without any "_acme-challenge." prefix.
CertDomains []string
Peer map[key.Public]*PeerStatus
Peer map[key.NodePublic]*PeerStatus
User map[tailcfg.UserID]tailcfg.UserProfile
}
func (s *Status) Peers() []key.Public {
kk := make([]key.Public, 0, len(s.Peer))
func (s *Status) Peers() []key.NodePublic {
kk := make([]key.NodePublic, 0, len(s.Peer))
for k := range s.Peer {
kk = append(kk, k)
}
sort.Slice(kk, func(i, j int) bool { return bytes.Compare(kk[i][:], kk[j][:]) < 0 })
sort.Slice(kk, func(i, j int) bool { return kk[i].Less(kk[j]) })
return kk
}
@@ -78,7 +77,7 @@ type PeerStatusLite struct {
type PeerStatus struct {
ID tailcfg.StableNodeID
PublicKey key.Public
PublicKey key.NodePublic
HostName string // HostInfo's Hostname (not a DNS name or necessarily unique)
DNSName string
OS string // HostInfo.OS
@@ -201,7 +200,7 @@ func (sb *StatusBuilder) AddTailscaleIP(ip netaddr.IP) {
// AddPeer adds a peer node to the status.
//
// Its PeerStatus is mixed with any previous status already added.
func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
func (sb *StatusBuilder) AddPeer(peer key.NodePublic, st *PeerStatus) {
if st == nil {
panic("nil PeerStatus")
}
@@ -214,7 +213,7 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
}
if sb.st.Peer == nil {
sb.st.Peer = make(map[key.Public]*PeerStatus)
sb.st.Peer = make(map[key.NodePublic]*PeerStatus)
}
e, ok := sb.st.Peer[peer]
if !ok {
@@ -478,5 +477,6 @@ func sortKey(ps *PeerStatus) string {
if len(ps.TailscaleIPs) > 0 {
return ps.TailscaleIPs[0].String()
}
return string(ps.PublicKey[:])
raw := ps.PublicKey.Raw32()
return string(raw[:])
}

View File

@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by the following command; DO NOT EDIT.
// tailscale.com/cmd/cloner -type Prefs
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
//go:generate go run tailscale.com/cmd/cloner -type=Prefs -output=prefs_clone.go
package ipn

View File

@@ -15,12 +15,13 @@ import (
"testing"
"time"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/types/persist"
"tailscale.com/types/preftype"
"tailscale.com/types/wgkey"
)
func fieldsOf(t reflect.Type) (fields []string) {
@@ -404,7 +405,7 @@ func TestPrefsPretty(t *testing.T) {
{
Prefs{
Persist: &persist.Persist{
PrivateNodeKey: wgkey.Private{1: 1},
PrivateNodeKey: key.NodePrivateFromRaw32(mem.B([]byte{1: 1, 31: 0})),
},
},
"linux",

View File

@@ -28,7 +28,7 @@ var ErrStateNotExist = errors.New("no state with given ID")
const (
// MachineKeyStateKey is the key under which we store the machine key,
// in its wgkey.Private.MarshalText representation.
// in its key.NodePrivate.MarshalText representation.
MachineKeyStateKey = StateKey("_machinekey")
// GlobalDaemonStateKey is the ipn.StateKey that tailscaled

View File

@@ -17,8 +17,11 @@ import (
var stderrFD = 2 // a variable for testing
const defaultMaxFileSize = 50 << 20
type Options struct {
ReplaceStderr bool // dup over fd 2 so everything written to stderr comes here
MaxFileSize int
}
// A Filch uses two alternating files as a simplistic ring buffer.
@@ -30,6 +33,10 @@ type Filch struct {
alt *os.File
altscan *bufio.Scanner
recovered int64
maxFileSize int64
writeCounter int
// buf is an initial buffer for altscan.
// As of August 2021, 99.96% of all log lines
// are below 4096 bytes in length.
@@ -38,7 +45,7 @@ type Filch struct {
// so that the whole struct takes 4096 bytes
// (less on 32 bit platforms).
// This reduces allocation waste.
buf [4096 - 48]byte
buf [4096 - 64]byte
}
// TryReadline implements the logtail.Buffer interface.
@@ -91,6 +98,22 @@ func (f *Filch) scan() ([]byte, error) {
func (f *Filch) Write(b []byte) (int, error) {
f.mu.Lock()
defer f.mu.Unlock()
if f.writeCounter == 100 {
// Check the file size every 100 writes.
f.writeCounter = 0
fi, err := f.cur.Stat()
if err != nil {
return 0, err
}
if fi.Size() >= f.maxFileSize {
// This most likely means we are not draining.
// To limit the amount of space we use, throw away the old logs.
if err := moveContents(f.alt, f.cur); err != nil {
return 0, err
}
}
}
f.writeCounter++
if len(b) == 0 || b[len(b)-1] != '\n' {
bnl := make([]byte, len(b)+1)
@@ -159,8 +182,13 @@ func New(filePrefix string, opts Options) (f *Filch, err error) {
return nil, err
}
mfs := defaultMaxFileSize
if opts.MaxFileSize > 0 {
mfs = opts.MaxFileSize
}
f = &Filch{
OrigStderr: os.Stderr, // temporary, for past logs recovery
OrigStderr: os.Stderr, // temporary, for past logs recovery
maxFileSize: int64(mfs),
}
// Neither, either, or both files may exist and contain logs from
@@ -234,6 +262,9 @@ func moveContents(dst, src *os.File) (err error) {
if _, err := src.Seek(0, io.SeekStart); err != nil {
return err
}
if _, err := dst.Seek(0, io.SeekStart); err != nil {
return err
}
if _, err := io.Copy(dst, src); err != nil {
return err
}

View File

@@ -57,6 +57,39 @@ func (f *filchTest) close(t *testing.T) {
}
}
func TestDropOldLogs(t *testing.T) {
const line1 = "123456789" // 10 bytes (9+newline)
tests := []struct {
write, read int
}{
{10, 10},
{100, 100},
{200, 200},
{250, 150},
{500, 200},
}
for _, tc := range tests {
t.Run(fmt.Sprintf("w%d-r%d", tc.write, tc.read), func(t *testing.T) {
filePrefix := t.TempDir()
f := newFilchTest(t, filePrefix, Options{ReplaceStderr: false, MaxFileSize: 1000})
defer f.close(t)
// Make filch rotate the logs 3 times
for i := 0; i < tc.write; i++ {
f.write(t, line1)
}
// We should only be able to read the last 150 lines
for i := 0; i < tc.read; i++ {
f.read(t, line1)
if t.Failed() {
t.Logf("could only read %d lines", i)
break
}
}
f.readEOF(t)
})
}
}
func TestQueue(t *testing.T) {
filePrefix := t.TempDir()
f := newFilchTest(t, filePrefix, Options{ReplaceStderr: false})

View File

@@ -14,6 +14,8 @@ import (
"strings"
"testing"
"time"
"tailscale.com/tstest"
)
func TestFastShutdown(t *testing.T) {
@@ -213,11 +215,11 @@ var sink []byte
func TestLoggerEncodeTextAllocs(t *testing.T) {
lg := &Logger{timeNow: time.Now}
inBuf := []byte("some text to encode")
n := testing.AllocsPerRun(1000, func() {
err := tstest.MinAllocsPerRun(t, 1, func() {
sink = lg.encodeText(inBuf, false)
})
if int(n) != 1 {
t.Logf("allocs = %d; want 1", int(n))
if err != nil {
t.Fatal(err)
}
}
@@ -298,15 +300,14 @@ func TestPublicIDUnmarshalText(t *testing.T) {
if id.String() != hexStr {
t.Errorf("String = %q; want %q", id.String(), hexStr)
}
n := int(testing.AllocsPerRun(1000, func() {
err := tstest.MinAllocsPerRun(t, 0, func() {
var id PublicID
if err := id.UnmarshalText(x); err != nil {
t.Fatal(err)
}
}))
if n != 0 {
t.Errorf("allocs = %v; want 0", n)
})
if err != nil {
t.Fatal(err)
}
}

View File

@@ -8,6 +8,8 @@ import (
"os"
"runtime"
"testing"
"tailscale.com/tstest"
)
func TestCurrentFileDescriptors(t *testing.T) {
@@ -19,11 +21,11 @@ func TestCurrentFileDescriptors(t *testing.T) {
t.Fatalf("got %v; want >= 3", n)
}
allocs := int(testing.AllocsPerRun(100, func() {
err := tstest.MinAllocsPerRun(t, 0, func() {
n = CurrentFDs()
}))
if allocs != 0 {
t.Errorf("allocs = %v; want 0", allocs)
})
if err != nil {
t.Fatal(err)
}
// Open some FDs.

View File

@@ -18,6 +18,7 @@ import (
"strings"
"inet.af/netaddr"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname"
)
@@ -76,28 +77,16 @@ func readResolv(r io.Reader) (config OSConfig, err error) {
return config, nil
}
func (m directManager) readResolvFile(path string) (OSConfig, error) {
b, err := m.fs.ReadFile(path)
if err != nil {
return OSConfig{}, err
}
return readResolv(bytes.NewReader(b))
}
// readResolvConf reads DNS configuration from /etc/resolv.conf.
func (m directManager) readResolvConf() (OSConfig, error) {
return m.readResolvFile(resolvConf)
}
// resolvOwner returns the apparent owner of the resolv.conf
// configuration in bs - one of "resolvconf", "systemd-resolved" or
// "NetworkManager", or "" if no known owner was found.
func resolvOwner(bs []byte) string {
likely := ""
b := bytes.NewBuffer(bs)
for {
line, err := b.ReadString('\n')
if err != nil {
return ""
return likely
}
line = strings.TrimSpace(line)
if line == "" {
@@ -106,15 +95,15 @@ func resolvOwner(bs []byte) string {
if line[0] != '#' {
// First non-empty, non-comment line. Assume the owner
// isn't hiding further down.
return ""
return likely
}
if strings.Contains(line, "systemd-resolved") {
return "systemd-resolved"
likely = "systemd-resolved"
} else if strings.Contains(line, "NetworkManager") {
return "NetworkManager"
likely = "NetworkManager"
} else if strings.Contains(line, "resolvconf") {
return "resolvconf"
likely = "resolvconf"
}
}
}
@@ -146,20 +135,46 @@ func isResolvedRunning() bool {
// The caller must call Down before program shutdown
// or as cleanup if the program terminates unexpectedly.
type directManager struct {
fs wholeFileFS
logf logger.Logf
fs wholeFileFS
// renameBroken is set if fs.Rename to or from /etc/resolv.conf
// fails. This can happen in some container runtimes, where
// /etc/resolv.conf is bind-mounted from outside the container,
// and therefore /etc and /etc/resolv.conf are different
// filesystems as far as rename(2) is concerned.
//
// In those situations, we fall back to emulating rename with file
// copies and truncations, which is not as good (opens up a race
// where a reader can see an empty or partial /etc/resolv.conf),
// but is better than having non-functioning DNS.
renameBroken bool
}
func newDirectManager() directManager {
return directManager{fs: directFS{}}
func newDirectManager(logf logger.Logf) *directManager {
return &directManager{
logf: logf,
fs: directFS{},
}
}
func newDirectManagerOnFS(fs wholeFileFS) directManager {
return directManager{fs: fs}
func newDirectManagerOnFS(logf logger.Logf, fs wholeFileFS) *directManager {
return &directManager{
logf: logf,
fs: fs,
}
}
func (m *directManager) readResolvFile(path string) (OSConfig, error) {
b, err := m.fs.ReadFile(path)
if err != nil {
return OSConfig{}, err
}
return readResolv(bytes.NewReader(b))
}
// ownedByTailscale reports whether /etc/resolv.conf seems to be a
// tailscale-managed file.
func (m directManager) ownedByTailscale() (bool, error) {
func (m *directManager) ownedByTailscale() (bool, error) {
isRegular, err := m.fs.Stat(resolvConf)
if err != nil {
if os.IsNotExist(err) {
@@ -182,7 +197,7 @@ func (m directManager) ownedByTailscale() (bool, error) {
// backupConfig creates or updates a backup of /etc/resolv.conf, if
// resolv.conf does not currently contain a Tailscale-managed config.
func (m directManager) backupConfig() error {
func (m *directManager) backupConfig() error {
if _, err := m.fs.Stat(resolvConf); err != nil {
if os.IsNotExist(err) {
// No resolv.conf, nothing to back up. Also get rid of any
@@ -201,10 +216,10 @@ func (m directManager) backupConfig() error {
return nil
}
return m.fs.Rename(resolvConf, backupConf)
return m.rename(resolvConf, backupConf)
}
func (m directManager) restoreBackup() (restored bool, err error) {
func (m *directManager) restoreBackup() (restored bool, err error) {
if _, err := m.fs.Stat(backupConf); err != nil {
if os.IsNotExist(err) {
// No backup, nothing we can do.
@@ -230,14 +245,48 @@ func (m directManager) restoreBackup() (restored bool, err error) {
}
// We own resolv.conf, and a backup exists.
if err := m.fs.Rename(backupConf, resolvConf); err != nil {
if err := m.rename(backupConf, resolvConf); err != nil {
return false, err
}
return true, nil
}
func (m directManager) SetDNS(config OSConfig) (err error) {
// rename tries to rename old to new using m.fs.Rename, and falls back
// to hand-copying bytes and truncating old if that fails.
//
// This is a workaround to /etc/resolv.conf being a bind-mounted file
// some container environments, which cannot be moved elsewhere in
// /etc (because that would be a cross-filesystem move) or deleted
// (because that would break the bind in surprising ways).
func (m *directManager) rename(old, new string) error {
if !m.renameBroken {
err := m.fs.Rename(old, new)
if err == nil {
return nil
}
m.logf("rename of %q to %q failed (%v), falling back to copy+delete", old, new, err)
m.renameBroken = true
}
bs, err := m.fs.ReadFile(old)
if err != nil {
return fmt.Errorf("reading %q to rename: %v", old, err)
}
if err := m.fs.WriteFile(new, bs, 0644); err != nil {
return fmt.Errorf("writing to %q in rename of %q: %v", new, old, err)
}
if err := m.fs.Remove(old); err != nil {
err2 := m.fs.Truncate(old)
if err2 != nil {
return fmt.Errorf("remove of %q failed (%v) and so did truncate: %v", old, err, err2)
}
}
return nil
}
func (m *directManager) SetDNS(config OSConfig) (err error) {
var changed bool
if config.IsZero() {
changed, err = m.restoreBackup()
@@ -252,7 +301,7 @@ func (m directManager) SetDNS(config OSConfig) (err error) {
buf := new(bytes.Buffer)
writeResolvConf(buf, config.Nameservers, config.SearchDomains)
if err := atomicWriteFile(m.fs, resolvConf, buf.Bytes(), 0644); err != nil {
if err := m.atomicWriteFile(m.fs, resolvConf, buf.Bytes(), 0644); err != nil {
return err
}
}
@@ -280,11 +329,11 @@ func (m directManager) SetDNS(config OSConfig) (err error) {
return nil
}
func (m directManager) SupportsSplitDNS() bool {
func (m *directManager) SupportsSplitDNS() bool {
return false
}
func (m directManager) GetBaseConfig() (OSConfig, error) {
func (m *directManager) GetBaseConfig() (OSConfig, error) {
owned, err := m.ownedByTailscale()
if err != nil {
return OSConfig{}, err
@@ -297,7 +346,7 @@ func (m directManager) GetBaseConfig() (OSConfig, error) {
return m.readResolvFile(fileToRead)
}
func (m directManager) Close() error {
func (m *directManager) Close() error {
// We used to keep a file for the tailscale config and symlinked
// to it, but then we stopped because /etc/resolv.conf being a
// symlink to surprising places breaks snaps and other sandboxing
@@ -329,7 +378,7 @@ func (m directManager) Close() error {
}
// We own resolv.conf, and a backup exists.
if err := m.fs.Rename(backupConf, resolvConf); err != nil {
if err := m.rename(backupConf, resolvConf); err != nil {
return err
}
@@ -340,7 +389,7 @@ func (m directManager) Close() error {
return nil
}
func atomicWriteFile(fs wholeFileFS, filename string, data []byte, perm os.FileMode) error {
func (m *directManager) atomicWriteFile(fs wholeFileFS, filename string, data []byte, perm os.FileMode) error {
var randBytes [12]byte
if _, err := rand.Read(randBytes[:]); err != nil {
return fmt.Errorf("atomicWriteFile: %w", err)
@@ -352,7 +401,7 @@ func atomicWriteFile(fs wholeFileFS, filename string, data []byte, perm os.FileM
if err := fs.WriteFile(tmpName, data, perm); err != nil {
return fmt.Errorf("atomicWriteFile: %w", err)
}
return fs.Rename(tmpName, filename)
return m.rename(tmpName, filename)
}
// wholeFileFS is a high-level file system abstraction designed just for use
@@ -364,6 +413,7 @@ type wholeFileFS interface {
Rename(oldName, newName string) error
Remove(name string) error
ReadFile(name string) ([]byte, error)
Truncate(name string) error
WriteFile(name string, contents []byte, perm os.FileMode) error
}
@@ -396,6 +446,10 @@ func (fs directFS) ReadFile(name string) ([]byte, error) {
return ioutil.ReadFile(fs.path(name))
}
func (fs directFS) Truncate(name string) error {
return os.Truncate(fs.path(name), 0)
}
func (fs directFS) WriteFile(name string, contents []byte, perm os.FileMode) error {
return ioutil.WriteFile(fs.path(name), contents, perm)
}

View File

@@ -5,31 +5,65 @@
package dns
import (
"io/ioutil"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"inet.af/netaddr"
"tailscale.com/util/dnsname"
)
func TestSetDNS(t *testing.T) {
const orig = "nameserver 9.9.9.9 # orig"
func TestDirectManager(t *testing.T) {
tmp := t.TempDir()
resolvPath := filepath.Join(tmp, "etc", "resolv.conf")
backupPath := filepath.Join(tmp, "etc", "resolv.pre-tailscale-backup.conf")
if err := os.MkdirAll(filepath.Dir(resolvPath), 0777); err != nil {
if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(resolvPath, []byte(orig), 0644); err != nil {
testDirect(t, directFS{prefix: tmp})
}
type boundResolvConfFS struct {
directFS
}
func (fs boundResolvConfFS) Rename(old, new string) error {
if old == "/etc/resolv.conf" || new == "/etc/resolv.conf" {
return errors.New("cannot move to/from /etc/resolv.conf")
}
return fs.directFS.Rename(old, new)
}
func (fs boundResolvConfFS) Remove(name string) error {
if name == "/etc/resolv.conf" {
return errors.New("cannot remove /etc/resolv.conf")
}
return fs.directFS.Remove(name)
}
func TestDirectBrokenRename(t *testing.T) {
tmp := t.TempDir()
if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil {
t.Fatal(err)
}
testDirect(t, boundResolvConfFS{directFS{prefix: tmp}})
}
func testDirect(t *testing.T, fs wholeFileFS) {
const orig = "nameserver 9.9.9.9 # orig"
resolvPath := "/etc/resolv.conf"
backupPath := "/etc/resolv.pre-tailscale-backup.conf"
if err := fs.WriteFile(resolvPath, []byte(orig), 0644); err != nil {
t.Fatal(err)
}
readFile := func(t *testing.T, path string) string {
t.Helper()
b, err := ioutil.ReadFile(path)
b, err := fs.ReadFile(path)
if err != nil {
t.Fatal(err)
}
@@ -39,12 +73,12 @@ func TestSetDNS(t *testing.T) {
if got := readFile(t, resolvPath); got != orig {
t.Fatalf("resolv.conf:\n%s, want:\n%s", got, orig)
}
if _, err := os.Stat(backupPath); !os.IsNotExist(err) {
if _, err := fs.Stat(backupPath); !os.IsNotExist(err) {
t.Fatalf("resolv.conf backup: want it to be gone but: %v", err)
}
}
m := directManager{fs: directFS{prefix: tmp}}
m := directManager{logf: t.Logf, fs: fs}
if err := m.SetDNS(OSConfig{
Nameservers: []netaddr.IP{netaddr.MustParseIP("8.8.8.8"), netaddr.MustParseIP("8.8.4.4")},
SearchDomains: []dnsname.FQDN{"ts.net.", "ts-dns.test."},
@@ -81,3 +115,26 @@ search ts.net ts-dns.test
}
assertBaseState(t)
}
type brokenRemoveFS struct {
directFS
}
func (b brokenRemoveFS) Rename(old, new string) error {
return errors.New("nyaaah I'm a silly container!")
}
func (b brokenRemoveFS) Remove(name string) error {
if strings.Contains(name, "/etc/resolv.conf") {
return fmt.Errorf("Faking remove failure: %q", &fs.PathError{Err: syscall.EBUSY})
}
return b.directFS.Remove(name)
}
func TestDirectBrokenRemove(t *testing.T) {
tmp := t.TempDir()
if err := os.MkdirAll(filepath.Join(tmp, "etc"), 0700); err != nil {
t.Fatal(err)
}
testDirect(t, brokenRemoveFS{directFS{prefix: tmp}})
}

View File

@@ -15,7 +15,7 @@ import (
func NewOSConfigurator(logf logger.Logf, _ string) (OSConfigurator, error) {
bs, err := ioutil.ReadFile("/etc/resolv.conf")
if os.IsNotExist(err) {
return newDirectManager(), nil
return newDirectManager(logf), nil
}
if err != nil {
return nil, fmt.Errorf("reading /etc/resolv.conf: %w", err)
@@ -25,16 +25,16 @@ func NewOSConfigurator(logf logger.Logf, _ string) (OSConfigurator, error) {
case "resolvconf":
switch resolvconfStyle() {
case "":
return newDirectManager(), nil
return newDirectManager(logf), nil
case "debian":
return newDebianResolvconfManager(logf)
case "openresolv":
return newOpenresolvManager()
default:
logf("[unexpected] got unknown flavor of resolvconf %q, falling back to direct manager", resolvconfStyle())
return newDirectManager(), nil
return newDirectManager(logf), nil
}
default:
return newDirectManager(), nil
return newDirectManager(logf), nil
}
}

View File

@@ -40,7 +40,7 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurat
}
switch mode {
case "direct":
return newDirectManagerOnFS(env.fs), nil
return newDirectManagerOnFS(logf, env.fs), nil
case "systemd-resolved":
return newResolvedManager(logf, interfaceName)
case "network-manager":
@@ -51,7 +51,7 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurat
return newOpenresolvManager()
default:
logf("[unexpected] detected unknown DNS mode %q, using direct manager as last resort", mode)
return newDirectManagerOnFS(env.fs), nil
return newDirectManagerOnFS(logf, env.fs), nil
}
}

View File

@@ -142,6 +142,36 @@ func TestLinuxDNSMode(t *testing.T) {
wantLog: "dns: [rc=resolved nm=no ret=systemd-resolved]",
want: "systemd-resolved",
},
{
// More than one user has had resolvconf write a config that points to
// systemd-resolved. We're better off using systemd-resolved.
// regression test for https://github.com/tailscale/tailscale/issues/3026
name: "allegedly_resolvconf_but_actually_systemd-resolved",
env: env(resolvDotConf(
"# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)",
"# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN",
"# 127.0.0.53 is the systemd-resolved stub resolver.",
"# run \"systemd-resolve --status\" to see details about the actual nameservers.",
"nameserver 127.0.0.53"),
resolvedRunning()),
wantLog: "dns: [rc=resolved nm=no ret=systemd-resolved]",
want: "systemd-resolved",
},
{
// More than one user has had resolvconf write a config that points to
// systemd-resolved. We're better off using systemd-resolved.
// ...but what if systemd-resolved isn't running?
// regression test for https://github.com/tailscale/tailscale/issues/3026
name: "allegedly_resolvconf_but_actually_systemd-resolved_but_not_really",
env: env(resolvDotConf(
"# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)",
"# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN",
"# 127.0.0.53 is the systemd-resolved stub resolver.",
"# run \"systemd-resolve --status\" to see details about the actual nameservers.",
"nameserver 127.0.0.53")),
wantLog: "dns: [rc=resolved resolved=no ret=direct]",
want: "direct",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -186,8 +216,20 @@ func (m memFS) ReadFile(name string) ([]byte, error) {
panic("TODO")
}
func (fs memFS) WriteFile(name string, contents []byte, perm os.FileMode) error {
fs[name] = string(contents)
func (m memFS) Truncate(name string) error {
v, ok := m[name]
if !ok {
return fs.ErrNotExist
}
if s, ok := v.(string); ok {
m[name] = s[:0]
}
return nil
}
func (m memFS) WriteFile(name string, contents []byte, perm os.FileMode) error {
m[name] = string(contents)
return nil
}

View File

@@ -6,6 +6,6 @@ package dns
import "tailscale.com/types/logger"
func NewOSConfigurator(logger.Logf, string) (OSConfigurator, error) {
return newDirectManager(), nil
func NewOSConfigurator(logf logger.Logf, _ string) (OSConfigurator, error) {
return newDirectManager(logf), nil
}

View File

@@ -68,7 +68,7 @@ func isResolvedActive() bool {
return false
}
config, err := newDirectManager().readResolvConf()
config, err := newDirectManager(logger.Discard).readResolvFile(resolvConf)
if err != nil {
return false
}
@@ -176,7 +176,13 @@ func (m *resolvedManager) SetDNS(config OSConfig) error {
}
if call := m.resolved.CallWithContext(ctx, "org.freedesktop.resolve1.Manager.SetLinkDefaultRoute", 0, m.ifidx, len(config.MatchDomains) == 0); call.Err != nil {
return fmt.Errorf("setLinkDefaultRoute: %w", call.Err)
if dbusErr, ok := call.Err.(dbus.Error); ok && dbusErr.Name == dbus.ErrMsgUnknownMethod.Name {
// on some older systems like Kubuntu 18.04.6 with systemd 237 method SetLinkDefaultRoute is absent,
// but otherwise it's working good
m.logf("[v1] failed to set SetLinkDefaultRoute: %v", call.Err)
} else {
return fmt.Errorf("setLinkDefaultRoute: %w", call.Err)
}
}
// Some best-effort setting of things, but resolved should do the

View File

@@ -17,12 +17,14 @@ import (
"net/http"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"time"
dns "golang.org/x/net/dns/dnsmessage"
"inet.af/netaddr"
"tailscale.com/hostinfo"
"tailscale.com/net/netns"
"tailscale.com/types/dnstype"
"tailscale.com/types/logger"
@@ -179,19 +181,37 @@ func init() {
rand.Seed(time.Now().UnixNano())
}
func newForwarder(logf logger.Logf, responses chan packet, linkMon *monitor.Mon, linkSel ForwardLinkSelector) *forwarder {
maxDoHInFlight := 1000 // effectively unlimited
if runtime.GOOS == "ios" {
// No HTTP/2 on iOS yet (for size reasons), so DoH is
// pricier.
maxDoHInFlight = 10
func maxDoHInFlight(goos string) int {
if goos != "ios" {
return 1000 // effectively unlimited
}
// iOS < 15 limits the memory to 15MB for NetworkExtensions.
// iOS >= 15 gives us 50MB.
// See: https://tailscale.com/blog/go-linker/
ver := hostinfo.GetOSVersion()
if ver == "" {
// Unknown iOS version, be cautious.
return 10
}
idx := strings.Index(ver, ".")
if idx == -1 {
// Unknown iOS version, be cautious.
return 10
}
major := ver[:idx]
if m, err := strconv.Atoi(major); err != nil || m < 15 {
return 10
}
return 1000
}
func newForwarder(logf logger.Logf, responses chan packet, linkMon *monitor.Mon, linkSel ForwardLinkSelector) *forwarder {
f := &forwarder{
logf: logger.WithPrefix(logf, "forward: "),
linkMon: linkMon,
linkSel: linkSel,
responses: responses,
dohSem: make(chan struct{}, maxDoHInFlight),
dohSem: make(chan struct{}, maxDoHInFlight(runtime.GOOS)),
}
f.ctx, f.ctxCancel = context.WithCancel(context.Background())
return f
@@ -538,7 +558,6 @@ func (f *forwarder) forward(query packet) error {
// when browsing for LAN devices. But even when filtering this
// out, playing on Sonos still works.
if hasRDNSBonjourPrefix(domain) {
f.logf("[v1] dropping %q", domain)
return nil
}

View File

@@ -13,6 +13,7 @@ import (
"time"
dns "golang.org/x/net/dns/dnsmessage"
"tailscale.com/hostinfo"
"tailscale.com/types/dnstype"
)
@@ -140,3 +141,30 @@ func TestGetRCode(t *testing.T) {
})
}
}
func TestMaxDoHInFlight(t *testing.T) {
tests := []struct {
goos string
ver string
want int
}{
{"ios", "", 10},
{"ios", "1532", 10},
{"ios", "9.3.2", 10},
{"ios", "14.3.2", 10},
{"ios", "15.3.2", 1000},
{"ios", "20.3.2", 1000},
{"android", "", 1000},
{"darwin", "", 1000},
{"linux", "", 1000},
}
for _, tc := range tests {
t.Run(fmt.Sprintf("%s-%s", tc.goos, tc.ver), func(t *testing.T) {
hostinfo.SetOSVersion(tc.ver)
got := maxDoHInFlight(tc.goos)
if got != tc.want {
t.Errorf("got %d; want %d", got, tc.want)
}
})
}
}

View File

@@ -952,7 +952,7 @@ func TestAllocs(t *testing.T) {
tests := []struct {
name string
query []byte
want int
want uint64
}{
// Name lowercasing, response slice created by dns.NewBuilder,
// and closure allocation from go call.
@@ -964,11 +964,11 @@ func TestAllocs(t *testing.T) {
}
for _, tt := range tests {
allocs := testing.AllocsPerRun(100, func() {
err := tstest.MinAllocsPerRun(t, tt.want, func() {
syncRespond(r, tt.query)
})
if int(allocs) > tt.want {
t.Errorf("%s: allocs = %v; want %v", tt.name, allocs, tt.want)
if err != nil {
t.Errorf("%s: %v", tt.name, err)
}
}
}

View File

@@ -99,9 +99,9 @@ func (wm *wslManager) SetDNS(cfg OSConfig) error {
} else if len(distros) == 0 {
return nil
}
managers := make(map[string]directManager)
managers := make(map[string]*directManager)
for _, distro := range distros {
managers[distro] = newDirectManagerOnFS(wslFS{
managers[distro] = newDirectManagerOnFS(wm.logf, wslFS{
user: "root",
distro: distro,
})
@@ -141,7 +141,7 @@ generateResolvConf = false
// setWSLConf attempts to disable generateResolvConf in each WSL2 linux.
// If any are changed, it reports true.
func (wm *wslManager) setWSLConf(managers map[string]directManager) (changed bool) {
func (wm *wslManager) setWSLConf(managers map[string]*directManager) (changed bool) {
for distro, m := range managers {
b, err := m.fs.ReadFile(wslConf)
if err != nil && !os.IsNotExist(err) {
@@ -189,6 +189,8 @@ func (fs wslFS) Rename(oldName, newName string) error {
}
func (fs wslFS) Remove(name string) error { return wslRun(fs.cmd("rm", "--", name)) }
func (fs wslFS) Truncate(name string) error { return fs.WriteFile(name, nil, 0644) }
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 {

View File

@@ -41,11 +41,11 @@
"RegionName": "r11",
"Nodes": [
{
"Name": "11b",
"Name": "11a",
"RegionID": 11,
"HostName": "derp11b.tailscale.com",
"IPv4": "15.228.50.175",
"IPv6": "2600:1f1e:26e:2f01:fca6:2392:ea86:c768"
"HostName": "derp11.tailscale.com",
"IPv4": "18.230.97.74",
"IPv6": "2600:1f1e:ee4:5611:ec5c:1736:d43b:a454"
}
]
},

View File

@@ -8,6 +8,7 @@ import (
"testing"
"inet.af/netaddr"
"tailscale.com/tstest"
)
func TestCache(t *testing.T) {
@@ -67,7 +68,7 @@ func TestCache(t *testing.T) {
wantVal(k3, 30)
wantLen(1)
allocs := int(testing.AllocsPerRun(1000, func() {
err := tstest.MinAllocsPerRun(t, 0, func() {
got, ok := c.Get(k3)
if !ok {
t.Fatal("missing k3")
@@ -75,8 +76,8 @@ func TestCache(t *testing.T) {
if got != 30 {
t.Fatalf("got = %d; want 30", got)
}
}))
if allocs != 0 {
t.Errorf("allocs = %v; want 0", allocs)
})
if err != nil {
t.Error(err)
}
}

View File

@@ -183,14 +183,20 @@ func (i Interface) Addrs() ([]net.Addr, error) {
return i.Interface.Addrs()
}
// ForeachInterfaceAddress calls fn for each interface's address on
// the machine. The IPPrefix's IP is the IP address assigned to the
// interface, and Bits are the subnet mask.
// ForeachInterfaceAddress is a wrapper for GetList, then
// List.ForeachInterfaceAddress.
func ForeachInterfaceAddress(fn func(Interface, netaddr.IPPrefix)) error {
ifaces, err := netInterfaces()
ifaces, err := GetList()
if err != nil {
return err
}
return ifaces.ForeachInterfaceAddress(fn)
}
// ForeachInterfaceAddress calls fn for each interface in ifaces, with
// all its addresses. The IPPrefix's IP is the IP address assigned to
// the interface, and Bits are the subnet mask.
func (ifaces List) ForeachInterfaceAddress(fn func(Interface, netaddr.IPPrefix)) error {
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
@@ -208,11 +214,21 @@ func ForeachInterfaceAddress(fn func(Interface, netaddr.IPPrefix)) error {
return nil
}
// ForeachInterface calls fn for each interface on the machine, with
// ForeachInterface is a wrapper for GetList, then
// List.ForeachInterface.
func ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
ifaces, err := GetList()
if err != nil {
return err
}
return ifaces.ForeachInterface(fn)
}
// ForeachInterface calls fn for each interface in ifaces, with
// all its addresses. The IPPrefix's IP is the IP address assigned to
// the interface, and Bits are the subnet mask.
func ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
ifaces, err := netInterfaces()
func (ifaces List) ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
ifaces, err := GetList()
if err != nil {
return err
}
@@ -398,6 +414,9 @@ func (s *State) HasPAC() bool { return s != nil && s.PAC != "" }
// AnyInterfaceUp reports whether any interface seems like it has Internet access.
func (s *State) AnyInterfaceUp() bool {
if runtime.GOOS == "js" {
return true
}
return s != nil && (s.HaveV4 || s.HaveV6)
}
@@ -589,6 +608,14 @@ func RegisterInterfaceGetter(getInterfaces func() ([]Interface, error)) {
altNetInterfaces = getInterfaces
}
// List is a list of interfaces on the machine.
type List []Interface
// GetList returns the list of interfaces on the machine.
func GetList() (List, error) {
return netInterfaces()
}
// netInterfaces is a wrapper around the standard library's net.Interfaces
// that returns a []*Interface instead of a []net.Interface.
// It exists because Android SDK 30 no longer permits Go's net.Interfaces

View File

@@ -17,6 +17,7 @@ import (
"net"
"net/http"
"os"
"runtime"
"sort"
"strconv"
"sync"
@@ -778,6 +779,13 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
c.curState = nil
}()
if runtime.GOOS == "js" {
if err := c.runHTTPOnlyChecks(ctx, last, rs, dm); err != nil {
return nil, err
}
return c.finishAndStoreReport(rs, dm), nil
}
ifState, err := interfaces.GetState()
if err != nil {
c.logf("[v1] interfaces: %v", err)
@@ -911,6 +919,10 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
wg.Wait()
}
return c.finishAndStoreReport(rs, dm), nil
}
func (c *Client) finishAndStoreReport(rs *reportState, dm *tailcfg.DERPMap) *Report {
rs.mu.Lock()
report := rs.report.Clone()
rs.mu.Unlock()
@@ -918,7 +930,56 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
c.addReportHistoryAndSetPreferredDERP(report)
c.logConciseReport(report, dm)
return report, nil
return report
}
// runHTTPOnlyChecks is the netcheck done by environments that can
// only do HTTP requests, such as ws/wasm.
func (c *Client) runHTTPOnlyChecks(ctx context.Context, last *Report, rs *reportState, dm *tailcfg.DERPMap) error {
var regions []*tailcfg.DERPRegion
if rs.incremental && last != nil {
for rid := range last.RegionLatency {
if dr, ok := dm.Regions[rid]; ok {
regions = append(regions, dr)
}
}
}
if len(regions) == 0 {
for _, dr := range dm.Regions {
regions = append(regions, dr)
}
}
c.logf("running HTTP-only netcheck against %v regions", len(regions))
var wg sync.WaitGroup
for _, rg := range regions {
if len(rg.Nodes) == 0 {
continue
}
wg.Add(1)
rg := rg
go func() {
defer wg.Done()
node := rg.Nodes[0]
req, _ := http.NewRequestWithContext(ctx, "HEAD", "https://"+node.HostName+"/derp/probe", nil)
// One warm-up one to get HTTP connection set
// up and get a connection from the browser's
// pool.
if _, err := http.DefaultClient.Do(req); err != nil {
c.logf("probing %s: %v", node.HostName, err)
return
}
t0 := c.timeNow()
if _, err := http.DefaultClient.Do(req); err != nil {
c.logf("probing %s: %v", node.HostName, err)
return
}
d := c.timeNow().Sub(t0)
rs.addNodeLatency(node, netaddr.IPPort{}, d)
}()
}
wg.Wait()
return nil
}
func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegion) (time.Duration, netaddr.IP, error) {

View File

@@ -10,6 +10,7 @@ import (
"testing"
"inet.af/netaddr"
"tailscale.com/tstest"
"tailscale.com/types/ipproto"
)
@@ -378,11 +379,11 @@ func TestParsedString(t *testing.T) {
})
}
allocs := testing.AllocsPerRun(1000, func() {
err := tstest.MinAllocsPerRun(t, 1, func() {
sinkString = tests[0].qdecode.String()
})
if allocs != 1 {
t.Errorf("allocs = %v; want 1", allocs)
if err != nil {
t.Error(err)
}
}
@@ -415,12 +416,12 @@ func TestDecode(t *testing.T) {
})
}
allocs := testing.AllocsPerRun(1000, func() {
err := tstest.MinAllocsPerRun(t, 0, func() {
var got Parsed
got.Decode(tests[0].buf)
})
if allocs != 0 {
t.Errorf("allocs = %v; want 0", allocs)
if err != nil {
t.Error(err)
}
}

View File

@@ -677,6 +677,7 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
pxpAddr := netaddr.IPPortFrom(gw, c.pxpPort()).UDPAddr()
upnpAddr := netaddr.IPPortFrom(gw, c.upnpPort()).UDPAddr()
upnpMulticastAddr := netaddr.IPPortFrom(netaddr.IPv4(239, 255, 255, 250), c.upnpPort()).UDPAddr()
// Don't send probes to services that we recently learned (for
// the same gw/myIP) are available. See
@@ -694,7 +695,47 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
if c.sawUPnPRecently() {
res.UPnP = true
} else if !DisableUPnP {
// Strictly speaking, you discover UPnP services by sending an
// SSDP query (which uPnPPacket is) to udp/1900 on the SSDP
// multicast address, and then get a flood of responses back
// from everything on your network.
//
// Empirically, many home routers also respond to SSDP queries
// directed at udp/1900 on their LAN unicast IP
// (e.g. 192.168.1.1). This is handy because it means we can
// probe the router directly and likely get a reply. However,
// the specs do not _require_ UPnP devices to respond to
// unicast SSDP queries, so some conformant UPnP
// implementations only respond to multicast queries.
//
// In theory, we could send just the multicast query and get
// all compliant devices to respond. However, we instead send
// to both a unicast and a multicast addresses, for a couple
// of reasons:
//
// First, some LANs and OSes have broken multicast in one way
// or another, so it's possible for the multicast query to be
// lost while the unicast query gets through. But we still
// have to send the multicast query to also get a response
// from strict-UPnP devices on multicast-working networks.
//
// Second, SSDP's packet dynamics are a bit weird: you send
// the SSDP query from your unicast IP to the SSDP multicast
// IP, but responses are from the UPnP devices's _unicast_ IP
// to your unicast IP. This can confuse some less-intelligent
// stateful host firewalls, who might block the responses. To
// work around this, we send the unicast query first, to teach
// the firewall to expect a unicast response from the router,
// and then send our multicast query. That way, even if the
// device doesn't respond to the unicast query, we've set the
// stage for the host firewall to accept the response to the
// multicast query.
//
// See https://github.com/tailscale/tailscale/issues/3197 for
// an example of a device that strictly implements UPnP, and
// only responds to multicast queries.
uc.WriteTo(uPnPPacket, upnpAddr)
uc.WriteTo(uPnPPacket, upnpMulticastAddr)
}
buf := make([]byte, 1500)
@@ -711,17 +752,19 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
}
return res, err
}
ip, ok := netaddr.FromStdIP(addr.(*net.UDPAddr).IP)
if !ok {
continue
}
port := uint16(addr.(*net.UDPAddr).Port)
switch port {
case c.upnpPort():
if mem.Contains(mem.B(buf[:n]), mem.S(":InternetGatewayDevice:")) {
if ip == gw && mem.Contains(mem.B(buf[:n]), mem.S(":InternetGatewayDevice:")) {
meta, err := parseUPnPDiscoResponse(buf[:n])
if err != nil {
c.logf("unrecognized UPnP discovery response; ignoring")
}
if VerboseLogs {
c.logf("UPnP reply %+v, %q", meta, buf[:n])
}
c.logf("[v1] UPnP reply %+v, %q", meta, buf[:n])
res.UPnP = true
c.mu.Lock()
c.uPnPSawTime = time.Now()
@@ -740,7 +783,7 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
c.mu.Unlock()
switch pres.ResultCode {
case pcpCodeOK:
c.logf("Got PCP response: epoch: %v", pres.Epoch)
c.logf("[v1] Got PCP response: epoch: %v", pres.Epoch)
res.PCP = true
continue
case pcpCodeNotAuthorized:
@@ -756,7 +799,7 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
}
if pres, ok := parsePMPResponse(buf[:n]); ok {
if pres.OpCode == pmpOpReply|pmpOpMapPublicAddr && pres.ResultCode == pmpCodeOK {
c.logf("Got PMP response; IP: %v, epoch: %v", pres.PublicAddr, pres.SecondsSinceEpoch)
c.logf("[v1] Got PMP response; IP: %v, epoch: %v", pres.PublicAddr, pres.SecondsSinceEpoch)
res.PMP = true
c.mu.Lock()
c.pmpPubIP = pres.PublicAddr

View File

@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !js
// +build !js
// Package tun creates a tuntap device, working around OS-specific
// quirks if necessary.
package tstun

View File

@@ -370,7 +370,7 @@ func TestAllocs(t *testing.T) {
defer tun.Close()
buf := []byte{0x00}
allocs := testing.AllocsPerRun(100, func() {
err := tstest.MinAllocsPerRun(t, 0, func() {
_, err := ftun.Write(buf, 0)
if err != nil {
t.Errorf("write: error: %v", err)
@@ -378,8 +378,8 @@ func TestAllocs(t *testing.T) {
}
})
if allocs > 0 {
t.Errorf("read allocs = %v; want 0", allocs)
if err != nil {
t.Error(err)
}
}

9
paths/paths_js.go Normal file
View File

@@ -0,0 +1,9 @@
// 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 paths
func ensureStateDirPerms(dirPath string) error {
return nil
}

View File

@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !windows
// +build !windows
//go:build !windows && !js
// +build !windows,!js
package paths
@@ -81,3 +81,8 @@ func ensureStateDirPerms(dir string) error {
}
return os.Chmod(dir, perm)
}
// LegacyStateFilePath is not applicable to UNIX; it is just stubbed out.
func LegacyStateFilePath() string {
return ""
}

View File

@@ -148,3 +148,9 @@ func ensureStateDirPerms(dirPath string) error {
return windows.SetNamedSecurityInfo(dirPath, windows.SE_FILE_OBJECT, flags,
sids.User, sids.PrimaryGroup, dacl, nil)
}
// LegacyStateFilePath returns the legacy path to the state file when it was stored under the
// current user's %LocalAppData%.
func LegacyStateFilePath() string {
return filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "server-state.conf")
}

17
portlist/portlist_js.go Normal file
View File

@@ -0,0 +1,17 @@
// 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 portlist
import "time"
const pollInterval = 365 * 24 * time.Hour
func listPorts() (List, error) {
return nil, nil
}
func addProcesses(pl []Port) ([]Port, error) {
return pl, nil
}

View File

@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !linux && !windows && !darwin
// +build !linux,!windows,!darwin
//go:build !linux && !windows && !darwin && !js
// +build !linux,!windows,!darwin,!js
package portlist

View File

@@ -8,6 +8,7 @@
package safesocket
import (
"errors"
"fmt"
"io"
"io/ioutil"
@@ -23,6 +24,9 @@ import (
// TODO(apenwarr): handle magic cookie auth
func connect(path string, port uint16) (net.Conn, error) {
if runtime.GOOS == "js" {
return nil, errors.New("safesocket.Connect not yet implemented on js/wasm")
}
if runtime.GOOS == "darwin" && path == "" && port == 0 {
return connectMacOSAppSandbox()
}

View File

@@ -23,6 +23,7 @@ main() {
OS=""
VERSION=""
PACKAGETYPE=""
APT_KEY_TYPE="" # Only for apt-based distros
if [ -f /etc/os-release ]; then
# /etc/os-release populates a number of shell variables. We care about the following:
@@ -35,22 +36,51 @@ main() {
OS="$ID"
VERSION="$VERSION_CODENAME"
PACKAGETYPE="apt"
# Third-party keyrings became the preferred method of
# installation in Ubuntu 20.04.
if expr "$VERSION_ID" : "2.*" >/dev/null; then
APT_KEY_TYPE="keyring"
else
APT_KEY_TYPE="legacy"
fi
;;
debian)
OS="$ID"
VERSION="$VERSION_CODENAME"
PACKAGETYPE="apt"
# Third-party keyrings became the preferred method of
# installation in Debian 11 (Bullseye).
if [ "$VERSION_ID" -lt 11 ]; then
APT_KEY_TYPE="legacy"
else
APT_KEY_TYPE="keyring"
fi
;;
raspbian)
OS="$ID"
VERSION="$VERSION_CODENAME"
PACKAGETYPE="apt"
# Third-party keyrings became the preferred method of
# installation in Raspbian 11 (Bullseye).
if [ "$VERSION_ID" -lt 11 ]; then
APT_KEY_TYPE="legacy"
else
APT_KEY_TYPE="keyring"
fi
;;
centos|ol)
centos)
OS="$ID"
VERSION="$VERSION_ID"
PACKAGETYPE="dnf"
if [ "$VERSION" =~ ^7 ]; then
if [ "$VERSION" = "7" ]; then
PACKAGETYPE="yum"
fi
;;
ol)
OS="oracle"
VERSION="$(echo "$VERSION_ID" | cut -f1 -d.)"
PACKAGETYPE="dnf"
if [ "$VERSION" = "7" ]; then
PACKAGETYPE="yum"
fi
;;
@@ -162,7 +192,8 @@ main() {
[ "$VERSION" != "eoan" ] && \
[ "$VERSION" != "focal" ] && \
[ "$VERSION" != "groovy" ] && \
[ "$VERSION" != "hirsute" ]
[ "$VERSION" != "hirsute" ] && \
[ "$VERSION" != "impish" ]
then
OS_UNSUPPORTED=1
fi
@@ -171,13 +202,15 @@ main() {
if [ "$VERSION" != "stretch" ] && \
[ "$VERSION" != "buster" ] && \
[ "$VERSION" != "bullseye" ] && \
[ "$VERSION" != "bookworm" ] && \
[ "$VERSION" != "sid" ]
then
OS_UNSUPPORTED=1
fi
;;
raspbian)
if [ "$VERSION" != "buster" ]
if [ "$VERSION" != "buster" ] && \
[ "$VERSION" != "bullseye" ]
then
OS_UNSUPPORTED=1
fi
@@ -189,6 +222,13 @@ main() {
OS_UNSUPPORTED=1
fi
;;
oracle)
if [ "$VERSION" != "7" ] && \
[ "$VERSION" != "8" ]
then
OS_UNSUPPORTED=1
fi
;;
rhel)
if [ "$VERSION" != "8" ]
then
@@ -204,6 +244,7 @@ main() {
opensuse)
if [ "$VERSION" != "leap/15.1" ] && \
[ "$VERSION" != "leap/15.2" ] && \
[ "$VERSION" != "leap/15.3" ] && \
[ "$VERSION" != "tumbleweed" ]
then
OS_UNSUPPORTED=1
@@ -322,11 +363,24 @@ main() {
echo "Please install either curl or wget to proceed."
exit 1
fi
if ! type gpg >/dev/null; then
echo "The installer needs gnupg to do keyring management."
echo "Please install gnupg to proceed".
exit 1
fi
# TODO: use newfangled per-repo signature scheme
set -x
$CURL "https://pkgs.tailscale.com/stable/$OS/$VERSION.gpg" | $SUDO apt-key add -
$CURL "https://pkgs.tailscale.com/stable/$OS/$VERSION.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list
$SUDO mkdir -p --mode=0755 /usr/share/keyrings
case "$APT_KEY_TYPE" in
legacy)
$CURL "https://pkgs.tailscale.com/stable/$OS/$VERSION.asc" | $SUDO apt-key add -
$CURL "https://pkgs.tailscale.com/stable/$OS/$VERSION.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list
;;
keyring)
$CURL "https://pkgs.tailscale.com/stable/$OS/$VERSION.noarmor.gpg" | $SUDO tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
$CURL "https://pkgs.tailscale.com/stable/$OS/$VERSION.tailscale-keyring.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list
;;
esac
$SUDO apt-get update
$SUDO apt-get install tailscale
set +x

View File

@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.13 && !go1.18
// +build go1.13,!go1.18
//go:build go1.13 && !go1.19
// +build go1.13,!go1.19
// This file makes assumptions about the inner workings of sync.Mutex and sync.RWMutex.
// This includes not just their memory layout but their invariants and functionality.

View File

@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.13 && !go1.18
// +build go1.13,!go1.18
//go:build go1.13 && !go1.19
// +build go1.13,!go1.19
package syncs

View File

@@ -78,13 +78,34 @@ func (u StableNodeID) IsZero() bool {
return u == ""
}
// NodeKey is the curve25519 public key for a node.
type NodeKey [32]byte
// NodeKey is the WireGuard public key for a node.
//
// Deprecated: prefer to use key.NodePublic instead. If you must have
// a NodeKey, use NodePublic.AsNodeKey.
type NodeKey = key.NodeKey
// NodeKeyFromNodePublic returns k converted to a NodeKey.
//
// Deprecated: exists only as a compatibility bridge while NodeKey
// gets removed from the codebase. Do not introduce new uses that
// aren't related to #3206.
func NodeKeyFromNodePublic(k key.NodePublic) NodeKey {
return k.AsNodeKey()
}
// DiscoKey is the curve25519 public key for path discovery key.
// It's never written to disk or reused between network start-ups.
type DiscoKey [32]byte
// DiscoKeyFromNodePublic returns k converted to a DiscoKey.
//
// Deprecated: exists only as a compatibility bridge while DiscoKey
// gets removed from the codebase. Do not introduce new uses that
// aren't related to #3206.
func DiscoKeyFromDiscoPublic(k key.DiscoPublic) DiscoKey {
return k.Raw32()
}
// User is an IPN user.
//
// A user can have multiple logins associated with it (e.g. gmail and github oauth).
@@ -164,6 +185,15 @@ type Node struct {
Hostinfo Hostinfo
Created time.Time
// Tags are the list of ACL tags applied to this node.
// Tags take the form of `tag:<value>` where value starts
// with a letter and only contains alphanumerics and dashes `-`.
// Some valid tag examples:
// `tag:prod`
// `tag:database`
// `tag:lab-1`
Tags []string `json:",omitempty"`
// PrimaryRoutes are the routes from AllowedIPs that this node
// is currently the primary subnet router for, as determined
// by the control plane. It does not include the self address
@@ -633,6 +663,11 @@ type RegisterRequest struct {
Followup string // response waits until AuthURL is visited
Hostinfo *Hostinfo
// Ephemeral is whether the client is requesting that this
// node be considered ephemeral and be automatically deleted
// when it stops being active.
Ephemeral bool `json:",omitempty"`
// The following fields are not used for SignatureNone and are required for
// SignatureV1:
SignatureType SignatureType `json:",omitempty"`
@@ -670,6 +705,10 @@ type RegisterResponse struct {
NodeKeyExpired bool // if true, the NodeKey needs to be replaced
MachineAuthorized bool // TODO(crawshaw): move to using MachineStatus
AuthURL string // if set, authorization pending
// Error indiciates that authorization failed. If this is non-empty,
// other status fields should be ignored.
Error string
}
// EndpointType distinguishes different sources of MapRequest.Endpoint values.
@@ -1109,32 +1148,21 @@ func keyMarshalText(prefix string, k [32]byte) []byte {
return appendKey(nil, prefix, k)
}
func keyUnmarshalText(dst []byte, prefix string, text []byte) error {
if len(text) < len(prefix) || string(text[:len(prefix)]) != prefix {
return fmt.Errorf("UnmarshalText: missing %q prefix", prefix)
func (k DiscoKey) String() string { return fmt.Sprintf("discokey:%x", k[:]) }
func (k DiscoKey) MarshalText() ([]byte, error) {
dk := key.DiscoPublicFromRaw32(mem.B(k[:]))
return dk.MarshalText()
}
func (k *DiscoKey) UnmarshalText(text []byte) error {
var dk key.DiscoPublic
if err := dk.UnmarshalText(text); err != nil {
return err
}
pub, err := key.NewPublicFromHexMem(mem.B(text[len(prefix):]))
if err != nil {
return fmt.Errorf("UnmarshalText: after %q: %v", prefix, err)
}
copy(dst[:], pub[:])
dk.AppendTo(k[:0])
return nil
}
func (k NodeKey) ShortString() string { return (key.Public(k)).ShortString() }
func (k NodeKey) String() string { return fmt.Sprintf("nodekey:%x", k[:]) }
func (k NodeKey) MarshalText() ([]byte, error) { return keyMarshalText("nodekey:", k), nil }
func (k *NodeKey) UnmarshalText(text []byte) error { return keyUnmarshalText(k[:], "nodekey:", text) }
// IsZero reports whether k is the zero value.
func (k NodeKey) IsZero() bool { return k == NodeKey{} }
func (k DiscoKey) String() string { return fmt.Sprintf("discokey:%x", k[:]) }
func (k DiscoKey) MarshalText() ([]byte, error) { return keyMarshalText("discokey:", k), nil }
func (k *DiscoKey) UnmarshalText(text []byte) error { return keyUnmarshalText(k[:], "discokey:", text) }
func (k DiscoKey) ShortString() string { return fmt.Sprintf("d:%x", k[:8]) }
func (k DiscoKey) AppendTo(b []byte) []byte { return appendKey(b, "discokey:", k) }
func (k DiscoKey) ShortString() string { return fmt.Sprintf("d:%x", k[:8]) }
func (k DiscoKey) AppendTo(b []byte) []byte { return appendKey(b, "discokey:", k) }
// IsZero reports whether k is the zero value.
func (k DiscoKey) IsZero() bool { return k == DiscoKey{} }
@@ -1172,7 +1200,8 @@ func (n *Node) Equal(n2 *Node) bool {
eqStrings(n.Capabilities, n2.Capabilities) &&
n.ComputedName == n2.ComputedName &&
n.computedHostIfDifferent == n2.computedHostIfDifferent &&
n.ComputedNameWithHost == n2.ComputedNameWithHost
n.ComputedNameWithHost == n2.ComputedNameWithHost &&
eqStrings(n.Tags, n2.Tags)
}
func eqBoolPtr(a, b *bool) bool {

View File

@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by the following command; DO NOT EDIT.
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
//go:generate go run tailscale.com/cmd/cloner -type=User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode -output=tailcfg_clone.go -clonefunc
package tailcfg
@@ -51,6 +51,7 @@ func (src *Node) Clone() *Node {
dst.AllowedIPs = append(src.AllowedIPs[:0:0], src.AllowedIPs...)
dst.Endpoints = append(src.Endpoints[:0:0], src.Endpoints...)
dst.Hostinfo = *src.Hostinfo.Clone()
dst.Tags = append(src.Tags[:0:0], src.Tags...)
dst.PrimaryRoutes = append(src.PrimaryRoutes[:0:0], src.PrimaryRoutes...)
if dst.LastSeen != nil {
dst.LastSeen = new(time.Time)
@@ -81,6 +82,7 @@ var _NodeCloneNeedsRegeneration = Node(struct {
DERP string
Hostinfo Hostinfo
Created time.Time
Tags []string
PrimaryRoutes []netaddr.IPPrefix
LastSeen *time.Time
Online *bool
@@ -241,6 +243,7 @@ var _RegisterResponseCloneNeedsRegeneration = RegisterResponse(struct {
NodeKeyExpired bool
MachineAuthorized bool
AuthURL string
Error string
}{})
// Clone makes a deep copy of DERPRegion.

View File

@@ -13,8 +13,8 @@ import (
"time"
"inet.af/netaddr"
"tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/types/wgkey"
"tailscale.com/version"
)
@@ -195,7 +195,7 @@ func TestNodeEqual(t *testing.T) {
"ID", "StableID", "Name", "User", "Sharer",
"Key", "KeyExpiry", "Machine", "DiscoKey",
"Addresses", "AllowedIPs", "Endpoints", "DERP", "Hostinfo",
"Created", "PrimaryRoutes",
"Created", "Tags", "PrimaryRoutes",
"LastSeen", "Online", "KeepAlive", "MachineAuthorized",
"Capabilities",
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
@@ -205,15 +205,7 @@ func TestNodeEqual(t *testing.T) {
have, nodeHandles)
}
newPublicKey := func(t *testing.T) wgkey.Key {
t.Helper()
k, err := wgkey.NewPrivate()
if err != nil {
t.Fatal(err)
}
return k.Public()
}
n1 := newPublicKey(t)
n1 := key.NewNode().Public()
m1 := key.NewMachine().Public()
now := time.Now()
@@ -272,13 +264,13 @@ func TestNodeEqual(t *testing.T) {
true,
},
{
&Node{Key: NodeKey(n1)},
&Node{Key: NodeKey(newPublicKey(t))},
&Node{Key: n1.AsNodeKey()},
&Node{Key: key.NewNode().Public().AsNodeKey()},
false,
},
{
&Node{Key: NodeKey(n1)},
&Node{Key: NodeKey(n1)},
&Node{Key: n1.AsNodeKey()},
&Node{Key: n1.AsNodeKey()},
true,
},
{
@@ -366,6 +358,26 @@ func TestNodeEqual(t *testing.T) {
&Node{DERP: "bar"},
false,
},
{
&Node{Tags: []string{"tag:foo"}},
&Node{Tags: []string{"tag:foo"}},
true,
},
{
&Node{Tags: []string{"tag:foo", "tag:bar"}},
&Node{Tags: []string{"tag:bar"}},
false,
},
{
&Node{Tags: []string{"tag:foo"}},
&Node{Tags: []string{"tag:bar"}},
false,
},
{
&Node{Tags: []string{"tag:foo"}},
&Node{},
false,
},
}
for i, tt := range tests {
got := tt.a.Equal(tt.b)
@@ -395,14 +407,6 @@ func TestNetInfoFields(t *testing.T) {
}
}
func TestNodeKeyMarshal(t *testing.T) {
var k1, k2 NodeKey
for i := range k1 {
k1[i] = byte(i)
}
testKey(t, "nodekey:", k1, &k2)
}
func TestDiscoKeyMarshal(t *testing.T) {
var k1, k2 DiscoKey
for i := range k1 {
@@ -530,11 +534,11 @@ func TestAppendKeyAllocs(t *testing.T) {
t.Skip("skipping in race detector") // append(b, make([]byte, N)...) not optimized in compiler with race
}
var k [32]byte
n := int(testing.AllocsPerRun(1000, func() {
err := tstest.MinAllocsPerRun(t, 1, func() {
sinkBytes = keyMarshalText("prefix", k)
}))
if n != 1 {
t.Fatalf("allocs = %v; want 1", n)
})
if err != nil {
t.Fatal(err)
}
}

51
tstest/allocs.go Normal file
View File

@@ -0,0 +1,51 @@
// 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 tstest
import (
"fmt"
"runtime"
"testing"
"time"
)
// MinAllocsPerRun asserts that f can run with no more than target allocations.
// It runs f up to 1000 times or 5s, whichever happens first.
// If f has executed more than target allocations on every run, it returns a non-nil error.
//
// MinAllocsPerRun sets GOMAXPROCS to 1 during its measurement and restores
// it before returning.
func MinAllocsPerRun(t *testing.T, target uint64, f func()) error {
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
var memstats runtime.MemStats
var min, max, sum uint64
start := time.Now()
var iters int
for {
runtime.ReadMemStats(&memstats)
startMallocs := memstats.Mallocs
f()
runtime.ReadMemStats(&memstats)
mallocs := memstats.Mallocs - startMallocs
// TODO: if mallocs < target, return an error? See discussion in #3204.
if mallocs <= target {
return nil
}
if min == 0 || mallocs < min {
min = mallocs
}
if mallocs > max {
max = mallocs
}
sum += mallocs
iters++
if iters == 1000 || time.Since(start) > 5*time.Second {
break
}
}
return fmt.Errorf("min allocs = %d, max allocs = %d, avg allocs/run = %f, want run with <= %d allocs", min, max, float64(sum)/float64(iters), target)
}

View File

@@ -10,7 +10,6 @@ package integration
import (
"bytes"
"crypto/rand"
"crypto/tls"
"encoding/json"
"fmt"
@@ -126,11 +125,7 @@ func exe() string {
func RunDERPAndSTUN(t testing.TB, logf logger.Logf, ipAddress string) (derpMap *tailcfg.DERPMap) {
t.Helper()
var serverPrivateKey key.Private
if _, err := rand.Read(serverPrivateKey[:]); err != nil {
t.Fatal(err)
}
d := derp.NewServer(serverPrivateKey, logf)
d := derp.NewServer(key.NewNode(), logf)
ln, err := net.Listen("tcp", net.JoinHostPort(ipAddress, "0"))
if err != nil {

View File

@@ -696,8 +696,8 @@ func (n *testNode) MustUp(extraArgs ...string) {
}
args = append(args, extraArgs...)
t.Logf("Running %v ...", args)
if err := n.Tailscale(args...).Run(); err != nil {
t.Fatalf("up: %v", err)
if b, err := n.Tailscale(args...).CombinedOutput(); err != nil {
t.Fatalf("up: %v, %v", string(b), err)
}
}

View File

@@ -274,9 +274,9 @@ func (s *Server) AddFakeNode() {
if s.nodes == nil {
s.nodes = make(map[tailcfg.NodeKey]*tailcfg.Node)
}
nk := tailcfg.NodeKey(key.NewPrivate().Public())
nk := tailcfg.NodeKeyFromNodePublic(key.NewNode().Public())
mk := key.NewMachine().Public()
dk := tailcfg.DiscoKey(key.NewPrivate().Public())
dk := tailcfg.DiscoKeyFromDiscoPublic(key.NewDisco().Public())
id := int64(binary.LittleEndian.Uint64(nk[:]))
ip := netaddr.IPv4(nk[0], nk[1], nk[2], nk[3])
addr := netaddr.IPPrefixFrom(ip, 32)

View File

@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by the following command; DO NOT EDIT.
// tailscale.com/cmd/cloner -type Resolver
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
//go:generate go run tailscale.com/cmd/cloner -type=Resolver -output=dnstype_clone.go -clonefunc
package dnstype

Some files were not shown because too many files have changed in this diff Show More