Compare commits

...

142 Commits

Author SHA1 Message Date
David Crawshaw
ad10cd71a6 tsweb: when unwrapping HTTPError, record the user-facing message also in the log
There's often some useful piece of information in there not already
repeated in the internal error.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-09-17 09:57:36 -04:00
David Crawshaw
dea3ef0597 tsweb: make JSONHandlerFunc implement ReturnHandler, not http.Handler
This way something is capable of logging errors on the server.

Fixes #766

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-09-17 09:07:32 -04:00
Brad Fitzpatrick
3aeb2e204c go.mod: bump github.com/tailscale/winipcfg-go for some Windows fixes 2020-09-16 14:49:00 -07:00
Brad Fitzpatrick
acafe9811f wgengine/router: run netsh advfirewall less, rename, document setFirewall 2020-09-16 14:42:37 -07:00
Christina Wen
48fbe93e72 wgengine/magicsock: clarify pre-disco 'tailscale ping' error message
This change clarifies the error message when a user pings a peer that is using an outdated version of Tailscale.
2020-09-16 11:54:00 -04:00
Brad Fitzpatrick
96fd20e3c0 ipn: bail out a bit earlier when a peer doesn't have a DNS name
It's properly handled later in tsdns.NewMap anyway, but there's work
done in the meantime that can be skipped when a peer lacks a DNS name.
It's also more clear that it's okay for it to be blank.
2020-09-16 07:55:16 -07:00
Josh Bleecher Snyder
7f97cf654d cmd/microproxy: add -insecure flag
This makes it easier to run microproxy locally during development.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-15 15:07:56 -07:00
Josh Bleecher Snyder
3fa863e6d9 cmd/derper: add missing html.EscapeString calls in /debug page
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-15 15:00:19 -07:00
Christina Wen
e862f90e34 wgengine/router/router_linux.go: fixed " route del failed" error (#756)
* wgengine/router/router_linux.go: Switched `cidrDiff("addr")` and `cidrDiff("route")` order

Signed-off-by: Christina Wen <christina@tailscale.com>

Co-authored-by: Christina Wen <christina@tailscale.com>
2020-09-15 14:14:33 -04:00
Brad Fitzpatrick
761fe19e5f control/controlclient: don't accept /32 routes without --accept-routes
Fixes tailscale/corp#500

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-09-15 10:18:14 -07:00
Brad Fitzpatrick
88107b1287 control/controlclient: add TS_DEBUG_MAPRESPONSE to dump all MapResponses
I'm always adding this by hand during development. Time to check it in.
2020-09-15 09:54:52 -07:00
Brad Fitzpatrick
931bcd44cb control/controlclient: report Synology "distro" + its version to control 2020-09-15 08:32:06 -07:00
Brad Fitzpatrick
7e9d1f7808 wgengine/router: tighten isMissingIPv6Err
So it doesn't false positive if misused.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-09-15 08:21:56 -07:00
David Anderson
8f5b52e571 net/netns: add windows support.
Also remove rebinding logic from the windows router. Magicsock will
instead rebind based on link change signals.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-09-14 16:28:49 -07:00
Josh Bleecher Snyder
3f4d93feb2 go.mod: bump depaware to get diffs out of -check, again
I had to use

go get -u github.com/tailscale/depaware@e09ee10c18249e4bf198e66bbd47babcd502637a

to force it to the correct version; it kept selecting head~1.

Maybe because the branch is called main instead of master?
Maybe because of some delay?
2020-09-14 16:25:09 -07:00
Josh Bleecher Snyder
a5d701095b wgengine/magicsock: increase test timeout to reduce flakiness
Updates #654. See that issue for a discussion of why
this timeout reduces flakiness, and what next steps are.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-14 14:29:28 -07:00
Josh Bleecher Snyder
0c0239242c wgengine/magicsock: make discoPingPurpose a stringer
It was useful for debugging once, it'll probably be useful again.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-14 14:29:28 -07:00
Josh Bleecher Snyder
6e38d29485 wgengine/magicsock: improve test logging output
This fixes line numbers and reduces timestamp
precision to overwhelming the output.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-14 14:29:28 -07:00
Josh Bleecher Snyder
41f6c78c53 go.mod: bump depaware to get diffs out of -check 2020-09-14 13:25:28 -07:00
Brad Fitzpatrick
662c19551a control/controlclient: deal with localized 'Version' string getting Windows version 2020-09-13 10:06:20 -07:00
Brad Fitzpatrick
4f7751e025 Update depaware for previous ipnserver change. 2020-09-11 19:35:12 -07:00
Brad Fitzpatrick
4f71319f7c ipn/ipnserver: make ipnserver also be an HTTP server for localhost clients
For now it just says hello to show auth works. More later.
2020-09-11 15:11:28 -07:00
Brad Fitzpatrick
3af64765fd ipn: fix Windows crash from improperly strict assertion 2020-09-11 15:10:29 -07:00
Brad Fitzpatrick
a084c44afc wgengine, wgengine/router, cmd/tailscale: force netfilter mode off on Synology
For now. Get it working again so it's not stuck on 0.98.

Subnet relay can come later.

Updates #451

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-09-11 13:12:40 -07:00
Brad Fitzpatrick
31c13013ae wgengine/router: tolerate disabled IPv6 on Windows
Fixes #412
Updates #524 (maybe fixes?)

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-09-11 12:50:35 -07:00
Josh Bleecher Snyder
9ab2b32569 syncs: add Watch, for monitoring mutex contention
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-11 11:36:07 -07:00
Brad Fitzpatrick
5a94317628 Makefile: remove tsshd from depaware
I'd removed it from the GitHub actions checks before submitted earlier
but forgot to update it here.
2020-09-11 11:09:32 -07:00
Josh Bleecher Snyder
37b40b035b wgengine/router/dns: appease staticcheck (again) 2020-09-11 11:02:57 -07:00
Brad Fitzpatrick
bc1751a376 util/pidowner: add missing copyright header 2020-09-11 08:57:10 -07:00
Brad Fitzpatrick
b14288f96c util/pidowner: add two missing copyright headers 2020-09-11 08:25:23 -07:00
Brad Fitzpatrick
23f01174ea util/pidowner: new package to map from process ID to its user ID 2020-09-11 08:19:21 -07:00
Brad Fitzpatrick
40e12c17ec net/netcheck: remove mistaken double Mutex.Unlock in error path
Thanks to @dotaheor for noticing.

Fixes #751
2020-09-11 07:55:49 -07:00
Brad Fitzpatrick
f65eb4e5c1 net/netstat: start of new netstat package, with Windows for now
This will be used in a future change to do localhost connection
authentication. This lets us quickly map a localhost TCP connection to
a PID. (A future change will then map a pid to a user)

TODO: pull portlist's netstat code into this package. Then portlist
will be fast on Windows without requiring shelling out to netstat.exe.
2020-09-10 15:24:49 -07:00
Brad Fitzpatrick
8b60936913 depaware: update deps 2020-09-10 09:29:15 -07:00
Brad Fitzpatrick
edb47b98a8 scripts: don't descend into .git for license header check 2020-09-10 09:23:33 -07:00
Josh Bleecher Snyder
a877dd575c wgengine/router/dns: remove unnecessary lint ignore line
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-09 12:27:52 -07:00
Josh Bleecher Snyder
bf24d54143 syncs: add AssertLocked
This allows us to check lock invariants.

It was proposed upstream and rejected in:
https://github.com/golang/go/issues/1366

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-09 12:27:52 -07:00
Brad Fitzpatrick
158202dbb1 go mod tidy 2020-09-09 12:17:44 -07:00
Brad Fitzpatrick
7795fcf464 Add tooldeps package to keep depaware pinned in go.mod. 2020-09-09 12:13:30 -07:00
Brad Fitzpatrick
22ed3c503e Add depaware.txt files and GitHub checks. (#745)
See https://github.com/tailscale/depaware

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-09-09 12:11:46 -07:00
Josh Bleecher Snyder
2e40c4b564 tstest: don't log on success
This is particularly important for benchmarks,
where stray output disrupts tooling.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-08 15:18:36 -07:00
Brad Fitzpatrick
913c1bd04f wgengine/router: on Windows, add a firewall rule to allow Tailscale
Updates tailscale/tailscale#454
2020-09-08 13:24:02 -07:00
Emmanuel T Odeke
688f923db1 log/logheap: properly document LogHeap as performing HTTP upload (#741)
LogHeap no longer logs to os.Stderr and instead uploads
the heap profile by means of an HTTP POST request to the
target URL endpoint.

While here, also ensured that an error from pprof.WriteHeapProfile
isn't ignored and will prevent the HTTP request from being made
if non-nil.

Signed-off-by: Emmanuel T Odeke <emmanuel@orijtech.com>
2020-09-07 19:17:53 -07:00
Josh Bleecher Snyder
96160973ce tailcfg: regenerate Clone methods
cmd/cloner has changed. Regenerate.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-04 16:25:18 -07:00
Josh Bleecher Snyder
7bd89359c9 cmd/cloner: generate a package-level Clone function
This Clone function knows how to clone any types
for which it has generated Clone methods.
This allows callers to efficiently clone
an inbound interface{} that might contain one of these types.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-04 16:25:18 -07:00
Josh Bleecher Snyder
99d223130c cmd/cloner: fix found type detection
This was causing any type to be reported as found,
as long as there were any type decls at all. Oops.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-04 13:43:17 -07:00
Josh Bleecher Snyder
2352690bde cmd/cloner: ensure cloner gets re-run when structs change
If you change a struct and don't re-run cloner,
your Cloner method might be inaccurate, leading to bad things.

To prevent this, write out the struct as it is at the moment that
cloner is caller, and attempt a conversion from that type.
If the struct gets changed in any way, this conversion will fail.

This will yield false positives: If you change a non-pointer field,
you will be forced to re-run cloner, even though the actual generated
code won't change. I think this is an acceptable cost: It is a minor
annoyance, which will prevent real bugs.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-04 13:11:21 -07:00
Brad Fitzpatrick
8ecee476f6 ipn: simplify TestLocalLogLines, defer a Shutdown of its LocalBackend
The test's LocalBackend was not shut down (Shutdown both releases
resources and waits for its various goroutines to end). This should
fix the test race we were seeing. It definitely fixes the file
descriptor leak that preventing -race -count=500 from passing before.
2020-09-04 08:36:07 -07:00
Brad Fitzpatrick
7fddc33481 tstest: make LogLineTracker pass variadic format args through
Omitting the "..." passed a literal []interface{} to the underlying
logger always.
2020-09-04 08:31:43 -07:00
Brad Fitzpatrick
68c42530e9 tstest: rename LogListener to LogLineTracker
But mostly to rename tstest.ListenFor which has no mention of log lines in it.
It sounded like a net.Listener or something.
2020-09-04 08:09:56 -07:00
David Crawshaw
95cddfcc75 tailcfg: add Clone methods to Login and DNSConfig
Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-09-04 07:48:45 -04:00
David Crawshaw
3baa084548 tstest: take testing.TB in helper for benchmarks
Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-09-04 07:48:45 -04:00
Brad Fitzpatrick
468bb3afce cmd/tailscale/cli: add debug -derp mode 2020-09-03 09:09:23 -07:00
Brad Fitzpatrick
9c25968b63 net/tshttpproxy: fix typo in Windows code added at least second
I ran tests & vet & staticcheck, but for Linux, not Windows.
(#728 would be nice)
2020-09-02 21:25:38 -07:00
Brad Fitzpatrick
82a3721661 go.sum: update 2020-09-02 21:25:28 -07:00
Brad Fitzpatrick
b026a638c7 net/tshttpproxy: if winhttp.GetProxyForURL blocks too long, use previous value
We currently have a chickend-and-egg situation in some environments
where we can set up routes that WinHTTP's WPAD/PAC resolution service
needs to download the PAC file to evaluate GetProxyForURL, but the PAC
file is behind a route for which we need to call GetProxyForURL to
e.g. dial a DERP server.

As a short-term fix, just assume that the most recently returned proxy
is good enough for such situations.
2020-09-02 21:15:36 -07:00
Josh Bleecher Snyder
a570c27577 portlist: reduce log spam on macOS
Running tailscaled on my machine yields lots of entries like:

weird: missing {tcp 6060}

parsePortsNetstat is filtering out loopback addresses as uninteresting.
Then addProcesses is surprised to discover these listening ports,
which results in spurious logging.
Teach addProcesses to also ignore loopback addresses.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-09-02 15:44:55 -07:00
Josh Bleecher Snyder
3b05cbacfb go.mod: update wireguard-go version 2020-09-02 12:22:49 -07:00
Josh Bleecher Snyder
57e642648f wgengine/magicsock: fix typo in comment 2020-09-02 11:34:20 -07:00
Brad Fitzpatrick
6d14678009 wgengine/router: remove unnecessary newlines in log.Printf formats 2020-09-01 13:27:42 -07:00
Brad Fitzpatrick
09d56f54a7 wgengine/router: fix Windows route sorting that caused de-dup to not work (#727)
Updates #725

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-09-01 13:24:58 -07:00
Brad Fitzpatrick
74ee374667 net/tshttpproxy: add more winhttp logging
Notably around duration of calls.
2020-08-28 21:37:49 -07:00
Brad Fitzpatrick
1e0be5a458 tshttp, derphttp: send Proxy-Authorization, not Authorization, to proxies
Whoops. But weirdly, sending Authorization sometimes worked?
2020-08-28 21:01:00 -07:00
halulu
3af2d671e6 cmd/tailscale/cli: add new flag --force-reauth to up subcommand (#717)
Signed-off-by: Halulu <lzjluzijie@gmail.com>
2020-08-28 20:49:14 +08:00
Brad Fitzpatrick
9b07517f18 wgengine: add Engine.SetLinkChangeCallback
Start of making the IPN state machine react to link changes and down
its DNS & routes if necessary to unblock proxy resolution (e.g. for
transitioning from public to corp networks where the corp network has
mandatory proxies and WPAD PAC files that can't be resolved while
using the DNS/routes configured previously)

This change should be a no-op. Just some callback plumbing.
2020-08-27 21:25:17 -07:00
halulu
bd37e40d2b cmd/tailscale/cli: status exit when disconnected (#720)
cmd/tailscale/cli: make status report stopped status, exit non-zero

Fixes #714
2020-08-27 19:02:32 -07:00
Dmytro Shynkevych
cb5f3c0819 tsdns: log when settings upstreams.
It turns out that otherwise we don't know what exactly was set.

Also remove the now unused RootDomain config option.

Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-27 17:01:59 -04:00
Brad Fitzpatrick
5acbb149a2 ipn/ipnstate: include DNSName in tailscale status --json output 2020-08-27 13:25:05 -07:00
Brad Fitzpatrick
2bac125cad net/tshttpproxy: move the TS_DEBUG_FAKE_PROXY_AUTH knob up a level 2020-08-27 08:17:57 -07:00
Dmytro Shynkevych
aa1da24f18 tsdns: remove now unused fields.
Missed in 7541982635.

Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-27 03:21:36 -04:00
Dmytro Shynkevych
7541982635 tsdns: remove forwarding queue.
Two levels of queueing are unnecessary.
The resulting implementation performs as follows
under request bursts (`count` packets sent concurrently):

lost  count            avg latency
   0 /  256 (00.00%) - 28ms
   0 /  512 (00.00%) - 146ms
   0 /  768 (00.00%) - 166ms
   0 / 1024 (00.00%) - 416ms
  11 / 1280 (00.86%) - 430ms
 145 / 1536 (09.44%) - 715ms
 364 / 2048 (17.77%) - 836ms

Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-27 03:18:17 -04:00
Dmytro Shynkevych
34a7e7c12b tsdns: be more strict with type validation.
Previously, a type AAAA query would be answered with an A record
if only an IPv4 address was available. This is irrelevant for us
while we only use IPv4, but it will be a bug one day,
so it's worth being precise about semantics.

Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-27 00:45:30 -04:00
Dmytro Shynkevych
bc34788e65 tsdns: fix accidental rejection of all non-{A, AAAA} questions.
This is a bug introduced in a903d6c2ed.

Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-27 00:42:56 -04:00
Brad Fitzpatrick
28f9cd06f5 tshttpproxy, controlclient, derphttp, logpolicy: send Negotiate auth to proxies
For Windows only, and only when built with Tailscale's Go tree.

Updates tailscale/corp#583
2020-08-26 20:08:05 -07:00
Brad Fitzpatrick
756d6a72bd wgengine: lazily create peer wireguard configs more explicitly
Rather than consider bigs jumps in last-received-from activity as a
signal to possibly reconfigure the set of wireguard peers to have
configured, instead just track the set of peers that are currently
excluded from the configuration. Easier to reason about.

Also adds a bit more logging.

This might fix an error we saw on a machine running a recent unstable
build:

2020-08-26 17:54:11.528033751 +0000 UTC: 8.6M/92.6M magicsock: [unexpected] lazy endpoint not created for [UcppE], d:42a770f678357249
2020-08-26 17:54:13.691305296 +0000 UTC: 8.7M/92.6M magicsock: DERP packet received from idle peer [UcppE]; created=false
2020-08-26 17:54:13.691383687 +0000 UTC: 8.7M/92.6M magicsock: DERP packet from unknown key: [UcppE]

If it does happen again, though, we'll have more logs.
2020-08-26 12:26:06 -07:00
Brad Fitzpatrick
483141094c cmd/tailscale/cli: add basic 'down' subcommand
RELNOTE=yes
2020-08-25 18:54:29 -07:00
halulu
f27a57911b cmd/tailscale: add derp and endpoints status (#703)
cmd/tailscale: add local node's information to status output (by default)

RELNOTE=yes

Updates #477

Signed-off-by: Halulu <lzjluzijie@gmail.com>
2020-08-25 16:26:10 -07:00
Brad Fitzpatrick
f915ab6552 net/tshttpproxy: add start of Kerberos Negotiate auth to proxies on Windows
For now only used by a new cmd/tailscale debug --get-url
subcommand. Not yet wired up to the places making HTTP requests.

Updates tailscale/corp#583
2020-08-25 13:49:06 -07:00
David Crawshaw
dd2c61a519 magicsock: call RequestStatus when DERP connects
Second attempt.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-08-25 16:35:28 -04:00
David Crawshaw
a67b174da1 Revert "magicsock: call RequestStatus when DERP connects"
Seems to break linux CI builder. Cannot reproduce locally,
so attempting a rollback.

This reverts commit cd7bc02ab1.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-08-25 15:15:37 -04:00
Wendi Yu
a3fb422a39 ipn: tag and test for grinder log lines (#711)
Signed-off-by: Wendi <wendi.yu@yahoo.ca>
2020-08-25 12:42:54 -06:00
David Crawshaw
cd7bc02ab1 magicsock: call RequestStatus when DERP connects
Without this, a freshly started ipn client will be stuck in the
"Starting" state until something triggers a call to RequestStatus.
Usually a UI does this, but until then we can sit in this state
until poked by an external event, as is evidenced by our e2e tests
locking up when DERP is attached.

(This only recently became a problem when we enabled lazy handshaking
everywhere, otherwise the wireugard tunnel creation would also
trigger a RequestStatus.)

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-08-25 10:38:02 -04:00
Brad Fitzpatrick
5e0375808b cmd/tailscale/cli: fix vet warning
And add vet to the "make check" target, like the CI has.
2020-08-24 21:35:27 -07:00
Brad Fitzpatrick
24d1a38e81 wgengine/monitor: add a poller to the Windows link change monitor
The poller is slow by default, but speeds up for a bit after a network
change, in case WPAD/PAC files are still loading.
2020-08-24 21:23:52 -07:00
Brad Fitzpatrick
1be6c6dd70 cmd/tailscale/cli: add hidden debug subcommand 2020-08-24 21:23:37 -07:00
Brad Fitzpatrick
169ff22a84 derp: set NotBefore and NotAfter in DERP server's metacert
Fixes regression from e415991256 that
only affected Windows users because Go only on Windows delegates x509
cert validation to the OS and Windows as unhappy with our "metacert"
lacking NotBefore and NotAfter.

Fixes #705
2020-08-24 14:57:44 -07:00
Dmytro Shynkevych
a903d6c2ed tailcfg, tsdns: derive root domains from list of nodes (#708)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-24 17:27:21 -04:00
renthraysk
10cad39abd net/stun: Fix STUN attribute padding (#710)
net/stun: fix STUN attribute padding

Signed-off-by: RenThraysk <renthraysk@gmail.com>
2020-08-24 12:52:13 -07:00
Brad Fitzpatrick
9be1917c5b net/tshttpproxy: discard secondary Windows proxies for now 2020-08-24 09:21:33 -07:00
Disconnect3d
44598e3e89 wgengine/monitor_freebsd.go: remove duplicated errcheck
Signed-off-by: disconnect3d <dominik.b.czarnota@gmail.com>
2020-08-21 09:48:22 -07:00
David Crawshaw
9e2e8c80af tailcfg: more Clone methods
Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-08-21 08:38:08 -04:00
Brad Fitzpatrick
7841c97af5 wgengine: make lazy wireguard on by default
It can still be explicitly enabled or disabled via the environment variable,
then via control.

But the default is to be lazy now.
2020-08-20 20:21:58 -07:00
Brad Fitzpatrick
557c23517b version: bump date 2020-08-20 20:21:58 -07:00
Dmytro Shynkevych
6c71e5b851 tsdns: copy name when loewrcasing.
The previous approach modifies name in-place in the request slice to avoid an allocation.
This is incorrect: the question section of a DNS request
must be copied verbatim, without any such modification.
Software may rely on it (we rely on other resolvers doing it it in tsdns/forwarder).

Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-20 19:02:23 -04:00
Dmytro Shynkevych
1886dfdca3 tsdns: lowercase the name in parseQuery.
Domains in DNS should be case-insensitive.

Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-20 18:05:40 -04:00
Brad Fitzpatrick
309c15dfdd types/key: restore Curve25519 clamping in NewPrivate
It was lost during a copy from wgcfg.NewPresharedKey (which doesn't
clamp) instead of wgcfg.NewPrivateKey (which does).

Fortunately this was only use for discovery messages (not WireGuard)
and only for ephemeral process-lifetime keys.
2020-08-20 14:25:28 -07:00
Brad Fitzpatrick
e415991256 derp, derp/derphttp: remove one RTT from DERP setup
* advertise server's DERP public key following its ServerHello
* have client look for that DEPR public key in the response
  PeerCertificates
* let client advertise it's going into a "fast start" mode
  if it finds it
* modify server to support that fast start mode, just not
  sending the HTTP response header

Cuts down another round trip, bringing the latency of being able to
write our first DERP frame from SF to Bangalore from ~725ms
(3 RTT) to ~481ms (2 RTT: TCP and TLS).

Fixes #693

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-08-20 14:00:55 -07:00
Brad Fitzpatrick
9337a99dff tailcfg, wgengine, controlclient: add control-side gating of lazy WG config 2020-08-20 13:21:25 -07:00
chungdaniel
4d56d19b46 control/controlclient, wgengine/filter: extract parsePacketFilter to … (#696)
control/controlclient, wgengine/filter: extract parsePacketFilter to new constructor in wgengine/filter

Signed-off-by: chungdaniel <daniel@tailscale.com>
2020-08-20 10:36:19 -07:00
Brad Fitzpatrick
9cb2df4ddd derp/derpmap: add London, Dallas, Seattle 2020-08-19 20:49:23 -07:00
Brad Fitzpatrick
1e562886f5 net/netcheck: in verbose mode, probe all regions
So 'tailscale netcheck --verbose' shows all regions' latencies.
2020-08-19 20:47:17 -07:00
Brad Fitzpatrick
461db356b9 wgengine/router/dns: fix staticcheck error on Mac 2020-08-19 15:12:30 -07:00
Brad Fitzpatrick
805850add9 derp: remove JSON struct tags in comments
They don't work in comments.

Added a test too to show that there's no change in behavior.
(It does case insensitive matching on parse anyway)
2020-08-19 14:36:43 -07:00
Dmytro Shynkevych
1af70e2468 tsdns: delegate requests asynchronously (#687)
Signed-Off-By: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-19 15:39:25 -04:00
Dmytro Shynkevych
a583e498b0 router/dns: set all domains on Windows (#672)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-19 14:16:57 -04:00
Brad Fitzpatrick
287522730d derp/derphttp: support standard-ish SSLKEYLOGFILE environment variable
For debugging.
2020-08-18 19:23:34 -07:00
Brad Fitzpatrick
862d223c39 Switch to Go 1.15.
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-08-18 15:48:30 -07:00
Brad Fitzpatrick
c5eb57f4d6 net/tshttpproxy: new package, support WPAD/PAC proxies on Windows
Updates tailscale/corp#553

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-08-18 15:40:56 -07:00
halulu
1835bb6f85 tsweb: rewrite JSONHandler without using reflect (#684)
Closes #656 #657

Signed-off-by: Zijie Lu <zijie@tailscale.com>
2020-08-18 17:37:01 -04:00
Brad Fitzpatrick
93ffc565e5 derp: remove protocol version 1 support
It hasn't existed for a long time and there are no current users.

Fixes #199
2020-08-17 16:17:56 -07:00
Brad Fitzpatrick
6b80bcf112 derp: remove a client round-trip waiting on serverInfo
It just has a version number in it and it's not really needed.
Instead just return it as a normal Recv message type for those
that care (currently only tests).

Updates #150 (in that it shares the same goal: initial DERP latency)
Updates #199 (in that it removes some DERP versioning)
2020-08-17 16:15:22 -07:00
Brad Fitzpatrick
f6dc47efe4 tailcfg, controlclient, magicsock: add control feature flag to enable DRPO
Updates #150
2020-08-17 13:01:39 -07:00
Brad Fitzpatrick
771e9541c7 cmd/tailscale/cli: appease staticcheck 2020-08-17 13:01:39 -07:00
Brad Fitzpatrick
337c86b89d control/controlclient: don't crash on invalid filter CIDR from server
Fixes #691
2020-08-17 07:56:03 -07:00
Brad Fitzpatrick
e64ab89712 derp/derpmap: add Bangalore and Tokyo 2020-08-14 13:29:14 -07:00
Brad Fitzpatrick
adf4f3cce0 cmd/tailscale/cli: make netcheck sort regions, show full region names 2020-08-14 13:29:02 -07:00
Brad Fitzpatrick
80d0b88a89 derp/derpmap: fix constructor argument order
Fix of 3e2bfe48c3
2020-08-14 13:21:48 -07:00
Ross Zurowski
f90f35c123 Merge pull request #686 from tailscale/rosszurowski/add-region-name-to-derpmap
derpmap: add full region name
2020-08-14 16:11:29 -04:00
Ross Zurowski
3e2bfe48c3 derpmap: add full region name
We're beginning to reference DERP region names in the admin UI, so it's
best to consolidate this information in our DERP map.

Signed-off-by: Ross Zurowski <ross@rosszurowski.com>
2020-08-14 15:57:11 -04:00
Josh Bleecher Snyder
062bd67d3b derp: use rand instead of crypto/rand to generate jitter
We don't need crypto/rand. Let the OS keep its entropy bits.

Signed-off-by: Josh Bleecher Snyder <josharian@gmail.com>
2020-08-13 14:06:50 -07:00
Brad Fitzpatrick
dbb4c246fa wgengine/monitor: add Windows linkchange monitor
Updates tailscale/corp#553

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-08-13 07:39:26 -07:00
Brad Fitzpatrick
85c3d17b3c wgengine/magicsock: use disco ping src as a candidate endpoint
Consider:

   Hard NAT (A) <---> Hard NAT w/ mapped port (B)

If A sends a packet to B's mapped port, A can disco ping B directly,
with low latency, without DERP.

But B couldn't establish a path back to A and needed to use DERP,
despite already logging about A's endpoint and adding a mapping to it
for other purposes (the wireguard conn.Endpoint lookup also needed
it).

This adds the tracking to discoEndpoint too so it'll be used for
finding a path back.

Fixes tailscale/corp#556

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-08-12 21:33:43 -07:00
Brad Fitzpatrick
0512fd89a1 wgengine/magicsock: simplify handlePingLocked
It's no longer true that 'de may be nil'
2020-08-12 19:25:38 -07:00
David Anderson
37c19970b3 derp: add a debug option to verbosely log drops to a destination.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-08-12 15:27:01 -07:00
Josh Bleecher Snyder
909c165382 derp: remove two key.Public allocations
Reading and writing a [32]byte key to a bufio.Reader/bufio.Writer
can easily by done without allocating. Do so.

It is slower; on my machine, it adds about 100ns per read/write.
However, the overall request takes a minimum of several µs,
and it cuts allocations meaningfully, so it is probably worth it.

name                      old time/op    new time/op    delta
SendRecv/msgsize=10-8       9.21µs ± 9%    9.08µs ± 8%     ~     (p=0.250 n=15+15)
SendRecv/msgsize=100-8      6.51µs ± 9%    6.60µs ± 7%     ~     (p=0.259 n=15+13)
SendRecv/msgsize=1000-8     7.24µs ±13%    7.61µs ±36%     ~     (p=1.000 n=11+15)
SendRecv/msgsize=10000-8    19.5µs ±15%    19.9µs ±25%     ~     (p=0.890 n=14+15)

name                      old speed      new speed      delta
SendRecv/msgsize=10-8     1.09MB/s ± 8%  1.10MB/s ± 8%     ~     (p=0.286 n=15+15)
SendRecv/msgsize=100-8    15.4MB/s ± 8%  15.1MB/s ± 6%     ~     (p=0.129 n=15+12)
SendRecv/msgsize=1000-8    139MB/s ±15%   135MB/s ±28%     ~     (p=1.000 n=11+15)
SendRecv/msgsize=10000-8   516MB/s ±17%   506MB/s ±21%     ~     (p=0.880 n=14+15)

name                      old alloc/op   new alloc/op   delta
SendRecv/msgsize=10-8         170B ± 1%      108B ± 1%  -36.63%  (p=0.000 n=15+15)
SendRecv/msgsize=100-8        265B ± 1%      203B ± 1%  -23.34%  (p=0.000 n=15+15)
SendRecv/msgsize=1000-8     1.18kB ± 1%    1.12kB ± 0%   -5.31%  (p=0.000 n=14+14)
SendRecv/msgsize=10000-8    18.8kB ± 2%    18.8kB ± 2%     ~     (p=0.443 n=12+12)

name                      old allocs/op  new allocs/op  delta
SendRecv/msgsize=10-8         4.00 ± 0%      2.00 ± 0%  -50.00%  (p=0.000 n=15+15)
SendRecv/msgsize=100-8        4.00 ± 0%      2.00 ± 0%  -50.00%  (p=0.000 n=15+15)
SendRecv/msgsize=1000-8       4.00 ± 0%      2.00 ± 0%  -50.00%  (p=0.000 n=15+15)
SendRecv/msgsize=10000-8      5.00 ± 0%      3.00 ± 0%  -40.00%  (p=0.000 n=13+14)

Signed-off-by: Josh Bleecher Snyder <josharian@gmail.com>
2020-08-12 15:15:58 -07:00
Brad Fitzpatrick
b983e5340f wgengine/monitor: add, clean up netlink logging on route changes
Updates #643
2020-08-12 13:27:14 -07:00
Mike Kramlich
6fa7a9a055 wgengine/router/router_userspace_bsd: on Mac the route program syntax expects delete not del -- this had caused router reconfig to fail in some cases. Fixes #673
Signed-off-by: Mike Kramlich <groglogic@gmail.com>
2020-08-12 13:22:19 -07:00
Dmytro Shynkevych
95a18f815c router/dns: detect host endianness for NetworkManager.
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
2020-08-12 16:13:05 -04:00
Brad Fitzpatrick
b97aac1718 net/interfaces: include DefaultRouteInterface in interfaces.State
And log it in wgengine on change. Changing bug in #643.

Updates #643
2020-08-12 12:48:34 -07:00
Brad Fitzpatrick
75225368a4 derp: fix 32-bit struct field alignment 2020-08-11 13:50:16 -07:00
David Anderson
15949ad77d derp: export the new expvar. 2020-08-11 19:59:08 +00:00
David Anderson
13661e195a derp: rename "wireguard" packet type to "other".
Strictly speaking, we don't know that it's a wireguard packet, just that
it doesn't look like a disco packet.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-08-11 19:30:39 +00:00
David Anderson
1b5b59231b derp: break down received packets by kind (disco vs. wireguard).
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-08-11 19:16:28 +00:00
David Anderson
c2b63ba363 cmd/microproxy: add a quick hack for some malformed variables.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-08-11 17:22:45 +00:00
Brad Fitzpatrick
5a0c37aafd logpolicy: consider /var/lib/tailscale when no STATE_DIRECTORY
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-08-10 20:44:32 -07:00
Brad Fitzpatrick
1f7a7a4ffe tailcfg: add missing Node.DERP check in Node.Equals
Updates tailscale/corp#549

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-08-10 19:46:22 -07:00
Brad Fitzpatrick
4e63a4fda3 cmd/tailscale/cli: remove already done TODO 2020-08-10 19:41:39 -07:00
Josh Bleecher Snyder
a9b1e3f9e8 net/interfaces: remove old debug old
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-08-10 14:28:16 -07:00
Josh Bleecher Snyder
e577303dc7 derp: make writeUint32 and readUint32 not allocate
The allocations are small, but they're easy enough to avoid.
And it removes some clutter from the pprof output.

name                      old time/op    new time/op    delta
SendRecv/msgsize=10-8       10.1µs ± 9%     9.7µs ± 7%    -3.45%  (p=0.035 n=14+14)
SendRecv/msgsize=100-8      8.12µs ± 7%    7.38µs ± 9%    -9.02%  (p=0.000 n=15+15)
SendRecv/msgsize=1000-8     9.51µs ±25%    8.76µs ±22%      ~     (p=0.202 n=15+15)
SendRecv/msgsize=10000-8    21.1µs ±25%    19.9µs ±14%      ~     (p=0.270 n=15+14)
WriteUint32-8               25.1ns ± 4%    21.3ns ±12%   -15.01%  (p=0.000 n=14+14)
ReadUint32-8                35.4ns ± 4%    21.9ns ± 4%   -38.06%  (p=0.000 n=15+15)

name                      old alloc/op   new alloc/op   delta
SendRecv/msgsize=10-8         182B ± 2%      169B ± 1%    -7.22%  (p=0.000 n=15+13)
SendRecv/msgsize=100-8        282B ± 1%      265B ± 1%    -5.85%  (p=0.000 n=15+15)
SendRecv/msgsize=1000-8     1.19kB ± 1%    1.18kB ± 0%    -1.26%  (p=0.000 n=14+15)
SendRecv/msgsize=10000-8    19.3kB ± 4%    18.7kB ± 4%    -3.44%  (p=0.006 n=12+12)
WriteUint32-8                4.00B ± 0%     0.00B       -100.00%  (p=0.000 n=15+15)
ReadUint32-8                 4.00B ± 0%     0.00B       -100.00%  (p=0.000 n=15+15)

name                      old allocs/op  new allocs/op  delta
SendRecv/msgsize=10-8         8.00 ± 0%      4.00 ± 0%   -50.00%  (p=0.000 n=15+15)
SendRecv/msgsize=100-8        8.00 ± 0%      4.00 ± 0%   -50.00%  (p=0.000 n=15+15)
SendRecv/msgsize=1000-8       8.00 ± 0%      4.00 ± 0%   -50.00%  (p=0.000 n=15+15)
SendRecv/msgsize=10000-8      8.47 ±17%      5.00 ± 0%   -40.94%  (p=0.000 n=15+14)
WriteUint32-8                 1.00 ± 0%      0.00       -100.00%  (p=0.000 n=15+15)
ReadUint32-8                  1.00 ± 0%      0.00       -100.00%  (p=0.000 n=15+15)

Signed-off-by: Josh Bleecher Snyder <josharian@gmail.com>
2020-08-10 14:15:37 -07:00
Josh Bleecher Snyder
355c6296f0 derp: add readUint32 and writeUint32 benchmarks
These aren't particularly performance critical,
but since I have an optimization pending for them,
it's worth having a corresponding benchmark.

Signed-off-by: Josh Bleecher Snyder <josharian@gmail.com>
2020-08-10 14:15:37 -07:00
Brad Fitzpatrick
25b021388b net/netns, net/interfaces: move defaultRouteInterface, add Android fallback
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-08-10 13:02:47 -07:00
Brad Fitzpatrick
84dc891843 cmd/tailscale/cli: add ping subcommand
For example:

$ tailscale ping -h
USAGE
  ping <hostname-or-IP>

FLAGS
  -c 10                   max number of pings to send
  -stop-once-direct true  stop once a direct path is established
  -verbose false          verbose output

$ tailscale ping mon.ts.tailscale.com
pong from monitoring (100.88.178.64) via DERP(sfo) in 65ms
pong from monitoring (100.88.178.64) via DERP(sfo) in 252ms
pong from monitoring (100.88.178.64) via [2604:a880:2:d1::36:d001]:41641 in 33ms

Fixes #661

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-08-10 12:50:56 -07:00
127 changed files with 6214 additions and 1199 deletions

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.14
go-version: 1.15
id: go
- name: Check out code into the Go module directory

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.14
go-version: 1.15
id: go
- name: Check out code into the Go module directory

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.14
go-version: 1.15
id: go
- name: Check out code into the Go module directory

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.14
go-version: 1.15
id: go
- name: Check out code into the Go module directory

28
.github/workflows/depaware.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: depaware
on:
push:
branches:
- main
pull_request:
branches:
- '*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
- name: Check out code
uses: actions/checkout@v1
- name: depaware tailscaled
run: go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscaled
- name: depaware tailscale
run: go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscale

View File

@@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.14
go-version: 1.15
- name: Check out code
uses: actions/checkout@v1

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.14
go-version: 1.15
id: go
- name: Check out code into the Go module directory

View File

@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.14
go-version: 1.15
id: go
- name: Check out code into the Go module directory

View File

@@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.14
go-version: 1.15
- name: Check out code
uses: actions/checkout@v1

View File

@@ -1,7 +1,18 @@
usage:
echo "See Makefile"
check: staticcheck
vet:
go vet ./...
updatedeps:
go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscaled
go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscale
depaware:
go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscaled
go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscale
check: staticcheck vet depaware
staticcheck:
go run honnef.co/go/tools/cmd/staticcheck -- $$(go list ./... | grep -v tempfork)

View File

@@ -31,7 +31,7 @@ go install tailscale.com/cmd/tailscale{,d}
```
We only guarantee to support the latest Go release and any Go beta or
release candidate builds (currently Go 1.14) in module mode. It might
release candidate builds (currently Go 1.15) in module mode. It might
work in earlier Go versions or in GOPATH mode, but we're making no
effort to keep those working.

View File

@@ -86,8 +86,8 @@ func main() {
}
pkg := typeNameObj.Pkg()
gen(buf, imports, typeName, typ, pkg)
found = true
}
found = true
}
}
if !found {
@@ -95,6 +95,29 @@ func main() {
}
}
w := func(format string, args ...interface{}) {
fmt.Fprintf(buf, format+"\n", args...)
}
w("// Clone duplicates src into dst and reports whether it succeeded.")
w("// To succeed, <src, dst> must be of types <*T, *T> or <*T, **T>,")
w("// where T is one of %s.", *flagTypes)
w("func Clone(dst, src interface{}) bool {")
w(" switch src := src.(type) {")
for _, typeName := range typeNames {
w(" case *%s:", typeName)
w(" switch dst := dst.(type) {")
w(" case *%s:", typeName)
w(" *dst = *src.Clone()")
w(" return true")
w(" case **%s:", typeName)
w(" *dst = src.Clone()")
w(" return true")
w(" }")
}
w(" }")
w(" return false")
w("}")
contents := new(bytes.Buffer)
fmt.Fprintf(contents, header, *flagTypes, pkg.Name)
fmt.Fprintf(contents, "import (\n")
@@ -143,7 +166,19 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, name string, typ *types
switch t := typ.Underlying().(type) {
case *types.Struct:
_ = t
// We generate two bits of code simultaneously while we walk the struct.
// One is the Clone method itself, which we write directly to buf.
// The other is a variable assignment that will fail if the struct
// changes without the Clone method getting regenerated.
// We write that to regenBuf, and then append it to buf at the end.
regenBuf := new(bytes.Buffer)
writeRegen := func(format string, args ...interface{}) {
fmt.Fprintf(regenBuf, format+"\n", args...)
}
writeRegen("// A compilation failure here means this code must be regenerated, with command:")
writeRegen("// tailscale.com/cmd/cloner -type %s", *flagTypes)
writeRegen("var _%sNeedsRegeneration = %s(struct {", name, name)
name := typ.Obj().Name()
fmt.Fprintf(buf, "// Clone makes a deep copy of %s.\n", name)
fmt.Fprintf(buf, "// The result aliases no memory with the original.\n")
@@ -159,6 +194,9 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, name string, typ *types
for i := 0; i < t.NumFields(); i++ {
fname := t.Field(i).Name()
ft := t.Field(i).Type()
writeRegen("\t%s %s", fname, importedName(ft))
if !containsPointers(ft) {
continue
}
@@ -220,6 +258,10 @@ func gen(buf *bytes.Buffer, imports map[string]struct{}, name string, typ *types
}
writef("return dst")
fmt.Fprintf(buf, "}\n\n")
writeRegen("}{})\n")
buf.Write(regenBuf.Bytes())
}
}

View File

@@ -7,11 +7,13 @@ package main // import "tailscale.com/cmd/derper"
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"expvar"
"flag"
"fmt"
"html"
"io"
"io/ioutil"
"log"
@@ -185,6 +187,15 @@ func main() {
certManager.Email = "security@tailscale.com"
}
httpsrv.TLSConfig = certManager.TLSConfig()
letsEncryptGetCert := httpsrv.TLSConfig.GetCertificate
httpsrv.TLSConfig.GetCertificate = func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert, err := letsEncryptGetCert(hi)
if err != nil {
return nil, err
}
cert.Certificate = append(cert.Certificate, s.MetaCert())
return cert, nil
}
go func() {
err := http.ListenAndServe(":80", certManager.HTTPHandler(tsweb.Port80Handler{Main: mux}))
if err != nil {
@@ -219,10 +230,10 @@ func debugHandler(s *derp.Server) http.Handler {
<h1>DERP debug</h1>
<ul>
`)
f("<li><b>Hostname:</b> %v</li>\n", *hostname)
f("<li><b>Hostname:</b> %v</li>\n", html.EscapeString(*hostname))
f("<li><b>Uptime:</b> %v</li>\n", tsweb.Uptime())
f("<li><b>Mesh Key:</b> %v</li>\n", s.HasMeshKey())
f("<li><b>Version:</b> %v</li>\n", version.LONG)
f("<li><b>Version:</b> %v</li>\n", html.EscapeString(version.LONG))
f(`<li><a href="/debug/vars">/debug/vars</a> (Go)</li>
<li><a href="/debug/varz">/debug/varz</a> (Prometheus)</li>

View File

@@ -19,6 +19,7 @@ import (
"net/http/httputil"
"net/url"
"path/filepath"
"strings"
"sync"
"time"
@@ -33,6 +34,7 @@ var (
logCollection = flag.String("logcollection", "", "If non-empty, logtail collection to log to")
nodeExporter = flag.String("node-exporter", "http://localhost:9100", "URL of the local prometheus node exporter")
goVarsURL = flag.String("go-vars-url", "http://localhost:8383/debug/vars", "URL of a local Go server's /debug/vars endpoint")
insecure = flag.Bool("insecure", false, "serve over http, for development")
)
func main() {
@@ -65,12 +67,15 @@ func main() {
httpsrv := &http.Server{
Addr: *addr,
Handler: mux,
TLSConfig: &tls.Config{
GetCertificate: ch.GetCertificate,
},
}
if err := httpsrv.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
if !*insecure {
httpsrv.TLSConfig = &tls.Config{GetCertificate: ch.GetCertificate}
err = httpsrv.ListenAndServeTLS("", "")
} else {
err = httpsrv.ListenAndServe()
}
if err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}
@@ -88,7 +93,16 @@ func promPrint(w io.Writer, prefix string, obj map[string]interface{}) {
case map[string]interface{}:
promPrint(w, k, v)
case float64:
fmt.Fprintf(w, "%s %f\n", k, v)
const saveConfigReject = "control_save_config_rejected_"
const saveConfig = "control_save_config_"
switch {
case strings.HasPrefix(k, saveConfigReject):
fmt.Fprintf(w, "control_save_config_rejected{reason=%q} %f\n", k[len(saveConfigReject):], v)
case strings.HasPrefix(k, saveConfig):
fmt.Fprintf(w, "control_save_config{reason=%q} %f\n", k[len(saveConfig):], v)
default:
fmt.Fprintf(w, "%s %f\n", k, v)
}
default:
fmt.Fprintf(w, "# Skipping key %q, unhandled type %T\n", k, v)
}

View File

@@ -31,7 +31,8 @@ func ActLikeCLI() bool {
return false
}
switch os.Args[1] {
case "up", "status", "netcheck", "version",
case "up", "down", "status", "netcheck", "ping", "version",
"debug",
"-V", "--version", "-h", "--help":
return true
}
@@ -57,14 +58,21 @@ change in the future.
`),
Subcommands: []*ffcli.Command{
upCmd,
downCmd,
netcheckCmd,
statusCmd,
pingCmd,
versionCmd,
},
FlagSet: rootfs,
Exec: func(context.Context, []string) error { return flag.ErrHelp },
}
// Don't advertise the debug command, but it exists.
if strSliceContains(args, "debug") {
rootCmd.Subcommands = append(rootCmd.Subcommands, debugCmd)
}
if err := rootCmd.Parse(args); err != nil {
return err
}
@@ -126,3 +134,12 @@ func pump(ctx context.Context, bc *ipn.BackendClient, conn net.Conn) {
bc.GotNotifyMsg(msg)
}
}
func strSliceContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}

175
cmd/tailscale/cli/debug.go Normal file
View File

@@ -0,0 +1,175 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cli
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"net/http"
"net/http/httptrace"
"net/url"
"os"
"time"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/derp/derphttp"
"tailscale.com/derp/derpmap"
"tailscale.com/net/interfaces"
"tailscale.com/net/tshttpproxy"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/wgengine/monitor"
)
var debugCmd = &ffcli.Command{
Name: "debug",
Exec: runDebug,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("debug", flag.ExitOnError)
fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run link monitor forever. Precludes all other options.")
fs.StringVar(&debugArgs.getURL, "get-url", "", "If non-empty, fetch provided URL.")
fs.StringVar(&debugArgs.derpCheck, "derp", "", "if non-empty, test a DERP ping via named region code")
return fs
})(),
}
var debugArgs struct {
monitor bool
getURL string
derpCheck string
}
func runDebug(ctx context.Context, args []string) error {
if len(args) > 0 {
return errors.New("unknown arguments")
}
if debugArgs.derpCheck != "" {
return checkDerp(ctx, debugArgs.derpCheck)
}
if debugArgs.monitor {
return runMonitor(ctx)
}
if debugArgs.getURL != "" {
return getURL(ctx, debugArgs.getURL)
}
return errors.New("only --monitor is available at the moment")
}
func runMonitor(ctx context.Context) error {
dump := func() {
st, err := interfaces.GetState()
if err != nil {
log.Printf("error getting state: %v", err)
return
}
j, _ := json.MarshalIndent(st, "", " ")
os.Stderr.Write(j)
}
mon, err := monitor.New(log.Printf, func() {
log.Printf("Link monitor fired. State:")
dump()
})
if err != nil {
return err
}
log.Printf("Starting link change monitor; initial state:")
dump()
mon.Start()
log.Printf("Started link change monitor; waiting...")
select {}
}
func getURL(ctx context.Context, urlStr string) error {
if urlStr == "login" {
urlStr = "https://login.tailscale.com"
}
log.SetOutput(os.Stdout)
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
GetConn: func(hostPort string) { log.Printf("GetConn(%q)", hostPort) },
GotConn: func(info httptrace.GotConnInfo) { log.Printf("GotConn: %+v", info) },
DNSStart: func(info httptrace.DNSStartInfo) { log.Printf("DNSStart: %+v", info) },
DNSDone: func(info httptrace.DNSDoneInfo) { log.Printf("DNSDoneInfo: %+v", info) },
TLSHandshakeStart: func() { log.Printf("TLSHandshakeStart") },
TLSHandshakeDone: func(cs tls.ConnectionState, err error) { log.Printf("TLSHandshakeDone: %+v, %v", cs, err) },
WroteRequest: func(info httptrace.WroteRequestInfo) { log.Printf("WroteRequest: %+v", info) },
})
req, err := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
if err != nil {
return fmt.Errorf("http.NewRequestWithContext: %v", err)
}
proxyURL, err := tshttpproxy.ProxyFromEnvironment(req)
if err != nil {
return fmt.Errorf("tshttpproxy.ProxyFromEnvironment: %v", err)
}
log.Printf("proxy: %v", proxyURL)
tr := &http.Transport{
Proxy: func(*http.Request) (*url.URL, error) { return proxyURL, nil },
ProxyConnectHeader: http.Header{},
DisableKeepAlives: true,
}
if proxyURL != nil {
auth, err := tshttpproxy.GetAuthHeader(proxyURL)
if err == nil && auth != "" {
tr.ProxyConnectHeader.Set("Proxy-Authorization", auth)
}
const truncLen = 20
if len(auth) > truncLen {
auth = fmt.Sprintf("%s...(%d total bytes)", auth[:truncLen], len(auth))
}
log.Printf("tshttpproxy.GetAuthHeader(%v) for Proxy-Auth: = %q, %v", proxyURL, auth, err)
}
res, err := tr.RoundTrip(req)
if err != nil {
return fmt.Errorf("Transport.RoundTrip: %v", err)
}
defer res.Body.Close()
return res.Write(os.Stdout)
}
func checkDerp(ctx context.Context, derpRegion string) error {
dmap := derpmap.Prod()
getRegion := func() *tailcfg.DERPRegion {
for _, r := range dmap.Regions {
if r.RegionCode == derpRegion {
return r
}
}
for _, r := range dmap.Regions {
log.Printf("Known region: %q", r.RegionCode)
}
log.Fatalf("unknown region %q", derpRegion)
panic("unreachable")
}
priv1 := key.NewPrivate()
priv2 := key.NewPrivate()
c1 := derphttp.NewRegionClient(priv1, log.Printf, getRegion)
c2 := derphttp.NewRegionClient(priv2, log.Printf, getRegion)
c2.NotePreferred(true) // just to open it
m, err := c2.Recv()
log.Printf("c2 got %T, %v", m, err)
t0 := time.Now()
if err := c1.Send(priv2.Public(), []byte("hello")); err != nil {
return err
}
fmt.Println(time.Since(t0))
m, err = c2.Recv()
log.Printf("c2 got %T, %v", m, err)
if err != nil {
return err
}
log.Printf("ok")
return err
}

67
cmd/tailscale/cli/down.go Normal file
View File

@@ -0,0 +1,67 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cli
import (
"context"
"log"
"time"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/ipn"
)
var downCmd = &ffcli.Command{
Name: "down",
ShortUsage: "down",
ShortHelp: "Disconnect from Tailscale",
Exec: runDown,
}
func runDown(ctx context.Context, args []string) error {
if len(args) > 0 {
log.Fatalf("too many non-flag arguments: %q", args)
}
c, bc, ctx, cancel := connect(ctx)
defer cancel()
timer := time.AfterFunc(5*time.Second, func() {
log.Fatalf("timeout running stop")
})
defer timer.Stop()
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
}
if n.Status != nil {
cur := n.Status.BackendState
switch cur {
case "Stopped":
log.Printf("already stopped")
cancel()
default:
log.Printf("was in state %q", cur)
}
return
}
if n.State != nil {
log.Printf("now in state %q", *n.State)
if *n.State == ipn.Stopped {
cancel()
}
return
}
log.Printf("Notify: %#v", n)
})
bc.RequestStatus()
bc.SetWantRunning(false)
pump(ctx, bc, c)
return nil
}

View File

@@ -125,20 +125,35 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
if len(report.RegionLatency) == 0 {
fmt.Printf("\t* Nearest DERP: unknown (no response to latency probes)\n")
} else {
fmt.Printf("\t* Nearest DERP: %v (%v)\n", report.PreferredDERP, dm.Regions[report.PreferredDERP].RegionCode)
fmt.Printf("\t* Nearest DERP: %v\n", dm.Regions[report.PreferredDERP].RegionName)
fmt.Printf("\t* DERP latency:\n")
var rids []int
for rid := range dm.Regions {
rids = append(rids, rid)
}
sort.Ints(rids)
sort.Slice(rids, func(i, j int) bool {
l1, ok1 := report.RegionLatency[rids[i]]
l2, ok2 := report.RegionLatency[rids[j]]
if ok1 != ok2 {
return ok1 // defined things sort first
}
if !ok1 {
return rids[i] < rids[j]
}
return l1 < l2
})
for _, rid := range rids {
d, ok := report.RegionLatency[rid]
var latency string
if ok {
latency = d.Round(time.Millisecond / 10).String()
}
fmt.Printf("\t\t- %v, %3s = %s\n", rid, dm.Regions[rid].RegionCode, latency)
r := dm.Regions[rid]
var derpNum string
if netcheckArgs.verbose {
derpNum = fmt.Sprintf("derp%d, ", rid)
}
fmt.Printf("\t\t- %3s: %-7s (%s%s)\n", r.RegionCode, latency, derpNum, r.RegionName)
}
}
return nil

130
cmd/tailscale/cli/ping.go Normal file
View File

@@ -0,0 +1,130 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cli
import (
"context"
"errors"
"flag"
"fmt"
"log"
"net"
"strings"
"time"
"github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
)
var pingCmd = &ffcli.Command{
Name: "ping",
ShortUsage: "ping <hostname-or-IP>",
ShortHelp: "Ping a host at the Tailscale layer, see how it routed",
LongHelp: strings.TrimSpace(`
The 'tailscale ping' command pings a peer node at the Tailscale layer
and reports which route it took for each response. The first ping or
so will likely go over DERP (Tailscale's TCP relay protocol) while NAT
traversal finds a direct path through.
If 'tailscale ping' works but a normal ping does not, that means one
side's operating system firewall is blocking packets; 'tailscale ping'
does not inject packets into either side's TUN devices.
By default, 'tailscale ping' stops after 10 pings or once a direct
(non-DERP) path has been established, whichever comes first.
The provided hostname must resolve to or be a Tailscale IP
(e.g. 100.x.y.z) or a subnet IP advertised by a Tailscale
relay node.
`),
Exec: runPing,
FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("ping", flag.ExitOnError)
fs.BoolVar(&pingArgs.verbose, "verbose", false, "verbose output")
fs.BoolVar(&pingArgs.untilDirect, "until-direct", true, "stop once a direct path is established")
fs.IntVar(&pingArgs.num, "c", 10, "max number of pings to send")
fs.DurationVar(&pingArgs.timeout, "timeout", 5*time.Second, "timeout before giving up on a ping")
return fs
})(),
}
var pingArgs struct {
num int
untilDirect bool
verbose bool
timeout time.Duration
}
func runPing(ctx context.Context, args []string) error {
c, bc, ctx, cancel := connect(ctx)
defer cancel()
if len(args) != 1 {
return errors.New("usage: ping <hostname-or-IP>")
}
hostOrIP := args[0]
var ip string
var res net.Resolver
if addrs, err := res.LookupHost(ctx, hostOrIP); err != nil {
return fmt.Errorf("error looking up IP of %q: %v", hostOrIP, err)
} else if len(addrs) == 0 {
return fmt.Errorf("no IPs found for %q", hostOrIP)
} else {
ip = addrs[0]
}
if pingArgs.verbose && ip != hostOrIP {
log.Printf("lookup %q => %q", hostOrIP, ip)
}
ch := make(chan *ipnstate.PingResult, 1)
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
log.Fatal(*n.ErrMessage)
}
if pr := n.PingResult; pr != nil && pr.IP == ip {
ch <- pr
}
})
go pump(ctx, bc, c)
n := 0
anyPong := false
for {
n++
bc.Ping(ip)
timer := time.NewTimer(pingArgs.timeout)
select {
case <-timer.C:
fmt.Printf("timeout waiting for ping reply\n")
case pr := <-ch:
timer.Stop()
if pr.Err != "" {
return errors.New(pr.Err)
}
latency := time.Duration(pr.LatencySeconds * float64(time.Second)).Round(time.Millisecond)
via := pr.Endpoint
if pr.DERPRegionID != 0 {
via = fmt.Sprintf("DERP(%s)", pr.DERPRegionCode)
}
anyPong = true
fmt.Printf("pong from %s (%s) via %v in %v\n", pr.NodeName, pr.NodeIP, via, latency)
if pr.Endpoint != "" && pingArgs.untilDirect {
return nil
}
time.Sleep(time.Second)
case <-ctx.Done():
return ctx.Err()
}
if n == pingArgs.num {
if !anyPong {
return errors.New("no reply")
}
return nil
}
}
}

