Compare commits

...

245 Commits

Author SHA1 Message Date
Denton Gentry
1691898a17 CI: try GitHub's Large runners
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-09-24 14:34:55 -07:00
Denton Gentry
3d24611e32 CI: use BuildJet & self-hosted Windows runners
1. Use buildjet for the longer Linux CI workflows.
2. Use a self-hosted Windows runner.
3. Make CIFuzz run on merge to main or release branch.

Two runs each of the original workflow files and the updated
workflows in this PR:

                                GitHub  GitHub   BuildJet BuildJet
codeql-analysis.yml             4m 30s  cached    2m 56s  2m 59s
cross-darwin.yml                3m 10s  3m 19s    1m 33s  1m 30s
cross-freebsd.yml               3m 33s  3m 10s    1m 28s  1m 22s
cross-openbsd.yml               3m 4s   2m 36s    1m 29s  1m 22s
cross-wasm.yml                  1m 59s  2m  2s    1m 12s  1m 16s
cross-windows.yml               2m 45s  3m  0s    1m 44s  1m 25s
linux32.yml                     4m 27s  4m  0s    1m 55s  2m  8s
linux-race.yml                  3m 54s  4m  7s    2m 22s  2m 12s
linux.yml                       4m 23s  4m 39s    2m 37s  2m 15s
static-analysis.yml
 /vet                           1m 41s  2m 22s       52s     56s
 /staticcheck(linux, amd64)     2m 47s  2m 38s    1m  7s  1m 10s
 /staticcheck(windows, amd64)   2m 5s   2m  4s    1m  6s  1m  8s
 /staticcheck(darwin, amd64)    2m 14s  2m 20s    1m 10s  1m 10s
 /staticcheck(windows, 386)     2m 36s  1m 58s    1m 23s  1m  8s
vm.yml                          1m 30s  1m 32s    2m 31s  2m 23s
windows.yml                     6m 23s  6m 19s    3m 40s  3m 53s

A few very short workflows are being left on GitHub-hosted runners, like
licenses and gofmt. These benefit from the quicker dispatch to GitHub
hosted runners.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-09-24 13:54:24 -07:00
Brad Fitzpatrick
fb4e23506f control/controlclient: stop restarting map polls on health change
At some point we started restarting map polls on health change, but we
don't remember why. Maybe it was a desperate workaround for something.
I'm not sure it ever worked.

Rather than have a haunted graveyard, remove it.

In its place, though, and somewhat as a safety backup, send those
updates over the HTTP/2 noise channel if we have one open. Then if
there was a reason that a map poll restart would help we could do it
server-side. But mostly we can gather error stats and show
machine-level health info for debugging.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-24 08:51:34 -07:00
Brad Fitzpatrick
6d04184325 control/controlclient: add a noiseClient.post helper method
In prep for a future change that would've been very copy/paste-y.

And because the set-dns call doesn't currently use a context,
so timeouts/cancelations are plumbed.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-23 13:18:22 -07:00
Will Norris
8c72aabbdf licenses: remove win.md file
This was renamed to windows.md
2022-09-23 11:21:25 -07:00
James Tucker
f7cb535693 net/speedtest: retune to meet iperf on localhost in a VM
- removed some in-flow time calls
- increase buffer size to 2MB to overcome syscall cost
- move relative time computation from record to report time

Signed-off-by: James Tucker <james@tailscale.com>
2022-09-23 10:46:04 -07:00
James Tucker
146f51ce76 net/packet: fix filtering of short IPv4 fragments
The fragment offset is an 8 byte offset rather than a byte offset, so
the short packet limit is now in fragment block size in order to compare
with the offset value.

The packet flags are in the first 3 bits of the flags/frags byte, and
so after conversion to a uint16 little endian value they are at the
start, not the end of the value - the mask for extracting "more
fragments" is adjusted to match this byte.

Extremely short fragments less than 80 bytes are dropped, but fragments
over 80 bytes are now accepted.

Fixes #5727

Signed-off-by: James Tucker <james@tailscale.com>
2022-09-23 10:43:28 -07:00
Mihai Parparita
c66e15772f tsweb: consider 304s as successful for quiet logging
Static resource handlers will generate lots of 304s, which are
effectively successful responses.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-23 10:40:32 -07:00
Andrew Dunham
e1bdbfe710 tailcfg, control/controlhttp, control/controlclient: add ControlDialPlan field (#5648)
* tailcfg, control/controlhttp, control/controlclient: add ControlDialPlan field

This field allows the control server to provide explicit information
about how to connect to it; useful if the client's link status can
change after the initial connection, or if the DNS settings pushed by
the control server break future connections.

Change-Id: I720afe6289ec27d40a41b3dcb310ec45bd7e5f3e
Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-09-23 13:06:55 -04:00
Aaron Klotz
acc7baac6d tailcfg, util/deephash: add DataPlaneAuditLogID to Node and DomainDataPlaneAuditLogID to MapResponse
We're adding two log IDs to facilitate data-plane audit logging: a node-specific
log ID, and a domain-specific log ID.

Updated util/deephash/deephash_test.go with revised expectations for tailcfg.Node.

Updates https://github.com/tailscale/corp/issues/6991

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-09-22 17:18:28 -06:00
Kyle Carberry
91794f6498 wgengine/magicsock: move firstDerp check after nil derpMap check
This fixes a race condition which caused `c.muCond.Broadcast()` to
never fire in the `firstDerp` if block. It resulted in `Close()`
hanging forever.

Signed-off-by: Kyle Carberry <kyle@carberry.com>
2022-09-22 11:54:56 -07:00
Brad Fitzpatrick
2c447de6cc cmd/tailscaled: use explicit equal sign in --port=$PORT in tailscaled.service
Personal preference (so it's obvious it's not a bool flag), but it
also matches the --state= before it.

Bonus: stop allowing PORT to sneak in extra flags to be passed as
their own arguments, as $FOO and ${FOO} expand differently. (${FOO} is
required to concat to strings)

Change-Id: I994626a5663fe0948116b46a971e5eb2c4023216
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-22 11:54:22 -07:00
Anton Schubert
021bedfb89 docker: add ability to use a custom control socket
Signed-off-by: Anton Schubert <anton.schubert@riedel.net>
2022-09-22 08:48:26 -07:00
hlts2
d988c9f098 fix auth key name
Signed-off-by: hlts2 <hiroto.funakoshi.hiroto@gmail.com>
2022-09-22 03:55:05 -07:00
Andrew Dunham
0607832397 wgengine/netstack: always respond to 4via6 echo requests (#5712)
As the comment in the code says, netstack should always respond to ICMP
echo requests to a 4via6 address, even if the netstack instance isn't
normally processing subnet traffic.

Follow-up to #5709

Change-Id: I504d0776c5824071b2a2e0e687bc33e24f6c4746
Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-09-21 18:07:57 -04:00
Will Norris
565dbc599a Revert "licenses: update win/apple licenses"
This reverts commit aadf63da1d.
2022-09-21 14:28:43 -07:00
License Updater
aadf63da1d licenses: update win/apple licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2022-09-21 14:19:57 -07:00
Maisem Ali
d5781f61a9 ipn/ipnlocal: return usernames when Tailscale SSH is enabled
It was checking if the sshServer was initialized as a proxy, but that
could either not have been initialized yet or Tailscale SSH could have
been disabled after intialized.

Also bump tailcfg.CurrentCapabilityVersion

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-09-21 14:06:40 -07:00
Mihai Parparita
a7a0baf6b9 cmd/tsconnect: add error callback for SSH sessions
We were just logging them to the console, which is useful for debugging,
but we may want to show them in the UI too.

Updates tailscale/corp#6939

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-21 13:09:53 -07:00
Tom DNetto
e9b98dd2e1 control/controlclient,ipn/ipnlocal: wire tka enable/disable
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-09-21 12:57:59 -07:00
Andrew Dunham
b9b0bf65a0 wgengine/netstack: handle 4via6 packets when pinging (#5709)
Change-Id: Ib6ebbaa11219fb91b550ed7fc6ede61f83262e89
Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-09-21 14:19:34 -04:00
Andrew Dunham
c6162c2a94 net/netcheck: add check for captive portal (#5593)
This doesn't change any behaviour for now, other than maybe running a
full netcheck more often. The intent is to start gathering data on
captive portals, and additionally, seeing this in the 'tailscale
netcheck' command should provide a bit of additional information to
users.

Updates #1634

Change-Id: I6ba08f9c584dc0200619fa97f9fde1a319f25c76
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2022-09-20 15:31:49 -04:00
Brad Fitzpatrick
aa5e494aba tsweb: export go_version in standard expvar vars
For monitoring.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-20 09:59:43 -07:00
Berk D. Demir
ff13c66f55 cmd/tailscale: fix configure-host command for Synology
d5e7e309 changed the `hostinfo.GetVersion` from distro and distro version
to UTS Name Release and moved distribution information under
`hostinfo.Distro*`.

`tailscale configure-host` command implementation for Synology DSM
environments relies on the old semantics of this string for matching DSM
Major version so it's been broken for a few days.

Pull in `hostinfo` and prefix match `hostinfo.DistroVersion` to match
DSM major version.

Signed-off-by: Berk D. Demir <bdd@mindcast.org>
2022-09-19 21:15:21 -07:00
Brad Fitzpatrick
ed248b04a7 cmd/tailscale: remove leftover debug prints from earlier commit
From 6632504f45

Change-Id: If21789232b3ecc14c1639cf87814af6fa73f535f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-19 21:13:56 -07:00
Mihai Parparita
8158dd2edc cmd/tsconnect: allow SSH connection timeout to be overridden
5 seconds may not be enough if we're still loading the derp map and
connecting to a slow machine.

Updates #5693

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-19 18:00:12 -07:00
Maisem Ali
6632504f45 cmd/tailscale/cli: [up] move lose-ssh check after other validations
The check was happening too early and in the case of error would wait 5
s and then error out. This makes it so that it does validations before
the SSH check.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-09-19 12:04:14 -07:00
Maisem Ali
054ef4de56 tailcfg: mark CapabilityFileSharingTarget as inter-node
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-09-19 11:08:34 -07:00
Brad Fitzpatrick
d045462dfb ipn/ipnlocal: add c2n method to get SSH username candidates
For control to fetch a list of Tailscale SSH username candidates to
filter against the Tailnet's SSH policy to present some valid
candidates to a user.

Updates #3802
Updates tailscale/corp#7007

Change-Id: I3dce57b7a35e66891d5e5572e13ae6ef3c898498
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-19 10:37:04 -07:00
Brad Fitzpatrick
d8eb111ac8 .github/workflows: add cross-android
This would've caught the regression from 7c49db02a before it was
submitted so 42f1d92ae0 wouldn't have been necessary to fix it.

Updates #4482

Change-Id: Ia4a9977e21853f68df96f043672c86a86c0181db
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-18 09:56:19 -07:00
Brad Fitzpatrick
832031d54b wgengine/magicsock: fix recently introduced data race
From 5c42990c2f, not yet released in a stable build.
Caught by existing tests.

Fixes #5685

Change-Id: Ia76bb328809d9644e8b96910767facf627830600
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-18 08:07:57 -07:00
Denton Gentry
42f1d92ae0 net/netns: implement UseSocketMark for Android.
Build fails on Android:
`../../../../go/pkg/mod/tailscale.com@v1.1.1-0.20220916223019-65c24b6334e9/wgengine/magicsock/magicsock_linux.go:133:12: undefined: netns.UseSocketMark`

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-09-17 23:19:24 -07:00
Brad Fitzpatrick
41bb47de0e cmd/tailscaled: respect $PORT on all platforms, not just Linux
Updates #5114

Change-Id: I6c6e28c493d6a026a03088157d08f9fd182ef373
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-17 12:30:29 -07:00
Brad Fitzpatrick
3562b5bdfa envknob, health: support Synology, show parse errors in status
Updates #5114

Change-Id: I8ac7a22a511f5a7d0dcb8cac470d4a403aa8c817
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-17 08:42:41 -07:00
phirework
5c42990c2f wgengine/magicsock: add client flag and envknob to disable heartbeat (#5638)
Baby steps towards turning off heartbeat pings entirely as per #540.
This doesn't change any current magicsock functionality and requires additional
changes to send/disco paths before the flag can be turned on.

Updates #540

Change-Id: Idc9a72748e74145b068d67e6dd4a4ffe3932efd0
Signed-off-by: Jenny Zhang <jz@tailscale.com>

Signed-off-by: Jenny Zhang <jz@tailscale.com>
2022-09-16 23:48:46 -04:00
Brad Fitzpatrick
65c24b6334 envknob: generalize Windows tailscaled-env.txt support
ipnserver previously had support for a Windows-only environment
variable mechanism that further only worked when Windows was running
as a service, not from a console.

But we want it to work from tailscaed too, and we want it to work on
macOS and Synology. So move it to envknob, now that envknob can change
values at runtime post-init.

A future change will wire this up for more platforms, and do something
more for CLI flags like --port, which the bug was originally about.

Updates #5114

Change-Id: I9fd69a9a91bb0f308fc264d4a6c33e0cbe352d71
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-16 15:30:19 -07:00
Brad Fitzpatrick
4bda41e701 Dockerfile: add test that build-env Alpine version matches go.mod
So things like #5660 don't happen in the future.

Change-Id: I01234f241e297d5b7bdd18da1bb3cc5420ad2225
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-16 12:19:09 -07:00
Andrew Dunham
9b71008ef2 control/controlhttp: move Dial options into options struct (#5661)
This turns 'dialParams' into something more like net.Dialer, where
configuration fields are public on the struct.

Split out of #5648

Change-Id: I0c56fd151dc5489c3c94fb40d18fd639e06473bc
Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-09-16 15:06:25 -04:00
Luis Peralta
5623ef0271 Update Dockerfile to use golang:1.19-alpine
Tailscale @4a82b31 does not build in the container image due to using golang:1.18 image

Signed-off-by: Luis Peralta <luis.peralta@gmail.com>
2022-09-16 11:40:31 -07:00
Tyler Lee
486eecc063 Switched Secret snippet to match run.sh
Signed-off-by: Tyler Lee <tyler.lee@radius.ai>
2022-09-16 11:20:33 -07:00
Tyler Lee
b830c9975f Updated secret example in readme to match the sidecar key value
Signed-off-by: Tyler Lee <tyler.lee@radius.ai>
2022-09-16 11:20:33 -07:00
Brad Fitzpatrick
4a82b317b7 ipn/{ipnlocal,localapi}: use strs.CutPrefix, add more domain validation
The GitHub CodeQL scanner flagged the localapi's cert domain usage as a problem
because user input in the URL made it to disk stat checks.

The domain is validated against the ipnstate.Status later, and only
authenticated root/configured users can hit this, but add some
paranoia anyway.

Change-Id: I373ef23832f1d8b3a27208bc811b6588ae5a1ddd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-16 05:52:33 -07:00
Eng Zer Jun
f0347e841f refactor: move from io/ioutil to io and os packages
The io/ioutil package has been deprecated as of Go 1.16 [1]. This commit
replaces the existing io/ioutil functions with their new definitions in
io and os packages.

Reference: https://golang.org/doc/go1.16#ioutil
Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2022-09-15 21:45:53 -07:00
Mihai Parparita
027111fb5a derp: update DERP acronym expansion
Makes the package description consistent with other documentation.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-15 16:08:12 -07:00
Mihai Parparita
1ce0e558a7 cmd/derper, control/controlhttp: disable WebSocket compression
The data that we send over WebSockets is encrypted and thus not
compressible. Additionally, Safari has a broken implementation of compression
(see nhooyr/websocket#218) that makes enabling it actively harmful.

Fixes tailscale/corp#6943

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-15 15:35:49 -07:00
Brad Fitzpatrick
74674b110d envknob: support changing envknobs post-init
Updates #5114

Change-Id: Ia423fc7486e1b3f3180a26308278be0086fae49b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-15 15:04:02 -07:00
Brad Fitzpatrick
33ee2c058e wgengine: update comments, remove redundant code in forceFullWireguardConfig
Change-Id: I464a0bce36e3a362c7d7ace0e8d2dd77fa825ee2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-15 13:03:18 -07:00
Brad Fitzpatrick
d34dd43562 ipn/ipnlocal: remove unused envknob
Change-Id: I6d18af2c469eb660e6ca81d1dcc2af33c9e628aa
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-15 11:06:15 -07:00
Andrew Dunham
cf61070e26 net/dnscache: add better logging to bootstrap DNS path (#5640)
Change-Id: I4cde3a72e06dac18df856a0cfeac10ab7e3a9108
Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-09-15 10:41:45 -04:00
Kristoffer Dalby
81574a5c8d portlist: normalise space delimited process names (#5634) 2022-09-15 12:17:31 +02:00
Mihai Parparita
9c6bdae556 cmd/tsconnect: use the parent window for beforeunload event listener
The SSH session may be rendered in a different window that the one that
is executing the script.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-14 11:35:13 -07:00
Mihai Parparita
82e82d9b7a net/dns/resolver: remove unused responseTimeout constant
Timeout is now enforced elsewhere, see discussion in https://github.com/tailscale/tailscale/pull/4408#discussion_r970092333.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-13 18:12:11 -07:00
nyghtowl
0f16640546 net/dns: fix fmt error on Revert print
Fixes #5619

Signed-off-by: nyghtowl <warrick@tailscale.com>
2022-09-13 16:36:15 -07:00
Joe Tsai
aa0064db4d logpolicy: add NewWithConfigPath (#5625)
The version.CmdName implementation is buggy such that it does not correctly
identify the binary name if it embeds other go binaries.
For now, add a NewWithConfigPath API that allows the caller to explicitly
specify this information.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-09-13 16:30:40 -07:00
Brad Fitzpatrick
45a3de14a6 cmd/tailscaled, tailcfg, hostinfo: add flag to disable logging + support
As noted in #5617, our documented method of blocking log.tailscale.io
DNS no longer works due to bootstrap DNS.

Instead, provide an explicit flag (--no-logs-no-support) and/or env
variable (TS_NO_LOGS_NO_SUPPORT=true) to explicitly disable logcatcher
uploads. It also sets a bit on Hostinfo to say that the node is in that
mode so we can end any support tickets from such nodes more quickly.

This does not yet provide an easy mechanism for users on some
platforms (such as Windows, macOS, Synology) to set flags/env. On
Linux you'd used /etc/default/tailscaled typically. Making it easier
to set flags for other platforms is tracked in #5114.

Fixes #5617
Fixes tailscale/corp#1475

Change-Id: I72404e1789f9e56ec47f9b7021b44c025f7a373a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-13 11:47:36 -07:00
Tom DNetto
f6da2220d3 wgengine: set fwmark masks in netfilter & ip rules
This change masks the bitspace used when setting and querying the fwmark on packets. This allows
tailscaled to play nicer with other networking software on the host, assuming the other networking
software is also using fwmarks & a different mask.

IPTables / mark module has always supported masks, so this is safe on the netfilter front.

However, busybox only gained support for parsing + setting masks in 1.33.0, so we make sure we
arent such a version before we add the "/<mask>" syntax to an ip rule command.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-09-13 09:52:26 -07:00
Mihai Parparita
b22b565947 cmd/tsconnect: allow xterm.js terminal options to be passed in
Allows clients to use a custom theme and other xterm.js customization
options.

Fixes #5610

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-12 16:39:02 -07:00
David Anderson
7c49db02a2 wgengine/magicsock: don't use BPF receive when SO_MARK doesn't work.
Fixes #5607

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-09-12 15:05:44 -07:00
Mihai Parparita
c312e0d264 cmd/tsconnect: allow hostname to be specified
The auto-generated hostname is nice as a default, but there are cases
where the client has a more specific name that it can generate.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-12 14:26:50 -07:00
Mihai Parparita
11fcc3a7b0 cmd/tsconnect: fix xterm.js link opening not working when rendered into another window
The default WebLinksAddon handler uses window.open(), but that gets blocked
by the popup blocker when the event being handled is another window. We
instead need to invoke open() on the window that the event was triggered
in.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-12 13:54:27 -07:00
Will Norris
f03a63910d cmd/tailscale: add licenses link to web UI
The `tailscale web` UI is the primary interface for Synology and Home
Assistant users (and perhaps others), so is the logical place to put our
open source license notices.  I don't love adding things to what is
currently a very minimal UI, but I'm not sure of a better option.

Updates tailscale/corp#5780

Signed-off-by: Will Norris <will@tailscale.com>
2022-09-12 12:06:44 -07:00
Brad Fitzpatrick
024257ef5a net/stun: unmap IPv4 addresses in 16 byte STUN replies
Updates #5602

Change-Id: I2276ad2bfb415b9ff52f37444f2a1d74b38543b1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-12 12:03:27 -07:00
Andrew Dunham
eb5939289c cmd/derper: add /generate_204 endpoint (#5601)
For captive portal detection.

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-09-12 13:43:50 -04:00
Brad Fitzpatrick
16939f0d56 hostinfo: detect being run in a container in more ways
Change-Id: I038ff7705ba232e6cf8dcc9775357ef708d43762
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-12 07:02:01 -07:00
Brad Fitzpatrick
d5e7e3093d hostinfo, tailcfg: split Hostinfo.OSVersion into separate fields
Stop jamming everything into one string.

Fixes #5578

Change-Id: I7dec8d6c073bddc7dc5f653e3baf2b4bf6b68378
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-11 21:40:28 -07:00
Brad Fitzpatrick
708b7bff3d net/dns/publicdns: also support NextDNS DoH query parameters
The plan has changed. Doing query parameters rather than path +
heades. NextDNS added support for query parameters.

Updates #2452

Change-Id: I4783c0a06d6af90756d9c80a7512644ba702388c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-11 09:01:03 -07:00
Brad Fitzpatrick
81bc4992f2 net/netns: add TS_FORCE_LINUX_BIND_TO_DEVICE for Linux
For debugging a macOS-specific magicsock issue. macOS runs in
bind-to-interface mode always. This lets me force Linux into the same
mode as macOS, even if the Linux kernel supports SO_MARK, as it
usually does.

Updates #2331 etc

Change-Id: Iac9e4a7429c1781337e716ffc914443b7aa2869d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-10 18:33:30 -07:00
Brad Fitzpatrick
f3ce1e2536 util/mak: deprecate NonNil, add type-safe NonNilSliceForJSON, NonNilMapForJSON
And put the rationale in the name too to save the callers the need for a comment.

Change-Id: I090f51b749a5a0641897ee89a8fb2e2080c8b782
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-10 12:19:22 -07:00
Brad Fitzpatrick
e7376aca25 net/dns/resolver: set DNS-over-HTTPS Accept and User-Agent header on requests
Change-Id: I14b821771681e70405a507f43229c694159265ff
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-10 08:57:26 -07:00
Tom DNetto
ed2b8b3e1d wgengine/router: reduce routing rule priority for openWRT + mwan3
Fixes #3659

Signed-off-by: Tom DNetto <tom@tailscale.com>
Co-authored-by: Ian Foster <ian@vorsk.com>
2022-09-09 18:21:24 -07:00
Brad Fitzpatrick
c14361e70e net/dns/publicdns: support NextDNS DoH URLs with path parameters
Updates #2452

Change-Id: I0f1c34cc1672e87e7efd0adfe4088724dd0de3ed
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-09 14:16:23 -07:00
Mihai Parparita
b302742137 cmd/tsconnect: enable web links addon in the terminal
More user friendly, and as a side-effect we handle SSH check mode better,
since the URL that's output is now clickable.

Fixes #5247

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-09 11:05:01 -07:00
Mihai Parparita
62035d6485 cmd/tsconnect: switch back to public version of xterm npm package
xtermjs/xterm.js#4069 was merged and published (in 5.0.0-beta.58),
no need for the fork added by 01e6565e8a.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-09 10:50:43 -07:00
Brad Fitzpatrick
89fee056d3 cmd/derper: add robots.txt to disallow all
Fixes #5565

Change-Id: I5626ec2116d9be451caef651dc301b7a82e35550
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-09 10:29:46 -07:00
License Updater
3ed366ee1e licenses: update android licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2022-09-09 09:14:22 -07:00
Brad Fitzpatrick
2aade349fc net/dns, types/dnstypes: update some comments, tests for DoH
Clarify & verify that some DoH URLs can be sent over tailcfg
in some limited cases.

Updates #2452

Change-Id: Ibb25db77788629c315dc26285a1059a763989e24
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-08 17:16:13 -07:00
Brad Fitzpatrick
58abae1f83 net/dns/{publicdns,resolver}: add NextDNS DoH support
NextDNS is unique in that users create accounts and then get
user-specific DNS IPs & DoH URLs.

For DoH, the customer ID is in the URL path.

For IPv6, the IP address includes the customer ID in the lower bits.

For IPv4, there's a fragile "IP linking" mechanism to associate your
public IPv4 with an assigned NextDNS IPv4 and that tuple maps to your
customer ID.

We don't use the IP linking mechanism.

Instead, NextDNS is DoH-only. Which means using NextDNS necessarily
shunts all DNS traffic through 100.100.100.100 (programming the OS to
use 100.100.100.100 as the global resolver) because operating systems
can't usually do DoH themselves.

Once it's in Tailscale's DoH client, we then connect out to the known
NextDNS IPv4/IPv6 anycast addresses.

If the control plane sends the client a NextDNS IPv6 address, we then
map it to the corresponding NextDNS DoH with the same client ID, and
we dial that DoH server using the combination of v4/v6 anycast IPs.

Updates #2452

Change-Id: I3439d798d21d5fc9df5a2701839910f5bef85463
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-08 12:50:32 -07:00
Mihai Parparita
01e6565e8a cmd/tsconnect: temporarily switch to xterm.js fork that handles popup windows
Allows other work to be unblocked while xtermjs/xterm.js#4069 is worked
through.

To enable testing the popup window handling, the standalone app allows
opening of SSH sessions in new windows by holding down the alt key
while pressing the SSH button.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-08 09:30:52 -07:00
Mihai Parparita
2400ba28b1 cmd/tsconnect: handle terminal resizes before the SSH session is created
Store the requested size is a struct field, and use that when actually
creating the SSH session.

Fixes #5567

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-08 09:30:52 -07:00
Brad Fitzpatrick
2266b59446 go.toolchain.rev: bump to Go 1.19.1
See https://github.com/tailscale/go/pull/34

Change-Id: I56806358cd1be4a2b8f509883e47c93083d82bdf
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-07 22:13:01 -07:00
Brad Fitzpatrick
ad7546fb9f tailcfg: fix broken test from comment change
Fix broken build from 255c0472fb

"Oh, that's safe to commit because most tests are passing and it's
just a comment change!", I thought, forgetting I'd added a test that
parses its comments.

Change-Id: Iae93d595e06fec48831215a98adbb270f3bfda05
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-07 22:03:38 -07:00
Brad Fitzpatrick
255c0472fb tailcfg: reformat CurrentCapabilityVersion to be a bulleted list
gofmt in 1.19 is now opinionated about structured text formatting in
comments. It did not like our style and kept fighting us whenever we
changed these lines. Give up the fight and be a bulleted list for it.

See:

* https://go.dev/doc/go1.19#go-doc and
* https://go.dev/doc/comment

Updates #4872

Change-Id: Ifae431218471217168c003ab3b4e03c394ca8105
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-07 21:53:16 -07:00
License Updater
c5adc5243c licenses: update win/apple licenses
Signed-off-by: GitHub <noreply@github.com>
2022-09-07 14:28:24 -07:00
Andrew Dunham
c9961b8b95 cmd/derper: filter out useless HTTP error logs (#5563)
These errors aren't actionable and just fill up logs with useless data.
See the following Go issue for more details:
  https://golang.org/issue/26918

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-09-07 16:31:06 -04:00
License Updater
8fdf137571 licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2022-09-06 19:19:15 -07:00
Colin Adler
9c8bbc7888 wgengine/magicsock: fix panic in http debug server
Fixes an panic in `(*magicsock.Conn).ServeHTTPDebug` when the
`recentPongs` ring buffer for an endpoint wraps around.

Signed-off-by: Colin Adler <colin1adler@gmail.com>
2022-09-06 15:02:07 -07:00
Andrew Dunham
9240f5c1e2 wgengine/netstack: only accept connection after dialing (#5503)
If we accept a forwarded TCP connection before dialing, we can
erroneously signal to a client that we support IPv6 (or IPv4) without
that actually being possible. Instead, we only complete the client's TCP
handshake after we've dialed the outbound connection; if that fails, we
respond with a RST.

Updates #5425 (maybe fixes!)

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-09-06 16:04:10 -04:00
Mihai Parparita
2f702b150e cmd/tsconnect: add dev-pkg command for two-sided development
Allows imports of the NPM package added by 1a093ef482
to be replaced with import("http://localhost:9090/pkg/pkg.js"), so that
changes can be made in parallel to both the module and code that uses
it (without any need for NPM publishing or even building of the package).

Updates #5415

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-09-06 12:42:58 -07:00
James Tucker
672c2c8de8 wgengine/magicsock: add filter to ignore disco to old/other ports
Incoming disco packets are now dropped unless they match one of the
current bound ports, or have a zero port*.

The BPF filter passes all packets with a disco header to the raw packet
sockets regardless of destination port (in order to avoid needing to
reconfigure BPF on rebind).

If a BPF enabled node has just rebound, due to restart or rebind, it may
receive and reply to disco ping packets destined for ports other than
those which are presently bound. If the pong is accepted, the pinging
node will now assume that it can send WireGuard traffic to the pinged
port - such traffic will not reach the node as it is not destined for a
bound port.

*The zero port is ignored, if received. This is a speculative defense
and would indicate a problem in the receive path, or the BPF filter.
This condition is allowed to pass as it may enable traffic to flow,
however it will also enable problems with the same symptoms this patch
otherwise fixes.

Fixes #5536

Signed-off-by: James Tucker <james@tailscale.com>
2022-09-06 12:25:04 -07:00
James Tucker
be140add75 wgengine/magicsock: fix regression in initial bind for js
1f959edeb0 introduced a regression for JS
where the initial bind no longer occurred at all for JS.

The condition is moved deeper in the call tree to avoid proliferation of
higher level conditions.

Updates #5537

Signed-off-by: James Tucker <james@tailscale.com>
2022-09-06 12:23:44 -07:00
James Tucker
1f959edeb0 wgengine/magicksock: remove nullability of RebindingUDPConns
Both RebindingUDPConns now always exist. the initial bind (which now
just calls rebind) now ensures that bind is called for both, such that
they both at least contain a blockForeverConn. Calling code no longer
needs to assert their state.

Signed-off-by: James Tucker <james@tailscale.com>
2022-09-06 12:08:31 -07:00
Brad Fitzpatrick
56f6fe204b go.mod, wgengine/wgint: bump wireguard-go
For b51010ba13

Change-Id: Ibf767dfad98aef7e9f0505d91c0d26f924e046d5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-06 11:34:30 -07:00
Andrew Dunham
f52a659076 net/dnsfallback: allow setting log function (#5550)
This broke a test in corp that enforces we don't use the log package.

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-09-06 11:19:50 -04:00
Andrew Dunham
b8596f2a2f net/dnsfallback: cache most recent DERP map on disk (#5545)
This is especially helpful as we launch newer DERPs over time, and older
clients have progressively out-of-date static DERP maps baked in. After
this, as long as the client has successfully connected once, it'll cache
the most recent DERP map it knows about.

Resolves an in-code comment from @bradfitz

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2022-09-05 14:36:30 -04:00
Maisem Ali
060ecb010f docs/k8s: make run.sh handle SIGINT
It was previously using jobcontrol to achieve this, but that apparently
doesn't work when there is no tty. This makes it so that it directly
handles SIGINT and SIGTERM and passes it on to tailscaled. I tested this
works on a Digital Ocean K8s cluster.

Fixes #5512

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-09-04 15:50:02 -07:00
Brad Fitzpatrick
02de34fb10 cmd/derper: add flag to run derper in bootstrap-dns-only mode
Change-Id: Iba128e94464afa605bc9df1f06a91d296380eed0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-03 19:51:00 -07:00
Will Norris
3344c3b89b tsnet: add Server method to listener
Allow callers to verify that a net.Listener is a tsnet.listener by type
asserting against this Server method, as well as providing access to the
underlying Server.

This is initially being added to support the caddy integration in
caddyserver/caddy#5002.

Signed-off-by: Will Norris <will@tailscale.com>
2022-09-02 16:29:49 -07:00
Andrew Dunham
a0bae4dac8 cmd/derper: add support for unpublished bootstrap DNS entries (#5529)
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2022-09-02 14:48:30 -04:00
Tom DNetto
9132b31e43 tailcfg: refactor/implement wire structs for TKA
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-09-02 10:29:46 -07:00
Kris Brandow
19008a3023 net/dnscache: use net/netip
Removes usage of net.IP and net.IPAddr where possible from net/dnscache.

Fixes #5282

Signed-off-by: Kris Brandow <kris.brandow@gmail.com>
2022-09-01 17:54:19 -04:00
Brad Fitzpatrick
ba3cc08b62 cmd/tailscale/cli: add backwards compatibility 'up' processing for legacy client
Updates tailscale/corp#6781

Change-Id: I843fc810cbec0140d423d65db81e90179d6e0fa5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-09-01 14:21:48 -07:00
License Updater
d8bfb7543e licenses: update win/apple licenses
Signed-off-by: GitHub <noreply@github.com>
2022-09-01 14:10:55 -07:00
James Tucker
265b008e49 wgengine: fix race on endpoints in getStatus
Signed-off-by: James Tucker <james@tailscale.com>
2022-09-01 10:58:04 -07:00
Bertrand Lorentz
a5ad57472a cli/cert: Fix help message for --key-file
Signed-off-by: Bertrand Lorentz <bertrand.lorentz@gmail.com>
2022-09-01 10:57:00 -07:00
Xe Iaso
3564fd61b5 cmd/gitops-pusher: standardize hujson before posting to validate (#5525)
Apparently the validate route doesn't check content-types or handle
hujson with comments correctly. This patch makes gitops-pusher convert
the hujson to normal json.

Signed-off-by: Xe <xe@tailscale.com>

Signed-off-by: Xe <xe@tailscale.com>
2022-09-01 13:38:32 -04:00
nyghtowl
cfbbcf6d07 cmd/nginx-auth/nginx-auth: update auth to allow for new domains
With MagicDNS GA, we are giving every tailnet a tailnet-<hex>.ts.net name.
We will only parse out if legacy domains include beta.tailscale.net; otherwise,
set tailnet to the full domain format going forward.

Signed-off-by: nyghtowl <warrick@tailscale.com>
2022-08-31 20:18:13 -07:00
License Updater
9c66dce8e0 licenses: update win/apple licenses
Signed-off-by: GitHub <noreply@github.com>
2022-08-31 15:38:41 -07:00
Brad Fitzpatrick
e470893ba0 wgengine/magicsock: use mak in another spot
Change-Id: I0a46d6243371ae6d126005a2bd63820cb2d1db6b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-31 15:30:26 -07:00
Andrew Dunham
c72caa6672 wgengine/magicsock: use AF_PACKET socket + BPF to read disco messages
This is entirely optional (i.e. failing in this code is non-fatal) and
only enabled on Linux for now. Additionally, this new behaviour can be
disabled by setting the TS_DEBUG_DISABLE_AF_PACKET environment variable.

Updates #3824
Replaces #5474

Co-authored-by: Andrew Dunham <andrew@du.nham.ca>
Signed-off-by: David Anderson <danderson@tailscale.com>
2022-08-31 14:52:31 -07:00
Mihai Parparita
58f35261d0 cmd/tsconnect: remove debugging code
Remove test prefix added to validate the error code from 27f36f77c3.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-31 10:46:47 -07:00
Tom DNetto
be95aebabd tka: implement credential signatures (key material delegation)
This will be needed to support preauth-keys with network lock in the future,
so getting the core mechanics out of the way now.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-31 10:13:13 -07:00
License Updater
490acdefb6 licenses: update android licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2022-08-31 09:55:41 -07:00
License Updater
84b74825f0 licenses: update tailscale{,d} licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2022-08-31 08:38:55 -07:00
Brad Fitzpatrick
9bd9f37d29 go.mod: bump wireguard/windows, which moves to using net/netip
Updates #5162

Change-Id: If99a3f0000bce0c01bdf44da1d513f236fd7cdf8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-31 08:36:56 -07:00
Charlotte Brandhorst-Satzkorn
185f2e4768 words: this title should have been a pun, but I chickened out (#5506)
Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2022-08-31 07:02:49 -07:00
Denton Gentry
53e08bd7ea VERSION.txt: this is 1.31
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-08-31 06:48:24 -07:00
Andrew Dunham
70ed22ccf9 util/uniq: add ModifySliceFunc (#5504)
Follow-up to #5491. This was used in control... oops!

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-08-30 18:51:18 -04:00
Tom DNetto
7ca17b6bdb tka: validate key after UpdateKey before applying state
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-30 15:23:30 -07:00
Andrew Dunham
e945d87d76 util/uniq: use generics instead of reflect (#5491)
This takes 75% less time per operation per some benchmarks on my mac.

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2022-08-30 17:56:51 -04:00
Andrew Dunham
1ac4a26fee ipn/localapi: send Tailscale version in ACME User-Agent (#5499)
Requested by a friend at Let's Encrypt.

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-08-30 16:48:59 -04:00
Brad Fitzpatrick
761163815c tailcfg: add Hostinfo.Userspace{,Router} bits
Change-Id: Iad47f904872f2df146c1f63945f79cfddeac7fe8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-30 12:39:03 -07:00
Brad Fitzpatrick
9f6c8517e0 net/dns: set OS DNS to 100.100.100.100 for route-less ExtraRecords [cap 41]
If ExtraRecords (Hosts) are specified without a corresponding split
DNS route and global DNS is specified, then program the host OS DNS to
use 100.100.100.100 so it can blend in those ExtraRecords.

Updates #1543

Change-Id: If49014a5ecc8e38978ff26e54d1f74fe8dbbb9bc
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-30 12:36:25 -07:00
Mihai Parparita
27f36f77c3 cmd/tsconnect: output errors to the JS console too
We were just outputting them to the terminal, but that's hard to debug
because we immediately tear down the terminal when getting an error.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-30 10:46:44 -07:00
Xe Iaso
122bd667dc cmd/gitops-pusher: be less paranoid about external modifications (#5488)
This makes a "modified externally" error turn into a "modified externally" warning. It means CI won't fail if someone does something manually in the admin console.

Signed-off-by: Xe <xe@tailscale.com>
2022-08-30 09:41:25 -04:00
Joe Tsai
21cd402204 logtail: do not log when backing off (#5485) 2022-08-30 06:21:03 -07:00
Denton Gentry
0ae0439668 docs/k8s: add IPv6 forwarding in proxy.yaml
Fixes https://github.com/tailscale/tailscale/issues/4999

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-08-30 06:03:15 -07:00
Denton Gentry
6dcc6313a6 CI: add go mod tidy workflow
Fixes https://github.com/tailscale/tailscale/issues/4567

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-08-30 06:02:34 -07:00
Denton Gentry
78dbb59a00 CI: make all workflows get Go version from go.mod
The next time we update the toolchain, all of the CI
Actions will automatically use it when go.mod is updated.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-08-30 06:02:14 -07:00
Joe Tsai
7e40071571 util/deephash: handle slice edge-cases (#5471)
It is unclear whether the lack of checking nil-ness of slices
was an oversight or a deliberate feature.
Lacking a comment, the assumption is that this was an oversight.

Also, expand the logic to perform cycle detection for recursive slices.
We do this on a per-element basis since a slice is semantically
equivalent to a list of pointers.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-30 00:33:18 -07:00
James Tucker
90dc0e1702 wgengine: remove unused singleflight group
Signed-off-by: James Tucker <james@tailscale.com>
2022-08-29 18:16:30 -07:00
Mihai Parparita
2c18517121 cmd/tsconnect: add npm publish workflow
Adds an on-demand GitHub Action that publishes the package to the npm
registry (currently under tailscale-connect, will be moved to
@tailscale/connect once we get control of the npm org).

Makes the package.json for the NPM package be dynamically generated to
have the current Tailscale client version.

Updates #5415

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-29 18:02:51 -07:00
Andrew Dunham
d6c3588ed3 wgengine/wgcfg: only write peer headers if necessary (#5449)
On sufficiently large tailnets, even writing the peer header (~95 bytes)
can result in a large amount of data that needs to be serialized and
deserialized. Only write headers for peers that need to have their
configuration changed.

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-08-29 20:47:52 -04:00
James Tucker
81dba3738e wgengine: remove all peer status from open timeout diagnostics
Avoid contention from fetching status for all peers, and instead fetch
status for a single peer.

Updates tailscale/coral#72
Signed-off-by: James Tucker <james@tailscale.com>
2022-08-29 15:54:33 -07:00
James Tucker
ad1cc6cff9 wgengine: use Go API rather than UAPI for status
Signed-off-by: James Tucker <james@tailscale.com>
2022-08-29 15:38:16 -07:00
Maisem Ali
68d9d161f4 net/dns: [win] fix regression in disableDynamicUpdate
Somehow I accidentally set the wrong registry value here.
It should be DisableDynamicUpdate=1 and not EnableDNSUpdate=0.

This is a regression from 545639e.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-29 15:28:59 -07:00
Brad Fitzpatrick
c66f99fcdc tailcfg, control/controlclient, ipn/ipnlocal: add c2n (control-to-node) system
This lets the control plane can make HTTP requests to nodes.

Then we can use this for future things rather than slapping more stuff
into MapResponse, etc.

Change-Id: Ic802078c50d33653ae1f79d1e5257e7ade4408fd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-29 15:18:40 -07:00
Brad Fitzpatrick
08b3f5f070 wgengine/wgint: add shady temporary package to get at wireguard internals
For #5451

Change-Id: I43482289e323ba9142a446d551ab7a94a467c43a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-29 10:03:51 -07:00
Nahum Shalman
66d7d2549f logger: migrate rusage syscall use to x/sys/unix
This will be helpful for illumos (#697) and should be safe
everywhere else.

Signed-off-by: Nahum Shalman <nahamu@gmail.com>
2022-08-28 08:29:41 -07:00
Nahum Shalman
d20392d413 cmd/tailscale: add emoji for illumos in status subcommand
Signed-off-by: Nahum Shalman <nahamu@gmail.com>
2022-08-28 08:29:31 -07:00
Andrew Dunham
58cc049a9f util/cstruct: add package for decoding padded C structures (#5429)
I was working on my "dump iptables rules using only syscalls" branch and
had a bunch of C structure decoding to do. Rather than manually
calculating the padding or using unsafe trickery to actually cast
variable-length structures to Go types, I'd rather use a helper package
that deals with padding for me.

Padding rules were taken from the following article:
  http://www.catb.org/esr/structure-packing/

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2022-08-28 11:12:09 -04:00
Andrew Dunham
9b77ac128a wgengine: print in-flight operations on watchdog trigger (#5447)
In addition to printing goroutine stacks, explicitly track all in-flight
operations and print them when the watchdog triggers (along with the
time they were started at). This should make debugging watchdog failures
easier, since we can look at the longest-running operation(s) first.

Signed-off-by: Andrew Dunham <andrew@tailscale.com>

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-08-27 22:06:18 -04:00
Andrew Dunham
e1738ea78e chirp: add a 10s timeout when communicating with BIRD (#5444)
Prior to this change, if BIRD stops responding wgengine.watchdogEngine
will crash tailscaled.

This happens because in wgengine.userspaceEngine, we end up blocking
forever trying to write a request to or read a response from BIRD with
wgLock held, and then future watchdog'd calls will block on acquiring
that mutex until the watchdog kills the process. With the timeout, we at
least get the chance to print an error message and decide whether we
want to crash or not.

Updates tailscale/coral#72

Signed-off-by: Andrew Dunham <andrew@tailscale.com>

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-08-27 20:49:31 -04:00
Joe Tsai
9bf13fc3d1 util/deephash: remove getTypeInfo (#5469)
Add a new lookupTypeHasher function that is just a cached front-end
around the makeTypeHasher function.
We do not need to worry about the recursive type cycle issue that
made getTypeInfo more complicated since makeTypeHasher
is not directly recursive. All calls to itself happen lazily
through a sync.Once upon first use.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-27 17:39:51 -07:00
Joe Tsai
ab7e6f3f11 util/deephash: require pointer in API (#5467)
The entry logic of Hash has extra complexity to make sure
we always have an addressable value on hand.
If not, we heap allocate the input.
For this reason we document that there are performance benefits
to always providing a pointer.
Rather than documenting this, just enforce it through generics.

Also, delete the unused HasherForType function.
It's an interesting use of generics, but not well tested.
We can resurrect it from code history if there's a need for it.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-27 16:08:31 -07:00
Joe Tsai
c5b1565337 util/deephash: move pointer and interface logic to separate function (#5465)
This helps pprof better identify which Go kinds take the most time
since the kind is always in the function name.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-27 15:51:34 -07:00
Joe Tsai
d2e2d8438b util/deephash: move map logic to separate function (#5464)
This helps pprof better identify which Go kinds take the most time
since the kind is always in the function name.

There is a minor adjustment where we hash the length of the map
to be more on the cautious side.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-27 15:49:26 -07:00
Joe Tsai
23c3831ff9 util/deephash: coalesce struct logic (#5466)
Rather than having two copies []fieldInfo,
just maintain one and perform merging in the same pass.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-27 15:39:46 -07:00
Joe Tsai
296b008b9f util/deephash: move array and slice logic to separate function (#5463)
This helps pprof better identify which Go kinds take the most time
since the kind is always in the function name.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-27 15:37:36 -07:00
Joe Tsai
31bf3874d6 util/deephash: use unsafe.Pointer instead of reflect.Value (#5459)
Use of reflect.Value.SetXXX panics if the provided argument was
obtained from an unexported struct field.
Instead, pass an unsafe.Pointer around and convert to a
reflect.Value when necessary (i.e., for maps and interfaces).
Converting from unsafe.Pointer to reflect.Value guarantees that
none of the read-only bits will be populated.

When running in race mode, we attach type information to the pointer
so that we can type check every pointer operation.
This also type-checks that direct memory hashing is within
the valid range of a struct value.

We add test cases that previously caused deephash to panic,
but now pass.

Performance:

	name              old time/op    new time/op    delta
	Hash              14.1µs ± 1%    14.1µs ± 1%    ~     (p=0.590 n=10+9)
	HashPacketFilter  2.53µs ± 2%    2.44µs ± 1%  -3.79%  (p=0.000 n=9+10)
	TailcfgNode       1.45µs ± 1%    1.43µs ± 0%  -1.36%  (p=0.000 n=9+9)
	HashArray         318ns ± 2%     318ns ± 2%    ~      (p=0.541 n=10+10)
	HashMapAcyclic    32.9µs ± 1%    31.6µs ± 1%  -4.16%  (p=0.000 n=10+9)

There is a slight performance gain due to the use of unsafe.Pointer
over reflect.Value methods. Also, passing an unsafe.Pointer (1 word)
on the stack is cheaper than passing a reflect.Value (3 words).

Performance gains are diminishing since SHA-256 hashing now dominates the runtime.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-27 12:30:35 -07:00
Joe Tsai
e0c5ac1f02 util/deephash: add debug printer (#5460)
When built with "deephash_debug", print the set of HashXXX methods.

Example usage:

	$ go test -run=GetTypeHasher/string_slice -tags=deephash_debug
	U64(2)+U64(3)+S("foo")+U64(3)+S("bar")+FIN

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-27 12:14:07 -07:00
Andrew Dunham
e8f09d24c7 wgengine: use a singleflight.Group to reduce status contention (#5450)
Updates tailscale/coral#72

Signed-off-by: Andrew Dunham <andrew@tailscale.com>

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-08-27 12:36:07 -04:00
Joe Tsai
70f9fc8c7a util/deephash: rely on direct memory hashing for primitive kinds (#5457)
Rather than separate functions to hash each kind,
just rely on the fact that these are direct memory hashable,
thus simplifying the code.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-26 20:50:56 -07:00
Brad Fitzpatrick
f1c9812188 tailcfg: add Hostinfo.GoVersion
So next time something like #5340 happens we can identify all affected
nodes and have the control plane send them health warnings.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-26 18:04:15 -07:00
Nahum Shalman
214242ff62 net/dns/publicdns: Add Mullvad DoH
See https://mullvad.net/en/help/dns-over-https-and-dns-over-tls/

The Mullvad DoH servers appear to only speak HTTP/2 and
the use of a non-nil DialContext in the http.Transport
means that ForceAttemptHTTP2 must be set to true to be
able to use them.

Signed-off-by: Nahum Shalman <nahamu@gmail.com>
2022-08-26 17:46:30 -07:00
Joe Tsai
531ccca648 util/deephash: delete slow path (#5423)
Every implementation of typeHasherFunc always returns true,
which implies that the slow path is no longer executed.
Delete it.

h.hashValueWithType(v, ti, ...) is deleted as it is equivalent to:
	ti.hasher()(h, v)

h.hashValue(v, ...) is deleted as it is equivalent to:
	ti := getTypeInfo(v.Type())
	ti.hasher()(h, v)

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-26 17:46:22 -07:00
Will Norris
3d328b82ee .github/actions: add signoff to go-licenses commits
also set git committer, which is apparently what this action uses for
signoff rather than git author.  Remove branch-suffix, which isn't
proving useful, and add installation_id, which isn't technically
necessary in the tailscale/tailscale repo, but makes this consistent
with the workflows in other repos.

Signed-off-by: Will Norris <will@tailscale.com>
2022-08-26 16:45:36 -07:00
License Updater
d95b95038c licenses: update android licenses
Signed-off-by: License Updater <noreply@tailscale.com>
2022-08-26 16:44:33 -07:00
License Updater
ceb8c5d1e9 licenses: update win/apple licenses
Signed-off-by: GitHub <noreply@github.com>
2022-08-26 14:12:52 -07:00
Will Norris
d1dd04e327 cmd/tailscale: use platform specific license link 2022-08-26 13:40:56 -07:00
Will Norris
79cf550823 cmd/tailscale: add licenses subcommand
Signed-off-by: Will Norris <will@tailscale.com>
2022-08-26 13:40:56 -07:00
Tom DNetto
79905a1162 tka: make storage a parameter rather than an Authority struct member
Updates #5435

Based on the discussion in #5435, we can better support transactional data models
by making the underlying storage layer a parameter (which can be specialized for
the request) rather than a long-lived member of Authority.

Now that Authority is just an instantaneous snapshot of state, we can do things
like provide idempotent methods and make it cloneable, too.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-26 10:44:28 -07:00
Mihai Parparita
7d1357162e cmd/tsconnect: expose runSSHSession in npm package
Move it to lib/ so that it can be used in both the app and the package.

Updates #5415

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-26 09:24:44 -07:00
License Updater
e4b5b92b82 licenses: update android licenses 2022-08-25 15:28:25 -07:00
License Updater
ff97a97f08 licenses: update win/apple licenses 2022-08-25 14:42:58 -07:00
Tom DNetto
f580f4484f tka: move disablement logic out-of-band from AUMs
It doesn't make a ton of sense for disablement to be communicated as an AUM, because
any failure in the AUM or chain mechanism will mean disablement wont function.

Instead, tracking of the disablement secrets remains inside the state machine, but
actual disablement and communication of the disablement secret is done by the caller.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-25 14:25:34 -07:00
Mihai Parparita
7a5cf39d0d ipn/ipnlocal: fix Taildrop not returning any sharing targets
The CapabilityFileSharingTarget capability added by eb32847d85
is meant to control the ability to share with nodes not owned by the
current user, not to restrict all sharing (the coordination server is
not currently populating the capability at all)

Fixes tailscale/corp#6669

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-25 12:47:20 -07:00
Tom DNetto
f81723ceac tailcfg: implement wire format for map request/response tka comms
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-25 11:02:58 -07:00
License Updater
9ccc52cd04 licenses: update tailscale{,d} licenses 2022-08-25 10:52:46 -07:00
Will Norris
5c7e960fa8 licenses: add GitHub Action to update license notices (#5433)
This will update a licenses/tailscale.md file with all of our go
dependencies and their respective licenses. Notices for other clients
will be triggered by similar actions in other repos.

Co-authored-by: Andrew Dunham <andrew@tailscale.com>
Signed-off-by: Will Norris <will@tailscale.com>
Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-08-25 10:28:10 -07:00
Mihai Parparita
1a093ef482 cmd/tsconnect: extract NPM package for reusing in other projects
`src/` is broken up into several subdirectories:
- `lib/` and `types`/ for shared code and type definitions (more code
  will be moved here)
- `app/` for the existing Preact-app
- `pkg/` for the new NPM package

A new `build-pkg` esbuild-based command is added to generate the files
for the NPM package. To generate type definitions (something that esbuild
does not do), we set up `dts-bundle-generator`.

Includes additional cleanups to the Wasm type definitions (we switch to
string literals for enums, since exported const enums are hard to use
via packages).

Also allows the control URL to be set a runtime (in addition to the
current build option), so that we don't have to rebuild the package
for dev vs. prod use.

Updates #5415

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-24 17:29:52 -07:00
Tom DNetto
472529af38 tka: optimize common case of processing updates built from head
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-24 11:35:55 -07:00
Tom DNetto
039def3b50 ipn,tailcfg: transmit NodeID in tka init RPCs
Needed to identify the node. A serverside-check the machine key (used
to authenticate the noise session) is that of the specified NodeID
ensures the authenticity of the request.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-24 11:35:55 -07:00
Tom DNetto
a78f8fa701 tka: support rotating node-keys in node-key signatures
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-24 10:41:01 -07:00
Will Norris
b3cc719add cmd/nginx-auth: allow use of shared nodes
When sharing nodes, the name of the sharee node is not exposed (instead
it is hardcoded to "device-of-shared-to-user"), which means that we
can't determine the tailnet of that node.  Don't immediately fail when
that happens, since it only matters if "Expected-Tailnet" is used.

Signed-off-by: Will Norris <will@tailscale.com>
2022-08-24 09:41:56 -07:00
Joe Tsai
3fc8683585 util/deephash: expand fast-path capabilities (#5404)
Add support for maps and interfaces to the fast path.
Add cycle-detection to the pointer handling logic.
This logic is mostly copied from the slow path.

A future commit will delete the slow path once
the fast path never falls back to the slow path.

Performance:

	name                 old time/op    new time/op    delta
	Hash-24                18.5µs ± 1%    14.9µs ± 2%  -19.52%  (p=0.000 n=10+10)
	HashPacketFilter-24    2.54µs ± 1%    2.60µs ± 1%   +2.19%  (p=0.000 n=10+10)
	HashMapAcyclic-24      31.6µs ± 1%    30.5µs ± 1%   -3.42%  (p=0.000 n=9+8)
	TailcfgNode-24         1.44µs ± 2%    1.43µs ± 1%     ~     (p=0.171 n=10+10)
	HashArray-24            324ns ± 1%     324ns ± 2%     ~     (p=0.425 n=9+9)

The additional cycle detection logic doesn't incur much slow down
since it only activates if a type is recursive, which does not apply
for any of the types that we care about.

There is a notable performance boost since we switch from the fath path
to the slow path less often. Most notably, a struct with a field that
could not be handled by the fast path would previously cause
the entire struct to go through the slow path.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-24 01:31:01 -07:00
Mihai Parparita
78b90c3685 cmd/tsconnect: stop writing build artifacts into src/
We can't write to src/ when tsconnect is used a dependency in another
repo (see also b763a12331). We therefore
need to switch from writing to src/ to using esbuild plugins to handle
the requests for wasm_exec.js (the Go JS runtime for Wasm) and the
Wasm build of the Go module.

This has the benefit of allowing Go/Wasm changes to be picked up without
restarting the server when in dev mode (Go compilation is fast enough
that we can do this on every request, CSS compilation continues to be
the long pole).

Fixes #5382

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-23 15:44:59 -07:00
Tom DNetto
facafd8819 client,cmd/tailscale,ipn,tka,types: implement tka initialization flow
This PR implements the client-side of initializing network-lock with the
Coordination server.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-22 11:35:16 -07:00
Tom DNetto
18edd79421 control/controlclient,tailcfg: [capver 40] create KeySignature field in tailcfg.Node
We calve out a space to put the node-key signature (used on tailnets where network lock is enabled).

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-22 11:25:41 -07:00
Kris Brandow
5d559141d5 wgengine/magicsock: remove mention of Start
The Start method was removed in 4c27e2fa22, but the comment on NewConn
still mentioned it doesn't do anything until this method is called.

Signed-off-by: Kris Brandow <kris.brandow@gmail.com>
2022-08-22 11:26:41 -04:00
Kamal Nasser
f983962fc6 fix typo in incomplete default routes error message
Signed-off-by: Kamal Nasser <hello@kamal.io>
2022-08-20 14:02:17 -07:00
Aaron Klotz
b997304bf6 tailcfg: add CapabilityDataPlaneAuditLogs.
We're going to want to enable audit logging on a per-Tailnet basis. When this
happens, we want control to inform the Tailnet's clients of this capability.

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-08-19 14:21:17 -06:00
Maisem Ali
9197dd14cc net/dns: [win] add MagicDNS entries to etc/hosts
This works around the 2.3s delay in short name lookups when SNR is
enabled.
C:\Windows\System32\drivers\etc\hosts file. We only add known hosts that
match the search domains, and we populate the list in order of
Search Domains so that our matching algorithm mimics what Windows would
otherwise do itself if SNR was off.

Updates #1659

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-19 12:38:11 -05:00
Denton Gentry
3c8d257b3e cmd/tailscale: set /dev/net perms in configure-host
Several customers have had issues due to the permissions
on /dev/net. Set permissions to 0755.

Fixes https://github.com/tailscale/tailscale/issues/5048

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-08-19 06:55:34 -07:00
Joe Tsai
d32700c7b2 util/deephash: specialize for netip.Addr and drop AppendTo support (#5402)
There are 5 types that we care about that implement AppendTo:

	key.DiscoPublic
	key.NodePublic
	netip.Prefix
	netipx.IPRange
	netip.Addr

The key types are thin wrappers around [32]byte and are memory hashable.
The netip.Prefix and netipx.IPRange types are thin wrappers over netip.Addr
and are hashable by default if netip.Addr is hashable.
The netip.Addr type is the only one with a complex structure where
the default behavior of deephash does not hash it correctly due to the presence
of the intern.Value type.

Drop support for AppendTo and instead add specialized hashing for netip.Addr
that would be semantically equivalent to == on the netip.Addr values.

The AppendTo support was already broken prior to this change.
It was fully removed (intentionally or not) in #4870.
It was partially restored in #4858 for the fast path,
but still broken in the slow path.
Just drop support for it altogether.

This does mean we lack any ability for types to self-hash themselves.
In the future we can add support for types that implement:

	interface { DeepHash() Sum }

Test and fuzz cases were added for the relevant types that
used to rely on the AppendTo method.
FuzzAddr has been executed on 1 billion samples without issues.

Signed-off-by: Joe Tsai joetsai@digital-static.net
2022-08-18 22:54:56 -07:00
Brad Fitzpatrick
0de66386d4 cmd/viewer: add flag to support Clone generation without Views
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-17 15:15:27 -07:00
Kris Brandow
9ae1161e85 net/dnscache: fix v4addrs to return only v4 addrs
Update the v4addrs function to filter out IPv6 addresses.

Fixes regression from 8725b14056.

Signed-off-by: Kris Brandow <kris.brandow@gmail.com>
2022-08-17 14:19:37 -04:00
Joe Tsai
03f7e4e577 util/hashx: move from sha256x (#5388) 2022-08-16 13:15:33 -07:00
Joe Tsai
f061d20c9d util/sha256x: rename Hash as Block512 (#5351)
Rename Hash as Block512 to indicate that this is a general-purpose
hash.Hash for any algorithm that operates on 512-bit block sizes.

While we rename the package as hashx in this commit,
a subsequent commit will move the sha256x package to hashx.
This is done separately to avoid confusing git.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-16 09:49:48 -07:00
Joe Tsai
44d62b65d0 util/deephash: move typeIsRecursive and canMemHash to types.go (#5386)
Also, rename canMemHash to typeIsMemHashable to be consistent.
There are zero changes to the semantics.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-16 09:31:19 -07:00
Joe Tsai
d53eb6fa11 util/deephash: simplify typeIsRecursive (#5385)
Any type that is memory hashable must not be recursive since
there are definitely no pointers involved to make a cycle.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-15 23:29:06 -07:00
Joe Tsai
23ec3c104a util/deephash: remove unused stack slice in typeIsRecursive (#5363)
No operation ever reads from this variable.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-15 22:36:08 -07:00
Joe Tsai
c200229f9e util/deephash: simplify canMemHash (#5384)
Put the t.Size() == 0 check first since this is applicable in all cases.
Drop the last struct field conditional since this is covered by the
sumFieldSize check at the end.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-15 22:22:18 -07:00
Brad Fitzpatrick
766ea96adf cmd/tailscaled: enable hybrid netstack mode on openbsd too
Apparently OpenBSD can forward packets with manual configuration,

https://github.com/tailscale/tailscale/issues/2498#issuecomment-1114216999

But this makes it work by default. People doing things by hand can
set TS_DEBUG_WRAP_NETSTACK=0 in the environment.

Change-Id: Iee5f32252f83af2baa0ebbe3f20ce9fec5f29e96
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-15 14:48:15 -07:00
Juan Font
ffc67806ef tailcfg: bump capver for clients to talk Noise over any HTTPS port [capver 39]
Signed-off-by: Juan Font Alonso <juanfontalonso@gmail.com>
2022-08-15 12:18:14 -07:00
Joe Tsai
32a1a3d1c0 util/deephash: avoid variadic argument for Update (#5372)
Hashing []any is slow since hashing of interfaces is slow.
Hashing of interfaces is slow since we pessimistically assume
that cycles can occur through them and start cycle tracking.

Drop the variadic signature of Update and fix callers to pass in
an anonymous struct so that we are hashing concrete types
near the root of the value tree.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-15 11:22:28 -07:00
Denton Gentry
1c0286e98a scripts/installer.sh: add -y for unattended install
Fixes https://github.com/tailscale/tailscale/issues/5377

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-08-15 10:28:55 -07:00
Kris Brandow
8f38afbf8e net/stun: convert to use net/netip.AddrPort
Convert ParseResponse and Response to use netip.AddrPort instead of
net.IP and separate port.

Fixes #5281

Signed-off-by: Kris Brandow <kris.brandow@gmail.com>
2022-08-15 12:46:01 -04:00
Maisem Ali
c3270af52b Makefile: add target for wasm and make it part of check
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-15 09:43:35 -07:00
Tom DNetto
06eac9bbff tka: Use strict decoding settings, implement Unserialize()
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-15 09:20:53 -07:00
Maisem Ali
dbcc34981a cmd/tailscale/cli: fix build break
Accidental break from 64d482ff48.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-15 07:31:03 -07:00
Maisem Ali
d4916a8be3 .github/workflows: cancel previous CI runs on PR update
Currently scheduled runs are not canceled when we update PRs, those
results are unused and the resources are just wasted. Instead of doing
that, this PR makes it so that when a PR is updated (force-pushed or new
commit added) the previous runs are canceled.

https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-using-a-fallback-value

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-15 07:27:24 -07:00
Juan Font Alonso
64d482ff48 Allow any port for HTTPS when using Noise over TLS
Signed-off-by: Juan Font Alonso <juanfontalonso@gmail.com>
2022-08-15 06:43:43 -07:00
Maisem Ali
25865f81ee net/dns: disable NetBIOS on Tailscale interfaces
Like LLMNR, NetBIOS also adds resolution delays and we don't support it
anyway so just disable it on the interface.

Updates #1659

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-14 22:55:08 -07:00
Maisem Ali
545639ee44 util/winutil: consolidate interface specific registry keys
Code movement to allow reuse in a follow up PR.

Updates #1659

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-14 22:34:59 -07:00
Maisem Ali
23f37b05a3 atomicfile: update docs to clarify behavior of argument
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-13 21:31:40 -07:00
Maisem Ali
1cff719015 net/dns: [win] respond with SERVFAIL queries when no resolvers
Currently we forward unmatched queries to the default resolver on
Windows. This results in duplicate queries being issued to the same
resolver which is just wasted.

Updates #1659

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-12 17:09:30 -07:00
Joe Tsai
548fa63e49 util/deephash: use binary encoding of time.Time (#5352)
Formatting a time.Time as RFC3339 is slow.
See https://go.dev/issue/54093

Now that we have efficient hashing of fixed-width integers,
just hash the time.Time as a binary value.

Performance:

	Hash-24                19.0µs ± 1%    18.6µs ± 1%   -2.03%  (p=0.000 n=10+9)
	TailcfgNode-24         1.79µs ± 1%    1.40µs ± 1%  -21.74%  (p=0.000 n=10+9)

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-12 14:42:51 -07:00
Brad Fitzpatrick
0476c8ebc6 .github/workflows: delete flaky windows-race.yml
It flakes more often than it runs. It provides no value and builds
failure blindness, making people get used to submitting on red.

Bye.

Change-Id: If5491c70737b4c9851c103733b1855af2a90a9e9
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-12 13:20:00 -07:00
Joe Tsai
1f7479466e util/deephash: use sha256x (#5339)
Switch deephash to use sha256x.Hash.

We add sha256x.HashString to efficiently hash a string.
It uses unsafe under the hood to convert a string to a []byte.
We also modify sha256x.Hash to export the underlying hash.Hash
for testing purposes so that we can intercept all hash.Hash calls.

Performance:

	name                 old time/op    new time/op    delta
	Hash-24                19.8µs ± 1%    19.2µs ± 1%  -3.01%  (p=0.000 n=10+10)
	HashPacketFilter-24    2.61µs ± 0%    2.53µs ± 1%  -3.01%  (p=0.000 n=8+10)
	HashMapAcyclic-24      31.3µs ± 1%    29.8µs ± 0%  -4.80%  (p=0.000 n=10+9)
	TailcfgNode-24         1.83µs ± 1%    1.82µs ± 2%    ~     (p=0.305 n=10+10)
	HashArray-24            344ns ± 2%     323ns ± 1%  -6.02%  (p=0.000 n=9+10)

The performance gains is not as dramatic as sha256x over sha256 due to:
1. most of the hashing already occurring through the direct memory hashing logic, and
2. what does not go through direct memory hashing is slowed down by reflect.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-11 17:44:09 -07:00
Andrew Dunham
d942a2ff56 net/dnscache: try IPv6 addresses first (#5349)
Signed-off-by: Andrew Dunham <andrew@tailscale.com>

Signed-off-by: Andrew Dunham <andrew@tailscale.com>
2022-08-11 19:00:39 -04:00
Brad Fitzpatrick
90555c5cb2 tailcfg, control/controlclient: add PingRequest.URLIsNoise [capver 38]
Change-Id: I19bb63b6d99e96b2f9fd2c440afcc31d38137ded
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-11 09:41:06 -07:00
Brad Fitzpatrick
b33c337baa tailcfg: actually bump capver to 37, add test
I documented capver 37 in 4ee64681a but forgot to bump the actual
constant. I've done this previously too, so add a test to prevent
it from happening again.

Change-Id: I6f7659db1243d30672121a384beb386d9f9f5b98
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-11 09:28:54 -07:00
Joe Tsai
77a92f326d util/deephash: avoid using sync.Pool for reflect.MapIter (#5333)
In Go 1.19, the reflect.Value.MapRange method uses "function outlining"
so that the allocation of reflect.MapIter is inlinable by the caller.
If the iterator doesn't escape the caller, it can be stack allocated.
See https://go.dev/cl/400675

Performance:

	name               old time/op    new time/op    delta
	HashMapAcyclic-24    31.9µs ± 2%    32.1µs ± 1%   ~     (p=0.075 n=10+10)

	name               old alloc/op   new alloc/op   delta
	HashMapAcyclic-24     0.00B          0.00B        ~     (all equal)

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-11 00:33:40 -07:00
Denton Gentry
5d731ca13f installer.sh: add manjaro-arm & EndeavourOS.
Fixes https://github.com/tailscale/tailscale/issues/5192
Fixes https://github.com/tailscale/tailscale/issues/5284

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-08-10 20:10:48 -07:00
Joe Tsai
1c3c6b5382 util/sha256x: make Hash.Sum non-escaping (#5338)
Since Hash is a concrete type, we can make it such that
Sum never escapes the input.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-10 17:31:44 -07:00
Joe Tsai
76b0e578c5 util/sha256x: new package (#5337)
The hash.Hash provided by sha256.New is much more efficient
if we always provide it with data a multiple of the block size.
This avoids double-copying of data into the internal block
of sha256.digest.x. Effectively, we are managing a block ourselves
to ensure we only ever call hash.Hash.Write with full blocks.

Performance:

	name    old time/op    new time/op    delta
	Hash    33.5µs ± 1%    20.6µs ± 1%  -38.40%  (p=0.000 n=10+9)

The logic has gone through CPU-hours of fuzzing.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-10 15:49:36 -07:00
Brad Fitzpatrick
090033ede5 cmd/derper: fix data race & server panic in manual cert mode
(Thanks for debugging, Roland!)

Fixes #4082

Change-Id: I400a64001c3c58899bb570b759b08e745abc0be1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-10 15:14:09 -07:00
dependabot[bot]
9996d94b3c build(deps): bump github.com/u-root/u-root from 0.8.0 to 0.9.0
Bumps [github.com/u-root/u-root](https://github.com/u-root/u-root) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/u-root/u-root/releases)
- [Changelog](https://github.com/u-root/u-root/blob/main/RELEASES)
- [Commits](https://github.com/u-root/u-root/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: github.com/u-root/u-root
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-10 09:52:57 -07:00
Brad Fitzpatrick
c8dd39fcbc .github/workflows: use a matrix for staticcheck GOOS/GOARCHes
Change-Id: Ic4eda169729117afc4031bbefbe265195b38c19b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-10 09:48:44 -07:00
Joe Tsai
539c5e44c5 util/deephash: always keep values addressable (#5328)
The logic of deephash is both simpler and easier to reason about
if values are always addressable.

In Go, the composite kinds are slices, arrays, maps, structs,
interfaces, pointers, channels, and functions,
where we define "composite" as a Go value that encapsulates
some other Go value (e.g., a map is a collection of key-value entries).

In the cases of pointers and slices, the sub-values are always addressable.

In the cases of arrays and structs, the sub-values are always addressable
if and only if the parent value is addressable.

In the case of maps and interfaces, the sub-values are never addressable.
To make them addressable, we need to copy them onto the heap.

For the purposes of deephash, we do not care about channels and functions.

For all non-composite kinds (e.g., strings and ints), they are only addressable
if obtained from one of the composite kinds that produce addressable values
(i.e., pointers, slices, addressable arrays, and addressable structs).
A non-addressible, non-composite kind can be made addressable by
allocating it on the heap, obtaining a pointer to it, and dereferencing it.

Thus, if we can ensure that values are addressable at the entry points,
and shallow copy sub-values whenever we encounter an interface or map,
then we can ensure that all values are always addressable and
assume such property throughout all the logic.

Performance:

	name                 old time/op    new time/op    delta
	Hash-24                21.5µs ± 1%    19.7µs ± 1%  -8.29%  (p=0.000 n=9+9)
	HashPacketFilter-24    2.61µs ± 1%    2.62µs ± 0%  +0.29%  (p=0.037 n=10+9)
	HashMapAcyclic-24      30.8µs ± 1%    30.9µs ± 1%    ~     (p=0.400 n=9+10)
	TailcfgNode-24         1.84µs ± 1%    1.84µs ± 2%    ~     (p=0.928 n=10+10)
	HashArray-24            324ns ± 2%     332ns ± 2%  +2.45%  (p=0.000 n=10+10)

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-09 22:00:02 -07:00
Brad Fitzpatrick
4ee64681ad tailcfg, control/controlclient: make Debug settings sticky in a map session [capver 37]
Fixes #4843

Change-Id: I3accfd91be474ac745cb47f5d6e866c37d5c5d2d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-09 14:36:09 -07:00
Brad Fitzpatrick
8e821d7aa8 types/opt: support an explicit "unset" value for Bool
Updates #4843

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-09 13:34:56 -07:00
Maisem Ali
3bb57504af net/dns/resolver: add comments clarifying nil error returns
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-09 13:32:11 -07:00
Maisem Ali
4497bb0b81 net/dns/resolver: return SERVFAIL when no upstream resolvers set
Otherwise we just keep looping over the same thing again and again.

```
dns udp query: upstream nameservers not set
dns udp query: upstream nameservers not set
dns udp query: upstream nameservers not set
```

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-09 13:28:03 -07:00
Charlotte Brandhorst-Satzkorn
0f12ead567 tsconnect: pass in authkey in dev mode (#5320)
This change allows for an auth key to be specified as a url query param
for use in development mode. If an auth key is specified and valid, it
will authorize the client for use immediately.

Updates #5144

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2022-08-09 13:07:01 -07:00
Mihai Parparita
ab159f748b cmd/tsconnect: switch UI to Preact
Reduces the amount of boilerplate to render the UI and makes it easier to
respond to state changes (e.g. machine getting authorized, netmap changing,
etc.)

Preact adds ~13K to our bundle size (5K after Brotli) thus is a neglibible
size contribution. We mitigate the delay in rendering the UI by having a static
placeholder in the HTML.

Required bumping the esbuild version to pick up evanw/esbuild#2349, which
makes it easier to support Preact's JSX code generation.

Fixes #5137
Fixes #5273

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-08 21:54:06 -07:00
David Crawshaw
15b8665787 tka: stable text representation of AUMHash
This makes debugging easier, you can pass an AUMHash to a printf and get
a string that is easy to debug.

Also rearrange how directories/files work in the FS store: use the first
two characters of the string representation as the prefix directory, and
use the entire AUMHash string as the file name. This is again to aid
debugging: you can `ls` a directory and line up what prints out easily
with what you get from a printf in debug code.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2022-08-08 11:10:56 -07:00
Maisem Ali
40ec8617ac util/must: rename Do->Get, add Do
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-06 09:30:10 -07:00
Brad Fitzpatrick
ec9d13bce5 hostinfo, net/netcheck: use CutPrefix
Updates #5309

Change-Id: I37e594cfd245784bf810c493de68a66d3ff20677
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-05 15:17:44 -07:00
Brad Fitzpatrick
01e8ef7293 util/strs: add new package for string utility funcs
Updates #5309

Change-Id: I677cc6e01050b6e10d8d6907d961b11c7a787a05
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-05 15:06:08 -07:00
Joe Tsai
622d80c007 util/must: new package (#5307)
The Do function assists in calling functions that must succeed.
It only interacts well with functions that return (T, err).
Signatures with more return arguments are not supported.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-05 12:20:50 -07:00
Maisem Ali
486cc9393c ipn/ipnlocal: fix log about local IPs when using exit nodes
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-05 11:45:22 -07:00
David Crawshaw
93324cc7b3 cmd/derper: add depaware.txt
Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2022-08-05 11:38:10 -07:00
Brad Fitzpatrick
18109c63b0 net/socks5: use new Go 1.19 binary.AppendByteOrder.AppendUintX
Updates #4872

Change-Id: I43db63d2ba237324bc1cd87d4261197f14cb1088
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-05 08:30:48 -07:00
Joe Tsai
b1fff4499f tsnet: cleanup resources upon start failure (#5301)
In a partially initialized state, we should cleanup
all prior resources when an error occurs.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-04 16:20:48 -07:00
Andrew Dunham
f0d6f173c9 net/netcheck: try ICMP if UDP is blocked (#5056)
Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2022-08-04 17:10:13 -04:00
Joe Tsai
ddebd30917 tsnet: fix closing of filch buffer (#5299)
It should be safe to initialize multiple Server instances
without any resource leaks what-so-ever.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-04 13:22:56 -07:00
Tom DNetto
f50043f6cb tka,types/key: remove dependency for tailcfg & types/ packages on tka
Following the pattern elsewhere, we create a new tka-specific types package for the types
that need to couple between the serialized structure types, and tka.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-08-04 12:51:58 -07:00
Maisem Ali
a9f6cd41fd all: use syncs.AtomicValue
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-04 11:52:16 -07:00
Maisem Ali
b75f81ec00 syncs: add generic AtomicValue
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-04 11:52:16 -07:00
Charlotte Brandhorst-Satzkorn
5055e00cf1 tsconnect: add flag to specify control server (#5294)
To improve the local development experience, this change allows a
control url to be passed in with the `--dev-control=` flag.

If the flag is passed in when not specifying dev, an error is returned.

If no flag is passed, the default remains the Tailscale controlled
control server set by `ipn.DefaultControlURL`.

Co-authored-by: Maisem Ali <maisem@tailscale.com>
Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2022-08-04 10:37:19 -07:00
Mihai Parparita
f371a1afd9 cmd/tsconnect: make logtail uploading work
Initialize logtail and provide an uploader that works in the
browser (we make a no-cors cross-origin request to avoid having to
open up the logcatcher servers to CORS).

Fixes #5147

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-04 09:10:20 -07:00
Brad Fitzpatrick
4950fe60bd syncs, all: move to using Go's new atomic types instead of ours
Fixes #5185

Change-Id: I850dd532559af78c3895e2924f8237ccc328449d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-04 07:47:59 -07:00
Maisem Ali
9bb5a038e5 all: use atomic.Pointer
Also add some missing docs.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-08-03 21:42:52 -07:00
Brad Fitzpatrick
5381437664 logtail, net/portmapper, wgengine/magicsock: use fmt.Appendf
Fixes #5206

Change-Id: I490bb92e774ce7c044040537e2cd864fcf1dbe5a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-03 21:35:51 -07:00
326 changed files with 13850 additions and 4308 deletions

17
.github/licenses.tmpl vendored Normal file
View File

@@ -0,0 +1,17 @@
# Tailscale CLI and daemon dependencies
The following open source dependencies are used to build the [tailscale][] and
[tailscaled][] commands. These are primarily used on Linux and BSD variants as
well as an [option for macOS][].
[tailscale]: https://pkg.go.dev/tailscale.com/cmd/tailscale
[tailscaled]: https://pkg.go.dev/tailscale.com/cmd/tailscaled
[option for macOS]: https://tailscale.com/kb/1065/macos-variants/
## Go Packages
Some packages may only be included on certain architectures or operating systems.
{{ range . }}
- [{{.Name}}](https://pkg.go.dev/{{.Name}}) ([{{.LicenseName}}]({{.LicenseURL}}))
{{- end }}

View File

@@ -1,8 +1,15 @@
name: CIFuzz
on: [pull_request]
on:
push:
branches: [ main, release-branch/* ]
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
Fuzzing:
runs-on: ubuntu-latest
runs-on: github-4vcpu-ubuntu-2204
steps:
- name: Build Fuzzers
id: build
@@ -15,7 +22,7 @@ jobs:
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'tailscale'
fuzz-seconds: 300
fuzz-seconds: 900
dry-run: false
language: go
- name: Upload Crash

View File

@@ -20,10 +20,14 @@ on:
schedule:
- cron: '31 14 * * 5'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
runs-on: github-4vcpu-ubuntu-2204
permissions:
actions: read
contents: read

54
.github/workflows/cross-android.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Android-Cross
on:
push:
branches:
- main
pull_request:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: github-4vcpu-ubuntu-2204
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
id: go
- name: Android smoke build
# Super minimal Android build that doesn't even use CGO and doesn't build everything that's needed
# and is only arm64. But it's a smoke build: it's not meant to catch everything. But it'll catch
# some Android breakages early.
# TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482
env:
GOOS: android
GOARCH: arm64
run: go install ./net/netns ./ipn/ipnlocal ./wgengine/magicsock/ ./wgengine/ ./wgengine/router/ ./wgengine/netstack ./util/dnsname/ ./ipn/ ./net/interfaces ./wgengine/router/ ./tailcfg/ ./types/logger/ ./net/dns ./hostinfo ./version
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -8,23 +8,26 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
runs-on: github-4vcpu-ubuntu-2204
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version-file: go.mod
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: macOS build cmd
env:
GOOS: darwin

View File

@@ -8,23 +8,26 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
runs-on: github-4vcpu-ubuntu-2204
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version-file: go.mod
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: FreeBSD build cmd
env:
GOOS: freebsd

View File

@@ -8,23 +8,26 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
runs-on: github-4vcpu-ubuntu-2204
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version-file: go.mod
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: OpenBSD build cmd
env:
GOOS: openbsd

View File

@@ -8,23 +8,26 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
runs-on: github-4vcpu-ubuntu-2204
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version-file: go.mod
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Wasm client build
env:
GOOS: js
@@ -34,7 +37,9 @@ jobs:
- name: tsconnect static build
# Use our custom Go toolchain, we set build tags (to control binary size)
# that depend on it.
run: ./tool/go run ./cmd/tsconnect --fast-compression build
run: |
./tool/go run ./cmd/tsconnect --fast-compression build
./tool/go run ./cmd/tsconnect build-pkg
- uses: k0kubun/action-slack@v2.0.0
with:

View File

@@ -8,23 +8,26 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
runs-on: github-4vcpu-ubuntu-2204
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version-file: go.mod
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Windows build cmd
env:
GOOS: windows

View File

@@ -8,21 +8,25 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Check out code
uses: actions/checkout@v3
- name: depaware tailscaled
run: go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscaled
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: depaware tailscale
run: go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscale
- name: depaware
run: go run github.com/tailscale/depaware --check
tailscale.com/cmd/tailscaled
tailscale.com/cmd/tailscale
tailscale.com/cmd/derper

64
.github/workflows/go-licenses.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: go-licenses
on:
# run action when a change lands in the main branch which updates go.mod or
# our license template file. Also allow manual triggering.
push:
branches:
- main
paths:
- go.mod
- .github/licenses.tmpl
- .github/workflows/go-licenses.yml
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
tailscale:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: Install go-licenses
run: |
go install github.com/google/go-licenses@v1.2.2-0.20220825154955-5eedde1c6584
- name: Run go-licenses
env:
# include all build tags to include platform-specific dependencies
GOFLAGS: "-tags=android,cgo,darwin,freebsd,ios,js,linux,openbsd,wasm,windows"
run: |
[ -d licenses ] || mkdir licenses
go-licenses report tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled > licenses/tailscale.md --template .github/licenses.tmpl
- name: Get access token
uses: tibdex/github-app-token@f717b5ecd4534d3c4df4ce9b5c1c2214f0f7cd06 # v1.6.0
id: generate-token
with:
app_id: ${{ secrets.LICENSING_APP_ID }}
installation_id: ${{ secrets.LICENSING_APP_INSTALLATION_ID }}
private_key: ${{ secrets.LICENSING_APP_PRIVATE_KEY }}
- name: Send pull request
uses: peter-evans/create-pull-request@18f90432bedd2afd6a825469ffd38aa24712a91d #v4.1.1
with:
token: ${{ steps.generate-token.outputs.token }}
author: License Updater <noreply@tailscale.com>
committer: License Updater <noreply@tailscale.com>
branch: licenses/cli
commit-message: "licenses: update tailscale{,d} licenses"
title: "licenses: update tailscale{,d} licenses"
body: Triggered by ${{ github.repository }}@${{ github.sha }}
signoff: true
delete-branch: true
team-reviewers: opensource-license-reviewers

View File

@@ -9,21 +9,25 @@ on:
branches:
- "*"
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: check 'go generate' is clean
run: |
if [[ "${{github.ref}}" == release-branch/* ]]

35
.github/workflows/go_mod_tidy.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: go mod tidy
on:
push:
branches:
- main
pull_request:
branches:
- "*"
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: check 'go mod tidy' is clean
run: |
go mod tidy
echo
echo
git diff --name-only --exit-code || (echo "Please run 'go mod tidy'."; exit 1)

View File

@@ -8,18 +8,22 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Check out code
uses: actions/checkout@v3
go-version-file: go.mod
- name: Run license checker
run: ./scripts/check_license_headers.sh .

View File

@@ -8,23 +8,26 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
runs-on: github-4vcpu-ubuntu-2204
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version-file: go.mod
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Basic build
run: go build ./cmd/...

View File

@@ -8,23 +8,26 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
runs-on: github-4vcpu-ubuntu-2204
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version-file: go.mod
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Basic build
run: go build ./cmd/...
@@ -35,10 +38,6 @@ jobs:
- name: Get QEMU
run: |
# The qemu in Ubuntu 20.04 (Focal) is too old; we need 5.x something
# to run Go binaries. 5.2.0 (Debian bullseye) empirically works, and
# use this PPA which brings in a modern qemu.
sudo add-apt-repository -y ppa:jacob/virtualisation
sudo apt-get -y update
sudo apt-get -y install qemu-user

View File

@@ -8,23 +8,26 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
runs-on: github-4vcpu-ubuntu-2204
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version-file: go.mod
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Basic build
run: GOARCH=386 go build ./cmd/...

112
.github/workflows/static-analysis.yml vendored Normal file
View File

@@ -0,0 +1,112 @@
name: static-analysis
on:
push:
branches:
- main
pull_request:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
gofmt:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: Run gofmt (goimports)
run: go run golang.org/x/tools/cmd/goimports -d --format-only .
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'
vet:
runs-on: github-4vcpu-ubuntu-2204
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Check out code
uses: actions/checkout@v3
- name: Run go vet
run: go vet ./...
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'
staticcheck:
runs-on: github-4vcpu-ubuntu-2204
strategy:
matrix:
goos: [linux, windows, darwin]
goarch: [amd64]
include:
- goos: windows
goarch: 386
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Check out code
uses: actions/checkout@v3
- name: Install staticcheck
run: "GOBIN=~/.local/bin go install honnef.co/go/tools/cmd/staticcheck"
- name: Print staticcheck version
run: "staticcheck -version"
- name: "Run staticcheck (${{ matrix.goos }}/${{ matrix.goarch }})"
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -1,73 +0,0 @@
name: staticcheck
on:
push:
branches:
- main
pull_request:
branches:
- '*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Check out code
uses: actions/checkout@v3
- name: Run gofmt (goimports)
run: go run golang.org/x/tools/cmd/goimports -d --format-only .
- name: Run go vet
run: go vet ./...
- name: Install staticcheck
run: "GOBIN=~/.local/bin go install honnef.co/go/tools/cmd/staticcheck"
- name: Print staticcheck version
run: "staticcheck -version"
- name: Run staticcheck (linux/amd64)
env:
GOOS: linux
GOARCH: amd64
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
- name: Run staticcheck (darwin/amd64)
env:
GOOS: darwin
GOARCH: amd64
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
- name: Run staticcheck (windows/amd64)
env:
GOOS: windows
GOARCH: amd64
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
- name: Run staticcheck (windows/386)
env:
GOOS: windows
GOARCH: "386"
run: "staticcheck -- $(go list ./... | grep -v tempfork)"
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -0,0 +1,30 @@
name: "@tailscale/connect npm publish"
on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up node
uses: actions/setup-node@v3
with:
node-version: "16.x"
registry-url: "https://registry.npmjs.org"
- name: Build package
# Build with build_dist.sh to ensure that version information is embedded.
# GOROOT is specified so that the Go/Wasm that is trigged by build-pk
# also picks up our custom Go toolchain.
run: |
./build_dist.sh tailscale.com/cmd/tsconnect
GOROOT="${HOME}/.cache/tailscale-go" ./tsconnect build-pkg
- name: Publish
env:
NODE_AUTH_TOKEN: ${{ secrets.TSCONNECT_NPM_PUBLISH_AUTH_TOKEN }}
run: ./tool/yarn --cwd ./cmd/tsconnect/pkg publish --access public

View File

@@ -5,6 +5,10 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
ubuntu2004-LTS-cloud-base:
runs-on: [ self-hosted, linux, vm ]
@@ -15,13 +19,13 @@ jobs:
- name: Set GOPATH
run: echo "GOPATH=$HOME/go" >> $GITHUB_ENV
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Checkout Code
uses: actions/checkout@v3
go-version-file: go.mod
- name: Run VM tests
run: go test ./tstest/integration/vms -v -no-s3 -run-vm-tests -run=TestRunUbuntu2004

View File

@@ -1,77 +0,0 @@
name: Windows race
on:
push:
branches:
- main
pull_request:
branches:
- '*'
jobs:
test:
runs-on: windows-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: 1.19.x
- name: Checkout code
uses: actions/checkout@v3
- name: Restore Cache
uses: actions/cache@v3
with:
# Note: unlike some other setups, this is only grabbing the mod download
# cache, rather than the whole mod directory, as the download cache
# contains zips that can be unpacked in parallel faster than they can be
# fetched and extracted by tar
path: |
~/go/pkg/mod/cache
~\AppData\Local\go-build
# The -2- here should be incremented when the scheme of data to be
# cached changes (e.g. path above changes).
# The -race- here ensures that non-race builds and race builds do not
# overwrite each others cache, as while they share some files, they
# differ in most by volume (build cache).
# TODO(raggi): add a go version here.
key: ${{ runner.os }}-go-2-race-${{ hashFiles('**/go.sum') }}
- name: Print toolchain details
run: gcc -v
# There is currently an issue in the race detector in Go on Windows when
# used with a newer version of GCC.
# See https://github.com/tailscale/tailscale/issues/4926.
- name: Downgrade MinGW
shell: bash
run: |
choco install mingw --version 10.2.0 --allow-downgrade
- name: Test with -race flag
# Don't use -bench=. -benchtime=1x.
# Somewhere in the layers (powershell?)
# the equals signs cause great confusion.
run: go test -race -bench . -benchtime 1x ./...
- uses: k0kubun/action-slack@v2.0.0
with:
payload: |
{
"attachments": [{
"text": "${{ job.status }}: ${{ github.workflow }} <https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks|${{ env.COMMIT_DATE }} #${{ env.COMMIT_NUMBER_OF_DAY }}> " +
"(<https://github.com/${{ github.repository }}/commit/${{ github.sha }}|" + "${{ github.sha }}".substring(0, 10) + ">) " +
"of ${{ github.repository }}@" + "${{ github.ref }}".split('/').reverse()[0] + " by ${{ github.event.head_commit.committer.name }}",
"color": "danger"
}]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: failure() && github.event_name == 'push'

View File

@@ -8,21 +8,24 @@ on:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
test:
runs-on: windows-latest
runs-on: windows-8vcpu
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: 1.19.x
- name: Checkout code
uses: actions/checkout@v3
go-version-file: go.mod
- name: Restore Cache
uses: actions/cache@v3

View File

@@ -32,7 +32,7 @@
# $ docker exec tailscaled tailscale status
FROM golang:1.18-alpine AS build-env
FROM golang:1.19-alpine AS build-env
WORKDIR /go/src/tailscale

View File

@@ -12,12 +12,16 @@ tidy:
./tool/go mod tidy
updatedeps:
./tool/go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscaled
./tool/go run github.com/tailscale/depaware --update tailscale.com/cmd/tailscale
./tool/go run github.com/tailscale/depaware --update \
tailscale.com/cmd/tailscaled \
tailscale.com/cmd/tailscale \
tailscale.com/cmd/derper
depaware:
./tool/go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscaled
./tool/go run github.com/tailscale/depaware --check tailscale.com/cmd/tailscale
./tool/go run github.com/tailscale/depaware --check \
tailscale.com/cmd/tailscaled \
tailscale.com/cmd/tailscale \
tailscale.com/cmd/derper
buildwindows:
GOOS=windows GOARCH=amd64 ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
@@ -28,10 +32,13 @@ build386:
buildlinuxarm:
GOOS=linux GOARCH=arm ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
buildwasm:
GOOS=js GOARCH=wasm ./tool/go install ./cmd/tsconnect/wasm ./cmd/tailscale/cli
buildmultiarchimage:
./build_docker.sh
check: staticcheck vet depaware buildwindows build386 buildlinuxarm
check: staticcheck vet depaware buildwindows build386 buildlinuxarm buildwasm
staticcheck:
./tool/go run honnef.co/go/tools/cmd/staticcheck -- $$(./tool/go list ./... | grep -v tempfork)

View File

@@ -1 +1 @@
1.29.0
1.31.0

View File

@@ -9,16 +9,15 @@
package atomicfile // import "tailscale.com/atomicfile"
import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
)
// WriteFile writes data to filename+some suffix, then renames it
// into filename.
// into filename. The perm argument is ignored on Windows.
func WriteFile(filename string, data []byte, perm os.FileMode) (err error) {
f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".tmp")
f, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename)+".tmp")
if err != nil {
return err
}

View File

@@ -11,15 +11,31 @@ import (
"fmt"
"net"
"strings"
"time"
)
const (
// Maximum amount of time we should wait when reading a response from BIRD.
responseTimeout = 10 * time.Second
)
// New creates a BIRDClient.
func New(socket string) (*BIRDClient, error) {
return newWithTimeout(socket, responseTimeout)
}
func newWithTimeout(socket string, timeout time.Duration) (*BIRDClient, error) {
conn, err := net.Dial("unix", socket)
if err != nil {
return nil, fmt.Errorf("failed to connect to BIRD: %w", err)
}
b := &BIRDClient{socket: socket, conn: conn, scanner: bufio.NewScanner(conn)}
b := &BIRDClient{
socket: socket,
conn: conn,
scanner: bufio.NewScanner(conn),
timeNow: time.Now,
timeout: timeout,
}
// Read and discard the first line as that is the welcome message.
if _, err := b.readResponse(); err != nil {
return nil, err
@@ -32,6 +48,8 @@ type BIRDClient struct {
socket string
conn net.Conn
scanner *bufio.Scanner
timeNow func() time.Time
timeout time.Duration
}
// Close closes the underlying connection to BIRD.
@@ -81,10 +99,15 @@ func (b *BIRDClient) EnableProtocol(protocol string) error {
// 1 means table entry, 8 runtime error and 9 syntax error.
func (b *BIRDClient) exec(cmd string, args ...any) (string, error) {
if err := b.conn.SetWriteDeadline(b.timeNow().Add(b.timeout)); err != nil {
return "", err
}
if _, err := fmt.Fprintf(b.conn, cmd, args...); err != nil {
return "", err
}
fmt.Fprintln(b.conn)
if _, err := fmt.Fprintln(b.conn); err != nil {
return "", err
}
return b.readResponse()
}
@@ -105,14 +128,20 @@ func hasResponseCode(s []byte) bool {
}
func (b *BIRDClient) readResponse() (string, error) {
// Set the read timeout before we start reading anything.
if err := b.conn.SetReadDeadline(b.timeNow().Add(b.timeout)); err != nil {
return "", err
}
var resp strings.Builder
var done bool
for !done {
if !b.scanner.Scan() {
return "", fmt.Errorf("reading response from bird failed: %q", resp.String())
}
if err := b.scanner.Err(); err != nil {
return "", err
if err := b.scanner.Err(); err != nil {
return "", err
}
return "", fmt.Errorf("reading response from bird failed (EOF): %q", resp.String())
}
out := b.scanner.Bytes()
if _, err := resp.Write(out); err != nil {

View File

@@ -8,9 +8,12 @@ import (
"errors"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
)
type fakeBIRD struct {
@@ -109,3 +112,82 @@ func TestChirp(t *testing.T) {
t.Fatalf("disabling %q succeded", "rando")
}
}
type hangingListener struct {
net.Listener
t *testing.T
done chan struct{}
wg sync.WaitGroup
sock string
}
func newHangingListener(t *testing.T) *hangingListener {
sock := filepath.Join(t.TempDir(), "sock")
l, err := net.Listen("unix", sock)
if err != nil {
t.Fatal(err)
}
return &hangingListener{
Listener: l,
t: t,
done: make(chan struct{}),
sock: sock,
}
}
func (hl *hangingListener) Stop() {
hl.Close()
close(hl.done)
hl.wg.Wait()
}
func (hl *hangingListener) listen() error {
for {
c, err := hl.Accept()
if err != nil {
if errors.Is(err, net.ErrClosed) {
return nil
}
return err
}
hl.wg.Add(1)
go hl.handle(c)
}
}
func (hl *hangingListener) handle(c net.Conn) {
defer hl.wg.Done()
// Write our fake first line of response so that we get into the read loop
fmt.Fprintln(c, "0001 BIRD 2.0.8 ready.")
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
hl.t.Logf("connection still hanging")
case <-hl.done:
return
}
}
}
func TestChirpTimeout(t *testing.T) {
fb := newHangingListener(t)
defer fb.Stop()
go fb.listen()
c, err := newWithTimeout(fb.sock, 500*time.Millisecond)
if err != nil {
t.Fatal(err)
}
err = c.EnableProtocol("tailscale")
if err == nil {
t.Fatal("got err=nil, want timeout")
}
if !os.IsTimeout(err) {
t.Fatalf("got err=%v, want os.IsTimeout(err)=true", err)
}
}

View File

@@ -15,7 +15,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httptrace"
@@ -36,6 +35,7 @@ import (
"tailscale.com/paths"
"tailscale.com/safesocket"
"tailscale.com/tailcfg"
"tailscale.com/tka"
)
// defaultLocalClient is the default LocalClient when using the legacy
@@ -136,7 +136,7 @@ func (lc *LocalClient) doLocalRequestNiceError(req *http.Request) (*http.Respons
onVersionMismatch(ipn.IPCVersion(), server)
}
if res.StatusCode == 403 {
all, _ := ioutil.ReadAll(res.Body)
all, _ := io.ReadAll(res.Body)
return nil, &AccessDeniedError{errors.New(errorMessageFromBody(all))}
}
return res, nil
@@ -206,7 +206,7 @@ func (lc *LocalClient) send(ctx context.Context, method, path string, wantStatus
return nil, err
}
defer res.Body.Close()
slurp, err := ioutil.ReadAll(res.Body)
slurp, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
@@ -364,7 +364,7 @@ func (lc *LocalClient) GetWaitingFile(ctx context.Context, baseName string) (rc
return nil, 0, fmt.Errorf("unexpected chunking")
}
if res.StatusCode != 200 {
body, _ := ioutil.ReadAll(res.Body)
body, _ := io.ReadAll(res.Body)
res.Body.Close()
return nil, 0, fmt.Errorf("HTTP %s: %s", res.Status, body)
}
@@ -680,6 +680,42 @@ func (lc *LocalClient) Ping(ctx context.Context, ip netip.Addr, pingtype tailcfg
return pr, nil
}
// NetworkLockStatus fetches information about the tailnet key authority, if one is configured.
func (lc *LocalClient) NetworkLockStatus(ctx context.Context) (*ipnstate.NetworkLockStatus, error) {
body, err := lc.send(ctx, "GET", "/localapi/v0/tka/status", 200, nil)
if err != nil {
return nil, fmt.Errorf("error: %w", err)
}
pr := new(ipnstate.NetworkLockStatus)
if err := json.Unmarshal(body, pr); err != nil {
return nil, err
}
return pr, nil
}
// NetworkLockInit initializes the tailnet key authority.
func (lc *LocalClient) NetworkLockInit(ctx context.Context, keys []tka.Key) (*ipnstate.NetworkLockStatus, error) {
var b bytes.Buffer
type initRequest struct {
Keys []tka.Key
}
if err := json.NewEncoder(&b).Encode(initRequest{Keys: keys}); err != nil {
return nil, err
}
body, err := lc.send(ctx, "POST", "/localapi/v0/tka/init", 200, &b)
if err != nil {
return nil, fmt.Errorf("error: %w", err)
}
pr := new(ipnstate.NetworkLockStatus)
if err := json.Unmarshal(body, pr); err != nil {
return nil, err
}
return pr, nil
}
// tailscaledConnectHint gives a little thing about why tailscaled (or
// platform equivalent) is not answering localapi connections.
//

View File

@@ -17,7 +17,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
)
@@ -131,7 +130,7 @@ func (c *Client) sendRequest(req *http.Request) ([]byte, *http.Response, error)
// Read response. Limit the response to 10MB.
body := io.LimitReader(resp.Body, maxReadSize+1)
b, err := ioutil.ReadAll(body)
b, err := io.ReadAll(body)
if len(b) > maxReadSize {
err = errors.New("API response too large")
}

View File

@@ -12,20 +12,36 @@ import (
"net"
"net/http"
"strings"
"sync/atomic"
"time"
"tailscale.com/syncs"
)
var dnsCache atomic.Value // of []byte
const refreshTimeout = time.Minute
var bootstrapDNSRequests = expvar.NewInt("counter_bootstrap_dns_requests")
type dnsEntryMap map[string][]net.IP
var (
dnsCache syncs.AtomicValue[dnsEntryMap]
dnsCacheBytes syncs.AtomicValue[[]byte] // of JSON
unpublishedDNSCache syncs.AtomicValue[dnsEntryMap]
)
var (
bootstrapDNSRequests = expvar.NewInt("counter_bootstrap_dns_requests")
publishedDNSHits = expvar.NewInt("counter_bootstrap_dns_published_hits")
publishedDNSMisses = expvar.NewInt("counter_bootstrap_dns_published_misses")
unpublishedDNSHits = expvar.NewInt("counter_bootstrap_dns_unpublished_hits")
unpublishedDNSMisses = expvar.NewInt("counter_bootstrap_dns_unpublished_misses")
)
func refreshBootstrapDNSLoop() {
if *bootstrapDNS == "" {
if *bootstrapDNS == "" && *unpublishedDNS == "" {
return
}
for {
refreshBootstrapDNS()
refreshUnpublishedDNS()
time.Sleep(10 * time.Minute)
}
}
@@ -34,10 +50,34 @@ func refreshBootstrapDNS() {
if *bootstrapDNS == "" {
return
}
dnsEntries := make(map[string][]net.IP)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
ctx, cancel := context.WithTimeout(context.Background(), refreshTimeout)
defer cancel()
names := strings.Split(*bootstrapDNS, ",")
dnsEntries := resolveList(ctx, strings.Split(*bootstrapDNS, ","))
j, err := json.MarshalIndent(dnsEntries, "", "\t")
if err != nil {
// leave the old values in place
return
}
dnsCache.Store(dnsEntries)
dnsCacheBytes.Store(j)
}
func refreshUnpublishedDNS() {
if *unpublishedDNS == "" {
return
}
ctx, cancel := context.WithTimeout(context.Background(), refreshTimeout)
defer cancel()
dnsEntries := resolveList(ctx, strings.Split(*unpublishedDNS, ","))
unpublishedDNSCache.Store(dnsEntries)
}
func resolveList(ctx context.Context, names []string) dnsEntryMap {
dnsEntries := make(dnsEntryMap)
var r net.Resolver
for _, name := range names {
addrs, err := r.LookupIP(ctx, "ip", name)
@@ -47,21 +87,47 @@ func refreshBootstrapDNS() {
}
dnsEntries[name] = addrs
}
j, err := json.MarshalIndent(dnsEntries, "", "\t")
if err != nil {
// leave the old values in place
return
}
dnsCache.Store(j)
return dnsEntries
}
func handleBootstrapDNS(w http.ResponseWriter, r *http.Request) {
bootstrapDNSRequests.Add(1)
w.Header().Set("Content-Type", "application/json")
j, _ := dnsCache.Load().([]byte)
// Bootstrap DNS requests occur cross-regions,
// and are randomized per request,
// so keeping a connection open is pointlessly expensive.
// Bootstrap DNS requests occur cross-regions, and are randomized per
// request, so keeping a connection open is pointlessly expensive.
w.Header().Set("Connection", "close")
// Try answering a query from our hidden map first
if q := r.URL.Query().Get("q"); q != "" {
if ips, ok := unpublishedDNSCache.Load()[q]; ok && len(ips) > 0 {
unpublishedDNSHits.Add(1)
// Only return the specific query, not everything.
m := dnsEntryMap{q: ips}
j, err := json.MarshalIndent(m, "", "\t")
if err == nil {
w.Write(j)
return
}
}
// If we have a "q" query for a name in the published cache
// list, then track whether that's a hit/miss.
if m, ok := dnsCache.Load()[q]; ok {
if len(m) > 0 {
publishedDNSHits.Add(1)
} else {
publishedDNSMisses.Add(1)
}
} else {
// If it wasn't in either cache, treat this as a query
// for the unpublished cache, and thus a cache miss.
unpublishedDNSMisses.Add(1)
}
}
// Fall back to returning the public set of cached DNS names
j := dnsCacheBytes.Load()
w.Write(j)
}

View File

@@ -5,7 +5,12 @@
package main
import (
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
)
@@ -17,11 +22,12 @@ func BenchmarkHandleBootstrapDNS(b *testing.B) {
}()
refreshBootstrapDNS()
w := new(bitbucketResponseWriter)
req, _ := http.NewRequest("GET", "https://localhost/bootstrap-dns?q="+url.QueryEscape("log.tailscale.io"), nil)
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(b *testing.PB) {
for b.Next() {
handleBootstrapDNS(w, nil)
handleBootstrapDNS(w, req)
}
})
}
@@ -33,3 +39,116 @@ func (b *bitbucketResponseWriter) Header() http.Header { return make(http.Header
func (b *bitbucketResponseWriter) Write(p []byte) (int, error) { return len(p), nil }
func (b *bitbucketResponseWriter) WriteHeader(statusCode int) {}
func getBootstrapDNS(t *testing.T, q string) dnsEntryMap {
t.Helper()
req, _ := http.NewRequest("GET", "https://localhost/bootstrap-dns?q="+url.QueryEscape(q), nil)
w := httptest.NewRecorder()
handleBootstrapDNS(w, req)
res := w.Result()
if res.StatusCode != 200 {
t.Fatalf("got status=%d; want %d", res.StatusCode, 200)
}
var ips dnsEntryMap
if err := json.NewDecoder(res.Body).Decode(&ips); err != nil {
t.Fatalf("error decoding response body: %v", err)
}
return ips
}
func TestUnpublishedDNS(t *testing.T) {
const published = "login.tailscale.com"
const unpublished = "log.tailscale.io"
prev1, prev2 := *bootstrapDNS, *unpublishedDNS
*bootstrapDNS = published
*unpublishedDNS = unpublished
t.Cleanup(func() {
*bootstrapDNS = prev1
*unpublishedDNS = prev2
})
refreshBootstrapDNS()
refreshUnpublishedDNS()
hasResponse := func(q string) bool {
_, found := getBootstrapDNS(t, q)[q]
return found
}
if !hasResponse(published) {
t.Errorf("expected response for: %s", published)
}
if !hasResponse(unpublished) {
t.Errorf("expected response for: %s", unpublished)
}
// Verify that querying for a random query or a real query does not
// leak our unpublished domain
m1 := getBootstrapDNS(t, published)
if _, found := m1[unpublished]; found {
t.Errorf("found unpublished domain %s: %+v", unpublished, m1)
}
m2 := getBootstrapDNS(t, "random.example.com")
if _, found := m2[unpublished]; found {
t.Errorf("found unpublished domain %s: %+v", unpublished, m2)
}
}
func resetMetrics() {
publishedDNSHits.Set(0)
publishedDNSMisses.Set(0)
unpublishedDNSHits.Set(0)
unpublishedDNSMisses.Set(0)
}
// Verify that we don't count an empty list in the unpublishedDNSCache as a
// cache hit in our metrics.
func TestUnpublishedDNSEmptyList(t *testing.T) {
pub := dnsEntryMap{
"tailscale.com": {net.IPv4(10, 10, 10, 10)},
}
dnsCache.Store(pub)
dnsCacheBytes.Store([]byte(`{"tailscale.com":["10.10.10.10"]}`))
unpublishedDNSCache.Store(dnsEntryMap{
"log.tailscale.io": {},
"controlplane.tailscale.com": {net.IPv4(1, 2, 3, 4)},
})
t.Run("CacheMiss", func(t *testing.T) {
// One domain in map but empty, one not in map at all
for _, q := range []string{"log.tailscale.io", "login.tailscale.com"} {
resetMetrics()
ips := getBootstrapDNS(t, q)
// Expected our public map to be returned on a cache miss
if !reflect.DeepEqual(ips, pub) {
t.Errorf("got ips=%+v; want %+v", ips, pub)
}
if v := unpublishedDNSHits.Value(); v != 0 {
t.Errorf("got hits=%d; want 0", v)
}
if v := unpublishedDNSMisses.Value(); v != 1 {
t.Errorf("got misses=%d; want 1", v)
}
}
})
// Verify that we do get a valid response and metric.
t.Run("CacheHit", func(t *testing.T) {
resetMetrics()
ips := getBootstrapDNS(t, "controlplane.tailscale.com")
want := dnsEntryMap{"controlplane.tailscale.com": {net.IPv4(1, 2, 3, 4)}}
if !reflect.DeepEqual(ips, want) {
t.Errorf("got ips=%+v; want %+v", ips, want)
}
if v := unpublishedDNSHits.Value(); v != 1 {
t.Errorf("got hits=%d; want 1", v)
}
if v := unpublishedDNSMisses.Value(); v != 0 {
t.Errorf("got misses=%d; want 0", v)
}
})
}

View File

@@ -20,6 +20,11 @@ var unsafeHostnameCharacters = regexp.MustCompile(`[^a-zA-Z0-9-\.]`)
type certProvider interface {
// TLSConfig creates a new TLS config suitable for net/http.Server servers.
//
// The returned Config must have a GetCertificate function set and that
// function must return a unique *tls.Certificate for each call. The
// returned *tls.Certificate will be mutated by the caller to append to the
// (*tls.Certificate).Certificate field.
TLSConfig() *tls.Config
// HTTPHandler handle ACME related request, if any.
HTTPHandler(fallback http.Handler) http.Handler
@@ -87,7 +92,13 @@ func (m *manualCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certif
if hi.ServerName != m.hostname {
return nil, fmt.Errorf("cert mismatch with hostname: %q", hi.ServerName)
}
return m.cert, nil
// Return a shallow copy of the cert so the caller can append to its
// Certificate field.
certCopy := new(tls.Certificate)
*certCopy = *m.cert
certCopy.Certificate = certCopy.Certificate[:len(certCopy.Certificate):len(certCopy.Certificate)]
return certCopy, nil
}
func (m *manualCertManager) HTTPHandler(fallback http.Handler) http.Handler {

197
cmd/derper/depaware.txt Normal file
View File

@@ -0,0 +1,197 @@
tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depaware)
filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus
filippo.io/edwards25519/field from filippo.io/edwards25519
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
github.com/fxamacker/cbor/v2 from tailscale.com/tka
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/hdevalence/ed25519consensus from tailscale.com/tka
L github.com/josharian/native from github.com/mdlayher/netlink+
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress/flate from nhooyr.io/websocket
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/client/tailscale+
go4.org/netipx from tailscale.com/wgengine/filter
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
nhooyr.io/websocket from tailscale.com/cmd/derper+
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
tailscale.com from tailscale.com/version
tailscale.com/atomicfile from tailscale.com/cmd/derper+
tailscale.com/client/tailscale from tailscale.com/derp
tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale
tailscale.com/derp from tailscale.com/cmd/derper+
tailscale.com/derp/derphttp from tailscale.com/cmd/derper
tailscale.com/disco from tailscale.com/derp
tailscale.com/envknob from tailscale.com/derp+
tailscale.com/hostinfo from tailscale.com/net/interfaces+
tailscale.com/ipn from tailscale.com/client/tailscale
tailscale.com/ipn/ipnstate from tailscale.com/client/tailscale+
💣 tailscale.com/metrics from tailscale.com/cmd/derper+
tailscale.com/net/dnscache from tailscale.com/derp/derphttp
tailscale.com/net/flowtrack from tailscale.com/net/packet+
💣 tailscale.com/net/interfaces from tailscale.com/net/netns+
tailscale.com/net/netaddr from tailscale.com/ipn+
tailscale.com/net/netknob from tailscale.com/net/netns
tailscale.com/net/netns from tailscale.com/derp/derphttp
tailscale.com/net/netutil from tailscale.com/client/tailscale
tailscale.com/net/packet from tailscale.com/wgengine/filter
tailscale.com/net/stun from tailscale.com/cmd/derper
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp
tailscale.com/net/tsaddr from tailscale.com/ipn+
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
tailscale.com/paths from tailscale.com/client/tailscale
tailscale.com/safesocket from tailscale.com/client/tailscale
tailscale.com/syncs from tailscale.com/cmd/derper+
tailscale.com/tailcfg from tailscale.com/client/tailscale+
tailscale.com/tka from tailscale.com/client/tailscale+
W tailscale.com/tsconst from tailscale.com/net/interfaces
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
tailscale.com/tsweb from tailscale.com/cmd/derper
tailscale.com/types/dnstype from tailscale.com/tailcfg
tailscale.com/types/empty from tailscale.com/ipn
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
tailscale.com/types/key from tailscale.com/cmd/derper+
tailscale.com/types/logger from tailscale.com/cmd/derper+
tailscale.com/types/netmap from tailscale.com/ipn
tailscale.com/types/opt from tailscale.com/client/tailscale+
tailscale.com/types/pad32 from tailscale.com/derp
tailscale.com/types/persist from tailscale.com/ipn
tailscale.com/types/preftype from tailscale.com/ipn
tailscale.com/types/structs from tailscale.com/ipn+
tailscale.com/types/tkatype from tailscale.com/types/key+
tailscale.com/types/views from tailscale.com/ipn/ipnstate+
tailscale.com/util/cloudenv from tailscale.com/hostinfo+
W tailscale.com/util/cmpver from tailscale.com/net/tshttpproxy
tailscale.com/util/dnsname from tailscale.com/hostinfo+
W tailscale.com/util/endian from tailscale.com/net/netns
tailscale.com/util/lineread from tailscale.com/hostinfo+
tailscale.com/util/singleflight from tailscale.com/net/dnscache
L tailscale.com/util/strs from tailscale.com/hostinfo
W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
tailscale.com/version from tailscale.com/derp+
tailscale.com/version/distro from tailscale.com/hostinfo+
tailscale.com/wgengine/filter from tailscale.com/types/netmap
golang.org/x/crypto/acme from golang.org/x/crypto/acme/autocert
golang.org/x/crypto/acme/autocert from tailscale.com/cmd/derper
golang.org/x/crypto/argon2 from tailscale.com/tka
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box+
golang.org/x/crypto/blake2s from tailscale.com/tka
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/chacha20poly1305 from crypto/tls
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+
golang.org/x/crypto/curve25519 from crypto/tls+
golang.org/x/crypto/hkdf from crypto/tls
golang.org/x/crypto/nacl/box from tailscale.com/types/key
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+
golang.org/x/net/http/httpguts from net/http
golang.org/x/net/http/httpproxy from net/http
golang.org/x/net/http2/hpack from net/http
golang.org/x/net/idna from golang.org/x/crypto/acme/autocert+
golang.org/x/net/proxy from tailscale.com/net/netns
D golang.org/x/net/route from net+
golang.org/x/sync/errgroup from github.com/mdlayher/socket+
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 golang.org/x/sys/windows/registry+
W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
golang.org/x/text/unicode/norm from golang.org/x/net/idna
golang.org/x/time/rate from tailscale.com/cmd/derper+
bufio from compress/flate+
bytes from bufio+
compress/flate from compress/gzip+
compress/gzip from internal/profile+
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 golang.org/x/crypto/acme+
crypto/x509 from crypto/tls+
crypto/x509/pkix from crypto/x509+
embed from crypto/internal/nistec+
encoding from encoding/json+
encoding/asn1 from crypto/x509+
encoding/base32 from tailscale.com/tka
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/cmd/derper+
flag from tailscale.com/cmd/derper
fmt from compress/flate+
hash from crypto+
hash/crc32 from compress/gzip+
hash/maphash from go4.org/mem
html from net/http/pprof+
io from bufio+
io/fs from crypto/x509+
io/ioutil from github.com/mitchellh/go-ps+
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 mime/multipart+
mime/multipart from net/http
mime/quotedprintable from mime/multipart
net from crypto/tls+
net/http from expvar+
net/http/httptrace from net/http+
net/http/internal from net/http
net/http/pprof from tailscale.com/tsweb
net/netip from go4.org/netipx+
net/textproto from golang.org/x/net/http/httpguts+
net/url from crypto/x509+
os from crypto/rand+
os/exec from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
path from golang.org/x/crypto/acme/autocert+
path/filepath from crypto/x509+
reflect from crypto/x509+
regexp from internal/profile+
regexp/syntax from regexp
runtime/debug from golang.org/x/crypto/acme+
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
time from compress/gzip+
unicode from bytes+
unicode/utf16 from crypto/x509+
unicode/utf8 from bufio+

View File

@@ -14,17 +14,18 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"math"
"net"
"net/http"
"net/netip"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"go4.org/mem"
"golang.org/x/time/rate"
"tailscale.com/atomicfile"
"tailscale.com/derp"
@@ -45,11 +46,13 @@ var (
certDir = flag.String("certdir", tsweb.DefaultCertDir("derper-certs"), "directory to store LetsEncrypt certs, if addr's port is :443")
hostname = flag.String("hostname", "derp.tailscale.com", "LetsEncrypt host name, if addr's port is :443")
runSTUN = flag.Bool("stun", true, "whether to run a STUN server. It will bind to the same IP (if any) as the --addr flag value.")
runDERP = flag.Bool("derp", true, "whether to run a DERP server. The only reason to set this false is if you're decommissioning a server but want to keep its bootstrap DNS functionality still running.")
meshPSKFile = flag.String("mesh-psk-file", defaultMeshPSKFile(), "if non-empty, path to file containing the mesh pre-shared key file. It should contain some hex string; whitespace is trimmed.")
meshWith = flag.String("mesh-with", "", "optional comma-separated list of hostnames to mesh with; the server's own hostname can be in the list")
bootstrapDNS = flag.String("bootstrap-dns-names", "", "optional comma-separated list of hostnames to make available at /bootstrap-dns")
verifyClients = flag.Bool("verify-clients", false, "verify clients to this DERP server through a local tailscaled instance.")
meshPSKFile = flag.String("mesh-psk-file", defaultMeshPSKFile(), "if non-empty, path to file containing the mesh pre-shared key file. It should contain some hex string; whitespace is trimmed.")
meshWith = flag.String("mesh-with", "", "optional comma-separated list of hostnames to mesh with; the server's own hostname can be in the list")
bootstrapDNS = flag.String("bootstrap-dns-names", "", "optional comma-separated list of hostnames to make available at /bootstrap-dns")
unpublishedDNS = flag.String("unpublished-bootstrap-dns-names", "", "optional comma-separated list of hostnames to make available at /bootstrap-dns and not publish in the list")
verifyClients = flag.Bool("verify-clients", false, "verify clients to this DERP server through a local tailscaled instance.")
acceptConnLimit = flag.Float64("accept-connection-limit", math.Inf(+1), "rate limit for accepting new connection")
acceptConnBurst = flag.Int("accept-connection-burst", math.MaxInt, "burst limit for accepting new connection")
@@ -95,7 +98,7 @@ func loadConfig() config {
}
log.Printf("no config path specified; using %s", *configPath)
}
b, err := ioutil.ReadFile(*configPath)
b, err := os.ReadFile(*configPath)
switch {
case errors.Is(err, os.ErrNotExist):
return writeNewConfig()
@@ -151,7 +154,7 @@ func main() {
s.SetVerifyClient(*verifyClients)
if *meshPSKFile != "" {
b, err := ioutil.ReadFile(*meshPSKFile)
b, err := os.ReadFile(*meshPSKFile)
if err != nil {
log.Fatal(err)
}
@@ -168,9 +171,15 @@ func main() {
expvar.Publish("derp", s.ExpVar())
mux := http.NewServeMux()
derpHandler := derphttp.Handler(s)
derpHandler = addWebSocketSupport(s, derpHandler)
mux.Handle("/derp", derpHandler)
if *runDERP {
derpHandler := derphttp.Handler(s)
derpHandler = addWebSocketSupport(s, derpHandler)
mux.Handle("/derp", derpHandler)
} else {
mux.Handle("/derp", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "derp server disabled", http.StatusNotFound)
}))
}
mux.HandleFunc("/derp/probe", probeHandler)
go refreshBootstrapDNSLoop()
mux.HandleFunc("/bootstrap-dns", handleBootstrapDNS)
@@ -186,10 +195,17 @@ func main() {
server.
</p>
`)
if !*runDERP {
io.WriteString(w, `<p>Status: <b>disabled</b></p>`)
}
if tsweb.AllowDebugAccess(r) {
io.WriteString(w, "<p>Debug info at <a href='/debug/'>/debug/</a>.</p>\n")
}
}))
mux.Handle("/robots.txt", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "User-agent: *\nDisallow: /\n")
}))
mux.Handle("/generate_204", http.HandlerFunc(serveNoContent))
debug := tsweb.Debugger(mux)
debug.KV("TLS hostname", *hostname)
debug.KV("Mesh key", s.HasMeshKey())
@@ -207,9 +223,11 @@ func main() {
go serveSTUN(listenHost, *stunPort)
}
quietLogger := log.New(logFilter{}, "", 0)
httpsrv := &http.Server{
Addr: *addr,
Handler: mux,
Addr: *addr,
Handler: mux,
ErrorLog: quietLogger,
// Set read/write timeout. For derper, this basically
// only affects TLS setup, as read/write deadlines are
@@ -275,9 +293,13 @@ func main() {
})
if *httpPort > -1 {
go func() {
port80mux := http.NewServeMux()
port80mux.HandleFunc("/generate_204", serveNoContent)
port80mux.Handle("/", certManager.HTTPHandler(tsweb.Port80Handler{Main: mux}))
port80srv := &http.Server{
Addr: net.JoinHostPort(listenHost, fmt.Sprintf("%d", *httpPort)),
Handler: certManager.HTTPHandler(tsweb.Port80Handler{Main: mux}),
Handler: port80mux,
ErrorLog: quietLogger,
ReadTimeout: 30 * time.Second,
// Crank up WriteTimeout a bit more than usually
// necessary just so we can do long CPU profiles
@@ -303,6 +325,11 @@ func main() {
}
}
// For captive portal detection
func serveNoContent(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// probeHandler is the endpoint that js/wasm clients hit to measure
// DERP latency, since they can't do UDP STUN queries.
func probeHandler(w http.ResponseWriter, r *http.Request) {
@@ -356,7 +383,8 @@ func serverSTUNListener(ctx context.Context, pc *net.UDPConn) {
} else {
stunIPv6.Add(1)
}
res := stun.Response(txid, ua.IP, uint16(ua.Port))
addr, _ := netip.AddrFromSlice(ua.IP)
res := stun.Response(txid, netip.AddrPortFrom(addr, uint16(ua.Port)))
_, err = pc.WriteTo(res, ua)
if err != nil {
stunWriteError.Add(1)
@@ -447,3 +475,22 @@ func (l *rateLimitedListener) Accept() (net.Conn, error) {
l.numAccepts.Add(1)
return cn, nil
}
// logFilter is used to filter out useless error logs that are logged to
// the net/http.Server.ErrorLog logger.
type logFilter struct{}
func (logFilter) Write(p []byte) (int, error) {
b := mem.B(p)
if mem.HasSuffix(b, mem.S(": EOF\n")) ||
mem.HasSuffix(b, mem.S(": i/o timeout\n")) ||
mem.HasSuffix(b, mem.S(": read: connection reset by peer\n")) ||
mem.HasSuffix(b, mem.S(": remote error: tls: bad certificate\n")) ||
mem.HasSuffix(b, mem.S(": tls: first record does not look like a TLS handshake\n")) {
// Skip this log message, but say that we processed it
return len(p), nil
}
log.Printf("%s", p)
return len(p), nil
}

View File

@@ -33,6 +33,12 @@ func addWebSocketSupport(s *derp.Server, base http.Handler) http.Handler {
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
Subprotocols: []string{"derp"},
OriginPatterns: []string{"*"},
// Disable compression because we transmit WireGuard messages that
// are not compressible.
// Additionally, Safari has a broken implementation of compression
// (see https://github.com/nhooyr/websocket/issues/218) that makes
// enabling it actively harmful.
CompressionMode: websocket.CompressionDisabled,
})
if err != nil {
log.Printf("websocket.Accept: %v", err)

View File

@@ -360,7 +360,7 @@ func probeUDP(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode) (la
time.Sleep(100 * time.Millisecond)
continue
}
txBack, _, _, err := stun.ParseResponse(buf[:n])
txBack, _, err := stun.ParseResponse(buf[:n])
if err != nil {
return 0, fmt.Errorf("parsing STUN response from %v: %v", ip, err)
}

View File

@@ -8,6 +8,7 @@
package main
import (
"bytes"
"context"
"crypto/sha256"
"encoding/json"
@@ -30,17 +31,14 @@ var (
cacheFname = rootFlagSet.String("cache-file", "./version-cache.json", "filename for the previous known version hash")
timeout = rootFlagSet.Duration("timeout", 5*time.Minute, "timeout for the entire CI run")
githubSyntax = rootFlagSet.Bool("github-syntax", true, "use GitHub Action error syntax (https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message)")
modifiedExternallyFailure = make(chan struct{}, 1)
)
func modifiedExternallyError() {
if *githubSyntax {
fmt.Printf("::error file=%s,line=1,col=1,title=Policy File Modified Externally::The policy file was modified externally in the admin console.\n", *policyFname)
fmt.Printf("::warning file=%s,line=1,col=1,title=Policy File Modified Externally::The policy file was modified externally in the admin console.\n", *policyFname)
} else {
fmt.Printf("The policy file was modified externally in the admin console.\n")
}
modifiedExternallyFailure <- struct{}{}
}
func apply(cache *Cache, tailnet, apiKey string) func(context.Context, []string) error {
@@ -207,10 +205,6 @@ func main() {
fmt.Println(err)
os.Exit(1)
}
if len(modifiedExternallyFailure) != 0 {
os.Exit(1)
}
}
func sumFile(fname string) (string, error) {
@@ -271,13 +265,16 @@ func applyNewACL(ctx context.Context, tailnet, apiKey, policyFname, oldEtag stri
}
func testNewACLs(ctx context.Context, tailnet, apiKey, policyFname string) error {
fin, err := os.Open(policyFname)
data, err := os.ReadFile(policyFname)
if err != nil {
return err
}
data, err = hujson.Standardize(data)
if err != nil {
return err
}
defer fin.Close()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("https://api.tailscale.com/api/v2/tailnet/%s/acl/validate", tailnet), fin)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("https://api.tailscale.com/api/v2/tailnet/%s/acl/validate", tailnet), bytes.NewBuffer(data))
if err != nil {
return err
}

View File

@@ -13,7 +13,6 @@ import (
"errors"
"flag"
"html/template"
"io/ioutil"
"log"
"net/http"
"os"
@@ -106,7 +105,7 @@ func devMode() bool { return *httpsAddr == "" && *httpAddr != "" }
func getTmpl() (*template.Template, error) {
if devMode() {
tmplData, err := ioutil.ReadFile("hello.tmpl.html")
tmplData, err := os.ReadFile("hello.tmpl.html")
if os.IsNotExist(err) {
log.Printf("using baked-in template in dev mode; can't find hello.tmpl.html in current directory")
return tmpl, nil

View File

@@ -63,17 +63,19 @@ func main() {
return
}
_, tailnet, ok := strings.Cut(info.Node.Name, info.Node.ComputedName+".")
if !ok {
w.WriteHeader(http.StatusUnauthorized)
log.Printf("can't extract tailnet name from hostname %q", info.Node.Name)
return
}
tailnet, _, ok = strings.Cut(tailnet, ".beta.tailscale.net")
if !ok {
w.WriteHeader(http.StatusUnauthorized)
log.Printf("can't extract tailnet name from hostname %q", info.Node.Name)
return
// tailnet of connected node. When accessing shared nodes, this
// will be empty because the tailnet of the sharee is not exposed.
var tailnet string
if !info.Node.Hostinfo.ShareeNode() {
var ok bool
_, tailnet, ok = strings.Cut(info.Node.Name, info.Node.ComputedName+".")
if !ok {
w.WriteHeader(http.StatusUnauthorized)
log.Printf("can't extract tailnet name from hostname %q", info.Node.Name)
return
}
tailnet = strings.TrimSuffix(tailnet, ".beta.tailscale.net")
}
if expectedTailnet := r.Header.Get("Expected-Tailnet"); expectedTailnet != "" && expectedTailnet != tailnet {

View File

@@ -110,11 +110,12 @@ func runSpeedtest(ctx context.Context, args []string) error {
w := tabwriter.NewWriter(os.Stdout, 12, 0, 0, ' ', tabwriter.TabIndent)
fmt.Println("Results:")
fmt.Fprintln(w, "Interval\t\tTransfer\t\tBandwidth\t\t")
startTime := results[0].IntervalStart
for _, r := range results {
if r.Total {
fmt.Fprintln(w, "-------------------------------------------------------------------------")
}
fmt.Fprintf(w, "%.2f-%.2f\tsec\t%.4f\tMBits\t%.4f\tMbits/sec\t\n", r.IntervalStart.Seconds(), r.IntervalEnd.Seconds(), r.MegaBits(), r.MBitsPerSecond())
fmt.Fprintf(w, "%.2f-%.2f\tsec\t%.4f\tMBits\t%.4f\tMbits/sec\t\n", r.IntervalStart.Sub(startTime).Seconds(), r.IntervalEnd.Sub(startTime).Seconds(), r.MegaBits(), r.MBitsPerSecond())
}
w.Flush()
return nil

View File

@@ -29,7 +29,7 @@ var certCmd = &ffcli.Command{
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("cert")
fs.StringVar(&certArgs.certFile, "cert-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.crt if --cert-file and --key-file are both unset")
fs.StringVar(&certArgs.keyFile, "key-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.key if --cert-file and --key-file are both unset")
fs.StringVar(&certArgs.keyFile, "key-file", "", "output key file or \"-\" for stdout; defaults to DOMAIN.key if --cert-file and --key-file are both unset")
fs.BoolVar(&certArgs.serve, "serve-demo", false, "if true, serve on port :443 using the cert as a demo, instead of writing out the files to disk")
return fs
})(),

View File

@@ -29,7 +29,6 @@ import (
"tailscale.com/ipn"
"tailscale.com/paths"
"tailscale.com/safesocket"
"tailscale.com/syncs"
"tailscale.com/version/distro"
)
@@ -170,6 +169,8 @@ change in the future.
fileCmd,
bugReportCmd,
certCmd,
netlockCmd,
licensesCmd,
},
FlagSet: rootfs,
Exec: func(context.Context, []string) error { return flag.ErrHelp },
@@ -230,8 +231,6 @@ var rootArgs struct {
socket string
}
var gotSignal syncs.AtomicBool
func connect(ctx context.Context) (net.Conn, *ipn.BackendClient, context.Context, context.CancelFunc) {
s := safesocket.DefaultConnectionStrategy(rootArgs.socket)
c, err := safesocket.Connect(s)
@@ -257,7 +256,6 @@ func connect(ctx context.Context) (net.Conn, *ipn.BackendClient, context.Context
signal.Reset(syscall.SIGINT, syscall.SIGTERM)
return
}
gotSignal.Set(true)
c.Close()
cancel()
}()

View File

@@ -762,6 +762,9 @@ func TestPrefFlagMapping(t *testing.T) {
case "NotepadURLs":
// TODO(bradfitz): https://github.com/tailscale/tailscale/issues/1830
continue
case "Egg":
// Not applicable.
continue
}
t.Errorf("unexpected new ipn.Pref field %q is not handled by up.go (see addPrefFlagMapping and checkForAccidentalSettingReverts)", prefName)
}
@@ -786,6 +789,10 @@ func TestUpdatePrefs(t *testing.T) {
curPrefs *ipn.Prefs
env upCheckEnv // empty goos means "linux"
// sshOverTailscale specifies if the cmd being run over SSH over Tailscale.
// It is used to test the --accept-risks flag.
sshOverTailscale bool
// checkUpdatePrefsMutations, if non-nil, is run with the new prefs after
// updatePrefs might've mutated them (from applyImplicitPrefs).
checkUpdatePrefsMutations func(t *testing.T, newPrefs *ipn.Prefs)
@@ -913,15 +920,159 @@ func TestUpdatePrefs(t *testing.T) {
}
},
},
{
name: "enable_ssh",
flags: []string{"--ssh"},
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{LoginName: "crawshaw.github"},
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
},
wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true,
WantRunningSet: true,
},
checkUpdatePrefsMutations: func(t *testing.T, newPrefs *ipn.Prefs) {
if !newPrefs.RunSSH {
t.Errorf("RunSSH not set to true")
}
},
env: upCheckEnv{backendState: "Running"},
},
{
name: "disable_ssh",
flags: []string{"--ssh=false"},
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{LoginName: "crawshaw.github"},
AllowSingleHosts: true,
CorpDNS: true,
RunSSH: true,
NetfilterMode: preftype.NetfilterOn,
},
wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true,
WantRunningSet: true,
},
checkUpdatePrefsMutations: func(t *testing.T, newPrefs *ipn.Prefs) {
if newPrefs.RunSSH {
t.Errorf("RunSSH not set to false")
}
},
env: upCheckEnv{backendState: "Running", upArgs: upArgsT{
runSSH: true,
}},
},
{
name: "disable_ssh_over_ssh_no_risk",
flags: []string{"--ssh=false"},
sshOverTailscale: true,
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{LoginName: "crawshaw.github"},
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
RunSSH: true,
},
wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true,
WantRunningSet: true,
},
checkUpdatePrefsMutations: func(t *testing.T, newPrefs *ipn.Prefs) {
if !newPrefs.RunSSH {
t.Errorf("RunSSH not set to true")
}
},
env: upCheckEnv{backendState: "Running"},
wantErrSubtr: "aborted, no changes made",
},
{
name: "enable_ssh_over_ssh_no_risk",
flags: []string{"--ssh=true"},
sshOverTailscale: true,
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{LoginName: "crawshaw.github"},
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
},
wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true,
WantRunningSet: true,
},
checkUpdatePrefsMutations: func(t *testing.T, newPrefs *ipn.Prefs) {
if !newPrefs.RunSSH {
t.Errorf("RunSSH not set to true")
}
},
env: upCheckEnv{backendState: "Running"},
wantErrSubtr: "aborted, no changes made",
},
{
name: "enable_ssh_over_ssh",
flags: []string{"--ssh=true", "--accept-risk=lose-ssh"},
sshOverTailscale: true,
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{LoginName: "crawshaw.github"},
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
},
wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true,
WantRunningSet: true,
},
checkUpdatePrefsMutations: func(t *testing.T, newPrefs *ipn.Prefs) {
if !newPrefs.RunSSH {
t.Errorf("RunSSH not set to true")
}
},
env: upCheckEnv{backendState: "Running"},
},
{
name: "disable_ssh_over_ssh",
flags: []string{"--ssh=false", "--accept-risk=lose-ssh"},
sshOverTailscale: true,
curPrefs: &ipn.Prefs{
ControlURL: "https://login.tailscale.com",
Persist: &persist.Persist{LoginName: "crawshaw.github"},
AllowSingleHosts: true,
CorpDNS: true,
RunSSH: true,
NetfilterMode: preftype.NetfilterOn,
},
wantJustEditMP: &ipn.MaskedPrefs{
RunSSHSet: true,
WantRunningSet: true,
},
checkUpdatePrefsMutations: func(t *testing.T, newPrefs *ipn.Prefs) {
if newPrefs.RunSSH {
t.Errorf("RunSSH not set to false")
}
},
env: upCheckEnv{backendState: "Running"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.sshOverTailscale {
old := getSSHClientEnvVar
getSSHClientEnvVar = func() string { return "100.100.100.100 1 1" }
t.Cleanup(func() { getSSHClientEnvVar = old })
}
if tt.env.goos == "" {
tt.env.goos = "linux"
}
tt.env.flagSet = newUpFlagSet(tt.env.goos, &tt.env.upArgs)
flags := CleanUpArgs(tt.flags)
tt.env.flagSet.Parse(flags)
if err := tt.env.flagSet.Parse(flags); err != nil {
t.Fatal(err)
}
newPrefs, err := prefsFromUpArgs(tt.env.upArgs, t.Logf, new(ipnstate.Status), tt.env.goos)
if err != nil {
@@ -936,6 +1087,8 @@ func TestUpdatePrefs(t *testing.T) {
return
}
t.Fatal(err)
} else if tt.wantErrSubtr != "" {
t.Fatalf("want error %q, got nil", tt.wantErrSubtr)
}
if tt.checkUpdatePrefsMutations != nil {
tt.checkUpdatePrefsMutations(t, newPrefs)
@@ -949,13 +1102,18 @@ func TestUpdatePrefs(t *testing.T) {
justEditMP.Prefs = ipn.Prefs{} // uninteresting
}
if !reflect.DeepEqual(justEditMP, tt.wantJustEditMP) {
t.Logf("justEditMP != wantJustEditMP; following diff omits the Prefs field, which was %+v", oldEditPrefs)
t.Logf("justEditMP != wantJustEditMP; following diff omits the Prefs field, which was \n%v", asJSON(oldEditPrefs))
t.Fatalf("justEditMP: %v\n\n: ", cmp.Diff(justEditMP, tt.wantJustEditMP, cmpIP))
}
})
}
}
func asJSON(v any) string {
b, _ := json.MarshalIndent(v, "", "\t")
return string(b)
}
var cmpIP = cmp.Comparer(func(a, b netip.Addr) bool {
return a == b
})

View File

@@ -48,11 +48,11 @@ func runConfigureHost(ctx context.Context, args []string) error {
if uid := os.Getuid(); uid != 0 {
return fmt.Errorf("must be run as root, not %q (%v)", os.Getenv("USER"), uid)
}
osVer := hostinfo.GetOSVersion()
isDSM6 := strings.HasPrefix(osVer, "Synology 6")
isDSM7 := strings.HasPrefix(osVer, "Synology 7")
hi:= hostinfo.New()
isDSM6 := strings.HasPrefix(hi.DistroVersion, "6.")
isDSM7 := strings.HasPrefix(hi.DistroVersion, "7.")
if !isDSM6 && !isDSM7 {
return fmt.Errorf("unsupported DSM version %q", osVer)
return fmt.Errorf("unsupported DSM version %q", hi.DistroVersion)
}
if _, err := os.Stat("/dev/net/tun"); os.IsNotExist(err) {
if err := os.MkdirAll("/dev/net", 0755); err != nil {
@@ -62,6 +62,9 @@ func runConfigureHost(ctx context.Context, args []string) error {
return fmt.Errorf("creating /dev/net/tun: %v, %s", err, out)
}
}
if err := os.Chmod("/dev/net", 0755); err != nil {
return err
}
if err := os.Chmod("/dev/net/tun", 0666); err != nil {
return err
}

View File

@@ -489,7 +489,15 @@ func runTS2021(ctx context.Context, args []string) error {
return c, err
}
conn, err := controlhttp.Dial(ctx, net.JoinHostPort(ts2021Args.host, "80"), machinePrivate, keys.PublicKey, uint16(ts2021Args.version), dialFunc)
conn, err := (&controlhttp.Dialer{
Hostname: ts2021Args.host,
HTTPPort: "80",
HTTPSPort: "443",
MachineKey: machinePrivate,
ControlKey: keys.PublicKey,
ProtocolVersion: uint16(ts2021Args.version),
Dialer: dialFunc,
}).Dial(ctx)
log.Printf("controlhttp.Dial = %p, %v", conn, err)
if err != nil {
return err

View File

@@ -22,9 +22,13 @@ var downCmd = &ffcli.Command{
FlagSet: newDownFlagSet(),
}
var downArgs struct {
acceptedRisks string
}
func newDownFlagSet() *flag.FlagSet {
downf := newFlagSet("down")
registerAcceptRiskFlag(downf)
registerAcceptRiskFlag(downf, &downArgs.acceptedRisks)
return downf
}
@@ -34,7 +38,7 @@ func runDown(ctx context.Context, args []string) error {
}
if isSSHOverTailscale() {
if err := presentRiskToUser(riskLoseSSH, `You are connected over Tailscale; this action will disable Tailscale and result in your session disconnecting.`); err != nil {
if err := presentRiskToUser(riskLoseSSH, `You are connected over Tailscale; this action will disable Tailscale and result in your session disconnecting.`, downArgs.acceptedRisks); err != nil {
return err
}
}

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cli
import (
"context"
"runtime"
"github.com/peterbourgon/ff/v3/ffcli"
)
var licensesCmd = &ffcli.Command{
Name: "licenses",
ShortUsage: "licenses",
ShortHelp: "Get open source license information",
LongHelp: "Get open source license information",
Exec: runLicenses,
}
// licensesURL returns the absolute URL containing open source license information for the current platform.
func licensesURL() string {
switch runtime.GOOS {
case "android":
return "https://tailscale.com/licenses/android"
case "darwin", "ios":
return "https://tailscale.com/licenses/apple"
case "windows":
return "https://tailscale.com/licenses/windows"
default:
return "https://tailscale.com/licenses/tailscale"
}
}
func runLicenses(ctx context.Context, args []string) error {
licenses := licensesURL()
outln(`
Tailscale wouldn't be possible without the contributions of thousands of open
source developers. To see the open source packages included in Tailscale and
their respective license information, visit:
` + licenses)
return nil
}

View File

@@ -10,7 +10,6 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"sort"
@@ -134,6 +133,9 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
printf("\t* MappingVariesByDestIP: %v\n", report.MappingVariesByDestIP)
printf("\t* HairPinning: %v\n", report.HairPinning)
printf("\t* PortMapping: %v\n", portMapping(report))
if report.CaptivePortal != "" {
printf("\t* CaptivePortal: %v\n", report.CaptivePortal)
}
// When DERP latency checking failed,
// magicsock will try to pick the DERP server that
@@ -202,7 +204,7 @@ func prodDERPMap(ctx context.Context, httpc *http.Client) (*tailcfg.DERPMap, err
return nil, fmt.Errorf("fetch prodDERPMap failed: %w", err)
}
defer res.Body.Close()
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
b, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
if err != nil {
return nil, fmt.Errorf("fetch prodDERPMap failed: %w", err)
}

View File

@@ -0,0 +1,101 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cli
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/tka"
"tailscale.com/types/key"
)
var netlockCmd = &ffcli.Command{
Name: "lock",
ShortUsage: "lock <sub-command> <arguments>",
ShortHelp: "Manipulate the tailnet key authority",
Subcommands: []*ffcli.Command{nlInitCmd, nlStatusCmd},
Exec: runNetworkLockStatus,
}
var nlInitCmd = &ffcli.Command{
Name: "init",
ShortUsage: "init <public-key>...",
ShortHelp: "Initialize the tailnet key authority",
Exec: runNetworkLockInit,
}
func runNetworkLockInit(ctx context.Context, args []string) error {
st, err := localClient.NetworkLockStatus(ctx)
if err != nil {
return fixTailscaledConnectError(err)
}
if st.Enabled {
return errors.New("network-lock is already enabled")
}
// Parse the set of initially-trusted keys.
// Keys are specified using their key.NLPublic.MarshalText representation,
// with an optional '?<votes>' suffix.
var keys []tka.Key
for i, a := range args {
var key key.NLPublic
spl := strings.SplitN(a, "?", 2)
if err := key.UnmarshalText([]byte(spl[0])); err != nil {
return fmt.Errorf("parsing key %d: %v", i+1, err)
}
k := tka.Key{
Kind: tka.Key25519,
Public: key.Verifier(),
Votes: 1,
}
if len(spl) > 1 {
votes, err := strconv.Atoi(spl[1])
if err != nil {
return fmt.Errorf("parsing key %d votes: %v", i+1, err)
}
k.Votes = uint(votes)
}
keys = append(keys, k)
}
status, err := localClient.NetworkLockInit(ctx, keys)
if err != nil {
return err
}
fmt.Printf("Status: %+v\n\n", status)
return nil
}
var nlStatusCmd = &ffcli.Command{
Name: "status",
ShortUsage: "status",
ShortHelp: "Outputs the state of network lock",
Exec: runNetworkLockStatus,
}
func runNetworkLockStatus(ctx context.Context, args []string) error {
st, err := localClient.NetworkLockStatus(ctx)
if err != nil {
return fixTailscaledConnectError(err)
}
if st.Enabled {
fmt.Println("Network-lock is ENABLED.")
} else {
fmt.Println("Network-lock is NOT enabled.")
}
p, err := st.PublicKey.MarshalText()
if err != nil {
return err
}
fmt.Printf("our public-key: %s\n", p)
return nil
}

View File

@@ -16,9 +16,8 @@ import (
)
var (
riskTypes []string
acceptedRisks string
riskLoseSSH = registerRiskType("lose-ssh")
riskTypes []string
riskLoseSSH = registerRiskType("lose-ssh")
)
func registerRiskType(riskType string) string {
@@ -28,12 +27,13 @@ func registerRiskType(riskType string) string {
// registerAcceptRiskFlag registers the --accept-risk flag. Accepted risks are accounted for
// in presentRiskToUser.
func registerAcceptRiskFlag(f *flag.FlagSet) {
f.StringVar(&acceptedRisks, "accept-risk", "", "accept risk and skip confirmation for risk types: "+strings.Join(riskTypes, ","))
func registerAcceptRiskFlag(f *flag.FlagSet, acceptedRisks *string) {
f.StringVar(acceptedRisks, "accept-risk", "", "accept risk and skip confirmation for risk types: "+strings.Join(riskTypes, ","))
}
// riskAccepted reports whether riskType is in acceptedRisks.
func riskAccepted(riskType string) bool {
// isRiskAccepted reports whether riskType is in the comma-separated list of
// risks in acceptedRisks.
func isRiskAccepted(riskType, acceptedRisks string) bool {
for _, r := range strings.Split(acceptedRisks, ",") {
if r == riskType {
return true
@@ -49,12 +49,16 @@ var errAborted = errors.New("aborted, no changes made")
// It is used by the presentRiskToUser function below.
const riskAbortTimeSeconds = 5
// presentRiskToUser displays the risk message and waits for the user to
// cancel. It returns errorAborted if the user aborts.
func presentRiskToUser(riskType, riskMessage string) error {
if riskAccepted(riskType) {
// presentRiskToUser displays the risk message and waits for the user to cancel.
// It returns errorAborted if the user aborts. In tests it returns errAborted
// immediately unless the risk has been explicitly accepted.
func presentRiskToUser(riskType, riskMessage, acceptedRisks string) error {
if isRiskAccepted(riskType, acceptedRisks) {
return nil
}
if inTest() {
return errAborted
}
outln(riskMessage)
printf("To skip this warning, use --accept-risk=%s\n", riskType)

View File

@@ -116,7 +116,7 @@ func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet {
upf.BoolVar(&upArgs.forceDaemon, "unattended", false, "run in \"Unattended Mode\" where Tailscale keeps running even after the current GUI user logs out (Windows-only)")
}
upf.DurationVar(&upArgs.timeout, "timeout", 0, "maximum amount of time to wait for tailscaled to enter a Running state; default (0s) blocks forever")
registerAcceptRiskFlag(upf)
registerAcceptRiskFlag(upf, &upArgs.acceptedRisks)
return upf
}
@@ -150,6 +150,7 @@ type upArgsT struct {
opUser string
json bool
timeout time.Duration
acceptedRisks string
}
func (a upArgsT) getAuthKey() (string, error) {
@@ -252,7 +253,7 @@ func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]
if default4 && !default6 {
return nil, fmt.Errorf("%s advertised without its IPv6 counterpart, please also advertise %s", ipv4default, ipv6default)
} else if default6 && !default4 {
return nil, fmt.Errorf("%s advertised without its IPv6 counterpart, please also advertise %s", ipv6default, ipv4default)
return nil, fmt.Errorf("%s advertised without its IPv4 counterpart, please also advertise %s", ipv6default, ipv4default)
}
}
if advertiseDefaultRoute {
@@ -376,6 +377,20 @@ func updatePrefs(prefs, curPrefs *ipn.Prefs, env upCheckEnv) (simpleUp bool, jus
return false, nil, fmt.Errorf("can't change --login-server without --force-reauth")
}
// Do this after validations to avoid the 5s delay if we're going to error
// out anyway.
wantSSH, haveSSH := env.upArgs.runSSH, curPrefs.RunSSH
if wantSSH != haveSSH && isSSHOverTailscale() {
if wantSSH {
err = presentRiskToUser(riskLoseSSH, `You are connected over Tailscale; this action will reroute SSH traffic to Tailscale SSH and will result in your session disconnecting.`, env.upArgs.acceptedRisks)
} else {
err = presentRiskToUser(riskLoseSSH, `You are connected using Tailscale SSH; this action will result in your session disconnecting.`, env.upArgs.acceptedRisks)
}
if err != nil {
return false, nil, err
}
}
tagsChanged := !reflect.DeepEqual(curPrefs.AdvertiseTags, prefs.AdvertiseTags)
simpleUp = env.flagSet.NFlag() == 0 &&
@@ -406,8 +421,12 @@ func updatePrefs(prefs, curPrefs *ipn.Prefs, env upCheckEnv) (simpleUp bool, jus
}
func runUp(ctx context.Context, args []string) (retErr error) {
var egg bool
if len(args) > 0 {
fatalf("too many non-flag arguments: %q", args)
egg = fmt.Sprint(args) == "[up down down left right left right b a]"
if !egg {
fatalf("too many non-flag arguments: %q", args)
}
}
st, err := localClient.Status(ctx)
@@ -471,17 +490,6 @@ func runUp(ctx context.Context, args []string) (retErr error) {
curExitNodeIP: exitNodeIP(curPrefs, st),
}
if upArgs.runSSH != curPrefs.RunSSH && isSSHOverTailscale() {
if upArgs.runSSH {
err = presentRiskToUser(riskLoseSSH, `You are connected over Tailscale; this action will reroute SSH traffic to Tailscale SSH and will result in your session disconnecting.`)
} else {
err = presentRiskToUser(riskLoseSSH, `You are connected using Tailscale SSH; this action will result in your session disconnecting.`)
}
if err != nil {
return err
}
}
defer func() {
if retErr == nil {
checkSSHUpWarnings(ctx)
@@ -493,6 +501,7 @@ func runUp(ctx context.Context, args []string) (retErr error) {
fatalf("%s", err)
}
if justEditMP != nil {
justEditMP.EggSet = true
_, err := localClient.EditPrefs(ctx, justEditMP)
return err
}

View File

@@ -15,7 +15,6 @@ import (
"fmt"
"html/template"
"io"
"io/ioutil"
"log"
"net"
"net/http"
@@ -59,6 +58,7 @@ type tmplData struct {
IP string
AdvertiseExitNode bool
AdvertiseRoutes string
LicensesURL string
}
var webCmd = &ffcli.Command{
@@ -253,7 +253,7 @@ func qnapAuthnFinish(user, url string) (string, *qnapAuthResponse, error) {
return "", nil, err
}
defer resp.Body.Close()
out, err := ioutil.ReadAll(resp.Body)
out, err := io.ReadAll(resp.Body)
if err != nil {
return "", nil, err
}
@@ -392,6 +392,7 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
Profile: profile,
Status: st.BackendState,
DeviceName: deviceName,
LicensesURL: licensesURL(),
}
exitNodeRouteV4 := netip.MustParsePrefix("0.0.0.0/0")
exitNodeRouteV6 := netip.MustParsePrefix("::/0")

View File

@@ -11,7 +11,7 @@
</head>
<body class="py-14">
<main class="container max-w-lg mx-auto py-6 px-8 bg-white rounded-md shadow-2xl" style="width: 95%">
<main class="container max-w-lg mx-auto mb-8 py-6 px-8 bg-white rounded-md shadow-2xl" style="width: 95%">
<header class="flex justify-between items-center min-width-0 py-2 mb-8">
<svg width="26" height="26" viewBox="0 0 23 23" title="Tailscale" fill="none" xmlns="http://www.w3.org/2000/svg"
class="flex-shrink-0 mr-4">
@@ -100,6 +100,9 @@
</div>
{{ end }}
</main>
<footer class="container max-w-lg mx-auto text-center">
<a class="text-xs text-gray-500 hover:text-gray-600" href="{{ .LicensesURL }}">Open Source Licenses</a>
</footer>
<script>(function () {
const advertiseExitNode = {{.AdvertiseExitNode}};
let fetchingUrl = false;

View File

@@ -64,6 +64,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/net/netns from tailscale.com/derp/derphttp+
tailscale.com/net/netutil from tailscale.com/client/tailscale+
tailscale.com/net/packet from tailscale.com/wgengine/filter
tailscale.com/net/ping from tailscale.com/net/netcheck
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
tailscale.com/net/stun from tailscale.com/net/netcheck
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp+
@@ -71,9 +72,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
tailscale.com/paths from tailscale.com/cmd/tailscale/cli+
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+
tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/syncs from tailscale.com/net/netcheck+
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
tailscale.com/tka from tailscale.com/types/key
tailscale.com/tka from tailscale.com/client/tailscale+
W tailscale.com/tsconst from tailscale.com/net/interfaces
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
@@ -89,6 +90,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/types/persist from tailscale.com/ipn
tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+
tailscale.com/types/structs from tailscale.com/ipn+
tailscale.com/types/tkatype from tailscale.com/types/key+
tailscale.com/types/views from tailscale.com/tailcfg+
tailscale.com/util/clientmetric from tailscale.com/net/netcheck+
tailscale.com/util/cloudenv from tailscale.com/net/dnscache+
@@ -97,7 +99,10 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
W tailscale.com/util/endian from tailscale.com/net/netns
tailscale.com/util/groupmember from tailscale.com/cmd/tailscale/cli
tailscale.com/util/lineread from tailscale.com/net/interfaces+
tailscale.com/util/mak from tailscale.com/net/netcheck
tailscale.com/util/multierr from tailscale.com/control/controlhttp
tailscale.com/util/singleflight from tailscale.com/net/dnscache
L tailscale.com/util/strs from tailscale.com/hostinfo
W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
@@ -114,12 +119,15 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/crypto/nacl/box from tailscale.com/types/key
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
L golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/dns/dnsmessage from net+
golang.org/x/net/http/httpguts from net/http+
golang.org/x/net/http/httpproxy from net/http
golang.org/x/net/http2/hpack from net/http
golang.org/x/net/icmp from tailscale.com/net/ping
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
golang.org/x/net/ipv4 from golang.org/x/net/icmp+
golang.org/x/net/ipv6 from golang.org/x/net/icmp
golang.org/x/net/proxy from tailscale.com/net/netns
D golang.org/x/net/route from net+
golang.org/x/sync/errgroup from tailscale.com/derp+

View File

@@ -15,7 +15,6 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
@@ -173,7 +172,7 @@ func checkDerp(ctx context.Context, derpRegion string) error {
return fmt.Errorf("fetch derp map failed: %w", err)
}
defer res.Body.Close()
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
b, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
if err != nil {
return fmt.Errorf("fetch derp map failed: %w", err)
}

View File

@@ -212,7 +212,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/logtail/filch from tailscale.com/logpolicy
💣 tailscale.com/metrics from tailscale.com/derp+
tailscale.com/net/dns from tailscale.com/ipn/ipnlocal+
tailscale.com/net/dns/publicdns from tailscale.com/net/dns/resolver
tailscale.com/net/dns/publicdns from tailscale.com/net/dns/resolver+
tailscale.com/net/dns/resolvconffile from tailscale.com/net/dns+
tailscale.com/net/dns/resolver from tailscale.com/ipn/ipnlocal+
tailscale.com/net/dnscache from tailscale.com/control/controlclient+
@@ -227,6 +227,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnserver
tailscale.com/net/netutil from tailscale.com/ipn/ipnlocal+
tailscale.com/net/packet from tailscale.com/net/tstun+
tailscale.com/net/ping from tailscale.com/net/netcheck
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled
tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled
@@ -241,10 +242,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/safesocket from tailscale.com/client/tailscale+
tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+
LD 💣 tailscale.com/ssh/tailssh from tailscale.com/cmd/tailscaled
tailscale.com/syncs from tailscale.com/control/controlknobs+
tailscale.com/syncs from tailscale.com/net/netcheck+
tailscale.com/tailcfg from tailscale.com/client/tailscale/apitype+
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
tailscale.com/tka from tailscale.com/types/key+
tailscale.com/tka from tailscale.com/ipn/ipnlocal+
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/tstime from tailscale.com/wgengine/magicsock
💣 tailscale.com/tstime/mono from tailscale.com/net/tstun+
@@ -263,6 +264,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/persist from tailscale.com/control/controlclient+
tailscale.com/types/preftype from tailscale.com/ipn+
tailscale.com/types/structs from tailscale.com/control/controlclient+
tailscale.com/types/tkatype from tailscale.com/tka+
tailscale.com/types/views from tailscale.com/ipn/ipnlocal+
tailscale.com/util/clientmetric from tailscale.com/control/controlclient+
tailscale.com/util/cloudenv from tailscale.com/net/dns/resolver+
@@ -271,6 +273,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/util/dnsname from tailscale.com/hostinfo+
LW tailscale.com/util/endian from tailscale.com/net/dns+
tailscale.com/util/groupmember from tailscale.com/ipn/ipnserver
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
tailscale.com/util/lineread from tailscale.com/hostinfo+
tailscale.com/util/mak from tailscale.com/control/controlclient+
tailscale.com/util/multierr from tailscale.com/control/controlclient+
@@ -278,6 +281,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
tailscale.com/util/racebuild from tailscale.com/logpolicy
tailscale.com/util/singleflight from tailscale.com/control/controlclient+
tailscale.com/util/strs from tailscale.com/hostinfo+
tailscale.com/util/systemd from tailscale.com/control/controlclient+
tailscale.com/util/uniq from tailscale.com/wgengine/magicsock
💣 tailscale.com/util/winutil from tailscale.com/cmd/tailscaled+
@@ -286,12 +290,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
W tailscale.com/wf from tailscale.com/cmd/tailscaled
tailscale.com/wgengine from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/monitor from tailscale.com/control/controlclient+
tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/router from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
💣 tailscale.com/wgengine/wgint from tailscale.com/wgengine
tailscale.com/wgengine/wglog from tailscale.com/wgengine
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
golang.org/x/crypto/acme from tailscale.com/ipn/localapi
@@ -312,7 +317,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+
golang.org/x/exp/constraints from golang.org/x/exp/slices
golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal
golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal+
golang.org/x/net/bpf from github.com/mdlayher/genetlink+
golang.org/x/net/dns/dnsmessage from net+
golang.org/x/net/http/httpguts from golang.org/x/net/http2+
@@ -320,8 +325,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/net/http2 from golang.org/x/net/http2/h2c+
golang.org/x/net/http2/h2c from tailscale.com/ipn/ipnlocal
golang.org/x/net/http2/hpack from golang.org/x/net/http2+
golang.org/x/net/icmp from tailscale.com/net/ping
golang.org/x/net/idna from golang.org/x/net/http/httpguts+
golang.org/x/net/ipv4 from golang.zx2c4.com/wireguard/device
golang.org/x/net/ipv4 from golang.zx2c4.com/wireguard/device+
golang.org/x/net/ipv6 from golang.zx2c4.com/wireguard/device+
golang.org/x/net/proxy from tailscale.com/net/netns
D golang.org/x/net/route from net+
@@ -399,6 +405,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
mime/quotedprintable from mime/multipart
net from crypto/tls+
net/http from expvar+
net/http/httptest from tailscale.com/control/controlclient
net/http/httptrace from github.com/tcnksm/go-httpstat+
net/http/httputil from github.com/aws/smithy-go/transport/http+
net/http/internal from net/http+

View File

@@ -11,7 +11,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -142,7 +141,7 @@ func installSystemDaemonDarwin(args []string) (err error) {
return err
}
if err := ioutil.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil {
if err := os.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil {
return err
}

View File

@@ -26,6 +26,7 @@ import (
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"time"
@@ -97,6 +98,20 @@ func defaultTunName() string {
return "tailscale0"
}
// defaultPort returns the default UDP port to listen on for disco+wireguard.
// By default it returns 0, to pick one randomly from the kernel.
// If the environment variable PORT is set, that's used instead.
// The PORT environment variable is chosen to match what the Linux systemd
// unit uses, to make documentation more consistent.
func defaultPort() uint16 {
if s := envknob.String("PORT"); s != "" {
if p, err := strconv.ParseUint(s, 10, 16); err == nil {
return uint16(p)
}
}
return 0
}
var args struct {
// tunname is a /dev/net/tun tunnel name ("tailscale0"), the
// string "userspace-networking", "tap:TAPNAME[:BRIDGENAME]"
@@ -113,6 +128,7 @@ var args struct {
verbose int
socksAddr string // listen address for SOCKS5 server
httpProxyAddr string // listen address for HTTP proxy server
disableLogs bool
}
var (
@@ -131,6 +147,9 @@ var subCommands = map[string]*func([]string) error{
var beCLI func() // non-nil if CLI is linked in
func main() {
envknob.PanicIfAnyEnvCheckedInInit()
envknob.ApplyDiskConfig()
printVersion := false
flag.IntVar(&args.verbose, "verbose", 0, "log verbosity level; 0 is default, 1 or higher are increasingly verbose")
flag.BoolVar(&args.cleanup, "cleanup", false, "clean up system state and exit")
@@ -138,12 +157,13 @@ func main() {
flag.StringVar(&args.socksAddr, "socks5-server", "", `optional [ip]:port to run a SOCK5 server (e.g. "localhost:1080")`)
flag.StringVar(&args.httpProxyAddr, "outbound-http-proxy-listen", "", `optional [ip]:port to run an outbound HTTP proxy (e.g. "localhost:8080")`)
flag.StringVar(&args.tunname, "tun", defaultTunName(), `tunnel interface name; use "userspace-networking" (beta) to not use TUN`)
flag.Var(flagtype.PortValue(&args.port, 0), "port", "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
flag.Var(flagtype.PortValue(&args.port, defaultPort()), "port", "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
flag.StringVar(&args.statepath, "state", "", "absolute path of state file; use 'kube:<secret-name>' to use Kubernetes secrets or 'arn:aws:ssm:...' to store in AWS SSM; use 'mem:' to not store state and register as an emphemeral node. If empty and --statedir is provided, the default is <statedir>/tailscaled.state. Default: "+paths.DefaultTailscaledStateFile())
flag.StringVar(&args.statedir, "statedir", "", "path to directory for storage of config state, TLS certs, temporary incoming Taildrop files, etc. If empty, it's derived from --state when possible.")
flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket")
flag.StringVar(&args.birdSocketPath, "bird-socket", "", "path of the bird unix socket")
flag.BoolVar(&printVersion, "version", false, "print version information and exit")
flag.BoolVar(&args.disableLogs, "no-logs-no-support", false, "disable log uploads; this also disables any technical support")
if len(os.Args) > 0 && filepath.Base(os.Args[0]) == "tailscale" && beCLI != nil {
beCLI()
@@ -199,6 +219,10 @@ func main() {
args.statepath = paths.DefaultTailscaledStateFile()
}
if args.disableLogs {
envknob.SetNoLogsNoSupport()
}
if beWindowsSubprocess() {
return
}
@@ -302,6 +326,10 @@ func run() error {
pol.Shutdown(ctx)
}()
if err := envknob.ApplyDiskConfigError(); err != nil {
log.Printf("Error reading environment config: %v", err)
}
if isWindowsService() {
// Run the IPN server from the Windows service manager.
log.Printf("Running service...")
@@ -370,7 +398,7 @@ func run() error {
return fmt.Errorf("newNetstack: %w", err)
}
ns.ProcessLocalIPs = useNetstack
ns.ProcessSubnets = useNetstack || wrapNetstack
ns.ProcessSubnets = useNetstack || shouldWrapNetstack()
if useNetstack {
dialer.UseNetstackForIP = func(ip netip.Addr) bool {
@@ -471,8 +499,6 @@ func createEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer)
return nil, false, multierr.New(errs...)
}
var wrapNetstack = shouldWrapNetstack()
func shouldWrapNetstack() bool {
if v, ok := envknob.LookupBool("TS_DEBUG_WRAP_NETSTACK"); ok {
return v
@@ -481,7 +507,7 @@ func shouldWrapNetstack() bool {
return true
}
switch runtime.GOOS {
case "windows", "darwin", "freebsd":
case "windows", "darwin", "freebsd", "openbsd":
// Enable on Windows and tailscaled-on-macOS (this doesn't
// affect the GUI clients), and on FreeBSD.
return true
@@ -543,7 +569,7 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer, na
}
conf.DNS = d
conf.Router = r
if wrapNetstack {
if shouldWrapNetstack() {
conf.Router = netstack.NewSubnetRouterWrapper(conf.Router)
}
}

View File

@@ -7,7 +7,7 @@ After=network-pre.target NetworkManager.service systemd-resolved.service
[Service]
EnvironmentFile=/etc/default/tailscaled
ExecStartPre=/usr/sbin/tailscaled --cleanup
ExecStart=/usr/sbin/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/run/tailscale/tailscaled.sock --port $PORT $FLAGS
ExecStart=/usr/sbin/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/run/tailscale/tailscaled.sock --port=${PORT} $FLAGS
ExecStopPost=/usr/sbin/tailscaled --cleanup
Restart=on-failure

View File

@@ -197,6 +197,9 @@ func beWindowsSubprocess() bool {
log.Printf("Program starting: v%v: %#v", version.Long, os.Args)
log.Printf("subproc mode: logid=%v", logid)
if err := envknob.ApplyDiskConfigError(); err != nil {
log.Printf("Error reading environment config: %v", err)
}
go func() {
b := make([]byte, 16)
@@ -274,7 +277,7 @@ func startIPNServer(ctx context.Context, logid string) error {
dev.Close()
return nil, nil, fmt.Errorf("router: %w", err)
}
if wrapNetstack {
if shouldWrapNetstack() {
r = netstack.NewSubnetRouterWrapper(r)
}
d, err := dns.NewOSConfigurator(logf, devName)
@@ -301,7 +304,7 @@ func startIPNServer(ctx context.Context, logid string) error {
return nil, nil, fmt.Errorf("newNetstack: %w", err)
}
ns.ProcessLocalIPs = false
ns.ProcessSubnets = wrapNetstack
ns.ProcessSubnets = shouldWrapNetstack()
if err := ns.Start(); err != nil {
return nil, nil, fmt.Errorf("failed to start netstack: %w", err)
}

View File

@@ -1,4 +1,3 @@
src/wasm_exec.js
src/main.wasm
node_modules/
dist/
/dist
/pkg

View File

@@ -28,3 +28,22 @@ To serve them, run:
```
By default the build output is placed in the `dist/` directory and embedded in the binary, but this can be controlled by the `-distdir` flag. The `-addr` flag controls the interface and port that the serve listens on.
# Library / NPM Package
The client is also available as an NPM package. To build it, run:
```
./tool/go run ./cmd/tsconnect build-pkg
```
That places the output in the `pkg/` directory, which may then be uploaded to a package registry (or installed from the file path directly).
To do two-sided development (on both the NPM package and code that uses it), run:
```
./tool/go run ./cmd/tsconnect dev-pkg
```
This serves the module at http://localhost:9090/pkg/pkg.js and the generated wasm file at http://localhost:9090/pkg/main.wasm. The two files can be used as drop-in replacements for normal imports of the NPM module.

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"path"
"github.com/tailscale/hujson"
"tailscale.com/version"
)
func runBuildPkg() {
buildOptions, err := commonPkgSetup(prodMode)
if err != nil {
log.Fatalf("Cannot setup: %v", err)
}
log.Printf("Linting...\n")
if err := runYarn("lint"); err != nil {
log.Fatalf("Linting failed: %v", err)
}
if err := cleanDir(*pkgDir, "package.json"); err != nil {
log.Fatalf("Cannot clean %s: %v", *pkgDir, err)
}
buildOptions.Write = true
buildOptions.MinifyWhitespace = true
buildOptions.MinifyIdentifiers = true
buildOptions.MinifySyntax = true
runEsbuild(*buildOptions)
log.Printf("Generating types...\n")
if err := runYarn("pkg-types"); err != nil {
log.Fatalf("Type generation failed: %v", err)
}
if err := updateVersion(); err != nil {
log.Fatalf("Cannot update version: %v", err)
}
log.Printf("Built package version %s", version.Long)
}
func updateVersion() error {
packageJSONBytes, err := os.ReadFile("package.json.tmpl")
if err != nil {
return fmt.Errorf("Could not read package.json: %w", err)
}
var packageJSON map[string]any
packageJSONBytes, err = hujson.Standardize(packageJSONBytes)
if err != nil {
return fmt.Errorf("Could not standardize template package.json: %w", err)
}
if err := json.Unmarshal(packageJSONBytes, &packageJSON); err != nil {
return fmt.Errorf("Could not unmarshal package.json: %w", err)
}
packageJSON["version"] = version.Long
packageJSONBytes, err = json.MarshalIndent(packageJSON, "", " ")
if err != nil {
return fmt.Errorf("Could not marshal package.json: %w", err)
}
return os.WriteFile(path.Join(*pkgDir, "package.json"), packageJSONBytes, 0644)
}

View File

@@ -7,13 +7,11 @@ package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
esbuild "github.com/evanw/esbuild/pkg/api"
"tailscale.com/util/precompress"
)
@@ -28,7 +26,7 @@ func runBuild() {
log.Fatalf("Linting failed: %v", err)
}
if err := cleanDist(); err != nil {
if err := cleanDir(*distDir, "placeholder"); err != nil {
log.Fatalf("Cannot clean %s: %v", *distDir, err)
}
@@ -41,28 +39,14 @@ func runBuild() {
buildOptions.AssetNames = "[name]-[hash]"
buildOptions.Metafile = true
log.Printf("Running esbuild...\n")
result := esbuild.Build(*buildOptions)
if len(result.Errors) > 0 {
log.Printf("ESBuild Error:\n")
for _, e := range result.Errors {
log.Printf("%v", e)
}
log.Fatal("Build failed")
}
if len(result.Warnings) > 0 {
log.Printf("ESBuild Warnings:\n")
for _, w := range result.Warnings {
log.Printf("%v", w)
}
}
result := runEsbuild(*buildOptions)
// Preserve build metadata so we can extract hashed file names for serving.
metadataBytes, err := fixEsbuildMetadataPaths(result.Metafile)
if err != nil {
log.Fatalf("Cannot fix esbuild metadata paths: %v", err)
}
if err := ioutil.WriteFile(path.Join(*distDir, "/esbuild-metadata.json"), metadataBytes, 0666); err != nil {
if err := os.WriteFile(path.Join(*distDir, "/esbuild-metadata.json"), metadataBytes, 0666); err != nil {
log.Fatalf("Cannot write metadata: %v", err)
}
@@ -98,8 +82,6 @@ func fixEsbuildMetadataPaths(metadataStr string) ([]byte, error) {
return json.Marshal(metadata)
}
// cleanDist removes files from the dist build directory, except the placeholder
// one that we keep to make sure Git still creates the directory.
func cleanDist() error {
log.Printf("Cleaning %s...\n", *distDir)
files, err := os.ReadDir(*distDir)

View File

@@ -7,6 +7,7 @@ package main
import (
"fmt"
"log"
"net"
"os"
"os/exec"
"path"
@@ -16,6 +17,7 @@ import (
"time"
esbuild "github.com/evanw/esbuild/pkg/api"
"golang.org/x/exp/slices"
)
const (
@@ -32,73 +34,185 @@ func commonSetup(dev bool) (*esbuild.BuildOptions, error) {
return nil, fmt.Errorf("Cannot change cwd: %w", err)
}
}
if err := buildDeps(dev); err != nil {
return nil, fmt.Errorf("Cannot build deps: %w", err)
if err := installJSDeps(); err != nil {
return nil, fmt.Errorf("Cannot install JS deps: %w", err)
}
return &esbuild.BuildOptions{
EntryPoints: []string{"src/index.ts", "src/index.css"},
Loader: map[string]esbuild.Loader{".wasm": esbuild.LoaderFile},
EntryPoints: []string{"src/app/index.ts", "src/app/index.css"},
Outdir: *distDir,
Bundle: true,
Sourcemap: esbuild.SourceMapLinked,
LogLevel: esbuild.LogLevelInfo,
Define: map[string]string{"DEBUG": strconv.FormatBool(dev)},
Target: esbuild.ES2017,
Plugins: []esbuild.Plugin{{
Name: "tailscale-tailwind",
Setup: func(build esbuild.PluginBuild) {
setupEsbuildTailwind(build, dev)
Plugins: []esbuild.Plugin{
{
Name: "tailscale-tailwind",
Setup: func(build esbuild.PluginBuild) {
setupEsbuildTailwind(build, dev)
},
},
}},
{
Name: "tailscale-go-wasm-exec-js",
Setup: setupEsbuildWasmExecJS,
},
{
Name: "tailscale-wasm",
Setup: func(build esbuild.PluginBuild) {
setupEsbuildWasm(build, dev)
},
},
},
JSXMode: esbuild.JSXModeAutomatic,
}, nil
}
// buildDeps builds the static assets that are needed for the server (except for
// JS/CSS bundling, which is handled by esbuild).
func buildDeps(dev bool) error {
if err := copyWasmExec(); err != nil {
return fmt.Errorf("Cannot copy wasm_exec.js: %w", err)
func commonPkgSetup(dev bool) (*esbuild.BuildOptions, error) {
buildOptions, err := commonSetup(dev)
if err != nil {
return nil, err
}
if err := buildWasm(dev); err != nil {
return fmt.Errorf("Cannot build main.wasm: %w", err)
buildOptions.EntryPoints = []string{"src/pkg/pkg.ts", "src/pkg/pkg.css"}
buildOptions.Outdir = *pkgDir
buildOptions.Format = esbuild.FormatESModule
buildOptions.AssetNames = "[name]"
return buildOptions, nil
}
// cleanDir removes files from dirPath, except the ones specified by
// preserveFiles.
func cleanDir(dirPath string, preserveFiles ...string) error {
log.Printf("Cleaning %s...\n", dirPath)
files, err := os.ReadDir(dirPath)
if err != nil {
if os.IsNotExist(err) {
return os.MkdirAll(dirPath, 0755)
}
return err
}
if err := installJSDeps(); err != nil {
return fmt.Errorf("Cannot install JS deps: %w", err)
for _, file := range files {
if !slices.Contains(preserveFiles, file.Name()) {
if err := os.Remove(filepath.Join(dirPath, file.Name())); err != nil {
return err
}
}
}
return nil
}
// copyWasmExec grabs the current wasm_exec.js runtime helper library from the
// Go toolchain.
func copyWasmExec() error {
log.Printf("Copying wasm_exec.js...\n")
wasmExecSrcPath := filepath.Join(runtime.GOROOT(), "misc", "wasm", "wasm_exec.js")
wasmExecDstPath := filepath.Join("src", "wasm_exec.js")
contents, err := os.ReadFile(wasmExecSrcPath)
func runEsbuildServe(buildOptions esbuild.BuildOptions) {
host, portStr, err := net.SplitHostPort(*addr)
if err != nil {
return err
log.Fatalf("Cannot parse addr: %v", err)
}
return os.WriteFile(wasmExecDstPath, contents, 0600)
port, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
log.Fatalf("Cannot parse port: %v", err)
}
result, err := esbuild.Serve(esbuild.ServeOptions{
Port: uint16(port),
Host: host,
Servedir: "./",
}, buildOptions)
if err != nil {
log.Fatalf("Cannot start esbuild server: %v", err)
}
log.Printf("Listening on http://%s:%d\n", result.Host, result.Port)
result.Wait()
}
// buildWasm builds the Tailscale wasm binary and places it where the JS can
// load it.
func buildWasm(dev bool) error {
log.Printf("Building wasm...\n")
func runEsbuild(buildOptions esbuild.BuildOptions) esbuild.BuildResult {
log.Printf("Running esbuild...\n")
result := esbuild.Build(buildOptions)
if len(result.Errors) > 0 {
log.Printf("ESBuild Error:\n")
for _, e := range result.Errors {
log.Printf("%v", e)
}
log.Fatal("Build failed")
}
if len(result.Warnings) > 0 {
log.Printf("ESBuild Warnings:\n")
for _, w := range result.Warnings {
log.Printf("%v", w)
}
}
return result
}
// setupEsbuildWasmExecJS generates an esbuild plugin that serves the current
// wasm_exec.js runtime helper library from the Go toolchain.
func setupEsbuildWasmExecJS(build esbuild.PluginBuild) {
wasmExecSrcPath := filepath.Join(runtime.GOROOT(), "misc", "wasm", "wasm_exec.js")
build.OnResolve(esbuild.OnResolveOptions{
Filter: "./wasm_exec$",
}, func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) {
return esbuild.OnResolveResult{Path: wasmExecSrcPath}, nil
})
}
// setupEsbuildWasm generates an esbuild plugin that builds the Tailscale wasm
// binary and serves it as a file that the JS can load.
func setupEsbuildWasm(build esbuild.PluginBuild, dev bool) {
// Add a resolve hook to convince esbuild that the path exists.
build.OnResolve(esbuild.OnResolveOptions{
Filter: "./main.wasm$",
}, func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) {
return esbuild.OnResolveResult{
Path: "./src/main.wasm",
Namespace: "generated",
}, nil
})
build.OnLoad(esbuild.OnLoadOptions{
Filter: "./src/main.wasm$",
}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
contents, err := buildWasm(dev)
if err != nil {
return esbuild.OnLoadResult{}, fmt.Errorf("Cannot build main.wasm: %w", err)
}
contentsStr := string(contents)
return esbuild.OnLoadResult{
Contents: &contentsStr,
Loader: esbuild.LoaderFile,
}, nil
})
}
func buildWasm(dev bool) ([]byte, error) {
start := time.Now()
outputFile, err := os.CreateTemp("", "main.*.wasm")
if err != nil {
return nil, fmt.Errorf("Cannot create main.wasm output file: %w", err)
}
outputPath := outputFile.Name()
defer os.Remove(outputPath)
args := []string{"build", "-tags", "tailscale_go,osusergo,netgo,nethttpomithttp2,omitidna,omitpemdecrypt"}
if !dev {
if *devControl != "" {
return nil, fmt.Errorf("Development control URL can only be used in dev mode.")
}
// Omit long paths and debug symbols in release builds, to reduce the
// generated WASM binary size.
args = append(args, "-trimpath", "-ldflags", "-s -w")
} else if *devControl != "" {
args = append(args, "-ldflags", fmt.Sprintf("-X 'main.ControlURL=%v'", *devControl))
}
args = append(args, "-o", "src/main.wasm", "./wasm")
args = append(args, "-o", outputPath, "./wasm")
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...)
cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
err = cmd.Run()
if err != nil {
return nil, fmt.Errorf("Cannot build main.wasm: %w", err)
}
log.Printf("Built wasm in %v\n", time.Since(start))
return os.ReadFile(outputPath)
}
// installJSDeps installs the JavaScript dependencies specified by package.json
@@ -128,7 +242,7 @@ type EsbuildMetadata struct {
func setupEsbuildTailwind(build esbuild.PluginBuild, dev bool) {
build.OnLoad(esbuild.OnLoadOptions{
Filter: "./src/index.css$",
Filter: "./src/.*\\.css$",
}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
start := time.Now()
yarnArgs := []string{"--silent", "tailwind", "-i", args.Path}

17
cmd/tsconnect/dev-pkg.go Normal file
View File

@@ -0,0 +1,17 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"log"
)
func runDevPkg() {
buildOptions, err := commonPkgSetup(devMode)
if err != nil {
log.Fatalf("Cannot setup: %v", err)
}
runEsbuildServe(*buildOptions)
}

View File

@@ -6,10 +6,6 @@ package main
import (
"log"
"net"
"strconv"
esbuild "github.com/evanw/esbuild/pkg/api"
)
func runDev() {
@@ -17,22 +13,5 @@ func runDev() {
if err != nil {
log.Fatalf("Cannot setup: %v", err)
}
host, portStr, err := net.SplitHostPort(*addr)
if err != nil {
log.Fatalf("Cannot parse addr: %v", err)
}
port, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
log.Fatalf("Cannot parse port: %v", err)
}
result, err := esbuild.Serve(esbuild.ServeOptions{
Port: uint16(port),
Host: host,
Servedir: "./",
}, *buildOptions)
if err != nil {
log.Fatalf("Cannot start esbuild server: %v", err)
}
log.Printf("Listening on http://%s:%d\n", result.Host, result.Port)
result.Wait()
runEsbuildServe(*buildOptions)
}

View File

@@ -8,37 +8,13 @@
<script src="dist/index.js" defer></script>
</head>
<body class="flex flex-col h-screen overflow-hidden">
<!-- Placeholder so that we don't have an empty page while the JS loads.
It should match the markup generated by Header component. -->
<div class="bg-gray-100 border-b border-gray-200 pt-4 pb-2">
<header class="container mx-auto px-4 flex flex-row items-center">
<h1 class="text-3xl font-bold grow">Tailscale Connect</h1>
<div class="text-gray-600" id="state">Loading…</div>
<div class="text-gray-600">Loading…</div>
</header>
</div>
<div
id="content"
class="flex-grow flex flex-col justify-center overflow-hidden"
>
<form
id="ssh-form"
class="container mx-auto px-4 hidden flex justify-center"
>
<input type="text" class="input username" placeholder="Username" />
<div class="select-with-arrow mx-2">
<select class="select"></select>
</div>
<input
type="submit"
class="button bg-green-500 border-green-500 text-white hover:bg-green-600 hover:border-green-600"
value="SSH"
/>
</form>
<div id="no-ssh" class="container mx-auto px-4 hidden text-center">
None of your machines have
<a href="https://tailscale.com/kb/1193/tailscale-ssh/" class="link"
>Tailscale SSH</a
>
enabled. Give it a try!
</div>
</div>
</body>
</html>

View File

@@ -5,14 +5,18 @@
"devDependencies": {
"@types/golang-wasm-exec": "^1.15.0",
"@types/qrcode": "^1.4.2",
"dts-bundle-generator": "^6.12.0",
"preact": "^10.10.0",
"qrcode": "^1.5.0",
"tailwindcss": "^3.1.6",
"typescript": "^4.7.4",
"xterm": "^4.18.0",
"xterm-addon-fit": "^0.5.0"
"xterm": "5.0.0-beta.58",
"xterm-addon-fit": "^0.5.0",
"xterm-addon-web-links": "0.7.0-beta.6"
},
"scripts": {
"lint": "tsc --noEmit"
"lint": "tsc --noEmit",
"pkg-types": "dts-bundle-generator --inline-declare-global=true --no-banner -o pkg/pkg.d.ts src/pkg/pkg.ts"
},
"prettier": {
"semi": false,

View File

@@ -0,0 +1,17 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Template for the package.json that is generated by the build-pkg command.
// The version number will be replaced by the current Tailscale client version
// number.
{
"author": "Tailscale Inc.",
"description": "Tailscale Connect SDK",
"license": "BSD-3-Clause",
"name": "tailscale-connect",
"type": "module",
"main": "./pkg.js",
"types": "./pkg.d.ts",
"version": "AUTO_GENERATED"
}

View File

@@ -11,7 +11,6 @@ import (
"fmt"
"io"
"io/fs"
"io/ioutil"
"log"
"net/http"
"os"
@@ -75,7 +74,7 @@ func generateServeIndex(distFS fs.FS) ([]byte, error) {
return nil, fmt.Errorf("Could not open esbuild-metadata.json: %w", err)
}
defer esbuildMetadataFile.Close()
esbuildMetadataBytes, err := ioutil.ReadAll(esbuildMetadataFile)
esbuildMetadataBytes, err := io.ReadAll(esbuildMetadataFile)
if err != nil {
return nil, fmt.Errorf("Could not read esbuild-metadata.json: %w", err)
}
@@ -115,8 +114,8 @@ func generateServeIndex(distFS fs.FS) ([]byte, error) {
}
var entryPointsToDefaultDistPaths = map[string]string{
"src/index.css": "dist/index.css",
"src/index.ts": "dist/index.js",
"src/app/index.css": "dist/index.css",
"src/app/index.ts": "dist/index.js",
}
func handleServeDist(w http.ResponseWriter, r *http.Request, distFS fs.FS) {

View File

@@ -0,0 +1,129 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import { render, Component } from "preact"
import { URLDisplay } from "./url-display"
import { Header } from "./header"
import { GoPanicDisplay } from "./go-panic-display"
import { SSH } from "./ssh"
type AppState = {
ipn?: IPN
ipnState: IPNState
netMap?: IPNNetMap
browseToURL?: string
goPanicError?: string
}
class App extends Component<{}, AppState> {
state: AppState = { ipnState: "NoState" }
#goPanicTimeout?: number
render() {
const { ipn, ipnState, goPanicError, netMap, browseToURL } = this.state
let goPanicDisplay
if (goPanicError) {
goPanicDisplay = (
<GoPanicDisplay error={goPanicError} dismiss={this.clearGoPanic} />
)
}
let urlDisplay
if (browseToURL) {
urlDisplay = <URLDisplay url={browseToURL} />
}
let machineAuthInstructions
if (ipnState === "NeedsMachineAuth") {
machineAuthInstructions = (
<div class="container mx-auto px-4 text-center">
An administrator needs to authorize this device.
</div>
)
}
let ssh
if (ipn && ipnState === "Running" && netMap) {
ssh = <SSH netMap={netMap} ipn={ipn} />
}
return (
<>
<Header state={ipnState} ipn={ipn} />
{goPanicDisplay}
<div class="flex-grow flex flex-col justify-center overflow-hidden">
{urlDisplay}
{machineAuthInstructions}
{ssh}
</div>
</>
)
}
runWithIPN(ipn: IPN) {
this.setState({ ipn }, () => {
ipn.run({
notifyState: this.handleIPNState,
notifyNetMap: this.handleNetMap,
notifyBrowseToURL: this.handleBrowseToURL,
notifyPanicRecover: this.handleGoPanic,
})
})
}
handleIPNState = (state: IPNState) => {
const { ipn } = this.state
this.setState({ ipnState: state })
if (state === "NeedsLogin") {
ipn?.login()
} else if (["Running", "NeedsMachineAuth"].includes(state)) {
this.setState({ browseToURL: undefined })
}
}
handleNetMap = (netMapStr: string) => {
const netMap = JSON.parse(netMapStr) as IPNNetMap
if (DEBUG) {
console.log("Received net map: " + JSON.stringify(netMap, null, 2))
}
this.setState({ netMap })
}
handleBrowseToURL = (url: string) => {
if (this.state.ipnState === "Running") {
// Ignore URL requests if we're already running -- it's most likely an
// SSH check mode trigger and we already linkify the displayed URL
// in the terminal.
return
}
this.setState({ browseToURL: url })
}
handleGoPanic = (error: string) => {
if (DEBUG) {
console.error("Go panic", error)
}
this.setState({ goPanicError: error })
if (this.#goPanicTimeout) {
window.clearTimeout(this.#goPanicTimeout)
}
this.#goPanicTimeout = window.setTimeout(this.clearGoPanic, 10000)
}
clearGoPanic = () => {
window.clearTimeout(this.#goPanicTimeout)
this.#goPanicTimeout = undefined
this.setState({ goPanicError: undefined })
}
}
export function renderApp(): Promise<App> {
return new Promise((resolve) => {
render(
<App ref={(app) => (app ? resolve(app) : undefined)} />,
document.body
)
})
}

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
export function GoPanicDisplay({
error,
dismiss,
}: {
error: string
dismiss: () => void
}) {
return (
<div
class="rounded bg-red-500 p-2 absolute top-2 right-2 text-white font-bold text-right cursor-pointer"
onClick={dismiss}
>
Tailscale has encountered an error.
<div class="text-sm font-normal">Click to reload</div>
</div>
)
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
export function Header({ state, ipn }: { state: IPNState; ipn?: IPN }) {
const stateText = STATE_LABELS[state]
let logoutButton
if (state === "Running") {
logoutButton = (
<button
class="button bg-gray-500 border-gray-500 text-white hover:bg-gray-600 hover:border-gray-600 ml-2 font-bold"
onClick={() => ipn?.logout()}
>
Logout
</button>
)
}
return (
<div class="bg-gray-100 border-b border-gray-200 pt-4 pb-2">
<header class="container mx-auto px-4 flex flex-row items-center">
<h1 class="text-3xl font-bold grow">Tailscale Connect</h1>
<div class="text-gray-600">{stateText}</div>
{logoutButton}
</header>
</div>
)
}
const STATE_LABELS = {
NoState: "Initializing…",
InUseOtherUser: "In-use by another user",
NeedsLogin: "Needs login",
NeedsMachineAuth: "Needs authorization",
Stopped: "Stopped",
Starting: "Starting…",
Running: "Running",
} as const

View File

@@ -73,7 +73,3 @@
background-color: currentColor;
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
}
body.ssh-active #ssh-form {
@apply hidden;
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import "../wasm_exec"
import wasmUrl from "./main.wasm"
import { sessionStateStorage } from "../lib/js-state-store"
import { renderApp } from "./app"
async function main() {
const app = await renderApp()
const go = new Go()
const wasmInstance = await WebAssembly.instantiateStreaming(
fetch(`./dist/${wasmUrl}`),
go.importObject
)
// The Go process should never exit, if it does then it's an unhandled panic.
go.run(wasmInstance.instance).then(() =>
app.handleGoPanic("Unexpected shutdown")
)
const params = new URLSearchParams(window.location.search)
const authKey = params.get("authkey") ?? undefined
const ipn = newIPN({
// Persist IPN state in sessionStorage in development, so that we don't need
// to re-authorize every time we reload the page.
stateStorage: DEBUG ? sessionStateStorage : undefined,
// authKey allows for an auth key to be
// specified as a url param which automatically
// authorizes the client for use.
authKey: DEBUG ? authKey : undefined,
})
app.runWithIPN(ipn)
}
main()

View File

@@ -0,0 +1,153 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import { useState, useCallback, useMemo, useEffect, useRef } from "preact/hooks"
import { createPortal } from "preact/compat"
import type { VNode } from "preact"
import { runSSHSession, SSHSessionDef } from "../lib/ssh"
export function SSH({ netMap, ipn }: { netMap: IPNNetMap; ipn: IPN }) {
const [sshSessionDef, setSSHSessionDef] = useState<SSHFormSessionDef | null>(
null
)
const clearSSHSessionDef = useCallback(() => setSSHSessionDef(null), [])
if (sshSessionDef) {
const sshSession = (
<SSHSession def={sshSessionDef} ipn={ipn} onDone={clearSSHSessionDef} />
)
if (sshSessionDef.newWindow) {
return <NewWindow close={clearSSHSessionDef}>{sshSession}</NewWindow>
}
return sshSession
}
const sshPeers = netMap.peers.filter(
(p) => p.tailscaleSSHEnabled && p.online !== false
)
if (sshPeers.length == 0) {
return <NoSSHPeers />
}
return <SSHForm sshPeers={sshPeers} onSubmit={setSSHSessionDef} />
}
type SSHFormSessionDef = SSHSessionDef & { newWindow?: boolean }
function SSHSession({
def,
ipn,
onDone,
}: {
def: SSHSessionDef
ipn: IPN
onDone: () => void
}) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if (ref.current) {
runSSHSession(ref.current, def, ipn, onDone, (err) => console.error(err))
}
}, [ref])
return <div class="flex-grow bg-black p-2 overflow-hidden" ref={ref} />
}
function NoSSHPeers() {
return (
<div class="container mx-auto px-4 text-center">
None of your machines have
<a href="https://tailscale.com/kb/1193/tailscale-ssh/" class="link">
Tailscale SSH
</a>
enabled. Give it a try!
</div>
)
}
function SSHForm({
sshPeers,
onSubmit,
}: {
sshPeers: IPNNetMapPeerNode[]
onSubmit: (def: SSHFormSessionDef) => void
}) {
sshPeers = sshPeers.slice().sort((a, b) => a.name.localeCompare(b.name))
const [username, setUsername] = useState("")
const [hostname, setHostname] = useState(sshPeers[0].name)
return (
<form
class="container mx-auto px-4 flex justify-center"
onSubmit={(e) => {
e.preventDefault()
onSubmit({ username, hostname })
}}
>
<input
type="text"
class="input username"
placeholder="Username"
onChange={(e) => setUsername(e.currentTarget.value)}
/>
<div class="select-with-arrow mx-2">
<select
class="select"
onChange={(e) => setHostname(e.currentTarget.value)}
>
{sshPeers.map((p) => (
<option key={p.nodeKey}>{p.name.split(".")[0]}</option>
))}
</select>
</div>
<input
type="submit"
class="button bg-green-500 border-green-500 text-white hover:bg-green-600 hover:border-green-600"
value="SSH"
onClick={(e) => {
if (e.altKey) {
e.preventDefault()
e.stopPropagation()
onSubmit({ username, hostname, newWindow: true })
}
}}
/>
</form>
)
}
const NewWindow = ({
children,
close,
}: {
children: VNode
close: () => void
}) => {
const newWindow = useMemo(() => {
const newWindow = window.open(undefined, undefined, "width=600,height=400")
if (newWindow) {
const containerNode = newWindow.document.createElement("div")
containerNode.className = "h-screen flex flex-col overflow-hidden"
newWindow.document.body.appendChild(containerNode)
for (const linkNode of document.querySelectorAll(
"head link[rel=stylesheet]"
)) {
const newLink = document.createElement("link")
newLink.rel = "stylesheet"
newLink.href = (linkNode as HTMLLinkElement).href
newWindow.document.head.appendChild(newLink)
}
}
return newWindow
}, [])
if (!newWindow) {
console.error("Could not open window")
return null
}
newWindow.onbeforeunload = () => {
close()
}
useEffect(() => () => newWindow.close(), [])
return createPortal(children, newWindow.document.body.lastChild as Element)
}

View File

@@ -0,0 +1,32 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import { useState } from "preact/hooks"
import * as qrcode from "qrcode"
export function URLDisplay({ url }: { url: string }) {
const [dataURL, setDataURL] = useState("")
qrcode.toDataURL(url, { width: 512 }, (err, dataURL) => {
if (err) {
console.error("Error generating QR code", err)
} else {
setDataURL(dataURL)
}
})
return (
<div class="flex flex-col items-center justify-items-center">
<a href={url} class="link" target="_blank">
<img
src={dataURL}
class="mx-auto"
width="256"
height="256"
alt="QR Code of URL"
/>
{url}
</a>
</div>
)
}

View File

@@ -1,58 +0,0 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import "./wasm_exec"
import wasmUrl from "./main.wasm"
import { notifyState, notifyNetMap, notifyBrowseToURL } from "./notifier"
import { sessionStateStorage } from "./js-state-store"
const go = new Go()
WebAssembly.instantiateStreaming(
fetch(`./dist/${wasmUrl}`),
go.importObject
).then((result) => {
// The Go process should never exit, if it does then it's an unhandled panic.
go.run(result.instance).then(() => handleGoPanic())
const ipn = newIPN({
// Persist IPN state in sessionStorage in development, so that we don't need
// to re-authorize every time we reload the page.
stateStorage: DEBUG ? sessionStateStorage : undefined,
})
ipn.run({
notifyState: notifyState.bind(null, ipn),
notifyNetMap: notifyNetMap.bind(null, ipn),
notifyBrowseToURL: notifyBrowseToURL.bind(null, ipn),
notifyPanicRecover: handleGoPanic,
})
})
function handleGoPanic(err?: string) {
if (DEBUG && err) {
console.error("Go panic", err)
}
if (panicNode) {
panicNode.remove()
}
panicNode = document.createElement("div")
panicNode.className =
"rounded bg-red-500 p-2 absolute top-2 right-2 text-white font-bold text-right cursor-pointer"
panicNode.textContent = "Tailscale has encountered an error."
const panicDetailNode = document.createElement("div")
panicDetailNode.className = "text-sm font-normal"
panicDetailNode.textContent = "Click to reload"
panicNode.appendChild(panicDetailNode)
panicNode.addEventListener("click", () => location.reload(), {
once: true,
})
document.body.appendChild(panicNode)
setTimeout(() => {
panicNode!.remove()
}, 10000)
}
let panicNode: HTMLDivElement | undefined
export function getContentNode(): HTMLDivElement {
return document.querySelector("#content") as HTMLDivElement
}

View File

@@ -0,0 +1,80 @@
import { Terminal, ITerminalOptions } from "xterm"
import { FitAddon } from "xterm-addon-fit"
import { WebLinksAddon } from "xterm-addon-web-links"
export type SSHSessionDef = {
username: string
hostname: string
/** Defaults to 5 seconds */
timeoutSeconds?: number
}
export function runSSHSession(
termContainerNode: HTMLDivElement,
def: SSHSessionDef,
ipn: IPN,
onDone: () => void,
onError?: (err: string) => void,
terminalOptions?: ITerminalOptions
) {
const parentWindow = termContainerNode.ownerDocument.defaultView ?? window
const term = new Terminal({
cursorBlink: true,
allowProposedApi: true,
...terminalOptions,
})
const fitAddon = new FitAddon()
term.loadAddon(fitAddon)
term.open(termContainerNode)
fitAddon.fit()
const webLinksAddon = new WebLinksAddon((event, uri) =>
event.view?.open(uri, "_blank", "noopener")
)
term.loadAddon(webLinksAddon)
let onDataHook: ((data: string) => void) | undefined
term.onData((e) => {
onDataHook?.(e)
})
term.focus()
let resizeObserver: ResizeObserver | undefined
let handleBeforeUnload: ((e: BeforeUnloadEvent) => void) | undefined
const sshSession = ipn.ssh(def.hostname, def.username, {
writeFn(input) {
term.write(input)
},
writeErrorFn(err) {
onError?.(err)
term.write(err)
},
setReadFn(hook) {
onDataHook = hook
},
rows: term.rows,
cols: term.cols,
onDone() {
resizeObserver?.disconnect()
term.dispose()
if (handleBeforeUnload) {
parentWindow.removeEventListener("beforeunload", handleBeforeUnload)
}
onDone()
},
timeoutSeconds: def.timeoutSeconds,
})
// Make terminal and SSH session track the size of the containing DOM node.
resizeObserver = new parentWindow.ResizeObserver(() => fitAddon.fit())
resizeObserver.observe(termContainerNode)
term.onResize(({ rows, cols }) => sshSession.resize(rows, cols))
// Close the session if the user closes the window without an explicit
// exit.
handleBeforeUnload = () => sshSession.close()
parentWindow.addEventListener("beforeunload", handleBeforeUnload)
}

View File

@@ -1,74 +0,0 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import * as qrcode from "qrcode"
import { getContentNode } from "./index"
export async function showLoginURL(url: string) {
if (loginNode) {
loginNode.remove()
}
loginNode = document.createElement("div")
loginNode.className = "flex flex-col items-center justify-items-center"
const linkNode = document.createElement("a")
linkNode.className = "link"
linkNode.href = url
linkNode.target = "_blank"
loginNode.appendChild(linkNode)
try {
const dataURL = await qrcode.toDataURL(url, { width: 512 })
const imageNode = document.createElement("img")
imageNode.className = "mx-auto"
imageNode.src = dataURL
imageNode.width = 256
imageNode.height = 256
linkNode.appendChild(imageNode)
} catch (err) {
console.error("Could not generate QR code:", err)
}
linkNode.appendChild(document.createTextNode(url))
getContentNode().appendChild(loginNode)
}
export function hideLoginURL() {
if (!loginNode) {
return
}
loginNode.remove()
loginNode = undefined
}
let loginNode: HTMLDivElement | undefined
export function showLogoutButton(ipn: IPN) {
if (logoutButtonNode) {
logoutButtonNode.remove()
}
logoutButtonNode = document.createElement("button")
logoutButtonNode.className =
"button bg-gray-500 border-gray-500 text-white hover:bg-gray-600 hover:border-gray-600 ml-2 font-bold"
logoutButtonNode.textContent = "Logout"
logoutButtonNode.addEventListener(
"click",
() => {
ipn.logout()
},
{ once: true }
)
const headerNode = document.getElementsByTagName("header")[0]!
headerNode.appendChild(logoutButtonNode)
}
export function hideLogoutButton() {
if (!logoutButtonNode) {
return
}
logoutButtonNode.remove()
logoutButtonNode = undefined
}
let logoutButtonNode: HTMLButtonElement | undefined

View File

@@ -1,65 +0,0 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import {
showLoginURL,
hideLoginURL,
showLogoutButton,
hideLogoutButton,
} from "./login"
import { showSSHForm, hideSSHForm } from "./ssh"
import { IPNState } from "./wasm_js"
/**
* @fileoverview Notification callback functions (bridged from ipn.Notify)
*/
export function notifyState(ipn: IPN, state: IPNState) {
let stateLabel
switch (state) {
case IPNState.NoState:
stateLabel = "Initializing…"
break
case IPNState.InUseOtherUser:
stateLabel = "In-use by another user"
break
case IPNState.NeedsLogin:
stateLabel = "Needs Login"
hideLogoutButton()
hideSSHForm()
ipn.login()
break
case IPNState.NeedsMachineAuth:
stateLabel = "Needs authorization"
break
case IPNState.Stopped:
stateLabel = "Stopped"
hideLogoutButton()
hideSSHForm()
break
case IPNState.Starting:
stateLabel = "Starting…"
break
case IPNState.Running:
stateLabel = "Running"
hideLoginURL()
showLogoutButton(ipn)
break
}
const stateNode = document.querySelector("#state") as HTMLDivElement
stateNode.textContent = stateLabel ?? ""
}
export function notifyNetMap(ipn: IPN, netMapStr: string) {
const netMap = JSON.parse(netMapStr) as IPNNetMap
if (DEBUG) {
console.log("Received net map: " + JSON.stringify(netMap, null, 2))
}
showSSHForm(netMap.peers, ipn)
}
export function notifyBrowseToURL(ipn: IPN, url: string) {
showLoginURL(url)
}

View File

@@ -0,0 +1,9 @@
/* Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. */
/* Use of this source code is governed by a BSD-style */
/* license that can be found in the LICENSE file. */
@import "xterm/css/xterm.css";
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Type definitions need to be manually imported for dts-bundle-generator to
// discover them.
/// <reference path="../types/esbuild.d.ts" />
/// <reference path="../types/wasm_js.d.ts" />
import "../wasm_exec"
import wasmURL from "./main.wasm"
/**
* Superset of the IPNConfig type, with additional configuration that is
* needed for the package to function.
*/
type IPNPackageConfig = IPNConfig & {
// Auth key used to intitialize the Tailscale client (required)
authKey: string
// URL of the main.wasm file that is included in the page, if it is not
// accessible via a relative URL.
wasmURL?: string
// Funtion invoked if the Go process panics or unexpectedly exits.
panicHandler: (err: string) => void
}
export async function createIPN(config: IPNPackageConfig): Promise<IPN> {
const go = new Go()
const wasmInstance = await WebAssembly.instantiateStreaming(
fetch(config.wasmURL ?? wasmURL),
go.importObject
)
// The Go process should never exit, if it does then it's an unhandled panic.
go.run(wasmInstance.instance).then(() =>
config.panicHandler("Unexpected shutdown")
)
return newIPN(config)
}
export { runSSHSession } from "../lib/ssh"

View File

@@ -1,98 +0,0 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import { Terminal } from "xterm"
import { FitAddon } from "xterm-addon-fit"
import { getContentNode } from "./index"
export function showSSHForm(peers: IPNNetMapPeerNode[], ipn: IPN) {
const formNode = document.querySelector("#ssh-form") as HTMLDivElement
const noSSHNode = document.querySelector("#no-ssh") as HTMLDivElement
const sshPeers = peers.filter(
(p) => p.tailscaleSSHEnabled && p.online !== false
)
if (sshPeers.length == 0) {
formNode.classList.add("hidden")
noSSHNode.classList.remove("hidden")
return
}
sshPeers.sort((a, b) => a.name.localeCompare(b.name))
const selectNode = formNode.querySelector("select")!
selectNode.innerHTML = ""
for (const p of sshPeers) {
const option = document.createElement("option")
option.textContent = p.name.split(".")[0]
option.value = p.name
selectNode.appendChild(option)
}
const usernameNode = formNode.querySelector(".username") as HTMLInputElement
formNode.onsubmit = (e) => {
e.preventDefault()
const hostname = selectNode.value
ssh(hostname, usernameNode.value, ipn)
}
noSSHNode.classList.add("hidden")
formNode.classList.remove("hidden")
}
export function hideSSHForm() {
const formNode = document.querySelector("#ssh-form") as HTMLDivElement
formNode.classList.add("hidden")
}
function ssh(hostname: string, username: string, ipn: IPN) {
document.body.classList.add("ssh-active")
const termContainerNode = document.createElement("div")
termContainerNode.className = "flex-grow bg-black p-2 overflow-hidden"
getContentNode().appendChild(termContainerNode)
const term = new Terminal({
cursorBlink: true,
})
const fitAddon = new FitAddon()
term.loadAddon(fitAddon)
term.open(termContainerNode)
fitAddon.fit()
let onDataHook: ((data: string) => void) | undefined
term.onData((e) => {
onDataHook?.(e)
})
term.focus()
const sshSession = ipn.ssh(hostname, username, {
writeFn: (input) => term.write(input),
setReadFn: (hook) => (onDataHook = hook),
rows: term.rows,
cols: term.cols,
onDone: () => {
resizeObserver.disconnect()
term.dispose()
termContainerNode.remove()
document.body.classList.remove("ssh-active")
window.removeEventListener("beforeunload", beforeUnloadListener)
},
})
// Make terminal and SSH session track the size of the containing DOM node.
const resizeObserver = new ResizeObserver((entries) => {
fitAddon.fit()
})
resizeObserver.observe(termContainerNode)
term.onResize(({ rows, cols }) => {
sshSession.resize(rows, cols)
})
// Close the session if the user closes the window without an explicit
// exit.
const beforeUnloadListener = () => {
sshSession.close()
}
window.addEventListener("beforeunload", beforeUnloadListener)
}

View File

@@ -4,8 +4,7 @@
/**
* @fileoverview Type definitions for types exported by the wasm_js.go Go
* module. Not actually a .d.ts file so that we can use enums from it in
* esbuild's simplified TypeScript compiler (see https://github.com/evanw/esbuild/issues/2298#issuecomment-1146378367)
* module.
*/
declare global {
@@ -20,15 +19,16 @@ declare global {
username: string,
termConfig: {
writeFn: (data: string) => void
writeErrorFn: (err: string) => void
setReadFn: (readFn: (data: string) => void) => void
rows: number
cols: number
/** Defaults to 5 seconds */
timeoutSeconds?: number
onDone: () => void
}
): IPNSSHSession
fetch(
url: string
): Promise<{
fetch(url: string): Promise<{
status: number
statusText: string
text: () => Promise<string>
@@ -47,6 +47,9 @@ declare global {
type IPNConfig = {
stateStorage?: IPNStateStorage
authKey?: string
controlURL?: string
hostname?: string
}
type IPNCallbacks = {
@@ -76,23 +79,23 @@ declare global {
online?: boolean
tailscaleSSHEnabled: boolean
}
/** Mirrors values from ipn/backend.go */
type IPNState =
| "NoState"
| "InUseOtherUser"
| "NeedsLogin"
| "NeedsMachineAuth"
| "Stopped"
| "Starting"
| "Running"
/** Mirrors values from MachineStatus in tailcfg.go */
type IPNMachineStatus =
| "MachineUnknown"
| "MachineUnauthorized"
| "MachineAuthorized"
| "MachineInvalid"
}
/** Mirrors values from ipn/backend.go */
export const enum IPNState {
NoState = 0,
InUseOtherUser = 1,
NeedsLogin = 2,
NeedsMachineAuth = 3,
Stopped = 4,
Starting = 5,
Running = 6,
}
/** Mirrors values from MachineStatus in tailcfg.go */
export const enum IPNMachineStatus {
MachineUnknown = 0,
MachineUnauthorized = 1,
MachineAuthorized = 2,
MachineInvalid = 3,
}
export {}

View File

@@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./index.html", "./src/**/*.ts"],
content: ["./index.html", "./src/**/*.ts", "./src/**/*.tsx"],
theme: {
extend: {},
},

View File

@@ -6,7 +6,9 @@
"isolatedModules": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true
"sourceMap": true,
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]

View File

@@ -20,8 +20,10 @@ import (
var (
addr = flag.String("addr", ":9090", "address to listen on")
distDir = flag.String("distdir", "./dist", "path of directory to place build output in")
pkgDir = flag.String("pkgdir", "./pkg", "path of directory to place NPM package build output in")
yarnPath = flag.String("yarnpath", "../../tool/yarn", "path yarn executable used to install JavaScript dependencies")
fastCompression = flag.Bool("fast-compression", false, "Use faster compression when building, to speed up build time. Meant to iterative/debugging use only.")
devControl = flag.String("dev-control", "", "URL of a development control server to be used with dev. If provided without specifying dev, an error will be returned.")
)
func main() {
@@ -34,8 +36,12 @@ func main() {
switch flag.Arg(0) {
case "dev":
runDev()
case "dev-pkg":
runDevPkg()
case "build":
runBuild()
case "build-pkg":
runBuildPkg()
case "serve":
runServe()
default:

View File

@@ -31,16 +31,20 @@ import (
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store/mem"
"tailscale.com/logpolicy"
"tailscale.com/logtail"
"tailscale.com/net/netns"
"tailscale.com/net/tsdial"
"tailscale.com/safesocket"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/wgengine"
"tailscale.com/wgengine/netstack"
"tailscale.com/words"
)
// ControlURL defines the URL to be used for connection to Control.
var ControlURL = ipn.DefaultControlURL
func main() {
js.Global().Set("newIPN", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
@@ -56,7 +60,41 @@ func main() {
func newIPN(jsConfig js.Value) map[string]any {
netns.SetEnabled(false)
var logf logger.Logf = log.Printf
var store ipn.StateStore
if jsStateStorage := jsConfig.Get("stateStorage"); !jsStateStorage.IsUndefined() {
store = &jsStateStore{jsStateStorage}
} else {
store = new(mem.Store)
}
controlURL := ControlURL
if jsControlURL := jsConfig.Get("controlURL"); jsControlURL.Type() == js.TypeString {
controlURL = jsControlURL.String()
}
var authKey string
if jsAuthKey := jsConfig.Get("authKey"); jsAuthKey.Type() == js.TypeString {
authKey = jsAuthKey.String()
}
var hostname string
if jsHostname := jsConfig.Get("hostname"); jsHostname.Type() == js.TypeString {
hostname = jsHostname.String()
} else {
hostname = generateHostname()
}
lpc := getOrCreateLogPolicyConfig(store)
c := logtail.Config{
Collection: lpc.Collection,
PrivateID: lpc.PrivateID,
// NewZstdEncoder is intentionally not passed in, compressed requests
// set HTTP headers that are not supported by the no-cors fetching mode.
HTTPC: &http.Client{Transport: &noCORSTransport{http.DefaultTransport}},
}
logtail := logtail.NewLogger(c, log.Printf)
logf := logtail.Logf
dialer := new(tsdial.Dialer)
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
@@ -86,14 +124,7 @@ func newIPN(jsConfig js.Value) map[string]any {
return ns.DialContextTCP(ctx, dst)
}
jsStateStorage := jsConfig.Get("stateStorage")
var store ipn.StateStore
if jsStateStorage.IsUndefined() {
store = new(mem.Store)
} else {
store = &jsStateStore{jsStateStorage}
}
srv, err := ipnserver.New(log.Printf, "some-logid", store, eng, dialer, nil, ipnserver.Options{
srv, err := ipnserver.New(logf, lpc.PublicID.String(), store, eng, dialer, nil, ipnserver.Options{
SurviveDisconnects: true,
LoginFlags: controlclient.LoginEphemeral,
})
@@ -104,9 +135,12 @@ func newIPN(jsConfig js.Value) map[string]any {
ns.SetLocalBackend(lb)
jsIPN := &jsIPN{
dialer: dialer,
srv: srv,
lb: lb,
dialer: dialer,
srv: srv,
lb: lb,
controlURL: controlURL,
authKey: authKey,
hostname: hostname,
}
return map[string]any{
@@ -162,14 +196,34 @@ func newIPN(jsConfig js.Value) map[string]any {
}
type jsIPN struct {
dialer *tsdial.Dialer
srv *ipnserver.Server
lb *ipnlocal.LocalBackend
dialer *tsdial.Dialer
srv *ipnserver.Server
lb *ipnlocal.LocalBackend
controlURL string
authKey string
hostname string
}
var jsIPNState = map[ipn.State]string{
ipn.NoState: "NoState",
ipn.InUseOtherUser: "InUseOtherUser",
ipn.NeedsLogin: "NeedsLogin",
ipn.NeedsMachineAuth: "NeedsMachineAuth",
ipn.Stopped: "Stopped",
ipn.Starting: "Starting",
ipn.Running: "Running",
}
var jsMachineStatus = map[tailcfg.MachineStatus]string{
tailcfg.MachineUnknown: "MachineUnknown",
tailcfg.MachineUnauthorized: "MachineUnauthorized",
tailcfg.MachineAuthorized: "MachineAuthorized",
tailcfg.MachineInvalid: "MachineInvalid",
}
func (i *jsIPN) run(jsCallbacks js.Value) {
notifyState := func(state ipn.State) {
jsCallbacks.Call("notifyState", int(state))
jsCallbacks.Call("notifyState", jsIPNState[state])
}
notifyState(ipn.NoState)
@@ -188,7 +242,7 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
if n.State != nil {
notifyState(*n.State)
}
if nm := n.NetMap; nm != nil && i.lb.State() == ipn.Running {
if nm := n.NetMap; nm != nil {
jsNetMap := jsNetMap{
Self: jsNetMapSelfNode{
jsNetMapNode: jsNetMapNode{
@@ -197,7 +251,7 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
NodeKey: nm.NodeKey.String(),
MachineKey: nm.MachineKey.String(),
},
MachineStatus: int(nm.MachineStatus),
MachineStatus: jsMachineStatus[nm.MachineStatus],
},
Peers: mapSlice(nm.Peers, func(p *tailcfg.Node) jsNetMapPeerNode {
name := p.Name
@@ -232,12 +286,13 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
err := i.lb.Start(ipn.Options{
StateKey: "wasm",
UpdatePrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL,
ControlURL: i.controlURL,
RouteAll: false,
AllowSingleHosts: true,
WantRunning: true,
Hostname: generateHostname(),
Hostname: i.hostname,
},
AuthKey: i.authKey,
})
if err != nil {
log.Printf("Start error: %v", err)
@@ -294,24 +349,29 @@ type jsSSHSession struct {
username string
termConfig js.Value
session *ssh.Session
pendingResizeRows int
pendingResizeCols int
}
func (s *jsSSHSession) Run() {
writeFn := s.termConfig.Get("writeFn")
writeErrorFn := s.termConfig.Get("writeErrorFn")
setReadFn := s.termConfig.Get("setReadFn")
rows := s.termConfig.Get("rows").Int()
cols := s.termConfig.Get("cols").Int()
timeoutSeconds := 5.0
if jsTimeoutSeconds := s.termConfig.Get("timeoutSeconds"); jsTimeoutSeconds.Type() == js.TypeNumber {
timeoutSeconds = jsTimeoutSeconds.Float()
}
onDone := s.termConfig.Get("onDone")
defer onDone.Invoke()
write := func(s string) {
writeFn.Invoke(s)
}
writeError := func(label string, err error) {
write(fmt.Sprintf("%s Error: %v\r\n", label, err))
writeErrorFn.Invoke(fmt.Sprintf("%s Error: %v\r\n", label, err))
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSeconds*float64(time.Second)))
defer cancel()
c, err := s.jsIPN.dialer.UserDial(ctx, "tcp", net.JoinHostPort(s.host, "22"))
if err != nil {
@@ -331,7 +391,6 @@ func (s *jsSSHSession) Run() {
return
}
defer sshConn.Close()
write("SSH Connected\r\n")
sshClient := ssh.NewClient(sshConn, nil, nil)
defer sshClient.Close()
@@ -342,7 +401,6 @@ func (s *jsSSHSession) Run() {
return
}
s.session = session
write("Session Established\r\n")
defer session.Close()
stdin, err := session.StdinPipe()
@@ -363,6 +421,14 @@ func (s *jsSSHSession) Run() {
return nil
}))
// We might have gotten a resize notification since we started opening the
// session, pick up the latest size.
if s.pendingResizeRows != 0 {
rows = s.pendingResizeRows
}
if s.pendingResizeCols != 0 {
cols = s.pendingResizeCols
}
err = session.RequestPty("xterm", rows, cols, ssh.TerminalModes{})
if err != nil {
@@ -388,6 +454,11 @@ func (s *jsSSHSession) Close() error {
}
func (s *jsSSHSession) Resize(rows, cols int) error {
if s.session == nil {
s.pendingResizeRows = rows
s.pendingResizeCols = cols
return nil
}
return s.session.WindowChange(rows, cols)
}
@@ -445,7 +516,7 @@ type jsNetMapNode struct {
type jsNetMapSelfNode struct {
jsNetMapNode
MachineStatus int `json:"machineStatus"`
MachineStatus string `json:"machineStatus"`
}
type jsNetMapPeerNode struct {
@@ -527,3 +598,40 @@ func makePromise(f func() (any, error)) js.Value {
promiseConstructor := js.Global().Get("Promise")
return promiseConstructor.New(handler)
}
const logPolicyStateKey = "log-policy"
func getOrCreateLogPolicyConfig(state ipn.StateStore) *logpolicy.Config {
if configBytes, err := state.ReadState(logPolicyStateKey); err == nil {
if config, err := logpolicy.ConfigFromBytes(configBytes); err == nil {
return config
} else {
log.Printf("Could not parse log policy config: %v", err)
}
} else if err != ipn.ErrStateNotExist {
log.Printf("Could not get log policy config from state store: %v", err)
}
config := logpolicy.NewConfig(logtail.CollectionNode)
if err := state.WriteState(logPolicyStateKey, config.ToBytes()); err != nil {
log.Printf("Could not save log policy config to state store: %v", err)
}
return config
}
// noCORSTransport wraps a RoundTripper and forces the no-cors mode on requests,
// so that we can use it with non-CORS-aware servers.
type noCORSTransport struct {
http.RoundTripper
}
func (t *noCORSTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("js.fetch:mode", "no-cors")
resp, err := t.RoundTripper.RoundTrip(req)
if err == nil {
// In no-cors mode no response properties are returned. Populate just
// the status so that callers do not think this was an error.
resp.StatusCode = http.StatusOK
resp.Status = http.StatusText(http.StatusOK)
}
return resp, err
}

View File

@@ -130,6 +130,15 @@ cliui@^6.0.0:
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@@ -181,6 +190,14 @@ dlv@^1.1.3:
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
dts-bundle-generator@^6.12.0:
version "6.12.0"
resolved "https://registry.yarnpkg.com/dts-bundle-generator/-/dts-bundle-generator-6.12.0.tgz#0a221bdce5fdd309a56c8556e645f16ed87ab07d"
integrity sha512-k/QAvuVaLIdyWRUHduDrWBe4j8PcE6TDt06+f32KHbW7/SmUPbX1O23fFtQgKwUyTBkbIjJFOFtNrF97tJcKug==
dependencies:
typescript ">=3.0.1"
yargs "^17.2.1"
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@@ -191,6 +208,11 @@ encode-utf8@^1.0.3:
resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
fast-glob@^3.2.11:
version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
@@ -234,7 +256,7 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
get-caller-file@^2.0.1:
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@@ -443,6 +465,11 @@ postcss@^8.4.14:
picocolors "^1.0.0"
source-map-js "^1.0.2"
preact@^10.10.0:
version "10.10.0"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.10.0.tgz#7434750a24b59dae1957d95dc0aa47a4a8e9a180"
integrity sha512-fszkg1iJJjq68I4lI8ZsmBiaoQiQHbxf1lNq+72EmC/mZOsFF5zn3k1yv9QGoFgIXzgsdSKtYymLJsrJPoamjQ==
qrcode@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b"
@@ -518,7 +545,7 @@ source-map-js@^1.0.2:
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
string-width@^4.1.0, string-width@^4.2.0:
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -574,7 +601,7 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
typescript@^4.7.4:
typescript@>=3.0.1, typescript@^4.7.4:
version "4.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
@@ -598,6 +625,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
@@ -608,16 +644,26 @@ xterm-addon-fit@^0.5.0:
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596"
integrity sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ==
xterm@^4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0.tgz#a1f6ab2c330c3918fb094ae5f4c2562987398ea1"
integrity sha512-JQoc1S0dti6SQfI0bK1AZvGnAxH4MVw45ZPFSO6FHTInAiau3Ix77fSxNx3mX4eh9OL4AYa8+4C8f5UvnSfppQ==
xterm@5.0.0-beta.58:
version "5.0.0-beta.58"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0-beta.58.tgz#e3e96ab9fd24d006ec16cc9351a060cc79e67e80"
integrity sha512-gjg39oKdgUKful27+7I1hvSK51lu/LRhdimFhfZyMvdk0iATH0FAfzv1eAvBKWY2UBgYUfxhicTkanYioANdMw==
xterm-addon-web-links@0.7.0-beta.6:
version "0.7.0-beta.6"
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.7.0-beta.6.tgz#ec63b681b4f0f0135fa039f53664f65fe9d9f43a"
integrity sha512-nD/r/GchGTN4c9gAIVLWVoxExTzAUV7E9xZnwsvhuwI4CEE6yqO15ns8g2hdcUrsPyCbNEw05mIrkF6W5Yj8qA==
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
@@ -631,6 +677,11 @@ yargs-parser@^18.1.2:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^21.0.0:
version "21.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^15.3.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
@@ -647,3 +698,16 @@ yargs@^15.3.1:
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"
yargs@^17.2.1:
version "17.5.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e"
integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.3"
y18n "^5.0.5"
yargs-parser "^21.0.0"

View File

@@ -10,7 +10,7 @@ import (
"net/netip"
)
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone --clone-only-type=OnlyGetClone
type StructWithoutPtrs struct {
Int int
@@ -58,3 +58,7 @@ type StructWithSlices struct {
Prefixes []netip.Prefix
Data []byte
}
type OnlyGetClone struct {
SinViewerPorFavor bool
}

View File

@@ -196,3 +196,19 @@ var _StructWithSlicesCloneNeedsRegeneration = StructWithSlices(struct {
Prefixes []netip.Prefix
Data []byte
}{})
// Clone makes a deep copy of OnlyGetClone.
// The result aliases no memory with the original.
func (src *OnlyGetClone) Clone() *OnlyGetClone {
if src == nil {
return nil
}
dst := new(OnlyGetClone)
*dst = *src
return dst
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _OnlyGetCloneCloneNeedsRegeneration = OnlyGetClone(struct {
SinViewerPorFavor bool
}{})

View File

@@ -15,7 +15,7 @@ import (
"tailscale.com/types/views"
)
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone
// View returns a readonly view of StructWithPtrs.
func (p *StructWithPtrs) View() StructWithPtrsView {

View File

@@ -327,6 +327,8 @@ var (
flagTypes = flag.String("type", "", "comma-separated list of types; required")
flagBuildTags = flag.String("tags", "", "compiler build tags to apply")
flagCloneFunc = flag.Bool("clonefunc", false, "add a top-level Clone func")
flagCloneOnlyTypes = flag.String("clone-only-type", "", "comma-separated list of types (a subset of --type) that should only generate a go:generate clone line and not actual views")
)
func main() {
@@ -353,10 +355,18 @@ func main() {
}
it := codegen.NewImportTracker(pkg.Types)
cloneOnlyType := map[string]bool{}
for _, t := range strings.Split(*flagCloneOnlyTypes, ",") {
cloneOnlyType[t] = true
}
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "//go:generate go run tailscale.com/cmd/cloner %s\n\n", strings.Join(flagArgs, " "))
runCloner := false
for _, typeName := range typeNames {
if cloneOnlyType[typeName] {
continue
}
typ, ok := namedTypes[typeName]
if !ok {
log.Fatalf("could not find type %s", typeName)

View File

@@ -114,19 +114,11 @@ func NewNoStart(opts Options) (*Auto, error) {
}
c.authCtx, c.authCancel = context.WithCancel(context.Background())
c.mapCtx, c.mapCancel = context.WithCancel(context.Background())
c.unregisterHealthWatch = health.RegisterWatcher(c.onHealthChange)
c.unregisterHealthWatch = health.RegisterWatcher(direct.ReportHealthChange)
return c, nil
}
func (c *Auto) onHealthChange(sys health.Subsystem, err error) {
if sys == health.SysOverall {
return
}
c.logf("controlclient: restarting map request for %q health change to new state: %v", sys, err)
c.cancelMapSafely()
}
// SetPaused controls whether HTTP activity should be paused.
//
// The client can be paused and unpaused repeatedly, unlike Start and Shutdown, which can only be used once.

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