View File

@@ -33,6 +33,7 @@ var statusCmd = &ffcli.Command{
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)")
fs.BoolVar(&statusArgs.self, "self", true, "show status of local machine")
fs.StringVar(&statusArgs.listen, "listen", "127.0.0.1:8384", "listen address; use port 0 for automatic")
fs.BoolVar(&statusArgs.browser, "browser", true, "Open a browser in web mode")
return fs
@@ -45,6 +46,7 @@ var statusArgs struct {
listen string // in web mode, webserver address to listen on, empty means auto
browser bool // in web mode, whether to open browser
active bool // in CLI mode, filter output to only peers with active sessions
self bool // in CLI mode, show status of local machine
}
func runStatus(ctx context.Context, args []string) error {
@@ -125,16 +127,17 @@ func runStatus(ctx context.Context, args []string) error {
return err
}
if st.BackendState == ipn.Stopped.String() {
fmt.Println("Tailscale is stopped.")
os.Exit(1)
}
var buf bytes.Buffer
f := func(format string, a ...interface{}) { fmt.Fprintf(&buf, format, a...) }
for _, peer := range st.Peers() {
ps := st.Peer[peer]
printPS := func(ps *ipnstate.PeerStatus) {
active := peerActive(ps)
if statusArgs.active && !active {
continue
}
f("%s %-7s %-15s %-18s tx=%8d rx=%8d ",
peer.ShortString(),
ps.PublicKey.ShortString(),
ps.OS,
ps.TailAddr,
ps.SimpleHostName(),
@@ -160,6 +163,18 @@ func runStatus(ctx context.Context, args []string) error {
}
f("\n")
}
if statusArgs.self && st.Self != nil {
printPS(st.Self)
}
for _, peer := range st.Peers() {
ps := st.Peer[peer]
active := peerActive(ps)
if statusArgs.active && !active {
continue
}
printPS(ps)
}
os.Stdout.Write(buf.Bytes())
return nil
}

View File

@@ -7,6 +7,7 @@ package cli
import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"log"
@@ -15,6 +16,7 @@ import (
"runtime"
"strconv"
"strings"
"sync"
"github.com/peterbourgon/ff/v2/ffcli"
"github.com/tailscale/wireguard-go/wgcfg"
@@ -22,6 +24,7 @@ import (
"tailscale.com/ipn"
"tailscale.com/tailcfg"
"tailscale.com/version"
"tailscale.com/version/distro"
"tailscale.com/wgengine/router"
)
@@ -53,6 +56,7 @@ specify any flags, options are reset to their default.
upf.BoolVar(&upArgs.acceptDNS, "accept-dns", true, "accept DNS configuration from the admin panel")
upf.BoolVar(&upArgs.singleRoutes, "host-routes", true, "install host routes to other Tailscale nodes")
upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
upf.BoolVar(&upArgs.forceReauth, "force-reauth", false, "force reauthentication")
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "ACL tags to request (comma-separated, e.g. eng,montreal,ssh)")
upf.StringVar(&upArgs.authKey, "authkey", "", "node authorization key")
upf.StringVar(&upArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
@@ -61,20 +65,28 @@ specify any flags, options are reset to their default.
upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. 10.0.0.0/8,192.168.0.0/24)")
}
if runtime.GOOS == "linux" {
upf.BoolVar(&upArgs.snat, "snat-subnet-routes", true, "source NAT traffic to local routes advertised with -advertise-routes")
upf.StringVar(&upArgs.netfilterMode, "netfilter-mode", "on", "netfilter mode (one of on, nodivert, off)")
upf.BoolVar(&upArgs.snat, "snat-subnet-routes", true, "source NAT traffic to local routes advertised with --advertise-routes")
upf.StringVar(&upArgs.netfilterMode, "netfilter-mode", defaultNetfilterMode(), "netfilter mode (one of on, nodivert, off)")
}
return upf
})(),
Exec: runUp,
}
func defaultNetfilterMode() string {
if distro.Get() == distro.Synology {
return "off"
}
return "on"
}
var upArgs struct {
server string
acceptRoutes bool
acceptDNS bool
singleRoutes bool
shieldsUp bool
forceReauth bool
advertiseRoutes string
advertiseTags string
enableDERP bool
@@ -148,6 +160,19 @@ func runUp(ctx context.Context, args []string) error {
log.Fatalf("too many non-flag arguments: %q", args)
}
if distro.Get() == distro.Synology {
notSupported := "not yet supported on Synology; see https://github.com/tailscale/tailscale/issues/451"
if upArgs.advertiseRoutes != "" {
return errors.New("--advertise-routes is " + notSupported)
}
if upArgs.acceptRoutes {
return errors.New("--accept-routes is " + notSupported)
}
if upArgs.netfilterMode != "off" {
return errors.New("--netfilter-mode values besides \"off\" " + notSupported)
}
}
var routes []wgcfg.CIDR
if upArgs.advertiseRoutes != "" {
advroutes := strings.Split(upArgs.advertiseRoutes, ",")
@@ -181,7 +206,6 @@ func runUp(ctx context.Context, args []string) error {
}
// TODO(apenwarr): fix different semantics between prefs and uflags
// TODO(apenwarr): allow setting/using CorpDNS
prefs := ipn.NewPrefs()
prefs.ControlURL = upArgs.server
prefs.WantRunning = true
@@ -213,6 +237,8 @@ func runUp(ctx context.Context, args []string) error {
defer cancel()
var printed bool
var loginOnce sync.Once
startLoginInteractive := func() { loginOnce.Do(func() { bc.StartLoginInteractive() }) }
bc.SetPrefs(prefs)
opts := ipn.Options{
@@ -226,7 +252,7 @@ func runUp(ctx context.Context, args []string) error {
switch *s {
case ipn.NeedsLogin:
printed = true
bc.StartLoginInteractive()
startLoginInteractive()
case ipn.NeedsMachineAuth:
printed = true
fmt.Fprintf(os.Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s/admin/machines\n\n", upArgs.server)
@@ -252,6 +278,10 @@ func runUp(ctx context.Context, args []string) error {
// ephemeral frontends that read/modify/write state, once
// Windows/Mac state is moved into backend.
bc.Start(opts)
if upArgs.forceReauth {
printed = true
startLoginInteractive()
}
pump(ctx, bc, c)
return nil

219
cmd/tailscale/depaware.txt Normal file
View File

@@ -0,0 +1,219 @@
tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/depaware)
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
github.com/apenwarr/fixconsole from tailscale.com/cmd/tailscale
W 💣 github.com/apenwarr/w32 from github.com/apenwarr/fixconsole
L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
L 💣 github.com/godbus/dbus/v5 from tailscale.com/wgengine/router/dns
github.com/golang/groupcache/lru from tailscale.com/wgengine/filter+
L github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
W 💣 github.com/tailscale/winipcfg-go from tailscale.com/net/interfaces+
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine+
github.com/tailscale/wireguard-go/device/tokenbucket from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/ipc from github.com/tailscale/wireguard-go/device
W 💣 github.com/tailscale/wireguard-go/ipc/winpipe from github.com/tailscale/wireguard-go/ipc
github.com/tailscale/wireguard-go/ratelimiter from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/iphlpapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/namespaceapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/nci from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/registry from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/setupapi from github.com/tailscale/wireguard-go/tun/wintun
github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/conn+
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
💣 go4.org/mem from tailscale.com/control/controlclient+
inet.af/netaddr from tailscale.com/cmd/tailscale/cli+
rsc.io/goversion/version from tailscale.com/version
tailscale.com/atomicfile from tailscale.com/ipn+
tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale
tailscale.com/control/controlclient from tailscale.com/ipn+
tailscale.com/derp from tailscale.com/derp/derphttp+
tailscale.com/derp/derphttp from tailscale.com/cmd/tailscale/cli+
tailscale.com/derp/derpmap from tailscale.com/cmd/tailscale/cli
tailscale.com/disco from tailscale.com/derp+
tailscale.com/internal/deepprint from tailscale.com/ipn+
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
tailscale.com/ipn/policy from tailscale.com/ipn
tailscale.com/log/logheap from tailscale.com/control/controlclient
tailscale.com/logtail/backoff from tailscale.com/control/controlclient
tailscale.com/metrics from tailscale.com/derp
tailscale.com/net/dnscache from tailscale.com/cmd/tailscale/cli+
tailscale.com/net/interfaces from tailscale.com/cmd/tailscale/cli+
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli+
💣 tailscale.com/net/netns from tailscale.com/control/controlclient+
tailscale.com/net/stun from tailscale.com/net/netcheck+
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
tailscale.com/net/tsaddr from tailscale.com/ipn+
💣 tailscale.com/net/tshttpproxy from tailscale.com/cmd/tailscale/cli+
tailscale.com/paths from tailscale.com/cmd/tailscale/cli
tailscale.com/portlist from tailscale.com/ipn
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
DW tailscale.com/tempfork/osexec from tailscale.com/portlist
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/types/empty from tailscale.com/control/controlclient+
tailscale.com/types/key from tailscale.com/cmd/tailscale/cli+
tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+
tailscale.com/types/nettype from tailscale.com/wgengine/magicsock
tailscale.com/types/opt from tailscale.com/control/controlclient+
tailscale.com/types/strbuilder from tailscale.com/wgengine/packet
tailscale.com/types/structs from tailscale.com/control/controlclient+
tailscale.com/util/lineread from tailscale.com/control/controlclient+
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
tailscale.com/wgengine from tailscale.com/ipn
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/magicsock from tailscale.com/wgengine
💣 tailscale.com/wgengine/monitor from tailscale.com/cmd/tailscale/cli+
tailscale.com/wgengine/packet from tailscale.com/wgengine+
tailscale.com/wgengine/router from tailscale.com/cmd/tailscale/cli+
💣 tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/tsdns from tailscale.com/ipn+
tailscale.com/wgengine/tstun from tailscale.com/wgengine
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/chacha20poly1305 from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/curve25519 from github.com/tailscale/wireguard-go/wgcfg+
golang.org/x/crypto/nacl/box from tailscale.com/control/controlclient+
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/context/ctxhttp from golang.org/x/oauth2/internal
golang.org/x/net/dns/dnsmessage from tailscale.com/wgengine/tsdns
golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/device
golang.org/x/net/ipv6 from github.com/tailscale/wireguard-go/device+
golang.org/x/net/proxy from tailscale.com/net/netns
golang.org/x/oauth2 from tailscale.com/control/controlclient+
golang.org/x/oauth2/internal from golang.org/x/oauth2
golang.org/x/sync/errgroup from tailscale.com/derp
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+
W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+
W golang.org/x/sys/windows/registry from github.com/tailscale/wireguard-go/tun/wintun+
W golang.org/x/text/transform from golang.org/x/text/unicode/norm
W golang.org/x/text/unicode/norm from github.com/tailscale/wireguard-go/tun/wintun
golang.org/x/time/rate from tailscale.com/types/logger+
vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305
vendor/golang.org/x/crypto/chacha20poly1305 from crypto/tls
vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+
vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
vendor/golang.org/x/crypto/curve25519 from crypto/tls
vendor/golang.org/x/crypto/hkdf from crypto/tls
vendor/golang.org/x/crypto/poly1305 from vendor/golang.org/x/crypto/chacha20poly1305
vendor/golang.org/x/net/dns/dnsmessage from net
vendor/golang.org/x/net/http/httpguts from net/http
vendor/golang.org/x/net/http/httpproxy from net/http
vendor/golang.org/x/net/http2/hpack from net/http
vendor/golang.org/x/net/idna from net/http+
D vendor/golang.org/x/net/route from net
vendor/golang.org/x/sys/cpu from vendor/golang.org/x/crypto/chacha20poly1305
vendor/golang.org/x/text/secure/bidirule from vendor/golang.org/x/net/idna
vendor/golang.org/x/text/transform from vendor/golang.org/x/text/secure/bidirule+
vendor/golang.org/x/text/unicode/bidi from vendor/golang.org/x/net/idna+
vendor/golang.org/x/text/unicode/norm from vendor/golang.org/x/net/idna
bufio from compress/flate+
bytes from bufio+
compress/flate from compress/gzip+
compress/gzip from net/http+
compress/zlib from debug/elf+
container/list from crypto/tls+
context from crypto/tls+
crypto from crypto/ecdsa+
crypto/aes from crypto/ecdsa+
crypto/cipher from crypto/aes+
crypto/des from crypto/tls+
crypto/dsa from crypto/x509
crypto/ecdsa from crypto/tls+
crypto/ed25519 from crypto/tls+
crypto/elliptic from crypto/ecdsa+
crypto/hmac from crypto/tls+
crypto/md5 from crypto/tls+
crypto/rand from crypto/ed25519+
crypto/rc4 from crypto/tls
crypto/rsa from crypto/tls+
crypto/sha1 from crypto/tls+
crypto/sha256 from crypto/tls+
crypto/sha512 from crypto/ecdsa+
crypto/subtle from crypto/aes+
crypto/tls from github.com/tcnksm/go-httpstat+
crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+
debug/dwarf from debug/elf+
debug/elf from rsc.io/goversion/version
debug/macho from rsc.io/goversion/version
debug/pe from rsc.io/goversion/version
encoding from encoding/json+
encoding/asn1 from crypto/x509+
encoding/base64 from encoding/json+
encoding/binary from compress/gzip+
encoding/hex from crypto/x509+
encoding/json from expvar+
encoding/pem from crypto/tls+
errors from bufio+
expvar from tailscale.com/derp+
flag from github.com/peterbourgon/ff/v2+
fmt from compress/flate+
hash from compress/zlib+
hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+
hash/fnv from tailscale.com/wgengine/magicsock
html from tailscale.com/ipn/ipnstate
io from bufio+
io/ioutil from crypto/tls+
log from expvar+
math from compress/flate+
math/big from crypto/dsa+
math/bits from compress/flate+
math/rand from github.com/mdlayher/netlink+
mime from golang.org/x/oauth2/internal+
mime/multipart from net/http
mime/quotedprintable from mime/multipart
net from crypto/tls+
net/http from expvar+
net/http/httptrace from github.com/tcnksm/go-httpstat+
net/http/internal from net/http
net/textproto from mime/multipart+
net/url from crypto/x509+
os from crypto/rand+
os/exec from github.com/coreos/go-iptables/iptables+
os/signal from tailscale.com/cmd/tailscale/cli
L os/user from github.com/godbus/dbus/v5
path from debug/dwarf+
path/filepath from crypto/x509+
reflect from crypto/x509+
regexp from github.com/coreos/go-iptables/iptables+
regexp/syntax from regexp
LD runtime/cgo
runtime/pprof from tailscale.com/log/logheap+
sort from compress/flate+
strconv from compress/flate+
strings from bufio+
sync from compress/flate+
sync/atomic from context+
syscall from crypto/rand+
text/tabwriter from github.com/peterbourgon/ff/v2/ffcli+
time from compress/gzip+
unicode from bytes+
unicode/utf16 from encoding/asn1+
unicode/utf8 from bufio+
unsafe from crypto/internal/subtle+

234
cmd/tailscaled/depaware.txt Normal file
View File

@@ -0,0 +1,234 @@
tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/depaware)
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
github.com/apenwarr/fixconsole from tailscale.com/cmd/tailscaled
W 💣 github.com/apenwarr/w32 from github.com/apenwarr/fixconsole
L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
L 💣 github.com/godbus/dbus/v5 from tailscale.com/wgengine/router/dns
github.com/golang/groupcache/lru from tailscale.com/wgengine/filter+
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/fse from github.com/klauspost/compress/huff0
github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd
github.com/klauspost/compress/snappy from github.com/klauspost/compress/zstd
github.com/klauspost/compress/zstd from tailscale.com/smallzstd
github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
github.com/pborman/getopt/v2 from tailscale.com/cmd/tailscaled
W 💣 github.com/tailscale/winipcfg-go from tailscale.com/net/interfaces+
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine+
github.com/tailscale/wireguard-go/device/tokenbucket from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/ipc from github.com/tailscale/wireguard-go/device
W 💣 github.com/tailscale/wireguard-go/ipc/winpipe from github.com/tailscale/wireguard-go/ipc
github.com/tailscale/wireguard-go/ratelimiter from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/iphlpapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/namespaceapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/nci from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/registry from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/setupapi from github.com/tailscale/wireguard-go/tun/wintun
github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/conn+
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
💣 go4.org/mem from tailscale.com/control/controlclient+
inet.af/netaddr from tailscale.com/control/controlclient+
rsc.io/goversion/version from tailscale.com/version
tailscale.com/atomicfile from tailscale.com/ipn+
tailscale.com/control/controlclient from tailscale.com/ipn+
tailscale.com/derp from tailscale.com/derp/derphttp+
tailscale.com/derp/derphttp from tailscale.com/net/netcheck+
tailscale.com/disco from tailscale.com/derp+
tailscale.com/internal/deepprint from tailscale.com/ipn+
tailscale.com/ipn from tailscale.com/ipn/ipnserver
tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled
tailscale.com/ipn/ipnstate from tailscale.com/ipn+
tailscale.com/ipn/policy from tailscale.com/ipn
tailscale.com/log/logheap from tailscale.com/control/controlclient
tailscale.com/logpolicy from tailscale.com/cmd/tailscaled
tailscale.com/logtail from tailscale.com/logpolicy
tailscale.com/logtail/backoff from tailscale.com/control/controlclient+
tailscale.com/logtail/filch from tailscale.com/logpolicy
tailscale.com/metrics from tailscale.com/derp
tailscale.com/net/dnscache from tailscale.com/derp/derphttp+
tailscale.com/net/interfaces from tailscale.com/ipn+
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
💣 tailscale.com/net/netns from tailscale.com/control/controlclient+
💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnserver
tailscale.com/net/stun from tailscale.com/net/netcheck+
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
tailscale.com/net/tsaddr from tailscale.com/ipn+
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
tailscale.com/paths from tailscale.com/cmd/tailscaled+
tailscale.com/portlist from tailscale.com/ipn
tailscale.com/safesocket from tailscale.com/ipn/ipnserver
tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/tailcfg from tailscale.com/control/controlclient+
DW tailscale.com/tempfork/osexec from tailscale.com/portlist
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/types/empty from tailscale.com/control/controlclient+
tailscale.com/types/key from tailscale.com/derp+
tailscale.com/types/logger from tailscale.com/cmd/tailscaled+
tailscale.com/types/nettype from tailscale.com/wgengine/magicsock
tailscale.com/types/opt from tailscale.com/control/controlclient+
tailscale.com/types/strbuilder from tailscale.com/wgengine/packet
tailscale.com/types/structs from tailscale.com/control/controlclient+
tailscale.com/util/lineread from tailscale.com/control/controlclient+
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
tailscale.com/version from tailscale.com/control/controlclient+
tailscale.com/version/distro from tailscale.com/control/controlclient+
tailscale.com/wgengine from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/magicsock from tailscale.com/cmd/tailscaled+
💣 tailscale.com/wgengine/monitor from tailscale.com/wgengine
tailscale.com/wgengine/packet from tailscale.com/wgengine+
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
💣 tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/tsdns from tailscale.com/ipn+
tailscale.com/wgengine/tstun from tailscale.com/wgengine
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/chacha20poly1305 from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/curve25519 from github.com/tailscale/wireguard-go/wgcfg+
golang.org/x/crypto/nacl/box from tailscale.com/control/controlclient+
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/crypto/ssh/terminal from tailscale.com/logpolicy
golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/context/ctxhttp from golang.org/x/oauth2/internal
golang.org/x/net/dns/dnsmessage from tailscale.com/wgengine/tsdns
golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/device
golang.org/x/net/ipv6 from github.com/tailscale/wireguard-go/device+
golang.org/x/net/proxy from tailscale.com/net/netns
golang.org/x/oauth2 from tailscale.com/control/controlclient+
golang.org/x/oauth2/internal from golang.org/x/oauth2
golang.org/x/sync/errgroup from tailscale.com/derp
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+
W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+
W golang.org/x/sys/windows/registry from github.com/tailscale/wireguard-go/tun/wintun+
W golang.org/x/text/transform from golang.org/x/text/unicode/norm
W golang.org/x/text/unicode/norm from github.com/tailscale/wireguard-go/tun/wintun
golang.org/x/time/rate from tailscale.com/types/logger+
vendor/golang.org/x/crypto/chacha20 from vendor/golang.org/x/crypto/chacha20poly1305
vendor/golang.org/x/crypto/chacha20poly1305 from crypto/tls
vendor/golang.org/x/crypto/cryptobyte from crypto/ecdsa+
vendor/golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
vendor/golang.org/x/crypto/curve25519 from crypto/tls
vendor/golang.org/x/crypto/hkdf from crypto/tls
vendor/golang.org/x/crypto/poly1305 from vendor/golang.org/x/crypto/chacha20poly1305
vendor/golang.org/x/net/dns/dnsmessage from net
vendor/golang.org/x/net/http/httpguts from net/http
vendor/golang.org/x/net/http/httpproxy from net/http
vendor/golang.org/x/net/http2/hpack from net/http
vendor/golang.org/x/net/idna from net/http+
D vendor/golang.org/x/net/route from net
vendor/golang.org/x/sys/cpu from vendor/golang.org/x/crypto/chacha20poly1305
vendor/golang.org/x/text/secure/bidirule from vendor/golang.org/x/net/idna
vendor/golang.org/x/text/transform from vendor/golang.org/x/text/secure/bidirule+
vendor/golang.org/x/text/unicode/bidi from vendor/golang.org/x/net/idna+
vendor/golang.org/x/text/unicode/norm from vendor/golang.org/x/net/idna
bufio from compress/flate+
bytes from bufio+
compress/flate from compress/gzip+
compress/gzip from internal/profile+
compress/zlib from debug/elf+
container/list from crypto/tls+
context from crypto/tls+
crypto from crypto/ecdsa+
crypto/aes from crypto/ecdsa+
crypto/cipher from crypto/aes+
crypto/des from crypto/tls+
crypto/dsa from crypto/x509
crypto/ecdsa from crypto/tls+
crypto/ed25519 from crypto/tls+
crypto/elliptic from crypto/ecdsa+
crypto/hmac from crypto/tls+
crypto/md5 from crypto/tls+
crypto/rand from crypto/ed25519+
crypto/rc4 from crypto/tls
crypto/rsa from crypto/tls+
crypto/sha1 from crypto/tls+
crypto/sha256 from crypto/tls+
crypto/sha512 from crypto/ecdsa+
crypto/subtle from crypto/aes+
crypto/tls from github.com/tcnksm/go-httpstat+
crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+
debug/dwarf from debug/elf+
debug/elf from rsc.io/goversion/version
debug/macho from rsc.io/goversion/version
debug/pe from rsc.io/goversion/version
encoding from encoding/json+
encoding/asn1 from crypto/x509+
encoding/base64 from encoding/json+
encoding/binary from compress/gzip+
encoding/hex from crypto/x509+
encoding/json from expvar+
encoding/pem from crypto/tls+
errors from bufio+
expvar from tailscale.com/derp+
L flag from tailscale.com/net/netns
fmt from compress/flate+
hash from compress/zlib+
hash/adler32 from compress/zlib
hash/crc32 from compress/gzip+
hash/fnv from tailscale.com/wgengine/magicsock
html from html/template+
html/template from net/http/pprof
io from bufio+
io/ioutil from crypto/tls+
log from expvar+
math from compress/flate+
math/big from crypto/dsa+
math/bits from compress/flate+
math/rand from github.com/mdlayher/netlink+
mime from golang.org/x/oauth2/internal+
mime/multipart from net/http
mime/quotedprintable from mime/multipart
net from crypto/tls+
net/http from expvar+
net/http/httptrace from github.com/tcnksm/go-httpstat+
net/http/internal from net/http
net/http/pprof from tailscale.com/cmd/tailscaled
net/textproto from mime/multipart+
net/url from crypto/x509+
os from crypto/rand+
os/exec from github.com/coreos/go-iptables/iptables+
os/signal from tailscale.com/cmd/tailscaled+
os/user from github.com/godbus/dbus/v5+
path from debug/dwarf+
path/filepath from crypto/x509+
reflect from crypto/x509+
regexp from github.com/coreos/go-iptables/iptables+
regexp/syntax from regexp
LD runtime/cgo
runtime/debug from github.com/klauspost/compress/zstd+
runtime/pprof from net/http/pprof+
runtime/trace from net/http/pprof
sort from compress/flate+
strconv from compress/flate+
strings from bufio+
sync from compress/flate+
sync/atomic from context+
syscall from crypto/rand+
text/tabwriter from runtime/pprof
text/template from html/template
text/template/parse from html/template+
time from compress/gzip+
unicode from bytes+
unicode/utf16 from encoding/asn1+
unicode/utf8 from bufio+
unsafe from crypto/internal/subtle+

View File

@@ -26,6 +26,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
@@ -35,8 +36,10 @@ import (
"tailscale.com/log/logheap"
"tailscale.com/net/netns"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
"tailscale.com/version"
)
@@ -145,6 +148,8 @@ func NewDirect(opts Options) (*Direct, error) {
if httpc == nil {
dialer := netns.NewDialer()
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
tr.DialContext = dialer.DialContext
tr.ForceAttemptHTTP2 = true
tr.TLSClientConfig = tlsdial.Config(serverURL.Host, tr.TLSClientConfig)
@@ -621,8 +626,12 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
vlogf("netmap: new map contains DERP map")
lastDERPMap = resp.DERPMap
}
if resp.Debug != nil && resp.Debug.LogHeapPprof {
go logheap.LogHeap(resp.Debug.LogHeapURL)
if resp.Debug != nil {
if resp.Debug.LogHeapPprof {
go logheap.LogHeap(resp.Debug.LogHeapURL)
}
setControlAtomic(&controlUseDERPRoute, resp.Debug.DERPRoute)
setControlAtomic(&controlTrimWGConfig, resp.Debug.TrimWGConfig)
}
// Temporarily (2020-06-29) support removing all but
// discovery-supporting nodes during development, for
@@ -708,6 +717,8 @@ func decode(res *http.Response, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg
return decodeMsg(msg, v, serverKey, mkey)
}
var dumpMapResponse, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_MAPRESPONSE"))
func (c *Direct) decodeMsg(msg []byte, v interface{}) error {
c.mu.Lock()
mkey := c.persist.PrivateMachineKey
@@ -732,6 +743,11 @@ func (c *Direct) decodeMsg(msg []byte, v interface{}) error {
return err
}
}
if dumpMapResponse {
var buf bytes.Buffer
json.Indent(&buf, b, "", " ")
log.Printf("MapResponse: %s", buf.Bytes())
}
if err := json.Unmarshal(b, v); err != nil {
return fmt.Errorf("response: %v", err)
}
@@ -953,3 +969,30 @@ func cloneNodes(v1 []*tailcfg.Node) []*tailcfg.Node {
}
return v2
}
// opt.Bool configs from control.
var (
controlUseDERPRoute atomic.Value
controlTrimWGConfig atomic.Value
)
func setControlAtomic(dst *atomic.Value, v opt.Bool) {
old, ok := dst.Load().(opt.Bool)
if !ok || old != v {
dst.Store(v)
}
}
// DERPRouteFlag reports the last reported value from control for whether
// DERP route optimization (Issue 150) should be enabled.
func DERPRouteFlag() opt.Bool {
v, _ := controlUseDERPRoute.Load().(opt.Bool)
return v
}
// TrimWGConfig reports the last reported value from control for whether
// we should do lazy wireguard configuration.
func TrimWGConfig() opt.Bool {
v, _ := controlTrimWGConfig.Load().(opt.Bool)
return v
}

View File

@@ -5,80 +5,16 @@
package controlclient
import (
"fmt"
"net"
"tailscale.com/tailcfg"
"tailscale.com/wgengine/filter"
)
func parseIP(host string, defaultBits int) (filter.Net, error) {
ip := net.ParseIP(host)
if ip != nil && ip.IsUnspecified() {
// For clarity, reject 0.0.0.0 as an input
return filter.NetNone, fmt.Errorf("ports=%#v: to allow all IP addresses, use *:port, not 0.0.0.0:port", host)
} else if ip == nil && host == "*" {
// User explicitly requested wildcard dst ip
return filter.NetAny, nil
} else {
if ip != nil {
ip = ip.To4()
}
if ip == nil || len(ip) != 4 {
return filter.NetNone, fmt.Errorf("ports=%#v: invalid IPv4 address", host)
}
return filter.Net{
IP: filter.NewIP(ip),
Mask: filter.Netmask(defaultBits),
}, nil
}
}
// Parse a backward-compatible FilterRule used by control's wire format,
// producing the most current filter.Matches format.
func (c *Direct) parsePacketFilter(pf []tailcfg.FilterRule) filter.Matches {
mm := make([]filter.Match, 0, len(pf))
var erracc error
for _, r := range pf {
m := filter.Match{}
for i, s := range r.SrcIPs {
bits := 32
if len(r.SrcBits) > i {
bits = r.SrcBits[i]
}
net, err := parseIP(s, bits)
if err != nil && erracc == nil {
erracc = err
continue
}
m.Srcs = append(m.Srcs, net)
}
for _, d := range r.DstPorts {
bits := 32
if d.Bits != nil {
bits = *d.Bits
}
net, err := parseIP(d.IP, bits)
if err != nil && erracc == nil {
erracc = err
continue
}
m.Dsts = append(m.Dsts, filter.NetPortRange{
Net: net,
Ports: filter.PortRange{
First: d.Ports.First,
Last: d.Ports.Last,
},
})
}
mm = append(mm, m)
}
if erracc != nil {
c.logf("parsePacketFilter: %s\n", erracc)
mm, err := filter.MatchesFromFilterRules(pf)
if err != nil {
c.logf("parsePacketFilter: %s\n", err)
}
return mm
}

View File

@@ -16,6 +16,7 @@ import (
"go4.org/mem"
"tailscale.com/util/lineread"
"tailscale.com/version/distro"
)
func init() {
@@ -23,8 +24,14 @@ func init() {
}
func osVersionLinux() string {
dist := distro.Get()
propFile := "/etc/os-release"
if dist == distro.Synology {
propFile = "/etc.defaults/VERSION"
}
m := map[string]string{}
lineread.File("/etc/os-release", func(line []byte) error {
lineread.File(propFile, func(line []byte) error {
eq := bytes.IndexByte(line, '=')
if eq == -1 {
return nil
@@ -71,6 +78,9 @@ func osVersionLinux() string {
return fmt.Sprintf("%s%s", v, attr)
}
}
if dist == distro.Synology {
return fmt.Sprintf("Synology %s%s", m["productversion"], attr)
}
return fmt.Sprintf("Other%s", attr)
}

View File

@@ -21,6 +21,10 @@ func osVersionWindows() string {
s := strings.TrimSpace(string(out))
s = strings.TrimPrefix(s, "Microsoft Windows [")
s = strings.TrimSuffix(s, "]")
s = strings.TrimPrefix(s, "Version ") // is this localized? do it last in case.
return s // "10.0.19041.388", ideally
// "Version 10.x.y.z", with "Version" localized. Keep only stuff after the space.
if sp := strings.Index(s, " "); sp != -1 {
s = s[sp+1:]
}
return s // "10.0.19041.388", ideally
}

View File

@@ -219,7 +219,6 @@ const (
AllowSingleHosts WGConfigFlags = 1 << iota
AllowSubnetRoutes
AllowDefaultRoute
HackDefaultRoute
)
// EndpointDiscoSuffix is appended to the hex representation of a peer's discovery key
@@ -274,11 +273,7 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi
logf("wgcfg: %v skipping default route", peer.Key.ShortString())
continue
}
if (flags & HackDefaultRoute) != 0 {
allowedIP = wgcfg.CIDR{IP: wgcfg.IPv4(10, 0, 0, 0), Mask: 8}
logf("wgcfg: %v converting default route => %v", peer.Key.ShortString(), allowedIP.String())
}
} else if allowedIP.Mask < 32 {
} else if cidrIsSubnet(peer, allowedIP) {
if (flags & AllowSubnetRoutes) == 0 {
logf("wgcfg: %v skipping subnet route", peer.Key.ShortString())
continue
@@ -291,6 +286,29 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi
return cfg, nil
}
// cidrIsSubnet reports whether cidr is a non-default-route subnet
// exported by node that is not one of its own self addresses.
func cidrIsSubnet(node *tailcfg.Node, cidr wgcfg.CIDR) bool {
if cidr.Mask == 0 {
return false
}
if cidr.Mask < 32 {
// Fast path for IPv4, to avoid loop below.
//
// TODO: if cidr.IP is an IPv6 address, we could do "< 128"
// to avoid the range over node.Addresses. Or we could
// just remove this fast path and unconditionally do the range
// loop.
return true
}
for _, selfCIDR := range node.Addresses {
if cidr == selfCIDR {
return false
}
}
return true
}
func appendEndpoint(peer *wgcfg.Peer, epStr string) error {
if epStr == "" {
return nil

View File

@@ -39,14 +39,10 @@ const (
keepAlive = 60 * time.Second
)
// protocolVersion is bumped whenever there's a wire-incompatible change.
// ProtocolVersion is bumped whenever there's a wire-incompatible change.
// * version 1 (zero on wire): consistent box headers, in use by employee dev nodes a bit
// * version 2: received packets have src addrs in frameRecvPacket at beginning
const protocolVersion = 2
const (
protocolSrcAddrs = 2 // protocol version at which client expects src addresses
)
const ProtocolVersion = 2
// frameType is the one byte frame type at the beginning of the frame
// header. The second field is a big-endian uint32 describing the
@@ -108,16 +104,31 @@ var bin = binary.BigEndian
func writeUint32(bw *bufio.Writer, v uint32) error {
var b [4]byte
bin.PutUint32(b[:], v)
_, err := bw.Write(b[:])
return err
// Writing a byte at a time is a bit silly,
// but it causes b not to escape,
// which more than pays for the silliness.
for _, c := range &b {
err := bw.WriteByte(c)
if err != nil {
return err
}
}
return nil
}
func readUint32(br *bufio.Reader) (uint32, error) {
b := make([]byte, 4)
if _, err := io.ReadFull(br, b); err != nil {
return 0, err
var b [4]byte
// Reading a byte at a time is a bit silly,
// but it causes b not to escape,
// which more than pays for the silliness.
for i := range &b {
c, err := br.ReadByte()
if err != nil {
return 0, err
}
b[i] = c
}
return bin.Uint32(b), nil
return bin.Uint32(b[:]), nil
}
func readFrameTypeHeader(br *bufio.Reader, wantType frameType) (frameLen uint32, err error) {
@@ -194,13 +205,6 @@ func writeFrame(bw *bufio.Writer, t frameType, b []byte) error {
return bw.Flush()
}
func minInt(a, b int) int {
if a < b {
return a
}
return b
}
func minUint32(a, b uint32) uint32 {
if a < b {
return a

View File

@@ -21,14 +21,13 @@ 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
protoVersion int // min of server+client
logf logger.Logf
nc Conn
br *bufio.Reader
meshKey string
serverKey key.Public // of the DERP server; not a machine or node key
privateKey key.Private
publicKey key.Public // of privateKey
logf logger.Logf
nc Conn
br *bufio.Reader
meshKey string
wmu sync.Mutex // hold while writing to bw
bw *bufio.Writer
@@ -49,7 +48,8 @@ func (f clientOptFunc) update(o *clientOpt) { f(o) }
// clientOpt are the options passed to newClient.
type clientOpt struct {
MeshKey string
MeshKey string
ServerPub key.Public
}
// MeshKey returns a ClientOpt to pass to the DERP server during connect to get
@@ -58,6 +58,12 @@ type clientOpt struct {
// An empty key means to not use a mesh key.
func MeshKey(key string) ClientOpt { return clientOptFunc(func(o *clientOpt) { o.MeshKey = key }) }
// 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 {
return clientOptFunc(func(o *clientOpt) { o.ServerPub = key })
}
func NewClient(privateKey key.Private, nc Conn, brw *bufio.ReadWriter, logf logger.Logf, opts ...ClientOpt) (*Client, error) {
var opt clientOpt
for _, o := range opts {
@@ -79,17 +85,16 @@ func newClient(privateKey key.Private, nc Conn, brw *bufio.ReadWriter, logf logg
bw: brw.Writer,
meshKey: opt.MeshKey,
}
if err := c.recvServerKey(); err != nil {
return nil, fmt.Errorf("derp.Client: failed to receive server key: %v", err)
if opt.ServerPub.IsZero() {
if err := c.recvServerKey(); err != nil {
return nil, fmt.Errorf("derp.Client: failed to receive server key: %v", err)
}
} else {
c.serverKey = opt.ServerPub
}
if err := c.sendClientKey(); err != nil {
return nil, fmt.Errorf("derp.Client: failed to send client key: %v", err)
}
info, err := c.recvServerInfo()
if err != nil {
return nil, fmt.Errorf("derp.Client: failed to receive server info: %v", err)
}
c.protoVersion = minInt(protocolVersion, info.Version)
return c, nil
}
@@ -110,12 +115,9 @@ func (c *Client) recvServerKey() error {
return nil
}
func (c *Client) recvServerInfo() (*serverInfo, error) {
fl, err := readFrameTypeHeader(c.br, frameServerInfo)
if err != nil {
return nil, err
}
func (c *Client) parseServerInfo(b []byte) (*serverInfo, error) {
const maxLength = nonceLen + maxInfoLen
fl := len(b)
if fl < nonceLen {
return nil, fmt.Errorf("short serverInfo frame")
}
@@ -124,33 +126,27 @@ func (c *Client) recvServerInfo() (*serverInfo, error) {
}
// TODO: add a read-nonce-and-box helper
var nonce [nonceLen]byte
if _, err := io.ReadFull(c.br, nonce[:]); err != nil {
return nil, fmt.Errorf("nonce: %v", err)
}
msgLen := fl - nonceLen
msgbox := make([]byte, msgLen)
if _, err := io.ReadFull(c.br, msgbox); err != nil {
return nil, fmt.Errorf("msgbox: %v", err)
}
copy(nonce[:], b)
msgbox := b[nonceLen:]
msg, ok := box.Open(nil, msgbox, &nonce, c.serverKey.B32(), c.privateKey.B32())
if !ok {
return nil, fmt.Errorf("msgbox: cannot open len=%d with server key %x", msgLen, c.serverKey[:])
return nil, fmt.Errorf("failed to open naclbox from server key %x", c.serverKey[:])
}
info := new(serverInfo)
if err := json.Unmarshal(msg, info); err != nil {
return nil, fmt.Errorf("msg: %v", err)
return nil, fmt.Errorf("invalid JSON: %v", err)
}
return info, nil
}
type clientInfo struct {
Version int // `json:"version,omitempty"`
Version int `json:"version,omitempty"`
// MeshKey optionally specifies a pre-shared key used by
// trusted clients. It's required to subscribe to the
// connection list & forward packets. It's empty for regular
// users.
MeshKey string // `json:"meshKey,omitempty"`
MeshKey string `json:"meshKey,omitempty"`
}
func (c *Client) sendClientKey() error {
@@ -159,7 +155,7 @@ func (c *Client) sendClientKey() error {
return err
}
msg, err := json.Marshal(clientInfo{
Version: protocolVersion,
Version: ProtocolVersion,
MeshKey: c.meshKey,
})
if err != nil {
@@ -318,6 +314,11 @@ type PeerPresentMessage key.Public
func (PeerPresentMessage) msg() {}
// ServerInfoMessage is sent by the server upon first connect.
type ServerInfoMessage struct{}
func (ServerInfoMessage) msg() {}
// Recv reads a message from the DERP server.
//
// The returned message may alias memory owned by the Client; it
@@ -364,7 +365,7 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
// If the frame fits in our bufio.Reader buffer, just use it.
// In practice it's 4KB (from derphttp.Client's bufio.NewReader(httpConn)) and
// in practive, WireGuard packets (and thus DERP frames) are under 1.5KB.
// So This is the common path.
// So this is the common path.
if int(n) <= c.br.Size() {
b, err = c.br.Peek(int(n))
c.peeked = int(n)
@@ -382,6 +383,19 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
switch t {
default:
continue
case frameServerInfo:
// Server sends this at start-up. Currently unused.
// Just has a JSON message saying "version: 2",
// but the protocol seems extensible enough as-is without
// needing to wait an RTT to discover the version at startup.
// We'd prefer to give the connection to the client (magicsock)
// to start writing as soon as possible.
_, err := c.parseServerInfo(b)
if err != nil {
return nil, fmt.Errorf("invalid server info frame: %v", err)
}
// TODO: add the results of parseServerInfo to ServerInfoMessage if we ever need it.
return ServerInfoMessage{}, nil
case frameKeepAlive:
// TODO: eventually we'll have server->client pings that
// require ack pongs.
@@ -406,16 +420,12 @@ func (c *Client) recvTimeout(timeout time.Duration) (m ReceivedMessage, err erro
case frameRecvPacket:
var rp ReceivedPacket
if c.protoVersion < protocolSrcAddrs {
rp.Data = b[:n]
} else {
if n < keyLen {
c.logf("[unexpected] dropping short packet from DERP server")
continue
}
copy(rp.Source[:], b[:keyLen])
rp.Data = b[keyLen:n]
if n < keyLen {
c.logf("[unexpected] dropping short packet from DERP server")
continue
}
copy(rp.Source[:], b[:keyLen])
rp.Data = b[keyLen:n]
return rp, nil
}
}

View File

@@ -9,14 +9,19 @@ package derp
import (
"bufio"
"context"
"crypto/ed25519"
crand "crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"errors"
"expvar"
"fmt"
"io"
"io/ioutil"
"log"
"math/big"
"math/rand"
"os"
"runtime"
"strconv"
@@ -24,8 +29,10 @@ import (
"sync"
"time"
"go4.org/mem"
"golang.org/x/crypto/nacl/box"
"golang.org/x/sync/errgroup"
"tailscale.com/disco"
"tailscale.com/metrics"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@@ -34,6 +41,29 @@ import (
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{}
func init() {
keys := os.Getenv("TS_DEBUG_VERBOSE_DROPS")
if keys == "" {
return
}
for _, keyStr := range strings.Split(keys, ",") {
k, err := key.NewPublicFromHexMem(mem.S(keyStr))
if err != nil {
log.Printf("ignoring invalid debug key %q: %v", keyStr, err)
} else {
verboseDropKeys[k] = true
}
}
}
func init() {
rand.Seed(time.Now().UnixNano())
}
const (
perClientSendQueueDepth = 32 // packets buffered for sending
writeTimeout = 2 * time.Second
@@ -52,16 +82,22 @@ type Server struct {
// before failing when writing to a client.
WriteTimeout time.Duration
privateKey key.Private
publicKey key.Public
logf logger.Logf
memSys0 uint64 // runtime.MemStats.Sys at start (or early-ish)
meshKey string
privateKey key.Private
publicKey key.Public
logf logger.Logf
memSys0 uint64 // runtime.MemStats.Sys at start (or early-ish)
meshKey string
limitedLogf logger.Logf
metaCert []byte // the encoded x509 cert to send after LetsEncrypt cert+intermediate
// Counters:
_ [pad32bit]byte
packetsSent, bytesSent expvar.Int
packetsRecv, bytesRecv expvar.Int
packetsRecvByKind metrics.LabelMap
packetsRecvDisco *expvar.Int
packetsRecvOther *expvar.Int
_ [pad32bit]byte
packetsDropped expvar.Int
packetsDroppedReason metrics.LabelMap
packetsDroppedUnknown *expvar.Int // unknown dst pubkey
@@ -136,6 +172,8 @@ func NewServer(privateKey key.Private, logf logger.Logf) *Server {
privateKey: privateKey,
publicKey: privateKey.Public(),
logf: logf,
limitedLogf: logger.RateLimitedFn(logf, 30*time.Second, 5, 100),
packetsRecvByKind: metrics.LabelMap{Label: "kind"},
packetsDroppedReason: metrics.LabelMap{Label: "reason"},
clients: map[key.Public]*sclient{},
clientsEver: map[key.Public]bool{},
@@ -145,6 +183,9 @@ func NewServer(privateKey key.Private, logf logger.Logf) *Server {
watchers: map[*sclient]bool{},
sentTo: map[key.Public]map[key.Public]int64{},
}
s.initMetacert()
s.packetsRecvDisco = s.packetsRecvByKind.Get("disco")
s.packetsRecvOther = s.packetsRecvByKind.Get("other")
s.packetsDroppedUnknown = s.packetsDroppedReason.Get("unknown_dest")
s.packetsDroppedFwdUnknown = s.packetsDroppedReason.Get("unknown_dest_on_fwd")
s.packetsDroppedGone = s.packetsDroppedReason.Get("gone")
@@ -236,6 +277,50 @@ func (s *Server) Accept(nc Conn, brw *bufio.ReadWriter, remoteAddr string) {
}
}
// initMetacert initialized s.metaCert with a self-signed x509 cert
// encoding this server's public key and protocol version. cmd/derper
// then sends this after the Let's Encrypt leaf + intermediate certs
// after the ServerHello (encrypted in TLS 1.3, not that it matters
// much).
//
// Then the client can save a round trip getting that and can start
// speaking DERP right away. (We don't use ALPN because that's sent in
// the clear and we're being paranoid to not look too weird to any
// middleboxes, given that DERP is an ultimate fallback path). But
// since the post-ServerHello certs are encrypted we can have the
// client also use them as a signal to be able to start speaking DERP
// right away, starting with its identity proof, encrypted to the
// server's public key.
//
// This RTT optimization fails where there's a corp-mandated
// TLS proxy with corp-mandated root certs on employee machines and
// and TLS proxy cleans up unnecessary certs. In that case we just fall
// back to the extra RTT.
func (s *Server) initMetacert() {
pub, priv, err := ed25519.GenerateKey(crand.Reader)
if err != nil {
log.Fatal(err)
}
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(ProtocolVersion),
Subject: pkix.Name{
CommonName: fmt.Sprintf("derpkey%x", s.publicKey[:]),
},
// Windows requires NotAfter and NotBefore set:
NotAfter: time.Now().Add(30 * 24 * time.Hour),
NotBefore: time.Now().Add(-30 * 24 * time.Hour),
}
cert, err := x509.CreateCertificate(crand.Reader, tmpl, tmpl, pub, priv)
if err != nil {
log.Fatalf("CreateCertificate: %v", err)
}
s.metaCert = cert
}
// MetaCert returns the server metadata cert that can be sent by the
// TLS server to let the client skip a round trip during start-up.
func (s *Server) MetaCert() []byte { return s.metaCert }
// registerClient notes that client c is now authenticated and ready for packets.
// If c's public key was already connected with a different connection, the prior one is closed.
func (s *Server) registerClient(c *sclient) {
@@ -580,10 +665,8 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
}
p := pkt{
bs: contents,
}
if dst.info.Version >= protocolSrcAddrs {
p.src = c.key
bs: contents,
src: c.key,
}
return c.sendPkt(dst, p)
}
@@ -616,6 +699,12 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
case <-dst.sendQueue:
s.packetsDropped.Add(1)
s.packetsDroppedQueueHead.Add(1)
if verboseDropKeys[dstKey] {
// Generate a full string including src and dst, so
// the limiter kicks in once per src.
msg := fmt.Sprintf("tail drop %s -> %s", p.src.ShortString(), dstKey.ShortString())
c.s.limitedLogf(msg)
}
if debug {
c.logf("dropping packet from client %x queue head", dstKey)
}
@@ -627,6 +716,12 @@ func (c *sclient) sendPkt(dst *sclient, p pkt) error {
// this case to keep reader unblocked.
s.packetsDropped.Add(1)
s.packetsDroppedQueueTail.Add(1)
if verboseDropKeys[dstKey] {
// Generate a full string including src and dst, so
// the limiter kicks in once per src.
msg := fmt.Sprintf("head drop %s -> %s", p.src.ShortString(), dstKey.ShortString())
c.s.limitedLogf(msg)
}
if debug {
c.logf("dropping packet from client %x queue tail", dstKey)
}
@@ -668,7 +763,7 @@ func (s *Server) sendServerKey(bw *bufio.Writer) error {
}
type serverInfo struct {
Version int // `json:"version,omitempty"`
Version int `json:"version,omitempty"`
}
func (s *Server) sendServerInfo(bw *bufio.Writer, clientKey key.Public) error {
@@ -676,7 +771,7 @@ func (s *Server) sendServerInfo(bw *bufio.Writer, clientKey key.Public) error {
if _, err := crand.Read(nonce[:]); err != nil {
return err
}
msg, err := json.Marshal(serverInfo{Version: protocolVersion})
msg, err := json.Marshal(serverInfo{Version: ProtocolVersion})
if err != nil {
return err
}
@@ -738,7 +833,7 @@ func (s *Server) recvPacket(br *bufio.Reader, frameLen uint32) (dstKey key.Publi
if frameLen < keyLen {
return zpub, nil, errors.New("short send packet frame")
}
if _, err := io.ReadFull(br, dstKey[:]); err != nil {
if err := readPublicKey(br, &dstKey); err != nil {
return zpub, nil, err
}
packetLen := frameLen - keyLen
@@ -751,6 +846,11 @@ func (s *Server) recvPacket(br *bufio.Reader, frameLen uint32) (dstKey key.Publi
}
s.packetsRecv.Add(1)
s.bytesRecv.Add(int64(len(contents)))
if disco.LooksLikeDiscoWrapper(contents) {
s.packetsRecvDisco.Add(1)
} else {
s.packetsRecvOther.Add(1)
}
return dstKey, contents, nil
}
@@ -880,11 +980,7 @@ func (c *sclient) sendLoop(ctx context.Context) error {
}
}()
jitterMs, err := crand.Int(crand.Reader, big.NewInt(5000))
if err != nil {
panic(err)
}
jitter := time.Duration(jitterMs.Int64()) * time.Millisecond
jitter := time.Duration(rand.Intn(5000)) * time.Millisecond
keepAliveTick := time.NewTicker(keepAlive + jitter)
defer keepAliveTick.Stop()
@@ -1040,7 +1136,8 @@ func (c *sclient) sendPacket(srcKey key.Public, contents []byte) (err error) {
return err
}
if withKey {
if _, err = c.bw.Write(srcKey[:]); err != nil {
err := writePublicKey(c.bw, &srcKey)
if err != nil {
return err
}
}
@@ -1181,6 +1278,7 @@ func (s *Server) ExpVar() expvar.Var {
m.Set("bytes_sent", &s.bytesSent)
m.Set("packets_dropped", &s.packetsDropped)
m.Set("counter_packets_dropped_reason", &s.packetsDroppedReason)
m.Set("counter_packets_received_kind", &s.packetsRecvByKind)
m.Set("packets_sent", &s.packetsSent)
m.Set("packets_received", &s.packetsRecv)
m.Set("unknown_frames", &s.unknownFrames)
@@ -1236,3 +1334,34 @@ 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
}

View File

@@ -8,10 +8,14 @@ import (
"bufio"
"context"
crand "crypto/rand"
"crypto/x509"
"encoding/json"
"errors"
"expvar"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"reflect"
"sync"
@@ -31,6 +35,22 @@ func newPrivateKey(tb testing.TB) (k key.Private) {
return
}
func TestClientInfoUnmarshal(t *testing.T) {
for i, in := range []string{
`{"Version":5,"MeshKey":"abc"}`,
`{"version":5,"meshKey":"abc"}`,
} {
var got clientInfo
if err := json.Unmarshal([]byte(in), &got); err != nil {
t.Fatalf("[%d]: %v", i, err)
}
want := clientInfo{Version: 5, MeshKey: "abc"}
if got != want {
t.Errorf("[%d]: got %+v; want %+v", i, got, want)
}
}
}
func TestSendRecv(t *testing.T) {
serverPrivateKey := newPrivateKey(t)
s := NewServer(serverPrivateKey, t.Logf)
@@ -79,6 +99,8 @@ func TestSendRecv(t *testing.T) {
if err != nil {
t.Fatalf("client %d: %v", i, err)
}
waitConnect(t, c)
clients = append(clients, c)
recvChs = append(recvChs, make(chan []byte))
t.Logf("Connected client %d.", i)
@@ -118,7 +140,7 @@ func TestSendRecv(t *testing.T) {
if got := string(b); got != want {
t.Errorf("client1.Recv=%q, want %q", got, want)
}
case <-time.After(1 * time.Second):
case <-time.After(5 * time.Second):
t.Errorf("client%d.Recv, got nothing, want %q", i, want)
}
}
@@ -224,6 +246,7 @@ func TestSendFreeze(t *testing.T) {
if err != nil {
t.Fatal(err)
}
waitConnect(t, c)
return c, c2
}
@@ -502,7 +525,13 @@ func newTestClient(t *testing.T, ts *testServer, name string, newClient func(net
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) {
brw := bufio.NewReadWriter(bufio.NewReader(nc), bufio.NewWriter(nc))
return NewClient(priv, nc, brw, logf)
c, err := NewClient(priv, nc, brw, logf)
if err != nil {
return nil, err
}
waitConnect(t, c)
return c, nil
})
}
@@ -513,6 +542,7 @@ func newTestWatcher(t *testing.T, ts *testServer, name string) *testClient {
if err != nil {
return nil, err
}
waitConnect(t, c)
if err := c.WatchConnectionChanges(); err != nil {
return nil, err
}
@@ -743,6 +773,24 @@ func TestForwarderRegistration(t *testing.T) {
})
}
func TestMetaCert(t *testing.T) {
priv := newPrivateKey(t)
pub := priv.Public()
s := NewServer(priv, t.Logf)
certBytes := s.MetaCert()
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
log.Fatal(err)
}
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 {
t.Errorf("CommonName = %q; want %q", g, w)
}
}
func BenchmarkSendRecv(b *testing.B) {
for _, size := range []int{10, 100, 1000, 10000} {
b.Run(fmt.Sprintf("msgsize=%d", size), func(b *testing.B) { benchmarkSendRecvSize(b, size) })
@@ -803,3 +851,42 @@ func benchmarkSendRecvSize(b *testing.B, packetSize int) {
}
}
}
func BenchmarkWriteUint32(b *testing.B) {
w := bufio.NewWriter(ioutil.Discard)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
writeUint32(w, 0x0ba3a)
}
}
type nopRead struct{}
func (r nopRead) Read(p []byte) (int, error) {
return len(p), nil
}
var sinkU32 uint32
func BenchmarkReadUint32(b *testing.B) {
r := bufio.NewReader(nopRead{})
var err error
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
sinkU32, err = readUint32(r)
if err != nil {
b.Fatal(err)
}
}
}
func waitConnect(t testing.TB, c *Client) {
t.Helper()
if m, err := c.Recv(); err != nil {
t.Fatalf("client first Recv: %v", err)
} else if v, ok := m.(ServerInfoMessage); !ok {
t.Fatalf("client first Recv was unexpected type %T", v)
}
}

View File

@@ -14,21 +14,27 @@ import (
"bufio"
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"strings"
"sync"
"time"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/derp"
"tailscale.com/net/dnscache"
"tailscale.com/net/netns"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
@@ -252,14 +258,41 @@ 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 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 serverProtoVersion int
if c.useHTTPS() {
httpConn = c.tlsClient(tcpConn, node)
tlsConn := c.tlsClient(tcpConn, node)
httpConn = tlsConn
// Force a handshake now (instead of waiting for it to
// be done implicitly on read/write) so we can check
// the ConnectionState.
if err := tlsConn.Handshake(); err != nil {
return nil, 0, err
}
// We expect to be using TLS 1.3 to our own servers, and only
// starting at TLS 1.3 are the server's returned certificates
// encrypted, so only look for and use our "meta cert" if we're
// using TLS 1.3. If we're not using TLS 1.3, it might be a user
// running cmd/derper themselves with a different configuration,
// in which case we can avoid this fast-start optimization.
// (If a corporate proxy is MITM'ing TLS 1.3 connections with
// corp-mandated TLS root certs than all bets are off anyway.)
// Note that we're not specifically concerned about TLS downgrade
// attacks. TLS handles that fine:
// https://blog.gypsyengineer.com/en/security/how-does-tls-1-3-protect-against-downgrade-attacks.html
connState := tlsConn.ConnectionState()
if connState.Version >= tls.VersionTLS13 {
serverPub, serverProtoVersion = parseMetaCert(connState.PeerCertificates)
}
} else {
httpConn = tcpConn
}
brw := bufio.NewReadWriter(bufio.NewReader(httpConn), bufio.NewWriter(httpConn))
var derpClient *derp.Client
req, err := http.NewRequest("GET", c.urlString(node), nil)
if err != nil {
@@ -268,24 +301,39 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
req.Header.Set("Upgrade", "DERP")
req.Header.Set("Connection", "Upgrade")
if err := req.Write(brw); err != nil {
return nil, 0, err
}
if err := brw.Flush(); err != nil {
return nil, 0, err
}
if !serverPub.IsZero() && serverProtoVersion != 0 {
// parseMetaCert found the server's public key (no TLS
// middlebox was in the way), so skip the HTTP upgrade
// exchange. See https://github.com/tailscale/tailscale/issues/693
// for an overview. We still send the HTTP request
// just to get routed into the server's HTTP Handler so it
// can Hijack the request, but we signal with a special header
// that we don't want to deal with its HTTP response.
req.Header.Set(fastStartHeader, "1") // suppresses the server's HTTP response
if err := req.Write(brw); err != nil {
return nil, 0, err
}
// No need to flush the HTTP request. the derp.Client's initial
// client auth frame will flush it.
} else {
if err := req.Write(brw); err != nil {
return nil, 0, err
}
if err := brw.Flush(); err != nil {
return nil, 0, err
}
resp, err := http.ReadResponse(brw.Reader, req)
if err != nil {
return nil, 0, err
resp, err := http.ReadResponse(brw.Reader, req)
if err != nil {
return nil, 0, err
}
if resp.StatusCode != http.StatusSwitchingProtocols {
b, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
return nil, 0, fmt.Errorf("GET failed: %v: %s", err, b)
}
}
if resp.StatusCode != http.StatusSwitchingProtocols {
b, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
return nil, 0, fmt.Errorf("GET failed: %v: %s", err, b)
}
derpClient, err := derp.NewClient(c.privateKey, httpConn, brw, c.logf, derp.MeshKey(c.MeshKey))
derpClient, err = derp.NewClient(c.privateKey, httpConn, brw, c.logf, derp.MeshKey(c.MeshKey), derp.ServerPublicKey(serverPub))
if err != nil {
return nil, 0, err
}
@@ -364,6 +412,14 @@ func (c *Client) tlsClient(nc net.Conn, node *tailcfg.DERPNode) *tls.Conn {
tlsdial.SetConfigExpectedCert(tlsConf, node.CertName)
}
}
if n := os.Getenv("SSLKEYLOGFILE"); n != "" {
f, err := os.OpenFile(n, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatal(err)
}
log.Printf("WARNING: writing to SSLKEYLOGFILE %v", n)
tlsConf.KeyLogWriter = f
}
return tls.Client(nc, tlsConf)
}
@@ -420,6 +476,19 @@ const dialNodeTimeout = 1500 * time.Millisecond
// TODO(bradfitz): longer if no options remain perhaps? ... Or longer
// overall but have dialRegion start overlapping races?
func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, error) {
// First see if we need to use an HTTP proxy.
proxyReq := &http.Request{
Method: "GET", // doesn't really matter
URL: &url.URL{
Scheme: "https",
Host: c.tlsServerName(n),
Path: "/", // unused
},
}
if proxyURL, err := tshttpproxy.ProxyFromEnvironment(proxyReq); err == nil && proxyURL != nil {
return c.dialNodeUsingProxy(ctx, n, proxyURL)
}
type res struct {
c net.Conn
err error
@@ -480,6 +549,77 @@ func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, e
}
}
func firstStr(a, b string) string {
if a != "" {
return a
}
return b
}
// dialNodeUsingProxy connects to n using a CONNECT to the HTTP(s) proxy in proxyURL.
func (c *Client) dialNodeUsingProxy(ctx context.Context, n *tailcfg.DERPNode, proxyURL *url.URL) (proxyConn net.Conn, err error) {
pu := proxyURL
if pu.Scheme == "https" {
var d tls.Dialer
proxyConn, err = d.DialContext(ctx, "tcp", net.JoinHostPort(pu.Hostname(), firstStr(pu.Port(), "443")))
} else {
var d net.Dialer
proxyConn, err = d.DialContext(ctx, "tcp", net.JoinHostPort(pu.Hostname(), firstStr(pu.Port(), "80")))
}
defer func() {
if err != nil && proxyConn != nil {
// In a goroutine in case it's a *tls.Conn (that can block on Close)
// TODO(bradfitz): track the underlying tcp.Conn and just close that instead.
go proxyConn.Close()
}
}()
if err != nil {
return nil, err
}
done := make(chan struct{})
defer close(done)
go func() {
select {
case <-done:
return
case <-ctx.Done():
proxyConn.Close()
}
}()
target := net.JoinHostPort(n.HostName, "443")
var authHeader string
if v, err := tshttpproxy.GetAuthHeader(pu); err != nil {
c.logf("derphttp: error getting proxy auth header for %v: %v", proxyURL, err)
} else if v != "" {
authHeader = fmt.Sprintf("Proxy-Authorization: %s\r\n", v)
}
if _, err := fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n%s\r\n", target, pu.Hostname(), authHeader); err != nil {
if ctx.Err() != nil {
return nil, ctx.Err()
}
return nil, err
}
br := bufio.NewReader(proxyConn)
res, err := http.ReadResponse(br, nil)
if err != nil {
if ctx.Err() != nil {
return nil, ctx.Err()
}
c.logf("derphttp: CONNECT dial to %s: %v", target, err)
return nil, err
}
c.logf("derphttp: CONNECT dial to %s: %v", target, res.Status)
if res.StatusCode != 200 {
return nil, fmt.Errorf("invalid response status from HTTP proxy %s on CONNECT to %s: %v", pu, target, res.Status)
}
return proxyConn, nil
}
func (c *Client) Send(dstKey key.Public, b []byte) error {
client, _, err := c.connect(context.TODO(), "derphttp.Client.Send")
if err != nil {
@@ -614,3 +754,16 @@ func (c *Client) closeForReconnect(brokenClient *derp.Client) {
}
var ErrClientClosed = errors.New("derphttp.Client closed")
func parseMetaCert(certs []*x509.Certificate) (serverPub key.Public, 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")))
if err == nil && cert.SerialNumber.BitLen() <= 8 { // supports up to version 255
return serverPub, int(cert.SerialNumber.Int64())
}
}
}
return key.Public{}, 0
}

View File

@@ -5,33 +5,51 @@
package derphttp
import (
"fmt"
"log"
"net/http"
"tailscale.com/derp"
)
// fastStartHeader is the header (with value "1") that signals to the HTTP
// server that the DERP HTTP client does not want the HTTP 101 response
// headers and it will begin writing & reading the DERP protocol immediately
// following its HTTP request.
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" {
http.Error(w, "DERP requires connection upgrade", http.StatusUpgradeRequired)
return
}
w.Header().Set("Upgrade", "DERP")
w.Header().Set("Connection", "Upgrade")
w.WriteHeader(http.StatusSwitchingProtocols)
fastStart := r.Header.Get(fastStartHeader) == "1"
h, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "HTTP does not support general TCP support", 500)
return
}
netConn, conn, err := h.Hijack()
if err != nil {
log.Printf("Hijack failed: %v", err)
http.Error(w, "HTTP does not support general TCP support", 500)
return
}
if !fastStart {
pubKey := s.PublicKey()
fmt.Fprintf(conn, "HTTP/1.1 101 Switching Protocols\r\n"+
"Upgrade: DERP\r\n"+
"Connection: Upgrade\r\n"+
"Derp-Version: %v\r\n"+
"Derp-Public-Key: %x\r\n\r\n",
derp.ProtocolVersion,
pubKey[:])
}
s.Accept(netConn, conn, netConn.RemoteAddr().String())
})
}

View File

@@ -6,7 +6,6 @@ package derphttp
import (
"context"
crand "crypto/rand"
"crypto/tls"
"net"
"net/http"
@@ -19,22 +18,15 @@ import (
)
func TestSendRecv(t *testing.T) {
serverPrivateKey := key.NewPrivate()
const numClients = 3
var serverPrivateKey key.Private
if _, err := crand.Read(serverPrivateKey[:]); err != nil {
t.Fatal(err)
}
var clientPrivateKeys []key.Private
for i := 0; i < numClients; i++ {
var key key.Private
if _, err := crand.Read(key[:]); err != nil {
t.Fatal(err)
}
clientPrivateKeys = append(clientPrivateKeys, key)
}
var clientKeys []key.Public
for _, privKey := range clientPrivateKeys {
clientKeys = append(clientKeys, privKey.Public())
for i := 0; i < numClients; i++ {
priv := key.NewPrivate()
clientPrivateKeys = append(clientPrivateKeys, priv)
clientKeys = append(clientKeys, priv.Public())
}
s := derp.NewServer(serverPrivateKey, t.Logf)
@@ -81,6 +73,7 @@ func TestSendRecv(t *testing.T) {
if err := c.Connect(context.Background()); err != nil {
t.Fatalf("client %d Connect: %v", i, err)
}
waitConnect(t, c)
clients = append(clients, c)
recvChs = append(recvChs, make(chan []byte))
@@ -95,6 +88,11 @@ func TestSendRecv(t *testing.T) {
}
m, err := c.Recv()
if err != nil {
select {
case <-done:
return
default:
}
t.Logf("client%d: %v", i, err)
break
}
@@ -118,7 +116,7 @@ func TestSendRecv(t *testing.T) {
if got := string(b); got != want {
t.Errorf("client1.Recv=%q, want %q", got, want)
}
case <-time.After(1 * time.Second):
case <-time.After(5 * time.Second):
t.Errorf("client%d.Recv, got nothing, want %q", i, want)
}
}
@@ -146,5 +144,13 @@ func TestSendRecv(t *testing.T) {
recv(2, string(msg2))
recvNothing(0)
recvNothing(1)
}
func waitConnect(t testing.TB, c *Client) {
t.Helper()
if m, err := c.Recv(); err != nil {
t.Fatalf("client first Recv: %v", err)
} else if v, ok := m.(derp.ServerInfoMessage); !ok {
t.Fatalf("client first Recv was unexpected type %T", v)
}
}

View File

@@ -3,6 +3,13 @@
// license that can be found in the LICENSE file.
// Package derpmap contains information about Tailscale.com's production DERP nodes.
//
// This package is only used by the "tailscale netcheck" command for debugging.
// In normal operation the Tailscale nodes get this sent to them from the control
// server.
//
// TODO: remove this package and make "tailscale netcheck" get the
// list from the control server too.
package derpmap
import (
@@ -21,9 +28,10 @@ func derpNode(suffix, v4, v6 string) *tailcfg.DERPNode {
}
}
func derpRegion(id int, code string, nodes ...*tailcfg.DERPNode) *tailcfg.DERPRegion {
func derpRegion(id int, code, name string, nodes ...*tailcfg.DERPNode) *tailcfg.DERPRegion {
region := &tailcfg.DERPRegion{
RegionID: id,
RegionName: name,
RegionCode: code,
Nodes: nodes,
}
@@ -45,21 +53,36 @@ func derpRegion(id int, code string, nodes ...*tailcfg.DERPNode) *tailcfg.DERPRe
func Prod() *tailcfg.DERPMap {
return &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
1: derpRegion(1, "nyc",
1: derpRegion(1, "nyc", "New York City",
derpNode("a", "159.89.225.99", "2604:a880:400:d1::828:b001"),
),
2: derpRegion(2, "sfo",
2: derpRegion(2, "sfo", "San Francisco",
derpNode("a", "167.172.206.31", "2604:a880:2:d1::c5:7001"),
),
3: derpRegion(3, "sin",
3: derpRegion(3, "sin", "Singapore",
derpNode("a", "68.183.179.66", "2400:6180:0:d1::67d:8001"),
),
4: derpRegion(4, "fra",
4: derpRegion(4, "fra", "Frankfurt",
derpNode("a", "167.172.182.26", "2a03:b0c0:3:e0::36e:9001"),
),
5: derpRegion(5, "syd",
5: derpRegion(5, "syd", "Sydney",
derpNode("a", "103.43.75.49", "2001:19f0:5801:10b7:5400:2ff:feaa:284c"),
),
6: derpRegion(6, "blr", "Bangalore",
derpNode("a", "68.183.90.120", "2400:6180:100:d0::982:d001"),
),
7: derpRegion(7, "tok", "Tokyo",
derpNode("a", "167.179.89.145", "2401:c080:1000:467f:5400:2ff:feee:22aa"),
),
8: derpRegion(8, "lhr", "London",
derpNode("a", "167.71.139.179", "2a03:b0c0:1:e0::3cc:e001"),
),
9: derpRegion(9, "dfw", "Dallas",
derpNode("a", "207.148.3.137", "2001:19f0:6401:1d9c:5400:2ff:feef:bb82"),
),
10: derpRegion(10, "sea", "Seattle",
derpNode("a", "137.220.36.168", "2001:19f0:8001:2d9:5400:2ff:feef:bbb1"),
),
},
}
}

8
go.mod
View File

@@ -3,6 +3,7 @@ module tailscale.com
go 1.14
require (
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29
github.com/coreos/go-iptables v0.4.5
@@ -20,8 +21,9 @@ require (
github.com/miekg/dns v1.1.30
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
github.com/peterbourgon/ff/v2 v2.0.0
github.com/tailscale/winipcfg-go v0.0.0-20200413171540-609dcf2df55f
github.com/tailscale/wireguard-go v0.0.0-20200806235025-91988cfbaa3a
github.com/tailscale/depaware v0.0.0-20200914232109-e09ee10c1824
github.com/tailscale/winipcfg-go v0.0.0-20200916205758-decb9ee8e170
github.com/tailscale/wireguard-go v0.0.0-20200902185615-1997cf6f9fe4
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
go4.org/mem v0.0.0-20200706164138-185c595c3ecc
@@ -29,7 +31,7 @@ require (
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425
honnef.co/go/tools v0.0.1-2020.1.4

23
go.sum
View File

@@ -9,6 +9,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4=
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 h1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=
@@ -74,6 +76,8 @@ github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3/go.mod h1:85jBQOZwp
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/peterbourgon/ff/v2 v2.0.0 h1:lx0oYI5qr/FU1xnpNhQ+EZM04gKgn46jyYvGEEqBBbY=
github.com/peterbourgon/ff/v2 v2.0.0/go.mod h1:xjwr+t+SjWm4L46fcj/D+Ap+6ME7+HqFzaP22pP5Ggk=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 h1:+/+DxvQaYifJ+grD4klzrS5y+KJXldn/2YTl5JG+vZ8=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -81,13 +85,24 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b h1:+gCnWOZV8Z/8jehJ2CdqB47Z3S+SREmQcuXkRFLNsiI=
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tailscale/depaware v0.0.0-20200909185729-8ca448326e3a h1:PjVmKyzFfgQrdrmX7kpRkKXkvwMZP/MF3nJT/WJyjW8=
github.com/tailscale/depaware v0.0.0-20200909185729-8ca448326e3a/go.mod h1:H0k9mKUzaDpb22Zn2FiSzY3zeRbAiZ7wUFxKJ7kp8GE=
github.com/tailscale/depaware v0.0.0-20200910145248-cb751026f10d h1:tjLqVTL0IZdV2kBvM2WqCjug6IBJTWOYiM8wqPk2Xp0=
github.com/tailscale/depaware v0.0.0-20200910145248-cb751026f10d/go.mod h1:H0k9mKUzaDpb22Zn2FiSzY3zeRbAiZ7wUFxKJ7kp8GE=
github.com/tailscale/depaware v0.0.0-20200914201916-3f1070fd0d55 h1:hLAgSpbb0rfOq9jziQbvOsOarpfTDxnFbG8kG6INFeY=
github.com/tailscale/depaware v0.0.0-20200914201916-3f1070fd0d55/go.mod h1:nyzwKFaLuckPu3dAJHH7B6lMi4xDBWzD0r3pEpGZm2Y=
github.com/tailscale/depaware v0.0.0-20200914232109-e09ee10c1824 h1:MD/YQ8xI070ZqFC3SnLAlhPPUNfeRKErQaAaXc/r4dQ=
github.com/tailscale/depaware v0.0.0-20200914232109-e09ee10c1824/go.mod h1:nyzwKFaLuckPu3dAJHH7B6lMi4xDBWzD0r3pEpGZm2Y=
github.com/tailscale/winipcfg-go v0.0.0-20200413171540-609dcf2df55f h1:uFj5bslHsMzxIM8UTjAhq4VXeo6GfNW91rpoh/WMJaY=
github.com/tailscale/winipcfg-go v0.0.0-20200413171540-609dcf2df55f/go.mod h1:x880GWw5fvrl2DVTQ04ttXQD4DuppTt1Yz6wLibbjNE=
github.com/tailscale/wireguard-go v0.0.0-20200806235025-91988cfbaa3a h1:dQEgNpoOJf+8MswlvXJicb8ZDQqZAGe8f/WfzbDMvtE=
github.com/tailscale/wireguard-go v0.0.0-20200806235025-91988cfbaa3a/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4=
github.com/tailscale/winipcfg-go v0.0.0-20200916205758-decb9ee8e170 h1:vJ0twi0120W/LKiDxzXROSVx1F4pIKZBQqvtPahnH60=
github.com/tailscale/winipcfg-go v0.0.0-20200916205758-decb9ee8e170/go.mod h1:x880GWw5fvrl2DVTQ04ttXQD4DuppTt1Yz6wLibbjNE=
github.com/tailscale/wireguard-go v0.0.0-20200902185615-1997cf6f9fe4 h1:UiTXdZChEWxxci7bx+jS9OyHQx2IA8zmMWQqp5wfP7c=
github.com/tailscale/wireguard-go v0.0.0-20200902185615-1997cf6f9fe4/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
@@ -139,6 +154,8 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d h1:QQrM/CCYEzTs91GZylDCQjGHudbPTxF/1fvXdVh5lMo=
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -166,8 +183,6 @@ gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
inet.af/netaddr v0.0.0-20200718043157-99321d6ad24c h1:si3Owrfem175Ry6gKqnh59eOXxDojyBTIHxUKuvK/Eo=
inet.af/netaddr v0.0.0-20200718043157-99321d6ad24c/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww=
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98 h1:bWyWDZP0l6VnQ1TDKf6yNwuiEDV6Q3q1Mv34m+lzT1I=
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww=
rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=

View File

@@ -0,0 +1,9 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tooldeps
import (
_ "github.com/tailscale/depaware/depaware"
)

View File

@@ -62,6 +62,7 @@ type Notify struct {
Status *ipnstate.Status // full status
BrowseToURL *string // UI should open a browser right now
BackendLogID *string // public logtail id used by backend
PingResult *ipnstate.PingResult
// LocalTCPPort, if non-nil, informs the UI frontend which
// (non-zero) localhost TCP port it's listening on.
@@ -143,6 +144,9 @@ type Backend interface {
// WantRunning. This may cause the wireguard engine to
// reconfigure or stop.
SetPrefs(*Prefs)
// SetWantRunning is like SetPrefs but sets only the
// WantRunning field.
SetWantRunning(wantRunning bool)
// RequestEngineStatus polls for an update from the wireguard
// engine. Only needed if you want to display byte
// counts. Connection events are emitted automatically without
@@ -156,4 +160,8 @@ type Backend interface {
// make sure they react properly with keys that are going to
// expire.
FakeExpireAfter(x time.Duration)
// Ping attempts to start connecting to the given IP and sends a Notify
// with its PingResult. If the host is down, there might never
// be a PingResult sent. The cmd/tailscale CLI client adds a timeout.
Ping(ip string)
}

View File

@@ -79,6 +79,10 @@ func (b *FakeBackend) SetPrefs(new *Prefs) {
}
}
func (b *FakeBackend) SetWantRunning(v bool) {
b.SetPrefs(&Prefs{WantRunning: v})
}
func (b *FakeBackend) RequestEngineStatus() {
b.notify(Notify{Engine: &EngineStatus{}})
}
@@ -90,3 +94,7 @@ func (b *FakeBackend) RequestStatus() {
func (b *FakeBackend) FakeExpireAfter(x time.Duration) {
b.notify(Notify{NetMap: &controlclient.NetworkMap{}})
}
func (b *FakeBackend) Ping(ip string) {
b.notify(Notify{PingResult: &ipnstate.PingResult{}})
}

View File

@@ -8,22 +8,29 @@ import (
"bufio"
"context"
"fmt"
"html"
"io"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"os/user"
"runtime"
"sync"
"syscall"
"time"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/ipn"
"tailscale.com/logtail/backoff"
"tailscale.com/net/netstat"
"tailscale.com/safesocket"
"tailscale.com/smallzstd"
"tailscale.com/types/logger"
"tailscale.com/util/pidowner"
"tailscale.com/version"
"tailscale.com/wgengine"
)
@@ -84,11 +91,22 @@ type server struct {
}
func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
br := bufio.NewReader(c)
// First see if it's an HTTP request.
c.SetReadDeadline(time.Now().Add(time.Second))
peek, _ := br.Peek(4)
c.SetReadDeadline(time.Time{})
if string(peek) == "GET " {
http.Serve(&oneConnListener{altReaderNetConn{br, c}}, localhostHandler(c))
return
}
s.addConn(c)
logf("incoming control connection")
defer s.removeAndCloseConn(c)
for ctx.Err() == nil {
msg, err := ipn.ReadMsg(c)
msg, err := ipn.ReadMsg(br)
if err != nil {
if ctx.Err() == nil {
logf("ReadMsg: %v", err)
@@ -394,3 +412,83 @@ func BabysitProc(ctx context.Context, args []string, logf logger.Logf) {
func FixedEngine(eng wgengine.Engine) func() (wgengine.Engine, error) {
return func() (wgengine.Engine, error) { return eng, nil }
}
type dummyAddr string
type oneConnListener struct {
conn net.Conn
}
func (l *oneConnListener) Accept() (c net.Conn, err error) {
c = l.conn
if c == nil {
err = io.EOF
return
}
err = nil
l.conn = nil
return
}
func (l *oneConnListener) Close() error { return nil }
func (l *oneConnListener) Addr() net.Addr { return dummyAddr("unused-address") }
func (a dummyAddr) Network() string { return string(a) }
func (a dummyAddr) String() string { return string(a) }
type altReaderNetConn struct {
r io.Reader
net.Conn
}
func (a altReaderNetConn) Read(p []byte) (int, error) { return a.r.Read(p) }
func localhostHandler(c net.Conn) http.Handler {
la, lerr := netaddr.ParseIPPort(c.LocalAddr().String())
ra, rerr := netaddr.ParseIPPort(c.RemoteAddr().String())
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<html><body><h1>tailscale</h1>\n")
if lerr != nil || rerr != nil {
io.WriteString(w, "failed to parse remote address")
return
}
if !la.IP.IsLoopback() || !ra.IP.IsLoopback() {
io.WriteString(w, "not loopback")
return
}
tab, err := netstat.Get()
if err == netstat.ErrNotImplemented {
io.WriteString(w, "status page not available on "+runtime.GOOS)
return
}
if err != nil {
io.WriteString(w, "failed to get netstat table")
return
}
pid := peerPid(tab.Entries, la, ra)
if pid == 0 {
io.WriteString(w, "peer pid not found")
return
}
uid, err := pidowner.OwnerOfPID(pid)
if err != nil {
io.WriteString(w, "owner of peer pid not found")
return
}
u, err := user.LookupId(uid)
if err != nil {
io.WriteString(w, "User lookup failed")
return
}
fmt.Fprintf(w, "Hello, %s", html.EscapeString(u.Username))
})
}
func peerPid(entries []netstat.Entry, la, ra netaddr.IPPort) int {
for _, e := range entries {
if e.Local == ra && e.Remote == la {
return e.Pid
}
}
return 0
}

View File

@@ -27,8 +27,10 @@ import (
type Status struct {
BackendState string
TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
Peer map[key.Public]*PeerStatus
User map[tailcfg.UserID]tailcfg.UserProfile
Self *PeerStatus
Peer map[key.Public]*PeerStatus
User map[tailcfg.UserID]tailcfg.UserProfile
}
func (s *Status) Peers() []key.Public {
@@ -43,6 +45,7 @@ func (s *Status) Peers() []key.Public {
type PeerStatus struct {
PublicKey key.Public
HostName string // HostInfo's Hostname (not a DNS name or necessarily unique)
DNSName string
OS string // HostInfo.OS
UserID tailcfg.UserID
@@ -88,6 +91,12 @@ type StatusBuilder struct {
st Status
}
func (sb *StatusBuilder) SetBackendState(v string) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.BackendState = v
}
func (sb *StatusBuilder) Status() *Status {
sb.mu.Lock()
defer sb.mu.Unlock()
@@ -95,6 +104,13 @@ func (sb *StatusBuilder) Status() *Status {
return &sb.st
}
// SetSelfStatus sets the status of the local machine.
func (sb *StatusBuilder) SetSelfStatus(ss *PeerStatus) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.Self = ss
}
// AddUser adds a user profile to the status.
func (sb *StatusBuilder) AddUser(id tailcfg.UserID, up tailcfg.UserProfile) {
sb.mu.Lock()
@@ -151,6 +167,9 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
if v := st.HostName; v != "" {
e.HostName = v
}
if v := st.DNSName; v != "" {
e.DNSName = v
}
if v := st.Relay; v != "" {
e.Relay = v
}
@@ -322,3 +341,21 @@ func osEmoji(os string) string {
}
return "👽"
}
// PingResult contains response information for the "tailscale ping" subcommand,
// saying how Tailscale can reach a Tailscale IP or subnet-routed IP.
type PingResult struct {
IP string // ping destination
NodeIP string // Tailscale IP of node handling IP (different for subnet routers)
NodeName string // DNS name base or (possibly not unique) hostname
Err string
LatencySeconds float64
Endpoint string // ip:port if direct UDP was used
DERPRegionID int // non-zero if DERP was used
DERPRegionCode string // three-letter airport/region code if DERP was used
// TODO(bradfitz): details like whether port mapping was used on either side? (Once supported)
}

View File

@@ -19,6 +19,7 @@ import (
"tailscale.com/internal/deepprint"
"tailscale.com/ipn/ipnstate"
"tailscale.com/ipn/policy"
"tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr"
"tailscale.com/portlist"
"tailscale.com/tailcfg"
@@ -109,11 +110,23 @@ func NewLocalBackend(logf logger.Logf, logid string, store StateStore, e wgengin
state: NoState,
portpoll: portpoll,
}
e.SetLinkChangeCallback(b.linkChange)
b.statusChanged = sync.NewCond(&b.statusLock)
return b, nil
}
func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
// TODO(bradfitz): on a major link change, ask controlclient
// whether its host (e.g. login.tailscale.com) is reachable.
// If not, down the world and poll for a bit. Windows' WinHTTP
// service might be unable to resolve its WPAD PAC URL if we
// have DNS/routes configured. So we need to remove that DNS
// and those routes to let it figure out its proxy
// settings. Once it's back up and happy, then we can resume
// and our connection to the control server would work again.
}
// Shutdown halts the backend and all its sub-components. The backend
// can no longer be used after Shutdown returns.
func (b *LocalBackend) Shutdown() {
@@ -144,6 +157,8 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
b.mu.Lock()
defer b.mu.Unlock()
sb.SetBackendState(b.state.String())
// TODO: hostinfo, and its networkinfo
// TODO: EngineStatus copy (and deprecate it?)
if b.netMap != nil {
@@ -164,6 +179,7 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
UserID: p.User,
TailAddr: tailAddr,
HostName: p.Hostinfo.Hostname,
DNSName: p.Name,
OS: p.Hostinfo.OS,
KeepAlive: p.KeepAlive,
Created: p.Created,
@@ -539,7 +555,7 @@ func (b *LocalBackend) updateDNSMap(netMap *controlclient.NetworkMap) {
nameToIP := make(map[string]netaddr.IP)
set := func(name string, addrs []wgcfg.CIDR) {
if len(addrs) == 0 {
if len(addrs) == 0 || name == "" {
return
}
nameToIP[name] = netaddr.IPFrom16(addrs[0].IP.Addr)
@@ -550,7 +566,7 @@ func (b *LocalBackend) updateDNSMap(netMap *controlclient.NetworkMap) {
}
set(netMap.Name, netMap.Addresses)
dnsMap := tsdns.NewMap(nameToIP)
dnsMap := tsdns.NewMap(nameToIP, domainsForProxying(netMap))
// map diff will be logged in tsdns.Resolver.SetMap.
b.e.SetDNSMap(dnsMap)
}
@@ -745,6 +761,17 @@ func (b *LocalBackend) FakeExpireAfter(x time.Duration) {
b.send(Notify{NetMap: b.netMap})
}
func (b *LocalBackend) Ping(ipStr string) {
ip, err := netaddr.ParseIP(ipStr)
if err != nil {
b.logf("ignoring Ping request to invalid IP %q", ipStr)
return
}
b.e.Ping(ip, func(pr *ipnstate.PingResult) {
b.send(Notify{PingResult: pr})
})
}
func (b *LocalBackend) parseWgStatus(s *wgengine.Status) (ret EngineStatus) {
var (
peerStats []string
@@ -765,7 +792,9 @@ func (b *LocalBackend) parseWgStatus(s *wgengine.Status) (ret EngineStatus) {
ret.WBytes += p.TxBytes
}
if len(peerStats) > 0 {
// [GRINDER STATS LINE] - please don't remove (used for log parsing)
b.keyLogf("peer keys: %s", strings.Join(peerKeys, " "))
// [GRINDER STATS LINE] - please don't remove (used for log parsing)
b.logf("v%v peers: %v", version.LONG, strings.Join(peerStats, " "))
}
return ret
@@ -783,6 +812,18 @@ func (b *LocalBackend) shieldsAreUp() bool {
return b.prefs.ShieldsUp
}
func (b *LocalBackend) SetWantRunning(wantRunning bool) {
b.mu.Lock()
new := b.prefs.Clone()
b.mu.Unlock()
if new.WantRunning == wantRunning {
return
}
new.WantRunning = wantRunning
b.logf("SetWantRunning: %v", wantRunning)
b.SetPrefs(new)
}
// SetPrefs saves new user preferences and propagates them throughout
// the system. Implements Backend.
func (b *LocalBackend) SetPrefs(new *Prefs) {
@@ -816,6 +857,7 @@ func (b *LocalBackend) SetPrefs(new *Prefs) {
}
}
// [GRINDER STATS LINE] - please don't remove (used for log parsing)
b.logf("SetPrefs: %v", new.Pretty())
if old.ShieldsUp != new.ShieldsUp || hostInfoChanged {
@@ -911,12 +953,6 @@ func (b *LocalBackend) authReconfig() {
flags |= controlclient.AllowDefaultRoute
// TODO(apenwarr): Make subnet routes a different pref?
flags |= controlclient.AllowSubnetRoutes
// TODO(apenwarr): Remove this once we sort out subnet routes.
// Right now default routes are broken in Windows, but
// controlclient doesn't properly send subnet routes. So
// let's convert a default route into a subnet route in order
// to allow experimentation.
flags |= controlclient.HackDefaultRoute
}
if uc.AllowSingleHosts {
flags |= controlclient.AllowSingleHosts
@@ -1209,11 +1245,21 @@ func (b *LocalBackend) requestEngineStatusAndWait() {
// rebooting will fix it.
func (b *LocalBackend) Logout() {
b.mu.Lock()
b.assertClientLocked()
c := b.c
b.netMap = nil
b.mu.Unlock()
if c == nil {
// Double Logout can happen via repeated IPN
// connections to ipnserver making it repeatedly
// transition from 1->0 total connections, which on
// Windows by default ("client mode") causes a Logout
// on the transition to zero.
// Previously this crashed when we asserted that c was non-nil
// here.
return
}
c.Logout()
b.mu.Lock()

89
ipn/loglines_test.go Normal file
View File

@@ -0,0 +1,89 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ipn
import (
"reflect"
"testing"
"time"
"tailscale.com/control/controlclient"
"tailscale.com/logtail"
"tailscale.com/tailcfg"
"tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/wgengine"
)
// TestLocalLogLines tests to make sure that the log lines required for log parsing are
// being logged by the expected functions. Update these tests if moving log lines between
// functions.
func TestLocalLogLines(t *testing.T) {
logListen := tstest.NewLogLineTracker(t.Logf, []string{
"SetPrefs: %v",
"peer keys: %s",
"v%v peers: %v",
})
logid := func(hex byte) logtail.PublicID {
var ret logtail.PublicID
for i := 0; i < len(ret); i++ {
ret[i] = hex
}
return ret
}
idA := logid(0xaa)
// set up a LocalBackend, super bare bones. No functional data.
store := &MemoryStore{
cache: make(map[StateKey][]byte),
}
e, err := wgengine.NewFakeUserspaceEngine(logListen.Logf, 0)
if err != nil {
t.Fatal(err)
}
lb, err := NewLocalBackend(logListen.Logf, idA.String(), store, e)
if err != nil {
t.Fatal(err)
}
defer lb.Shutdown()
// custom adjustments for required non-nil fields
lb.prefs = NewPrefs()
lb.hostinfo = &tailcfg.Hostinfo{}
// hacky manual override of the usual log-on-change behaviour of keylogf
lb.keyLogf = logListen.Logf
testWantRemain := func(wantRemain ...string) func(t *testing.T) {
return func(t *testing.T) {
if remain := logListen.Check(); !reflect.DeepEqual(remain, wantRemain) {
t.Errorf("remain %q, want %q", remain, wantRemain)
}
}
}
// log prefs line
persist := &controlclient.Persist{}
prefs := NewPrefs()
prefs.Persist = persist
lb.SetPrefs(prefs)
t.Run("after_prefs", testWantRemain("peer keys: %s", "v%v peers: %v"))
// log peers, peer keys
status := &wgengine.Status{
Peers: []wgengine.PeerStatus{wgengine.PeerStatus{
TxBytes: 10,
RxBytes: 10,
LastHandshake: time.Now(),
NodeKey: tailcfg.NodeKey(key.NewPrivate()),
}},
LocalAddrs: []string{"idk an address"},
}
lb.parseWgStatus(status)
t.Run("after_peers", testWantRemain())
}

View File

@@ -33,6 +33,10 @@ type FakeExpireAfterArgs struct {
Duration time.Duration
}
type PingArgs struct {
IP string
}
// Command is a command message that is JSON encoded and sent by a
// frontend to a backend.
type Command struct {
@@ -53,9 +57,11 @@ type Command struct {
Login *oauth2.Token
Logout *NoArgs
SetPrefs *SetPrefsArgs
SetWantRunning *bool
RequestEngineStatus *NoArgs
RequestStatus *NoArgs
FakeExpireAfter *FakeExpireAfterArgs
Ping *PingArgs
}
type BackendServer struct {
@@ -139,6 +145,9 @@ func (bs *BackendServer) GotCommand(cmd *Command) error {
} else if c := cmd.SetPrefs; c != nil {
bs.b.SetPrefs(c.New)
return nil
} else if c := cmd.SetWantRunning; c != nil {
bs.b.SetWantRunning(*c)
return nil
} else if c := cmd.RequestEngineStatus; c != nil {
bs.b.RequestEngineStatus()
return nil
@@ -148,6 +157,9 @@ func (bs *BackendServer) GotCommand(cmd *Command) error {
} else if c := cmd.FakeExpireAfter; c != nil {
bs.b.FakeExpireAfter(c.Duration)
return nil
} else if c := cmd.Ping; c != nil {
bs.b.Ping(c.IP)
return nil
} else {
return fmt.Errorf("BackendServer.Do: no command specified")
}
@@ -254,6 +266,14 @@ func (bc *BackendClient) FakeExpireAfter(x time.Duration) {
bc.send(Command{FakeExpireAfter: &FakeExpireAfterArgs{Duration: x}})
}
func (bc *BackendClient) Ping(ip string) {
bc.send(Command{Ping: &PingArgs{IP: ip}})
}
func (bc *BackendClient) SetWantRunning(v bool) {
bc.send(Command{SetWantRunning: &v})
}
// MaxMessageSize is the maximum message size, in bytes.
const MaxMessageSize = 10 << 20

View File

@@ -15,15 +15,18 @@ import (
"time"
)
// LogHeap writes a JSON logtail record with the base64 heap pprof to
// os.Stderr.
// LogHeap uploads a JSON logtail record with the base64 heap pprof by means
// of an HTTP POST request to the endpoint referred to in postURL.
func LogHeap(postURL string) {
if postURL == "" {
return
}
runtime.GC()
buf := new(bytes.Buffer)
pprof.WriteHeapProfile(buf)
if err := pprof.WriteHeapProfile(buf); err != nil {
log.Printf("LogHeap: %v", err)
return
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

View File

@@ -31,6 +31,8 @@ import (
"tailscale.com/logtail/filch"
"tailscale.com/net/netns"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy"
"tailscale.com/paths"
"tailscale.com/smallzstd"
"tailscale.com/types/logger"
"tailscale.com/version"
@@ -103,12 +105,23 @@ func (l logWriter) Write(buf []byte) (int, error) {
// logsDir returns the directory to use for log configuration and
// buffer storage.
func logsDir(logf logger.Logf) string {
// STATE_DIRECTORY is set by systemd 240+ but we support older
// systems-d. For example, Ubuntu 18.04 (Bionic Beaver) is 237.
systemdStateDir := os.Getenv("STATE_DIRECTORY")
if systemdStateDir != "" {
logf("logpolicy: using $STATE_DIRECTORY, %q", systemdStateDir)
return systemdStateDir
}
// Default to e.g. /var/lib/tailscale or /var/db/tailscale on Unix.
if d := paths.DefaultTailscaledStateFile(); d != "" {
d = filepath.Dir(d) // directory of e.g. "/var/lib/tailscale/tailscaled.state"
if err := os.MkdirAll(d, 0700); err == nil {
logf("logpolicy: using system state directory %q", d)
return d
}
}
cacheDir, err := os.UserCacheDir()
if err == nil {
d := filepath.Join(cacheDir, "Tailscale")
@@ -419,6 +432,9 @@ func newLogtailTransport(host string) *http.Transport {
// Start with a copy of http.DefaultTransport and tweak it a bit.
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
// We do our own zstd compression on uploads, and responses never contain any payload,
// so don't send "Accept-Encoding: gzip" to save a few bytes on the wire, since there
// will never be any body to decompress:

View File

@@ -8,13 +8,19 @@ package interfaces
import (
"fmt"
"net"
"net/http"
"reflect"
"strings"
"inet.af/netaddr"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tshttpproxy"
)
// LoginEndpointForProxyDetermination is the URL used for testing
// which HTTP proxy the system should use.
var LoginEndpointForProxyDetermination = "https://login.tailscale.com/"
// Tailscale returns the current machine's Tailscale interface, if any.
// If none is found, all zero values are returned.
// A non-nil error is only returned on a problem listing the system interfaces.
@@ -43,7 +49,8 @@ func Tailscale() (net.IP, *net.Interface, error) {
// maybeTailscaleInterfaceName reports whether s is an interface
// name that might be used by Tailscale.
func maybeTailscaleInterfaceName(s string) bool {
return strings.HasPrefix(s, "wg") ||
return s == "Tailscale" ||
strings.HasPrefix(s, "wg") ||
strings.HasPrefix(s, "ts") ||
strings.HasPrefix(s, "tailscale") ||
strings.HasPrefix(s, "utun")
@@ -163,6 +170,13 @@ type State struct {
// considered "expensive", which currently means LTE/etc
// instead of Wifi. This field is not populated by GetState.
IsExpensive bool
// DefaultRouteInterface is the interface name for the machine's default route.
// It is not yet populated on all OSes.
DefaultRouteInterface string
// HTTPProxy is the HTTP proxy to use.
HTTPProxy string
}
func (s *State) Equal(s2 *State) bool {
@@ -175,7 +189,8 @@ func (s *State) Equal(s2 *State) bool {
// /^tailscale/)
func (s *State) RemoveTailscaleInterfaces() {
for name := range s.InterfaceIPs {
if strings.HasPrefix(name, "tailscale") { // TODO: use --tun flag value, etc; see TODO in method doc
if name == "Tailscale" || // as it is on Windows
strings.HasPrefix(name, "tailscale") { // TODO: use --tun flag value, etc; see TODO in method doc
delete(s.InterfaceIPs, name)
delete(s.InterfaceUp, name)
}
@@ -198,6 +213,16 @@ func GetState() (*State, error) {
}); err != nil {
return nil, err
}
s.DefaultRouteInterface, _ = DefaultRouteInterface()
req, err := http.NewRequest("GET", LoginEndpointForProxyDetermination, nil)
if err != nil {
return nil, err
}
if u, err := tshttpproxy.ProxyFromEnvironment(req); err == nil && u != nil {
s.HTTPProxy = u.String()
}
return s, nil
}

View File

@@ -105,8 +105,6 @@ import "C"
import (
"encoding/binary"
"fmt"
"os"
"inet.af/netaddr"
)
@@ -117,7 +115,6 @@ func init() {
func likelyHomeRouterIPDarwinSyscall() (ret netaddr.IP, ok bool) {
ip := C.privateGatewayIP()
fmt.Fprintln(os.Stderr, "likelyHomeRouterIPDarwinSyscall", ip)
if ip < 255 {
return netaddr.IP{}, false
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !linux
package interfaces
import "errors"
var errTODO = errors.New("TODO")
func DefaultRouteInterface() (string, error) {
return "TODO", errTODO
}

View File

@@ -5,10 +5,15 @@
package interfaces
import (
"bufio"
"bytes"
"errors"
"io"
"log"
"os"
"os/exec"
"runtime"
"strings"
"go4.org/mem"
"inet.af/netaddr"
@@ -115,3 +120,91 @@ func likelyHomeRouterIPAndroid() (ret netaddr.IP, ok bool) {
cmd.Wait()
return ret, !ret.IsZero()
}
// DefaultRouteInterface returns the name of the network interface that owns
// the default route, not including any tailscale interfaces.
func DefaultRouteInterface() (string, error) {
v, err := defaultRouteInterfaceProcNet()
if err == nil {
return v, nil
}
if runtime.GOOS == "android" {
return defaultRouteInterfaceAndroidIPRoute()
}
return v, err
}
var zeroRouteBytes = []byte("00000000")
func defaultRouteInterfaceProcNet() (string, error) {
f, err := os.Open("/proc/net/route")
if err != nil {
return "", err
}
defer f.Close()
br := bufio.NewReaderSize(f, 128)
for {
line, err := br.ReadSlice('\n')
if err == io.EOF {
break
}
if err != nil {
return "", err
}
if !bytes.Contains(line, zeroRouteBytes) {
continue
}
fields := strings.Fields(string(line))
ifc := fields[0]
ip := fields[1]
netmask := fields[7]
if strings.HasPrefix(ifc, "tailscale") ||
strings.HasPrefix(ifc, "wg") {
continue
}
if ip == "00000000" && netmask == "00000000" {
// default route
return ifc, nil // interface name
}
}
return "", errors.New("no default routes found")
}
// defaultRouteInterfaceAndroidIPRoute tries to find the machine's default route interface name
// by parsing the "ip route" command output. We use this on Android where /proc/net/route
// can be missing entries or have locked-down permissions.
// See also comments in https://github.com/tailscale/tailscale/pull/666.
func defaultRouteInterfaceAndroidIPRoute() (ifname string, err error) {
cmd := exec.Command("/system/bin/ip", "route", "show", "table", "0")
out, err := cmd.StdoutPipe()
if err != nil {
return "", err
}
if err := cmd.Start(); err != nil {
log.Printf("interfaces: running /system/bin/ip: %v", err)
return "", err
}
// Search for line like "default via 10.0.2.2 dev radio0 table 1016 proto static mtu 1500 "
lineread.Reader(out, func(line []byte) error {
const pfx = "default via "
if !mem.HasPrefix(mem.B(line), mem.S(pfx)) {
return nil
}
ff := strings.Fields(string(line))
for i, v := range ff {
if i > 0 && ff[i-1] == "dev" && ifname == "" {
ifname = v
}
}
return nil
})
cmd.Process.Kill()
cmd.Wait()
if ifname == "" {
return "", errors.New("no default routes found")
}
return ifname, nil
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package interfaces
import "testing"
func BenchmarkDefaultRouteInterface(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := DefaultRouteInterface(); err != nil {
b.Fatal(err)
}
}
}

View File

@@ -8,8 +8,10 @@ import (
"os/exec"
"syscall"
"github.com/tailscale/winipcfg-go"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/tsconst"
"tailscale.com/util/lineread"
)
@@ -71,3 +73,22 @@ func likelyHomeRouterIPWindows() (ret netaddr.IP, ok bool) {
})
return ret, !ret.IsZero()
}
// NonTailscaleMTUs returns a map of interface LUID to interface MTU,
// for all interfaces except Tailscale tunnels.
func NonTailscaleMTUs() (map[uint64]uint32, error) {
ifs, err := winipcfg.GetInterfaces()
if err != nil {
return nil, err
}
ret := map[uint64]uint32{}
for _, iface := range ifs {
if iface.Description == tsconst.WintunInterfaceDesc {
continue
}
ret[iface.Luid] = iface.Mtu
}
return ret, nil
}

View File

@@ -171,6 +171,15 @@ type STUNConn interface {
ReadFrom([]byte) (int, net.Addr, error)
}
func (c *Client) enoughRegions() int {
if c.Verbose {
// Abuse verbose a bit here so netcheck can show all region latencies
// in verbose mode.
return 100
}
return 3
}
func (c *Client) logf(format string, a ...interface{}) {
if c.Logf != nil {
c.Logf(format, a...)
@@ -227,7 +236,6 @@ func (c *Client) ReceiveSTUNPacket(pkt []byte, src netaddr.IPPort) {
tx, addr, port, err := stun.ParseResponse(pkt)
if err != nil {
c.mu.Unlock()
if _, err := stun.ParseBindingRequest(pkt); err == nil {
// This was probably our own netcheck hairpin
// check probe coming in late. Ignore.
@@ -615,12 +623,13 @@ func (rs *reportState) addNodeLatency(node *tailcfg.DERPNode, ipp netaddr.IPPort
ret.UDP = true
updateLatency(ret.RegionLatency, node.RegionID, d)
// Once we've heard from 3 regions, start a timer to give up
// on the other ones. The timer's duration is a function of
// whether this is our initial full probe or an incremental
// one. For incremental ones, wait for the duration of the
// slowest region. For initial ones, double that.
if len(ret.RegionLatency) == 3 {
// Once we've heard from enough regions (3), start a timer to
// give up on the other ones. The timer's duration is a
// function of whether this is our initial full probe or an
// incremental one. For incremental ones, wait for the
// duration of the slowest region. For initial ones, double
// that.
if len(ret.RegionLatency) == rs.c.enoughRegions() {
timeout := maxDurationValue(ret.RegionLatency)
if !rs.incremental {
timeout *= 2

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !linux
// +build !linux,!windows
package netns

View File

@@ -5,19 +5,15 @@
package netns
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"golang.org/x/sys/unix"
"tailscale.com/net/interfaces"
)
// tailscaleBypassMark is the mark indicating that packets originating
@@ -43,47 +39,6 @@ func ipRuleAvailable() bool {
return ipRuleOnce.v
}
var zeroRouteBytes = []byte("00000000")
// defaultRouteInterface returns the name of the network interface that owns
// the default route, not including any tailscale interfaces. We only use
// this in SO_BINDTODEVICE mode.
func defaultRouteInterface() (string, error) {
f, err := os.Open("/proc/net/route")
if err != nil {
return "", err
}
defer f.Close()
br := bufio.NewReaderSize(f, 128)
for {
line, err := br.ReadSlice('\n')
if err == io.EOF {
break
}
if err != nil {
return "", err
}
if !bytes.Contains(line, zeroRouteBytes) {
continue
}
fields := strings.Fields(string(line))
ifc := fields[0]
ip := fields[1]
netmask := fields[7]
if strings.HasPrefix(ifc, "tailscale") ||
strings.HasPrefix(ifc, "wg") {
continue
}
if ip == "00000000" && netmask == "00000000" {
// default route
return ifc, nil // interface name
}
}
return "", errors.New("no default routes found")
}
// ignoreErrors returns true if we should ignore setsocketopt errors in
// this instance.
func ignoreErrors() bool {
@@ -133,7 +88,7 @@ func setBypassMark(fd uintptr) error {
}
func bindToDevice(fd uintptr) error {
ifc, err := defaultRouteInterface()
ifc, err := interfaces.DefaultRouteInterface()
if err != nil {
// Make sure we bind to *some* interface,
// or we could get a routing loop.

View File

@@ -49,12 +49,3 @@ func TestBypassMarkInSync(t *testing.T) {
}
t.Errorf("tailscaleBypassMark not found in router_linux.go")
}
func BenchmarkDefaultRouteInterface(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := defaultRouteInterface(); err != nil {
b.Fatal(err)
}
}
}

123
net/netns/netns_windows.go Normal file
View File

@@ -0,0 +1,123 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package netns
import (
"encoding/binary"
"syscall"
"unsafe"
"github.com/tailscale/winipcfg-go"
"golang.org/x/sys/windows"
"tailscale.com/net/interfaces"
)
// control binds c to the Windows interface that holds a default
// route, and is not the Tailscale WinTun interface.
func control(network, address string, c syscall.RawConn) error {
canV4, canV6 := false, false
switch network {
case "tcp", "udp":
canV4, canV6 = true, true
case "tcp4", "udp4":
canV4 = true
case "tcp6", "udp6":
canV6 = true
}
if canV4 {
if4, err := getDefaultInterface(winipcfg.AF_INET)
if err != nil {
return err
}
if err := bindSocket4(c, if4); err != nil {
return err
}
}
if canV6 {
if6, err := getDefaultInterface(winipcfg.AF_INET6)
if err != nil {
return err
}
if err := bindSocket6(c, if6); err != nil {
return err
}
}
return nil
}
// getDefaultInterface returns the index of the interface that has the
// non-Tailscale default route for the given address family.
func getDefaultInterface(family winipcfg.AddressFamily) (ifidx uint32, err error) {
ifs, err := interfaces.NonTailscaleMTUs()
if err != nil {
return 0, err
}
routes, err := winipcfg.GetRoutes(family)
if err != nil {
return 0, err
}
bestMetric := ^uint32(0)
// The zero index means "unspecified". If we end up passing zero
// to bindSocket*(), it unsets the binding and lets the socket
// behave as normal again, which is what we want if there's no
// default route we can use.
var index uint32
for _, route := range routes {
if route.DestinationPrefix.PrefixLength != 0 || ifs[route.InterfaceLuid] == 0 {
continue
}
if route.Metric < bestMetric {
bestMetric = route.Metric
index = route.InterfaceIndex
}
}
return index, nil
}
const sockoptBoundInterface = 31
// bindSocket4 binds the given RawConn to the network interface with
// index ifidx, for IPv4 traffic only.
func bindSocket4(c syscall.RawConn, ifidx uint32) error {
// For v4 the interface index must be passed as a big-endian
// integer, regardless of platform endianness.
index := nativeToBigEndian(ifidx)
var controlErr error
err := c.Control(func(fd uintptr) {
controlErr = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, sockoptBoundInterface, int(index))
})
if err != nil {
return err
}
return controlErr
}
// bindSocket6 binds the given RawConn to the network interface with
// index ifidx, for IPv6 traffic only.
func bindSocket6(c syscall.RawConn, ifidx uint32) error {
var controlErr error
err := c.Control(func(fd uintptr) {
controlErr = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, sockoptBoundInterface, int(ifidx))
})
if err != nil {
return err
}
return controlErr
}
// nativeToBigEndian returns i converted into big-endian
// representation, suitable for passing to Windows APIs that require a
// mangled uint32.
func nativeToBigEndian(i uint32) uint32 {
var b [4]byte
binary.BigEndian.PutUint32(b[:], i)
return *(*uint32)(unsafe.Pointer(&b[0]))
}

36
net/netstat/netstat.go Normal file
View File

@@ -0,0 +1,36 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package netstat returns the local machine's network connection table.
package netstat
import (
"errors"
"runtime"
"inet.af/netaddr"
)
var ErrNotImplemented = errors.New("not implemented for GOOS=" + runtime.GOOS)
type Entry struct {
Local, Remote netaddr.IPPort
Pid int
State string // TODO: type?
}
// Table contains local machine's TCP connection entries.
//
// Currently only TCP (IPv4 and IPv6) are included.
type Table struct {
Entries []Entry
}
// Get returns the connection table.
//
// It returns ErrNotImplemented if the table is not available for the
// current operating system.
func Get() (*Table, error) {
return get()
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !windows
package netstat
func get() (*Table, error) {
return nil, ErrNotImplemented
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package netstat
import (
"testing"
)
func TestGet(t *testing.T) {
nt, err := Get()
if err == ErrNotImplemented {
t.Skip("TODO: not implemented")
}
if err != nil {
t.Fatal(err)
}
for _, e := range nt.Entries {
t.Logf("Entry: %+v", e)
}
}

View File

@@ -0,0 +1,178 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package netstat returns the local machine's network connection table.
package netstat
import (
"encoding/binary"
"errors"
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
"inet.af/netaddr"
)
// See https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getextendedtcptable
// TCP_TABLE_OWNER_PID_ALL means to include the PID info. The table type
// we get back from Windows depends on AF_INET vs AF_INET6:
// MIB_TCPTABLE_OWNER_PID for v4 or MIB_TCP6TABLE_OWNER_PID for v6.
const tcpTableOwnerPidAll = 5
var (
iphlpapi = syscall.NewLazyDLL("iphlpapi.dll")
getTCPTable = iphlpapi.NewProc("GetExtendedTcpTable")
// TODO: GetExtendedUdpTable also? if/when needed.
)
type _MIB_TCPROW_OWNER_PID struct {
state uint32
localAddr uint32
localPort uint32
remoteAddr uint32
remotePort uint32
pid uint32
}
type _MIB_TCP6ROW_OWNER_PID struct {
localAddr [16]byte
localScope uint32
localPort uint32
remoteAddr [16]byte
remoteScope uint32
remotePort uint32
state uint32
pid uint32
}
func get() (*Table, error) {
t := new(Table)
if err := t.addEntries(windows.AF_INET); err != nil {
return nil, fmt.Errorf("failed to get IPv4 entries: %w", err)
}
if err := t.addEntries(windows.AF_INET6); err != nil {
return nil, fmt.Errorf("failed to get IPv6 entries: %w", err)
}
return t, nil
}
func (t *Table) addEntries(fam int) error {
var size uint32
var addr unsafe.Pointer
var buf []byte
for {
err, _, _ := getTCPTable.Call(
uintptr(addr),
uintptr(unsafe.Pointer(&size)),
1, // sorted
uintptr(fam),
tcpTableOwnerPidAll,
0, // reserved; "must be zero"
)
if err == 0 {
break
}
if err == uintptr(syscall.ERROR_INSUFFICIENT_BUFFER) {
const maxSize = 10 << 20
if size > maxSize || size < 4 {
return fmt.Errorf("unreasonable kernel-reported size %d", size)
}
buf = make([]byte, size)
addr = unsafe.Pointer(&buf[0])
continue
}
return syscall.Errno(err)
}
if len(buf) < int(size) {
return errors.New("unexpected size growth from system call")
}
buf = buf[:size]
numEntries := *(*uint32)(unsafe.Pointer(&buf[0]))
buf = buf[4:]
var recSize int
switch fam {
case windows.AF_INET:
recSize = 6 * 4
case windows.AF_INET6:
recSize = 6*4 + 16*2
}
dataLen := numEntries * uint32(recSize)
if uint32(len(buf)) > dataLen {
buf = buf[:dataLen]
}
for len(buf) >= recSize {
switch fam {
case windows.AF_INET:
row := (*_MIB_TCPROW_OWNER_PID)(unsafe.Pointer(&buf[0]))
t.Entries = append(t.Entries, Entry{
Local: ipport4(row.localAddr, port(&row.localPort)),
Remote: ipport4(row.remoteAddr, port(&row.remotePort)),
Pid: int(row.pid),
State: state(row.state),
})
case windows.AF_INET6:
row := (*_MIB_TCP6ROW_OWNER_PID)(unsafe.Pointer(&buf[0]))
t.Entries = append(t.Entries, Entry{
Local: ipport6(row.localAddr, row.localScope, port(&row.localPort)),
Remote: ipport6(row.remoteAddr, row.remoteScope, port(&row.remotePort)),
Pid: int(row.pid),
State: state(row.state),
})
}
buf = buf[recSize:]
}
return nil
}
var states = []string{
"",
"CLOSED",
"LISTEN",
"SYN-SENT",
"SYN-RECEIVED",
"ESTABLISHED",
"FIN-WAIT-1",
"FIN-WAIT-2",
"CLOSE-WAIT",
"CLOSING",
"LAST-ACK",
"DELETE-TCB",
}
func state(v uint32) string {
if v < uint32(len(states)) {
return states[v]
}
return fmt.Sprintf("unknown-state-%d", v)
}
func ipport4(addr uint32, port uint16) netaddr.IPPort {
a4 := (*[4]byte)(unsafe.Pointer(&addr))
return netaddr.IPPort{
IP: netaddr.IPv4(a4[0], a4[1], a4[2], a4[3]),
Port: port,
}
}
func ipport6(addr [16]byte, scope uint32, port uint16) netaddr.IPPort {
ip := netaddr.IPFrom16(addr)
if scope != 0 {
// TODO: something better here?
ip = ip.WithZone(fmt.Sprint(scope))
}
return netaddr.IPPort{
IP: ip,
Port: port,
}
}
func port(v *uint32) uint16 {
p := (*[4]byte)(unsafe.Pointer(v))
return binary.BigEndian.Uint16(p[:2])
}

View File

@@ -137,15 +137,15 @@ func foreachAttr(b []byte, fn func(attrType uint16, a []byte) error) error {
}
attrType := binary.BigEndian.Uint16(b[:2])
attrLen := int(binary.BigEndian.Uint16(b[2:4]))
attrLenPad := attrLen % 4
attrLenWithPad := (attrLen + 3) &^ 3
b = b[4:]
if attrLen+attrLenPad > len(b) {
if attrLenWithPad > len(b) {
return ErrMalformedAttrs
}
if err := fn(attrType, b[:attrLen]); err != nil {
return err
}
b = b[attrLen+attrLenPad:]
b = b[attrLenWithPad:]
}
return nil
}

View File

@@ -140,6 +140,41 @@ var responseTests = []struct {
wantAddr: net.ParseIP("2602:d1:b4cf:c100:38b2:31ff:feef:96f6"),
wantPort: 37070,
},
// Testing STUN attribute padding rules using STUN software attribute
// with values of 1 & 3 length respectively before the XorMappedAddress attribute
{
name: "software-a",
data: []byte{
0x01, 0x01, 0x00, 0x14, 0x21, 0x12, 0xa4, 0x42,
0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
0x4f, 0x3e, 0x30, 0x8e, 0x80, 0x22, 0x00, 0x01,
0x61, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x08,
0x00, 0x01, 0xce, 0x66, 0x5e, 0x12, 0xa4, 0x43,
},
wantTID: []byte{
0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
0x4f, 0x3e, 0x30, 0x8e,
},
wantAddr: []byte{127, 0, 0, 1},
wantPort: 61300,
},
{
name: "software-abc",
data: []byte{
0x01, 0x01, 0x00, 0x14, 0x21, 0x12, 0xa4, 0x42,
0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
0x4f, 0x3e, 0x30, 0x8e, 0x80, 0x22, 0x00, 0x03,
0x61, 0x62, 0x63, 0x00, 0x00, 0x20, 0x00, 0x08,
0x00, 0x01, 0xce, 0x66, 0x5e, 0x12, 0xa4, 0x43,
},
wantTID: []byte{
0xeb, 0xc2, 0xd3, 0x6e, 0xf4, 0x71, 0x21, 0x7c,
0x4f, 0x3e, 0x30, 0x8e,
},
wantAddr: []byte{127, 0, 0, 1},
wantPort: 61300,
},
}
func TestParseResponse(t *testing.T) {

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package tshttpproxy contains Tailscale additions to httpproxy not available
// in golang.org/x/net/http/httpproxy. Notably, it aims to support Windows better.
package tshttpproxy
import (
"net/http"
"net/url"
"os"
)
// sysProxyFromEnv, if non-nil, specifies a platform-specific ProxyFromEnvironment
// func to use if http.ProxyFromEnvironment doesn't return a proxy.
// For example, WPAD PAC files on Windows.
var sysProxyFromEnv func(*http.Request) (*url.URL, error)
func ProxyFromEnvironment(req *http.Request) (*url.URL, error) {
u, err := http.ProxyFromEnvironment(req)
if u != nil && err == nil {
return u, nil
}
if sysProxyFromEnv != nil {
u, err := sysProxyFromEnv(req)
if u != nil && err == nil {
return u, nil
}
}
return nil, err
}
var sysAuthHeader func(*url.URL) (string, error)
// GetAuthHeader returns the Authorization header value to send to proxy u.
func GetAuthHeader(u *url.URL) (string, error) {
if fake := os.Getenv("TS_DEBUG_FAKE_PROXY_AUTH"); fake != "" {
return fake, nil
}
if sysAuthHeader != nil {
return sysAuthHeader(u)
}
return "", nil
}
var condSetTransportGetProxyConnectHeader func(*http.Transport)
// SetTarnsportGetProxyConnectHeader sets the provided Transport's
// GetProxyConnectHeader field, if the current build of Go supports
// it.
//
// See https://github.com/golang/go/issues/41048.
func SetTransportGetProxyConnectHeader(tr *http.Transport) {
if f := condSetTransportGetProxyConnectHeader; f != nil {
f(tr)
}
}

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build tailscale_go
// We want to use https://github.com/golang/go/issues/41048 but it's only in the
// Tailscale Go tree for now. Hence the build tag above.
package tshttpproxy
import (
"context"
"fmt"
"log"
"net/http"
"net/url"
)
const proxyAuthHeader = "Proxy-Authorization"
func init() {
condSetTransportGetProxyConnectHeader = func(tr *http.Transport) {
tr.GetProxyConnectHeader = func(ctx context.Context, proxyURL *url.URL, target string) (http.Header, error) {
v, err := GetAuthHeader(proxyURL)
if err != nil {
log.Printf("failed to get proxy Auth header for %v; ignoring: %v", proxyURL, err)
return nil, nil
}
if v == "" {
return nil, nil
}
return http.Header{proxyAuthHeader: []string{v}}, nil
}
tr.OnProxyConnectResponse = func(ctx context.Context, proxyURL *url.URL, connectReq *http.Request, res *http.Response) error {
auth := connectReq.Header.Get(proxyAuthHeader)
const truncLen = 20
if len(auth) > truncLen {
auth = fmt.Sprintf("%s...(%d total bytes)", auth[:truncLen], len(auth))
}
log.Printf("tshttpproxy: CONNECT response from %v for target %q (auth %q): %v", proxyURL, connectReq.Host, auth, res.Status)
return nil
}
}
}

View File

@@ -0,0 +1,213 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tshttpproxy
import (
"context"
"encoding/base64"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"sync"
"syscall"
"time"
"unsafe"
"github.com/alexbrainman/sspi/negotiate"
"golang.org/x/sys/windows"
)
var (
winHTTP = windows.NewLazySystemDLL("winhttp.dll")
httpOpenProc = winHTTP.NewProc("WinHttpOpen")
closeHandleProc = winHTTP.NewProc("WinHttpCloseHandle")
getProxyForUrlProc = winHTTP.NewProc("WinHttpGetProxyForUrl")
)
func init() {
sysProxyFromEnv = proxyFromWinHTTPOrCache
sysAuthHeader = sysAuthHeaderWindows
}
var cachedProxy struct {
sync.Mutex
val *url.URL
}
func proxyFromWinHTTPOrCache(req *http.Request) (*url.URL, error) {
if req.URL == nil {
return nil, nil
}
urlStr := req.URL.String()
ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second)
defer cancel()
type result struct {
proxy *url.URL
err error
}
resc := make(chan result, 1)
go func() {
proxy, err := proxyFromWinHTTP(ctx, urlStr)
resc <- result{proxy, err}
}()
select {
case res := <-resc:
err := res.err
if err == nil {
cachedProxy.Lock()
defer cachedProxy.Unlock()
if was, now := fmt.Sprint(cachedProxy.val), fmt.Sprint(res.proxy); was != now {
log.Printf("tshttpproxy: winhttp: updating cached proxy setting from %v to %v", was, now)
}
cachedProxy.val = res.proxy
return res.proxy, nil
}
// See https://docs.microsoft.com/en-us/windows/win32/winhttp/error-messages
const ERROR_WINHTTP_AUTODETECTION_FAILED = 12180
if err == syscall.Errno(ERROR_WINHTTP_AUTODETECTION_FAILED) {
return nil, nil
}
log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q): %v/%#v", urlStr, err, err)
return nil, err
case <-ctx.Done():
cachedProxy.Lock()
defer cachedProxy.Unlock()
log.Printf("tshttpproxy: winhttp: GetProxyForURL(%q): timeout; using cached proxy %v", urlStr, cachedProxy.val)
return cachedProxy.val, nil
}
}
func proxyFromWinHTTP(ctx context.Context, urlStr string) (proxy *url.URL, err error) {
whi, err := winHTTPOpen()
if err != nil {
log.Printf("winhttp: Open: %v", err)
return nil, err
}
defer whi.Close()
t0 := time.Now()
v, err := whi.GetProxyForURL(urlStr)
td := time.Since(t0).Round(time.Millisecond)
if err := ctx.Err(); err != nil {
log.Printf("tshttpproxy: winhttp: context canceled, ignoring GetProxyForURL(%q) after %v", urlStr, td)
return nil, err
}
if err != nil {
return nil, err
}
if v == "" {
return nil, nil
}
// Discard all but first proxy value for now.
if i := strings.Index(v, ";"); i != -1 {
v = v[:i]
}
if !strings.HasPrefix(v, "https://") {
v = "http://" + v
}
return url.Parse(v)
}
var userAgent = windows.StringToUTF16Ptr("Tailscale")
const (
winHTTP_ACCESS_TYPE_AUTOMATIC_PROXY = 4
winHTTP_AUTOPROXY_ALLOW_AUTOCONFIG = 0x00000100
winHTTP_AUTOPROXY_AUTO_DETECT = 1
winHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001
winHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002
)
func winHTTPOpen() (winHTTPInternet, error) {
if err := httpOpenProc.Find(); err != nil {
return 0, err
}
r, _, err := httpOpenProc.Call(
uintptr(unsafe.Pointer(userAgent)),
winHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
0, /* WINHTTP_NO_PROXY_NAME */
0, /* WINHTTP_NO_PROXY_BYPASS */
0)
if r == 0 {
return 0, err
}
return winHTTPInternet(r), nil
}
type winHTTPInternet windows.Handle
func (hi winHTTPInternet) Close() error {
if err := closeHandleProc.Find(); err != nil {
return err
}
r, _, err := closeHandleProc.Call(uintptr(hi))
if r == 1 {
return nil
}
return err
}
// WINHTTP_AUTOPROXY_OPTIONS
// https://docs.microsoft.com/en-us/windows/win32/api/winhttp/ns-winhttp-winhttp_autoproxy_options
type autoProxyOptions struct {
DwFlags uint32
DwAutoDetectFlags uint32
AutoConfigUrl *uint16
_ uintptr
_ uint32
FAutoLogonIfChallenged bool
}
// WINHTTP_PROXY_INFO
// https://docs.microsoft.com/en-us/windows/win32/api/winhttp/ns-winhttp-winhttp_proxy_info
type winHTTPProxyInfo struct {
AccessType uint16
Proxy *uint16
ProxyBypass *uint16
}
var proxyForURLOpts = &autoProxyOptions{
DwFlags: winHTTP_AUTOPROXY_ALLOW_AUTOCONFIG | winHTTP_AUTOPROXY_AUTO_DETECT,
DwAutoDetectFlags: winHTTP_AUTO_DETECT_TYPE_DHCP, // | winHTTP_AUTO_DETECT_TYPE_DNS_A,
}
func (hi winHTTPInternet) GetProxyForURL(urlStr string) (string, error) {
if err := getProxyForUrlProc.Find(); err != nil {
return "", err
}
var out winHTTPProxyInfo
r, _, err := getProxyForUrlProc.Call(
uintptr(hi),
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(urlStr))),
uintptr(unsafe.Pointer(proxyForURLOpts)),
uintptr(unsafe.Pointer(&out)))
if r == 1 {
return windows.UTF16PtrToString(out.Proxy), nil
}
return "", err
}
func sysAuthHeaderWindows(u *url.URL) (string, error) {
spn := "HTTP/" + u.Hostname()
creds, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
return "", fmt.Errorf("negotiate.AcquireCurrentUserCredentials: %w", err)
}
defer creds.Release()
secCtx, token, err := negotiate.NewClientContext(creds, spn)
if err != nil {
return "", fmt.Errorf("negotiate.NewClientContext: %w", err)
}
defer secCtx.Release()
return "Negotiate " + base64.StdEncoding.EncodeToString(token), nil
}

View File

@@ -41,6 +41,10 @@ func parsePort(s string) int {
return int(port)
}
func isLoopbackAddr(s string) bool {
return strings.HasPrefix(s, "127.0.0.1:") || strings.HasPrefix(s, "127.0.0.1.")
}
type nothing struct{}
// Lowest common denominator parser for "netstat -na" format.
@@ -74,7 +78,7 @@ func parsePortsNetstat(output string) List {
// not interested in non-listener sockets
continue
}
if strings.HasPrefix(laddr, "127.0.0.1:") || strings.HasPrefix(laddr, "127.0.0.1.") {
if isLoopbackAddr(laddr) {
// not interested in loopback-bound listeners
continue
}
@@ -85,7 +89,7 @@ func parsePortsNetstat(output string) List {
proto = "udp"
laddr = cols[len(cols)-2]
raddr = cols[len(cols)-1]
if strings.HasPrefix(laddr, "127.0.0.1:") || strings.HasPrefix(laddr, "127.0.0.1.") {
if isLoopbackAddr(laddr) {
// not interested in loopback-bound listeners
continue
}

View File

@@ -95,9 +95,12 @@ func addProcesses(pl []Port) ([]Port, error) {
if port > 0 {
pp := ProtoPort{proto, uint16(port)}
p := m[pp]
if p != nil {
switch {
case p != nil:
p.Process = cmd
} else {
case isLoopbackAddr(val):
// ignore
default:
fmt.Fprintf(os.Stderr, "weird: missing %v\n", pp)
}
}

View File

@@ -30,7 +30,7 @@ if [ $# != 1 ]; then
fi
fail=0
for file in $(find $1 -name '*.go'); do
for file in $(find $1 -name '*.go' -not -path '*/.git/*'); do
case $file in
$1/tempfork/*)
# Skip, tempfork of third-party code

58
syncs/locked.go Normal file
View File

@@ -0,0 +1,58 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.13,!go1.16
// 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.
// To prevent accidents, it is limited to a known good subset of Go versions.
package syncs
import (
"sync"
"sync/atomic"
"unsafe"
)
const (
mutexLocked = 1
// sync.Mutex field offsets
stateOffset = 0
// sync.RWMutext field offsets
mutexOffset = 0
readerCountOffset = 16
)
// add returns a pointer with value p + off.
func add(p unsafe.Pointer, off uintptr) unsafe.Pointer {
return unsafe.Pointer(uintptr(p) + off)
}
// AssertLocked panics if m is not locked.
func AssertLocked(m *sync.Mutex) {
p := add(unsafe.Pointer(m), stateOffset)
if atomic.LoadInt32((*int32)(p))&mutexLocked == 0 {
panic("mutex is not locked")
}
}
// AssertRLocked panics if rw is not locked for reading or writing.
func AssertRLocked(rw *sync.RWMutex) {
p := add(unsafe.Pointer(rw), readerCountOffset)
if atomic.LoadInt32((*int32)(p)) != 0 {
// There are readers present or writers pending, so someone has a read lock.
return
}
// No readers.
AssertWLocked(rw)
}
// AssertWLocked panics if rw is not locked for writing.
func AssertWLocked(rw *sync.RWMutex) {
m := (*sync.Mutex)(add(unsafe.Pointer(rw), mutexOffset))
AssertLocked(m)
}

123
syncs/locked_test.go Normal file
View File

@@ -0,0 +1,123 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.13,!go1.16
//lint:file-ignore SA2001 the empty critical sections are part of triggering different internal mutex states
package syncs
import (
"sync"
"testing"
"time"
)
func wantPanic(t *testing.T, fn func()) {
t.Helper()
defer func() {
recover()
}()
fn()
t.Fatal("failed to panic")
}
func TestAssertLocked(t *testing.T) {
m := new(sync.Mutex)
wantPanic(t, func() { AssertLocked(m) })
m.Lock()
AssertLocked(m)
m.Unlock()
wantPanic(t, func() { AssertLocked(m) })
// Test correct handling of mutex with waiter.
m.Lock()
AssertLocked(m)
go func() {
m.Lock()
m.Unlock()
}()
// Give the goroutine above a few moments to get started.
// The test will pass whether or not we win the race,
// but we want to run sometimes, to get the test coverage.
time.Sleep(10 * time.Millisecond)
AssertLocked(m)
}
func TestAssertWLocked(t *testing.T) {
m := new(sync.RWMutex)
wantPanic(t, func() { AssertWLocked(m) })
m.Lock()
AssertWLocked(m)
m.Unlock()
wantPanic(t, func() { AssertWLocked(m) })
// Test correct handling of mutex with waiter.
m.Lock()
AssertWLocked(m)
go func() {
m.Lock()
m.Unlock()
}()
// Give the goroutine above a few moments to get started.
// The test will pass whether or not we win the race,
// but we want to run sometimes, to get the test coverage.
time.Sleep(10 * time.Millisecond)
AssertWLocked(m)
}
func TestAssertRLocked(t *testing.T) {
m := new(sync.RWMutex)
wantPanic(t, func() { AssertRLocked(m) })
m.Lock()
AssertRLocked(m)
m.Unlock()
m.RLock()
AssertRLocked(m)
m.RUnlock()
wantPanic(t, func() { AssertRLocked(m) })
// Test correct handling of mutex with waiter.
m.RLock()
AssertRLocked(m)
go func() {
m.RLock()
m.RUnlock()
}()
// Give the goroutine above a few moments to get started.
// The test will pass whether or not we win the race,
// but we want to run sometimes, to get the test coverage.
time.Sleep(10 * time.Millisecond)
AssertRLocked(m)
m.RUnlock()
// Test correct handling of rlock with write waiter.
m.RLock()
AssertRLocked(m)
go func() {
m.Lock()
m.Unlock()
}()
// Give the goroutine above a few moments to get started.
// The test will pass whether or not we win the race,
// but we want to run sometimes, to get the test coverage.
time.Sleep(10 * time.Millisecond)
AssertRLocked(m)
m.RUnlock()
// Test correct handling of rlock with other rlocks.
// This is a bit racy, but losing the race hurts nothing,
// and winning the race means correct test coverage.
m.RLock()
AssertRLocked(m)
go func() {
m.RLock()
time.Sleep(10 * time.Millisecond)
m.RUnlock()
}()
time.Sleep(5 * time.Millisecond)
AssertRLocked(m)
m.RUnlock()
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package syncs contains addition sync types.
// Package syncs contains additional sync types and functionality.
package syncs
import "sync/atomic"

95
syncs/watchdog.go Normal file
View File

@@ -0,0 +1,95 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syncs
import (
"context"
"sync"
"time"
)
// Watch monitors mu for contention.
// On first call, and at every tick, Watch locks and unlocks mu.
// (Tick should be large to avoid adding contention to mu.)
// Max is the maximum length of time Watch will wait to acquire the lock.
// The time required to lock mu is sent on the returned channel.
// Watch exits when ctx is done, and closes the returned channel.
func Watch(ctx context.Context, mu sync.Locker, tick, max time.Duration) chan time.Duration {
// Set up the return channel.
c := make(chan time.Duration)
var (
closemu sync.Mutex
closed bool
)
sendc := func(d time.Duration) {
closemu.Lock()
defer closemu.Unlock()
if closed {
// Drop values written after c is closed.
return
}
c <- d
}
closec := func() {
closemu.Lock()
defer closemu.Unlock()
close(c)
closed = true
}
// check locks the mutex and writes how long it took to c.
// check returns ~immediately.
check := func() {
// Start a race between two goroutines.
// One locks the mutex; the other times out.
// Ensure that only one of the two gets to write its result.
// Since the common case is that locking the mutex is fast,
// let the timeout goroutine exit early when that happens.
var sendonce sync.Once
done := make(chan bool)
go func() {
start := time.Now()
mu.Lock()
mu.Unlock() //lint:ignore SA2001 ignore the empty critical section
elapsed := time.Since(start)
if elapsed > max {
elapsed = max
}
close(done)
sendonce.Do(func() { sendc(elapsed) })
}()
go func() {
select {
case <-time.After(max):
// the other goroutine may not have sent a value
sendonce.Do(func() { sendc(max) })
case <-done:
// the other goroutine sent a value
}
}()
}
// Check once at startup.
// This is mainly to make testing easier.
check()
// Start the watchdog goroutine.
// It checks the mutex every tick, until ctx is done.
go func() {
ticker := time.NewTicker(tick)
for {
select {
case <-ctx.Done():
closec()
ticker.Stop()
return
case <-ticker.C:
check()
}
}
}()
return c
}

71
syncs/watchdog_test.go Normal file
View File

@@ -0,0 +1,71 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syncs
import (
"context"
"sync"
"testing"
"time"
)
// Time-based tests are fundamentally flaky.
// We use exaggerated durations in the hopes of minimizing such issues.
func TestWatchUncontended(t *testing.T) {
mu := new(sync.Mutex)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Once an hour, and now, check whether we can lock mu in under an hour.
tick := time.Hour
max := time.Hour
c := Watch(ctx, mu, tick, max)
d := <-c
if d == max {
t.Errorf("uncontended mutex did not lock in under %v", max)
}
}
func TestWatchContended(t *testing.T) {
mu := new(sync.Mutex)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Every hour, and now, check whether we can lock mu in under a millisecond,
// which is enough time for an uncontended mutex by several orders of magnitude.
tick := time.Hour
max := time.Millisecond
mu.Lock()
defer mu.Unlock()
c := Watch(ctx, mu, tick, max)
d := <-c
if d != max {
t.Errorf("contended mutex locked in under %v", max)
}
}
func TestWatchMultipleValues(t *testing.T) {
mu := new(sync.Mutex)
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // not necessary, but keep vet happy
// Check the mutex every millisecond.
// The goal is to see that we get a sufficient number of values out of the channel.
tick := time.Millisecond
max := time.Millisecond
c := Watch(ctx, mu, tick, max)
start := time.Now()
n := 0
for d := range c {
n++
if d == max {
t.Errorf("uncontended mutex did not lock in under %v", max)
}
if n == 10 {
cancel()
}
}
if elapsed := time.Since(start); elapsed > 100*time.Millisecond {
t.Errorf("expected 1 event per millisecond, got only %v events in %v", n, elapsed)
}
}

View File

@@ -51,6 +51,10 @@ type DERPRegion struct {
// "fra", etc.
RegionCode string
// RegionName is a long English name for the region: "New York City",
// "San Francisco", "Singapore", "Frankfurt", etc.
RegionName string
// Nodes are the DERP nodes running in this region, in
// priority order for the current client. Client TLS
// connections should ideally only go to the first entry

View File

@@ -4,7 +4,7 @@
package tailcfg
//go:generate go run tailscale.com/cmd/cloner -type=User,Node,Hostinfo,NetInfo -output=tailcfg_clone.go
//go:generate go run tailscale.com/cmd/cloner -type=User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig -output=tailcfg_clone.go
import (
"bytes"
@@ -496,10 +496,18 @@ var FilterAllowAll = []FilterRule{
// DNSConfig is the DNS configuration.
type DNSConfig struct {
// Nameservers are the IP addresses of the nameservers to use.
Nameservers []netaddr.IP `json:",omitempty"`
Domains []string `json:",omitempty"`
PerDomain bool
Proxied bool
// Domains are the search domains to use.
Domains []string `json:",omitempty"`
// PerDomain indicates whether it is preferred to use Nameservers
// only for DNS queries for subdomains of Domains.
// Some OSes and OS configurations don't support per-domain DNS configuration,
// in which case Nameservers applies to all DNS requests regardless of PerDomain's value.
PerDomain bool
// Proxied indicates whether DNS requests are proxied through a tsdns.Resolver.
// This enables Magic DNS. It is togglable independently of PerDomain.
Proxied bool
}
type MapResponse struct {
@@ -563,6 +571,17 @@ type Debug struct {
// always do its background STUN queries (see magicsock's
// periodicReSTUN), regardless of inactivity.
ForceBackgroundSTUN bool `json:",omitempty"`
// DERPRoute controls whether the DERP reverse path
// optimization (see Issue 150) should be enabled or
// disabled. The environment variable in magicsock is the
// highest priority (if set), then this (if set), then the
// binary default value.
DERPRoute opt.Bool `json:",omitempty"`
// TrimWGConfig controls whether Tailscale does lazy, on-demand
// wireguard configuration of peers.
TrimWGConfig opt.Bool `json:",omitempty"`
}
func (k MachineKey) String() string { return fmt.Sprintf("mkey:%x", k[:]) }
@@ -628,6 +647,7 @@ func (n *Node) Equal(n2 *Node) bool {
eqCIDRs(n.Addresses, n2.Addresses) &&
eqCIDRs(n.AllowedIPs, n2.AllowedIPs) &&
eqStrings(n.Endpoints, n2.Endpoints) &&
n.DERP == n2.DERP &&
n.Hostinfo.Equal(&n2.Hostinfo) &&
n.Created.Equal(n2.Created) &&
eqTimePtr(n.LastSeen, n2.LastSeen) &&

View File

@@ -2,11 +2,15 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo; DO NOT EDIT.
// Code generated by tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig; DO NOT EDIT.
package tailcfg
import (
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
"time"
)
@@ -23,6 +27,19 @@ func (src *User) Clone() *User {
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig
var _UserNeedsRegeneration = User(struct {
ID UserID
LoginName string
DisplayName string
ProfilePicURL string
Domain string
Logins []LoginID
Roles []RoleID
Created time.Time
}{})
// Clone makes a deep copy of Node.
// The result aliases no memory with the original.
func (src *Node) Clone() *Node {
@@ -42,6 +59,27 @@ func (src *Node) Clone() *Node {
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig
var _NodeNeedsRegeneration = Node(struct {
ID NodeID
Name string
User UserID
Key NodeKey
KeyExpiry time.Time
Machine MachineKey
DiscoKey DiscoKey
Addresses []wgcfg.CIDR
AllowedIPs []wgcfg.CIDR
Endpoints []string
DERP string
Hostinfo Hostinfo
Created time.Time
LastSeen *time.Time
KeepAlive bool
MachineAuthorized bool
}{})
// Clone makes a deep copy of Hostinfo.
// The result aliases no memory with the original.
func (src *Hostinfo) Clone() *Hostinfo {
@@ -57,6 +95,23 @@ func (src *Hostinfo) Clone() *Hostinfo {
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig
var _HostinfoNeedsRegeneration = Hostinfo(struct {
IPNVersion string
FrontendLogID string
BackendLogID string
OS string
OSVersion string
DeviceModel string
Hostname string
GoArch string
RoutableIPs []wgcfg.CIDR
RequestTags []string
Services []Service
NetInfo *NetInfo
}{})
// Clone makes a deep copy of NetInfo.
// The result aliases no memory with the original.
func (src *NetInfo) Clone() *NetInfo {
@@ -73,3 +128,212 @@ func (src *NetInfo) Clone() *NetInfo {
}
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig
var _NetInfoNeedsRegeneration = NetInfo(struct {
MappingVariesByDestIP opt.Bool
HairPinning opt.Bool
WorkingIPv6 opt.Bool
WorkingUDP opt.Bool
UPnP opt.Bool
PMP opt.Bool
PCP opt.Bool
PreferredDERP int
LinkType string
DERPLatency map[string]float64
}{})
// Clone makes a deep copy of Group.
// The result aliases no memory with the original.
func (src *Group) Clone() *Group {
if src == nil {
return nil
}
dst := new(Group)
*dst = *src
dst.Members = append(src.Members[:0:0], src.Members...)
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig
var _GroupNeedsRegeneration = Group(struct {
ID GroupID
Name string
Members []ID
}{})
// Clone makes a deep copy of Role.
// The result aliases no memory with the original.
func (src *Role) Clone() *Role {
if src == nil {
return nil
}
dst := new(Role)
*dst = *src
dst.Capabilities = append(src.Capabilities[:0:0], src.Capabilities...)
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig
var _RoleNeedsRegeneration = Role(struct {
ID RoleID
Name string
Capabilities []CapabilityID
}{})
// Clone makes a deep copy of Capability.
// The result aliases no memory with the original.
func (src *Capability) Clone() *Capability {
if src == nil {
return nil
}
dst := new(Capability)
*dst = *src
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig
var _CapabilityNeedsRegeneration = Capability(struct {
ID CapabilityID
Type CapType
Val ID
}{})
// Clone makes a deep copy of Login.
// The result aliases no memory with the original.
func (src *Login) Clone() *Login {
if src == nil {
return nil
}
dst := new(Login)
*dst = *src
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig
var _LoginNeedsRegeneration = Login(struct {
_ structs.Incomparable
ID LoginID
Provider string
LoginName string
DisplayName string
ProfilePicURL string
Domain string
}{})
// Clone makes a deep copy of DNSConfig.
// The result aliases no memory with the original.
func (src *DNSConfig) Clone() *DNSConfig {
if src == nil {
return nil
}
dst := new(DNSConfig)
*dst = *src
dst.Nameservers = append(src.Nameservers[:0:0], src.Nameservers...)
dst.Domains = append(src.Domains[:0:0], src.Domains...)
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig
var _DNSConfigNeedsRegeneration = DNSConfig(struct {
Nameservers []netaddr.IP
Domains []string
PerDomain bool
Proxied bool
}{})
// Clone duplicates src into dst and reports whether it succeeded.
// To succeed, <src, dst> must be of types <*T, *T> or <*T, **T>,
// where T is one of User,Node,Hostinfo,NetInfo,Group,Role,Capability,Login,DNSConfig.
func Clone(dst, src interface{}) bool {
switch src := src.(type) {
case *User:
switch dst := dst.(type) {
case *User:
*dst = *src.Clone()
return true
case **User:
*dst = src.Clone()
return true
}
case *Node:
switch dst := dst.(type) {
case *Node:
*dst = *src.Clone()
return true
case **Node:
*dst = src.Clone()
return true
}
case *Hostinfo:
switch dst := dst.(type) {
case *Hostinfo:
*dst = *src.Clone()
return true
case **Hostinfo:
*dst = src.Clone()
return true
}
case *NetInfo:
switch dst := dst.(type) {
case *NetInfo:
*dst = *src.Clone()
return true
case **NetInfo:
*dst = src.Clone()
return true
}
case *Group:
switch dst := dst.(type) {
case *Group:
*dst = *src.Clone()
return true
case **Group:
*dst = src.Clone()
return true
}
case *Role:
switch dst := dst.(type) {
case *Role:
*dst = *src.Clone()
return true
case **Role:
*dst = src.Clone()
return true
}
case *Capability:
switch dst := dst.(type) {
case *Capability:
*dst = *src.Clone()
return true
case **Capability:
*dst = src.Clone()
return true
}
case *Login:
switch dst := dst.(type) {
case *Login:
*dst = *src.Clone()
return true
case **Login:
*dst = src.Clone()
return true
}
case *DNSConfig:
switch dst := dst.(type) {
case *DNSConfig:
*dst = *src.Clone()
return true
case **DNSConfig:
*dst = src.Clone()
return true
}
}
return false
}

View File

@@ -315,6 +315,11 @@ func TestNodeEqual(t *testing.T) {
&Node{LastSeen: &now},
true,
},
{
&Node{DERP: "foo"},
&Node{DERP: "bar"},
false,
},
}
for i, tt := range tests {
got := tt.a.Equal(tt.b)

11
tsconst/interface.go Normal file
View File

@@ -0,0 +1,11 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package tsconst exports some constants used elsewhere in the
// codebase.
package tsconst
// WintunInterfaceDesc is the description attached to Tailscale
// interfaces on Windows. This is set by our modified WinTun driver.
const WintunInterfaceDesc = "Tailscale Tunnel"

View File

@@ -7,7 +7,10 @@ package tstest
import (
"log"
"os"
"sync"
"testing"
"tailscale.com/types/logger"
)
type testLogWriter struct {
@@ -41,3 +44,49 @@ func (panicLogWriter) Write(b []byte) (int, error) {
func PanicOnLog() {
log.SetOutput(panicLogWriter{})
}
// NewLogLineTracker produces a LogLineTracker wrapping a given logf that tracks whether expectedFormatStrings were seen.
func NewLogLineTracker(logf logger.Logf, expectedFormatStrings []string) *LogLineTracker {
ret := &LogLineTracker{
logf: logf,
listenFor: expectedFormatStrings,
seen: make(map[string]bool),
}
for _, line := range expectedFormatStrings {
ret.seen[line] = false
}
return ret
}
// LogLineTracker is a logger that tracks which log format patterns it's
// seen and can report which expected ones were not seen later.
type LogLineTracker struct {
logf logger.Logf
listenFor []string
mu sync.Mutex
seen map[string]bool // format string => false (if not yet seen but wanted) or true (once seen)
}
// Logf logs to its underlying logger and also tracks that the given format pattern has been seen.
func (lt *LogLineTracker) Logf(format string, args ...interface{}) {
lt.mu.Lock()
if v, ok := lt.seen[format]; ok && !v {
lt.seen[format] = true
}
lt.mu.Unlock()
lt.logf(format, args...)
}
// Check returns which format strings haven't been logged yet.
func (lt *LogLineTracker) Check() []string {
lt.mu.Lock()
defer lt.mu.Unlock()
var notSeen []string
for _, format := range lt.listenFor {
if !lt.seen[format] {
notSeen = append(notSeen, format)
}
}
return notSeen
}

48
tstest/log_test.go Normal file
View File

@@ -0,0 +1,48 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tstest
import (
"reflect"
"testing"
)
func TestLogLineTracker(t *testing.T) {
const (
l1 = "line 1: %s"
l2 = "line 2: %s"
l3 = "line 3: %s"
)
lt := NewLogLineTracker(t.Logf, []string{l1, l2})
if got, want := lt.Check(), []string{l1, l2}; !reflect.DeepEqual(got, want) {
t.Errorf("Check = %q; want %q", got, want)
}
lt.Logf(l3, "hi")
if got, want := lt.Check(), []string{l1, l2}; !reflect.DeepEqual(got, want) {
t.Errorf("Check = %q; want %q", got, want)
}
lt.Logf(l1, "hi")
if got, want := lt.Check(), []string{l2}; !reflect.DeepEqual(got, want) {
t.Errorf("Check = %q; want %q", got, want)
}
lt.Logf(l1, "bye")
if got, want := lt.Check(), []string{l2}; !reflect.DeepEqual(got, want) {
t.Errorf("Check = %q; want %q", got, want)
}
lt.Logf(l2, "hi")
if got, want := lt.Check(), []string(nil); !reflect.DeepEqual(got, want) {
t.Errorf("Check = %q; want %q", got, want)
}
}

View File

@@ -39,7 +39,7 @@ func goroutineDump() (int, string) {
return p.Count(), b.String()
}
func (r *ResourceCheck) Assert(t *testing.T) {
func (r *ResourceCheck) Assert(t testing.TB) {
t.Helper()
want := r.startNumRoutines
@@ -68,5 +68,4 @@ func (r *ResourceCheck) Assert(t *testing.T) {
}
}
}
t.Logf("ResourceCheck ok: goroutines before=%d after=%d\n", got, want)
}

View File

@@ -6,8 +6,8 @@ package tsweb
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
)
type response struct {
@@ -16,119 +16,70 @@ type response struct {
Data interface{} `json:"data,omitempty"`
}
func responseSuccess(data interface{}) *response {
return &response{
Status: "success",
Data: data,
}
}
// JSONHandlerFunc is an HTTP ReturnHandler that writes JSON responses to the client.
//
// Return a HTTPError to show an error message, otherwise JSONHandlerFunc will
// only report "internal server error" to the user.
type JSONHandlerFunc func(r *http.Request) (status int, data interface{}, err error)
func responseError(e string) *response {
return &response{
Status: "error",
Error: e,
}
}
func writeResponse(w http.ResponseWriter, s int, resp *response) {
b, _ := json.Marshal(resp)
// ServeHTTPReturn implements the ReturnHandler interface.
//
// Use the following code to unmarshal the request body
//
// body := new(DataType)
// if err := json.NewDecoder(r.Body).Decode(body); err != nil {
// return http.StatusBadRequest, nil, err
// }
//
// See jsonhandler_text.go for examples.
func (fn JSONHandlerFunc) ServeHTTPReturn(w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(s)
w.Write(b)
}
func checkFn(t reflect.Type) {
h := reflect.TypeOf(http.HandlerFunc(nil))
switch t.NumIn() {
case 2, 3:
if !t.In(0).AssignableTo(h.In(0)) {
panic("first argument must be http.ResponseWriter")
var resp *response
status, data, err := fn(r)
if status == 0 {
status = http.StatusInternalServerError
resp = &response{
Status: "error",
Error: "internal server error",
}
if !t.In(1).AssignableTo(h.In(1)) {
panic("second argument must be *http.Request")
} else if err == nil {
resp = &response{
Status: "success",
Data: data,
}
default:
panic("JSONHandler: number of input parameter should be 2 or 3")
}
switch t.NumOut() {
case 1:
if !t.Out(0).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
panic("return value must be error")
}
case 2:
if !t.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
panic("second return value must be error")
}
default:
panic("JSONHandler: number of return values should be 1 or 2")
}
}
// JSONHandler wraps an HTTP handler function with a version that automatically
// unmarshals and marshals requests and responses respectively into fn's arguments
// and results.
//
// The fn parameter is a function. It must take two or three input arguments.
// The first two arguments must be http.ResponseWriter and *http.Request.
// The optional third argument can be of any type representing the JSON input.
// The function's results can be either (error) or (T, error), where T is the
// JSON-marshalled result type.
//
// For example:
// fn := func(w http.ResponseWriter, r *http.Request, in *Req) (*Res, error) { ... }
func JSONHandler(fn interface{}) http.Handler {
v := reflect.ValueOf(fn)
t := v.Type()
checkFn(t)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
wv := reflect.ValueOf(w)
rv := reflect.ValueOf(r)
var vs []reflect.Value
switch t.NumIn() {
case 2:
vs = v.Call([]reflect.Value{wv, rv})
case 3:
dv := reflect.New(t.In(2))
err := json.NewDecoder(r.Body).Decode(dv.Interface())
if err != nil {
writeResponse(w, http.StatusBadRequest, responseError("bad json"))
return
} else {
if werr, ok := err.(HTTPError); ok {
resp = &response{
Status: "error",
Error: werr.Msg,
Data: data,
}
vs = v.Call([]reflect.Value{wv, rv, dv.Elem()})
default:
panic("JSONHandler: number of input parameter should be 2 or 3")
}
var e reflect.Value
switch len(vs) {
case 1:
// todo support other error types
if vs[0].IsZero() {
writeResponse(w, http.StatusOK, responseSuccess(nil))
return
// Unwrap the HTTPError here because we are communicating with
// the client in this handler. We don't want the wrapping
// ReturnHandler to do it too.
err = werr.Err
if werr.Msg != "" {
err = fmt.Errorf("%s: %w", werr.Msg, err)
}
e = vs[0]
case 2:
if vs[1].IsZero() {
if !vs[0].IsZero() {
writeResponse(w, http.StatusOK, responseSuccess(vs[0].Interface()))
}
return
}
e = vs[1]
default:
panic("JSONHandler: number of return values should be 1 or 2")
}
if e.Type().AssignableTo(reflect.TypeOf(HTTPError{})) {
err := e.Interface().(HTTPError)
writeResponse(w, err.Code, responseError(err.Error()))
} else {
err := e.Interface().(error)
writeResponse(w, http.StatusBadRequest, responseError(err.Error()))
resp = &response{
Status: "error",
Error: "internal server error",
}
}
})
}
b, jerr := json.Marshal(resp)
if jerr != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"status":"error","error":"json marshal error"}`))
if err != nil {
return fmt.Errorf("%w, and then we could not respond: %v", err, jerr)
}
return jerr
}
w.WriteHeader(status)
w.Write(b)
return err
}

View File

@@ -5,9 +5,8 @@
package tsweb
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"strings"
@@ -26,7 +25,7 @@ type Response struct {
}
func TestNewJSONHandler(t *testing.T) {
checkStatus := func(w *httptest.ResponseRecorder, status string) *Response {
checkStatus := func(w *httptest.ResponseRecorder, status string, code int) *Response {
d := &Response{
Data: &Data{},
}
@@ -44,6 +43,10 @@ func TestNewJSONHandler(t *testing.T) {
t.Fatalf("wrong status: %s %s", d.Status, status)
}
if w.Code != code {
t.Fatalf("wrong status code: %d %d", w.Code, code)
}
if w.Header().Get("Content-Type") != "application/json" {
t.Fatalf("wrong content type: %s", w.Header().Get("Content-Type"))
}
@@ -51,163 +54,139 @@ func TestNewJSONHandler(t *testing.T) {
return d
}
// 2 1
h21 := JSONHandler(func(w http.ResponseWriter, r *http.Request) error {
return nil
h21 := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
return http.StatusOK, nil, nil
})
t.Run("2 1 simple", func(t *testing.T) {
t.Run("200 simple", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
h21.ServeHTTP(w, r)
checkStatus(w, "success")
h21.ServeHTTPReturn(w, r)
checkStatus(w, "success", http.StatusOK)
})
t.Run("2 1 HTTPError", func(t *testing.T) {
h := JSONHandler(func(w http.ResponseWriter, r *http.Request) HTTPError {
return Error(http.StatusForbidden, "forbidden", nil)
t.Run("403 HTTPError", func(t *testing.T) {
h := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
return http.StatusForbidden, nil, fmt.Errorf("forbidden")
})
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
h.ServeHTTP(w, r)
if w.Code != http.StatusForbidden {
t.Fatalf("wrong code: %d %d", w.Code, http.StatusForbidden)
}
h.ServeHTTPReturn(w, r)
checkStatus(w, "error", http.StatusForbidden)
})
// 2 2
h22 := JSONHandler(func(w http.ResponseWriter, r *http.Request) (*Data, error) {
return &Data{Name: "tailscale"}, nil
h22 := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
return http.StatusOK, &Data{Name: "tailscale"}, nil
})
t.Run("2 2 get data", func(t *testing.T) {
t.Run("200 get data", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
h22.ServeHTTP(w, r)
checkStatus(w, "success")
h22.ServeHTTPReturn(w, r)
checkStatus(w, "success", http.StatusOK)
})
// 3 1
h31 := JSONHandler(func(w http.ResponseWriter, r *http.Request, d *Data) error {
if d.Name == "" {
return errors.New("name is empty")
h31 := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
body := new(Data)
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
return http.StatusBadRequest, nil, err
}
return nil
if body.Name == "" {
return http.StatusBadRequest, nil, Error(http.StatusBadGateway, "name is empty", nil)
}
return http.StatusOK, nil, nil
})
t.Run("3 1 post data", func(t *testing.T) {
t.Run("200 post data", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{"Name": "tailscale"}`))
h31.ServeHTTP(w, r)
checkStatus(w, "success")
h31.ServeHTTPReturn(w, r)
checkStatus(w, "success", http.StatusOK)
})
t.Run("3 1 bad json", func(t *testing.T) {
t.Run("400 bad json", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{`))
h31.ServeHTTP(w, r)
checkStatus(w, "error")
h31.ServeHTTPReturn(w, r)
checkStatus(w, "error", http.StatusBadRequest)
})
t.Run("3 1 post data error", func(t *testing.T) {
t.Run("400 post data error", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{}`))
h31.ServeHTTP(w, r)
resp := checkStatus(w, "error")
h31.ServeHTTPReturn(w, r)
resp := checkStatus(w, "error", http.StatusBadRequest)
if resp.Error != "name is empty" {
t.Fatalf("wrong error")
}
})
// 3 2
h32 := JSONHandler(func(w http.ResponseWriter, r *http.Request, d *Data) (*Data, error) {
if d.Price == 0 {
return nil, errors.New("price is empty")
h32 := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
body := new(Data)
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
return http.StatusBadRequest, nil, err
}
if body.Name == "root" {
return http.StatusInternalServerError, nil, fmt.Errorf("invalid name")
}
if body.Price == 0 {
return http.StatusBadRequest, nil, Error(http.StatusBadGateway, "price is empty", nil)
}
return &Data{Price: d.Price * 2}, nil
return http.StatusOK, &Data{Price: body.Price * 2}, nil
})
t.Run("3 2 post data", func(t *testing.T) {
t.Run("200 post data", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{"Price": 10}`))
h32.ServeHTTP(w, r)
resp := checkStatus(w, "success")
h32.ServeHTTPReturn(w, r)
resp := checkStatus(w, "success", http.StatusOK)
t.Log(resp.Data)
if resp.Data.Price != 20 {
t.Fatalf("wrong price: %d %d", resp.Data.Price, 10)
}
})
t.Run("3 2 post data error", func(t *testing.T) {
t.Run("400 post data error", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", strings.NewReader(`{}`))
h32.ServeHTTP(w, r)
resp := checkStatus(w, "error")
h32.ServeHTTPReturn(w, r)
resp := checkStatus(w, "error", http.StatusBadRequest)
if resp.Error != "price is empty" {
t.Fatalf("wrong error")
}
})
// fn check
shouldPanic := func() {
r := recover()
if r == nil {
t.Fatalf("should panic")
}
t.Log(r)
}
t.Run("2 0 panic", func(t *testing.T) {
defer shouldPanic()
JSONHandler(func(w http.ResponseWriter, r *http.Request) {})
})
t.Run("2 1 panic return value", func(t *testing.T) {
defer shouldPanic()
JSONHandler(func(w http.ResponseWriter, r *http.Request) string {
return ""
})
})
t.Run("2 1 panic arguments", func(t *testing.T) {
defer shouldPanic()
JSONHandler(func(r *http.Request, w http.ResponseWriter) error {
return nil
})
})
t.Run("3 1 panic arguments", func(t *testing.T) {
defer shouldPanic()
JSONHandler(func(name string, r *http.Request, w http.ResponseWriter) error {
return nil
})
})
t.Run("3 2 panic return value", func(t *testing.T) {
defer shouldPanic()
//lint:ignore ST1008 intentional
JSONHandler(func(name string, r *http.Request, w http.ResponseWriter) (error, string) {
return nil, "panic"
})
})
t.Run("2 2 forbidden", func(t *testing.T) {
code := http.StatusForbidden
body := []byte("forbidden")
h := JSONHandler(func(w http.ResponseWriter, r *http.Request) (*Data, error) {
w.WriteHeader(code)
w.Write(body)
return nil, nil
})
t.Run("500 internal server error", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
h.ServeHTTP(w, r)
if w.Code != http.StatusForbidden {
t.Fatalf("wrong code: %d %d", w.Code, code)
r := httptest.NewRequest("POST", "/", strings.NewReader(`{"Name": "root"}`))
h32.ServeHTTPReturn(w, r)
resp := checkStatus(w, "error", http.StatusInternalServerError)
if resp.Error != "internal server error" {
t.Fatalf("wrong error")
}
if !bytes.Equal(w.Body.Bytes(), []byte("forbidden")) {
t.Fatalf("wrong body: %s %s", w.Body.Bytes(), body)
})
t.Run("500 misuse", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", nil)
JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
return http.StatusOK, make(chan int), nil
}).ServeHTTPReturn(w, r)
resp := checkStatus(w, "error", http.StatusInternalServerError)
if resp.Error != "json marshal error" {
t.Fatalf("wrong error")
}
})
t.Run("500 empty status code", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", nil)
JSONHandlerFunc(func(r *http.Request) (status int, data interface{}, err error) {
return
}).ServeHTTPReturn(w, r)
checkStatus(w, "error", http.StatusInternalServerError)
})
}

View File

@@ -236,8 +236,13 @@ func (h retHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case hErrOK:
// Handler asked us to send an error. Do so, if we haven't
// already sent a response.
msg.Err = hErr.Msg
if hErr.Err != nil {
msg.Err = hErr.Err.Error()
if msg.Err == "" {
msg.Err = hErr.Err.Error()
} else {
msg.Err = msg.Err + ": " + hErr.Err.Error()
}
}
if lw.code != 0 {
h.logf("[unexpected] handler returned HTTPError %v, but already sent a response with code %d", hErr, lw.code)

View File

@@ -122,7 +122,7 @@ func TestStdHandler(t *testing.T) {
Host: "example.com",
Method: "GET",
RequestURI: "/foo",
Err: testErr.Error(),
Err: "not found: " + testErr.Error(),
Code: 404,
},
},
@@ -139,6 +139,7 @@ func TestStdHandler(t *testing.T) {
Host: "example.com",
Method: "GET",
RequestURI: "/foo",
Err: "not found",
Code: 404,
},
},
@@ -189,7 +190,7 @@ func TestStdHandler(t *testing.T) {
Host: "example.com",
Method: "GET",
RequestURI: "/foo",
Err: testErr.Error(),
Err: "not found: " + testErr.Error(),
Code: 200,
},
},

View File

@@ -28,6 +28,8 @@ func NewPrivate() Private {
if _, err := io.ReadFull(crand.Reader, p[:]); err != nil {
panic(err)
}
p[0] &= 248
p[31] = (p[31] & 127) | 64
return p
}

View File

@@ -6,6 +6,8 @@ package key
import (
"testing"
"github.com/tailscale/wireguard-go/wgcfg"
)
func TestTextUnmarshal(t *testing.T) {
@@ -22,3 +24,31 @@ func TestTextUnmarshal(t *testing.T) {
t.Fatalf("mismatch; got %x want %x", p2, p)
}
}
func TestClamping(t *testing.T) {
t.Run("NewPrivate", func(t *testing.T) { testClamping(t, NewPrivate) })
// Also test the wgcfg package, as their behavior should match.
t.Run("wgcfg", func(t *testing.T) {
testClamping(t, func() Private {
k, err := wgcfg.NewPrivateKey()
if err != nil {
t.Fatal(err)
}
return Private(k)
})
})
}
func testClamping(t *testing.T, newKey func() Private) {
for i := 0; i < 100; i++ {
k := newKey()
if k[0]&0b111 != 0 {
t.Fatalf("Bogus clamping in first byte: %#08b", k[0])
return
}
if k[31]>>6 != 1 {
t.Fatalf("Bogus clamping in last byte: %#08b", k[0])
}
}
}

25
util/pidowner/pidowner.go Normal file
View File

@@ -0,0 +1,25 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package pidowner handles lookups from process ID to its owning user.
package pidowner
import (
"errors"
"runtime"
)
var ErrNotImplemented = errors.New("not implemented for GOOS=" + runtime.GOOS)
var ErrProcessNotFound = errors.New("process not found")
// OwnerOfPID returns the user ID that owns the given process ID.
//
// The returned user ID is suitable to passing to os/user.LookupId.
//
// The returned error will be ErrNotImplemented for operating systems where
// this isn't supported.
func OwnerOfPID(pid int) (userID string, err error) {
return ownerOfPID(pid)
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pidowner
import (
"fmt"
"os"
"strings"
"tailscale.com/util/lineread"
)
func ownerOfPID(pid int) (userID string, err error) {
file := fmt.Sprintf("/proc/%d/status", pid)
err = lineread.File(file, func(line []byte) error {
if len(line) < 4 || string(line[:4]) != "Uid:" {
return nil
}
f := strings.Fields(string(line))
if len(f) >= 2 {
userID = f[1] // real userid
}
return nil
})
if os.IsNotExist(err) {
return "", ErrProcessNotFound
}
if err != nil {
return
}
if userID == "" {
return "", fmt.Errorf("missing Uid line in %s", file)
}
return userID, nil
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !windows,!linux
package pidowner
func ownerOfPID(pid int) (userID string, err error) { return "", ErrNotImplemented }

View File

@@ -0,0 +1,51 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pidowner
import (
"math/rand"
"os"
"os/user"
"testing"
)
func TestOwnerOfPID(t *testing.T) {
id, err := OwnerOfPID(os.Getpid())
if err == ErrNotImplemented {
t.Skip(err)
}
if err != nil {
t.Fatal(err)
}
t.Logf("id=%q", id)
u, err := user.LookupId(id)
if err != nil {
t.Fatalf("LookupId: %v", err)
}
t.Logf("Got: %+v", u)
}
// validate that OS implementation returns ErrProcessNotFound.
func TestNotFoundError(t *testing.T) {
// Try a bunch of times to stumble upon a pid that doesn't exist...
const tries = 50
for i := 0; i < tries; i++ {
_, err := OwnerOfPID(rand.Intn(1e9))
if err == ErrNotImplemented {
t.Skip(err)
}
if err == nil {
// We got unlucky and this pid existed. Try again.
continue
}
if err == ErrProcessNotFound {
// Pass.
return
}
t.Fatalf("Error is not ErrProcessNotFound: %T %v", err, err)
}
t.Errorf("after %d tries, couldn't find a process that didn't exist", tries)
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pidowner
import (
"fmt"
"syscall"
"golang.org/x/sys/windows"
)
func ownerOfPID(pid int) (userID string, err error) {
procHnd, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, uint32(pid))
if err == syscall.Errno(0x57) { // invalid parameter, for PIDs that don't exist
return "", ErrProcessNotFound
}
if err != nil {
return "", fmt.Errorf("OpenProcess: %T %#v", err, err)
}
defer windows.CloseHandle(procHnd)
var tok windows.Token
if err := windows.OpenProcessToken(procHnd, windows.TOKEN_QUERY, &tok); err != nil {
return "", fmt.Errorf("OpenProcessToken: %w", err)
}
tokUser, err := tok.GetTokenUser()
if err != nil {
return "", fmt.Errorf("GetTokenUser: %w", err)
}
sid := tokUser.User.Sid
return sid.String(), nil
}

40
version/distro/distro.go Normal file
View File

@@ -0,0 +1,40 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package distro reports which distro we're running on.
package distro
import (
"os"
"runtime"
)
type Distro string
const (
Debian = Distro("debian")
Arch = Distro("arch")
Synology = Distro("synology")
)
// Get returns the current distro, or the empty string if unknown.
func Get() Distro {
if runtime.GOOS == "linux" {
return linuxDistro()
}
return ""
}
func linuxDistro() Distro {
if fi, err := os.Stat("/usr/syno"); err == nil && fi.IsDir() {
return Synology
}
if _, err := os.Stat("/etc/debian_version"); err == nil {
return Debian
}
if _, err := os.Stat("/etc/arch-release"); err == nil {
return Arch
}
return ""
}

View File

@@ -7,5 +7,5 @@
// Package version provides the version that the binary was built at.
package version
const LONG = "date.20200806"
const LONG = "date.20200820"
const SHORT = LONG

View File

@@ -7,11 +7,13 @@ package filter
import (
"fmt"
"net"
"sync"
"time"
"github.com/golang/groupcache/lru"
"golang.org/x/time/rate"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/wgengine/packet"
)
@@ -129,6 +131,79 @@ func maybeHexdump(flag RunFlags, b []byte) string {
return packet.Hexdump(b) + "\n"
}
// MatchesFromFilterRules parse a number of wire-format FilterRule values into
// the Matches format.
// If an error is returned, the Matches result is still valid, containing the rules that
// were successfully converted.
func MatchesFromFilterRules(pf []tailcfg.FilterRule) (Matches, error) {
mm := make([]Match, 0, len(pf))
var erracc error
for _, r := range pf {
m := Match{}
for i, s := range r.SrcIPs {
bits := 32
if len(r.SrcBits) > i {
bits = r.SrcBits[i]
}
net, err := parseIP(s, bits)
if err != nil && erracc == nil {
erracc = err
continue
}
m.Srcs = append(m.Srcs, net)
}
for _, d := range r.DstPorts {
bits := 32
if d.Bits != nil {
bits = *d.Bits
}
net, err := parseIP(d.IP, bits)
if err != nil && erracc == nil {
erracc = err
continue
}
m.Dsts = append(m.Dsts, NetPortRange{
Net: net,
Ports: PortRange{
First: d.Ports.First,
Last: d.Ports.Last,
},
})
}
mm = append(mm, m)
}
return mm, erracc
}
func parseIP(host string, defaultBits int) (Net, error) {
ip := net.ParseIP(host)
if ip != nil && ip.IsUnspecified() {
// For clarity, reject 0.0.0.0 as an input
return NetNone, fmt.Errorf("ports=%#v: to allow all IP addresses, use *:port, not 0.0.0.0:port", host)
} else if ip == nil && host == "*" {
// User explicitly requested wildcard dst ip
return NetAny, nil
} else {
if ip != nil {
ip = ip.To4()
}
if ip == nil || len(ip) != 4 {
return NetNone, fmt.Errorf("ports=%#v: invalid IPv4 address", host)
}
if len(ip) == 4 && (defaultBits < 0 || defaultBits > 32) {
return NetNone, fmt.Errorf("invalid CIDR size %d for host %q", defaultBits, host)
}
return Net{
IP: NewIP(ip),
Mask: Netmask(defaultBits),
}, nil
}
}
// TODO(apenwarr): use a bigger bucket for specifically TCP SYN accept logging?
// Logging is a quick way to record every newly opened TCP connection, but
// we have to be cautious about flooding the logs vs letting people use

View File

@@ -8,6 +8,7 @@ import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"net"
"strings"
"testing"
@@ -162,6 +163,35 @@ func TestNoAllocs(t *testing.T) {
}
}
func TestParseIP(t *testing.T) {
tests := []struct {
host string
bits int
want Net
wantErr string
}{
{"8.8.8.8", 24, Net{IP: packet.NewIP(net.ParseIP("8.8.8.8")), Mask: packet.NewIP(net.ParseIP("255.255.255.0"))}, ""},
{"8.8.8.8", 33, Net{}, `invalid CIDR size 33 for host "8.8.8.8"`},
{"8.8.8.8", -1, Net{}, `invalid CIDR size -1 for host "8.8.8.8"`},
{"0.0.0.0", 24, Net{}, `ports="0.0.0.0": to allow all IP addresses, use *:port, not 0.0.0.0:port`},
{"*", 24, NetAny, ""},
{"fe80::1", 128, NetNone, `ports="fe80::1": invalid IPv4 address`},
}
for _, tt := range tests {
got, err := parseIP(tt.host, tt.bits)
if err != nil {
if err.Error() == tt.wantErr {
continue
}
t.Errorf("parseIP(%q, %v) error: %v; want error %q", tt.host, tt.bits, err, tt.wantErr)
}
if got != tt.want {
t.Errorf("parseIP(%q, %v) = %#v; want %#v", tt.host, tt.bits, got, tt.want)
continue
}
}
}
func BenchmarkFilter(b *testing.B) {
acl := newFilter(b.Logf)

View File

@@ -0,0 +1,29 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by "stringer -type=discoPingPurpose -trimprefix=ping"; DO NOT EDIT.
package magicsock
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[pingDiscovery-0]
_ = x[pingHeartbeat-1]
_ = x[pingCLI-2]
}
const _discoPingPurpose_name = "DiscoveryHeartbeatCLI"
var _discoPingPurpose_index = [...]uint8{0, 9, 18, 21}
func (i discoPingPurpose) String() string {
if i < 0 || i >= discoPingPurpose(len(_discoPingPurpose_index)-1) {
return "discoPingPurpose(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _discoPingPurpose_name[_discoPingPurpose_index[i]:_discoPingPurpose_index[i+1]]
}

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