Compare commits

...

86 Commits

Author SHA1 Message Date
Mihai Parparita
db9962d63b cmd/tailscale/cli: remove special-casing of JS/WASM
We're no longer compiling the entire CLI for WASM, so we can remove
some wrapper functions and GOOS checks.

Effectively reverts 75de4e9cc2.
5df7ac70d6, eda647cb47
and other changes.

Fixes #5146

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-08-02 11:26:27 -07:00
Brad Fitzpatrick
5d0e3d379c go.mod: bump gvisor
For https://github.com/google/gvisor/pull/7850 to quiet macOS warnings.

Updates #5240

Change-Id: Iaa7abab20485c8ff40e5c9b16013aef4fd297a64
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-08-01 16:48:27 -07:00
Joe Tsai
b905db7a56 cmd/derper: remove support for logtail logging (#5248)
There aren't really any useful logs produced by derper.

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
2022-08-01 14:36:55 -07:00
Brad Fitzpatrick
357fd85ecf go.toolchain.rev: bump tailscale.go1.19 commit
for mutex slow path metrics: https://github.com/tailscale/go/pull/33

(a maybe temporary experiment)

Change-Id: Idba1ef866e2e1764728bdd869c25becccc1051b0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-31 22:10:31 -07:00
Mihai Parparita
c06758c83b cmd/tsconnect: allow SSH username to be specified
Redoes the UI to be a form, with a username field and a host drop-down.

Fixes #5139

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-29 15:54:06 -07:00
Tom DNetto
47f91dd732 cmd/tailscale{,d}: update depaware
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-07-29 12:16:32 -07:00
Tom DNetto
023d4e2216 tka,types/key: implement NLPrivate glue for tailnet key authority keys
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-07-29 12:16:32 -07:00
Tom DNetto
7a74466998 shell.nix: update Go toolchain to 1.19
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-07-29 11:25:47 -07:00
Tom DNetto
44a9b0170b tka: support processing non-primary forks, scenario-driven tests
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-07-29 11:18:06 -07:00
Maisem Ali
8fd5d3eaf3 tstest: do not error if the there are fewer goroutines than at start
This fixes test failures like:
```
    resource.go:34: goroutine count: expected 3, got 2
```

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-29 09:53:01 -07:00
Tom DNetto
5e61d52f91 tka: implement API surface for generating updates
Based on the builder pattern.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-07-29 09:36:32 -07:00
Brad Fitzpatrick
acc3b7f259 go.mod: bump inet.af/wf, tidy
This removes inet.af/netaddr from go.{mod,sum}.

Updates #5162

Change-Id: I7121e9fbb96d036cf188c51f0b53731570252d69
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-28 14:50:50 -07:00
Brad Fitzpatrick
f541e00db2 go.toolchain.rev: bump for VERSION file
Updates #5210

Change-Id: Ib6db8b010a6a9369a3eda8a86a49e538e376aff6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-28 14:29:15 -07:00
Brad Fitzpatrick
eae003e56f ipn/ipnlocal: blend existing host SSH keys + newly-generated types as needed
If the host only has RSA, use its RSA + generate ecdsa + ed25519, etc.

Perhaps fixes https://twitter.com/colek42c/status/1550554439299244032 and
something else that was reported.

Change-Id: I88dc475c8e3d95b6f25288ff7664b8e72655fd16
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-28 11:42:58 -07:00
Brad Fitzpatrick
e5176f572e go.toolchain.rev: switch to Go 1.19rc2+
Switch to Go 1.19rc2 in prep for the Go 1.19 GA release on Tuesday.

(We won't be using any Go 1.19 features until then.)

Updates #5210

Change-Id: I94fa0ae8f5645fb7579429668f3970c18d1796d8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-28 11:28:21 -07:00
Brad Fitzpatrick
48e73e147a logtail,logpolicy: tweak minor cosmetic things
Just reading the code again in prep for some alloc reductions.

Change-Id: I065226ea794b7ec7144c2b15942d35131c9313a8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-27 21:13:46 -07:00
Mihai Parparita
ab60f28227 cmd/tsconnect: fix xterm CSS not being imported
@import rules need to come first, they are (silently) ignored otherwise.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-27 16:16:13 -07:00
Mihai Parparita
7c3f480767 cmd/tsconnect: lint during build step
Ensures that TypeScript checks pass before we deploy.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-27 16:12:22 -07:00
James Tucker
d5fb852718 build_dist.sh: add --box and --extra-small flag to produce smaller and boxed binaries
- `--box` when ./cmd/tailscaled is built with this flag, it builds a
  "toybox" style binary that includes tailscale and tailscaled.
- `--extra-small` strip the output binary and omit some dependencies
  (currently AWS integration).

Signed-off-by: James Tucker <james@tailscale.com>
2022-07-27 16:08:52 -07:00
Mihai Parparita
a3d74c4548 cmd/tsconnect: add basic panic handling
The go wasm process exiting is a sign of an unhandled panic. Also
add a explicit recover() call in the notify callback, that's where most
logic bugs are likely to happen (and they may not be fatal).

Also fixes the one panic that was encountered (nill pointer dereference
when generating the JS view of the netmap).

Fixes #5132

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-27 15:42:58 -07:00
Aaron Klotz
4dbdb19c26 net/tshttpproxy: fix incorrect type in Windows implementation, switch to mkwinsyscall, fix memory leak
The definition of winHTTPProxyInfo was using the wrong type (uint16 vs uint32)
for its first field. I fixed that type.

Furthermore, any UTF16 strings returned in that structure must be explicitly
freed. I added code to do this.

Finally, since this is the second time I've seen type safety errors in this code,
I switched the native API calls over to use wrappers generated by mkwinsyscall.
I know that would not have helped prevent the previous two problems, but every
bit helps IMHO.

Updates https://github.com/tailscale/tailscale/issues/4811

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-07-27 16:33:57 -06:00
Aaron Klotz
446d03e108 scripts: update check_license_headers.sh to skip zsyscall_windows.go
Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-07-27 16:19:39 -06:00
Brad Fitzpatrick
97b8c4fa1b ipn/store/awsstore: add "ts_omit_aws" build tag to reduce binary size
Drops tailscaled from 23M to 21M.

Change-Id: I731c542d03113ac94abb695e3c8fcacbc5542712
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-27 14:19:33 -07:00
nyghtowl
e6e1976c3a net/dns: remove systemd-resolved ping
Ping only needed to ensure system awak otherwise utilizing resolvConf to set dns mode.

Signed-off-by: nyghtowl <warrick@tailscale.com>
2022-07-27 14:15:22 -07:00
Mihai Parparita
617a2ec7cc cmd/tsconnect: add Tailwind CSS support and switch to using it
Integrates Tailwind CSS as an esbuild plugin that invokes the CLI
to process the input. It takes ~400ms, so it seems like the easiest
option (vs running a separate process for dev mode).

Existing minimal look and feel is replicated with Tailwind classes,
mostly to prove that the entire system works, including unused
class removal.

Also fixes yarn warnings about package.json not having a license
(which were showing up when invoking any scripts).

Fixes #5136
Fixes #5129

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-27 14:05:45 -07:00
Mihai Parparita
389629258b cmd/tsconnect: switch to TypeScript
Continues to use esbuild for development mode and building. Also
includes a `yarn lint` script that uses tsc to do full type checking.

Fixes #5138

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-27 13:50:34 -07:00
Brad Fitzpatrick
0a6aa75a2d cmd/tailscaled: add opt-in support for linking CLI into daemon
Doesn't help much, though.

    $ go install --tags=ts_include_cli ./cmd/tailscaled/
    $ ls -lh ~/go/bin/tailscaled
    -rwxr-xr-x 2 bradfitz bradfitz 34M Jul 27 11:00 /home/bradfitz/go/bin/tailscaled
    $ go install --tags= ./cmd/tailscaled/
    $ ls -lh ~/go/bin/tailscaled
    -rwxr-xr-x 1 bradfitz bradfitz 23M Jul 27 11:00 /home/bradfitz/go/bin/tailscaled
    $ ls -lh ~/go/bin/tailscale
    -rwxr-xr-x 1 bradfitz bradfitz 13M Jul 25 21:30 /home/bradfitz/go/bin/tailscale

Fixes #2233

Change-Id: I46bae91bb38eb47a76251c1b5c1e9e455fc234b6
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-27 11:15:52 -07:00
Brad Fitzpatrick
04cf46a762 util/deephash: fix unexported time.Time hashing
Updates tailscale/corp#6311

Change-Id: I33cd7e4040966261c2f2eb3d32f29936aeb7f632
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-27 09:28:23 -07:00
Maisem Ali
51c3d74095 types/views: add BenchmarkSliceIteration
```
goos: darwin
goarch: arm64
pkg: tailscale.com/types/views
BenchmarkSliceIteration/Len-10            340093              3212 ns/op               0 B/op          0 allocs/op
BenchmarkSliceIteration/Cached-Len-10     366727              3211 ns/op               0 B/op          0 allocs/op
BenchmarkSliceIteration/direct-10         361561              3290 ns/op               0 B/op          0 allocs/op
PASS
ok      tailscale.com/types/views       3.662s
```

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-26 21:25:26 -07:00
Maisem Ali
fa2fbaf3aa tailcfg: add views for SSHRule and SSHPrincipal
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-26 20:57:33 -07:00
Brad Fitzpatrick
7c671b0220 .github/workflows: add gofmt (goimports) check
Change-Id: Iceb3182827b9c65f28f0351e0e254abe4a95e4de
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-26 09:46:06 -07:00
Brad Fitzpatrick
dd3e91b678 go.mod: tidy, remove inet.af/netaddr
It was actually unused earlier, but I had a test program
in my git workdir, keeping go mod tidy from cleaning it.
(more CI needed, perhaps)

Updates #5162

Change-Id: I9047a9aaa6fde7736d6ef516dc3bb652d06fe921
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-25 22:08:20 -07:00
Brad Fitzpatrick
a12aad6b47 all: convert more code to use net/netip directly
perl -i -npe 's,netaddr.IPPrefixFrom,netip.PrefixFrom,' $(git grep -l -F netaddr.)
    perl -i -npe 's,netaddr.IPPortFrom,netip.AddrPortFrom,' $(git grep -l -F netaddr. )
    perl -i -npe 's,netaddr.IPPrefix,netip.Prefix,g' $(git grep -l -F netaddr. )
    perl -i -npe 's,netaddr.IPPort,netip.AddrPort,g' $(git grep -l -F netaddr. )
    perl -i -npe 's,netaddr.IP\b,netip.Addr,g' $(git grep -l -F netaddr. )
    perl -i -npe 's,netaddr.IPv6Raw\b,netip.AddrFrom16,g' $(git grep -l -F netaddr. )
    goimports -w .

Then delete some stuff from the net/netaddr shim package which is no
longer neeed.

Updates #5162

Change-Id: Ia7a86893fe21c7e3ee1ec823e8aba288d4566cd8
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-25 21:53:49 -07:00
Brad Fitzpatrick
6a396731eb all: use various net/netip parse funcs directly
Mechanical change with perl+goimports.

Changed {Must,}Parse{IP,IPPrefix,IPPort} to their netip variants, then
goimports -d .

Finally, removed the net/netaddr wrappers, to prevent future use.

Updates #5162

Change-Id: I59c0e38b5fbca5a935d701645789cddf3d7863ad
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-25 21:12:28 -07:00
Brad Fitzpatrick
730ca4203c cmd/tsshd: add a package line to appease gofmt
Change-Id: I2fbbe983186169ddf1995d2f51c7b5a6164a0904
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-25 20:52:04 -07:00
Mihai Parparita
7e4883b261 .github/workflows: add tsconnect static build to wasm GitHub action
Technically not the same as the wasm cross-compilation, but it's
closely connected to it.

Also includes some fixes to tool/yasm to make it actually work on
non-ARM platforms.

Fixes #5134

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-25 18:02:22 -07:00
Brad Fitzpatrick
7eaf5e509f net/netaddr: start migrating to net/netip via new netaddr adapter package
Updates #5162

Change-Id: Id7bdec303b25471f69d542f8ce43805328d56c12
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-25 16:20:43 -07:00
James Tucker
7b1a91dfd3 tool/go: accept a marker file with no line terminator
Somewhere my local configuration or program versions are producing
marker files earlier in the process that lack a line terminator. This
doesn't need to cause an exit via set -e, we can just continue the
process. $extracted matches $REV anyway, so the process works.

Signed-off-by: James Tucker <james@tailscale.com>
2022-07-25 16:06:52 -07:00
Walter Poupore
df9f3edea3 docs/k8s: add prefix to (#5167)
Signed-off-by: Walter Poupore <walterp@tailscale.com>
2022-07-25 15:10:07 -07:00
Denton Gentry
7fd03ad4b4 logpolicy: put QNAP logs buffer in /tmp
Ongoing log writing keeps the spinning disks from hibernating.
Extends earlier implementation for Synology to also handle QNAP.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-07-25 09:45:04 -07:00
Denton Gentry
f85bb60eba ipn/ipnlocal: prevent attempting to run SSH on QNAP for now
tailscaled runs as a non-root user, SSH is not immediately working.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-07-25 09:45:04 -07:00
Xe Iaso
904723691b cmd/gitops-pusher: things i forgot to push last PR (#5128)
Signed-off-by: Xe <xe@tailscale.com>
2022-07-25 09:09:32 -04:00
Denton Gentry
4dd799ec43 hostinfo: determine QNAP QTS version
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-07-22 19:43:47 -07:00
Denton Gentry
d17849461c ipn/{ipnserver,ipnlocal}: support incoming Taildrop on QNAP
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-07-22 19:43:47 -07:00
Aaron Klotz
1cae618b03 net/dns: add Windows group policy notifications to the NRPT rule manager
As discussed in previous PRs, we can register for notifications when group
policies are updated and act accordingly.

This patch changes nrptRuleDatabase to receive notifications that group policy
has changed and automatically move our NRPT rules between the local and
group policy subkeys as needed.

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2022-07-22 14:24:39 -06:00
Melanie Warrick
f17873e0f4 net/dns: handle D-Bus restarts in resolved manager (#5026)
When dbus restarts it can cause the tailscaled to crash because the nil
signal was not handled in resolved.Fixing so the nil signal leads to a
connection reset and tailscaled stays connected to systemd when dbus restarted.

Fixes #4645

Co-authored-by: James Tucker <james@tailscale.com>
Signed-off-by: nyghtowl <warrick@tailscale.com>

Co-authored-by: James Tucker <james@tailscale.com>
2022-07-22 12:49:18 -07:00
Xe Iaso
898695e312 cmd/gitops-pusher: add etag cache file for the three version problem (#5124)
This allows gitops-pusher to detect external ACL changes. I'm not
sure what to call this problem, so I've been calling it the "three
version problem" in my notes. The basic problem is that at any given
time we only have two versions of the ACL file at any given point:
the version in CONTROL and the one in the git repo. In order to
check if there has been tampering of the ACL files in the admin
panel, we need to have a _third_ version to compare against.

In this case I am not storing the old ACL entirely (though that could
be a reasonable thing to add in the future), but only its sha256sum.
This allows us to detect if the shasum in control matches the shasum
we expect, and if that expectation fails, then we can react
accordingly.

This will require additional configuration in CI, but I'm sure that
can be done.

Signed-off-by: Xe <xe@tailscale.com>
2022-07-22 15:07:38 -04:00
Brad Fitzpatrick
2024008667 types/key: add MachinePrecomputedSharedKey.Open
Follow-up to cfdb862673

Updates tailscale/corp#1709

Change-Id: I7af931a2cb55f9006e1029381663ac21d1794242
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-22 12:05:29 -07:00
Mihai Parparita
be8a0859a9 cmd/tsconnect: pin yarn and node
Adds a tool/yarn helper script that uses specific versions of yarn and
node, downloading them if necessary.

Modeled after tool/go (and the yarn and node Redo scripts from the
corp repo).

Also allows the path to yarn to be overidden (in case the user does not
want to use this script) and always pipes yarn output (to make debugging
and viewing of process easier).

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-22 11:36:49 -07:00
Xe Iaso
92357a54ec cmd/gitops-pusher: fix minor bug with ACL tests (#5123)
Signed-off-by: Xe <xe@tailscale.com>
2022-07-22 13:53:42 -04:00
Brad Fitzpatrick
ba91f57ddd ipn/ipnlocal: ignore empty SSH host key files
Change-Id: I332b0d7d01386111d0af4adf98c96c04d3d12fbb
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-22 10:52:04 -07:00
Brad Fitzpatrick
227c6b2a53 ipn/ipnlocal: flesh out error on ssh host key parse error
Change-Id: Iedd2d3898befa536181036b9e9dea59bc777a440
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-22 08:09:59 -07:00
Xe Iaso
90ccba6730 cmd/gitops-pusher: port to use ffcli (#5113)
Signed-off-by: Xe <xe@tailscale.com>
2022-07-22 09:39:24 -04:00
Logan Saso
f7a36dfeb1 api.md: added missing quote to POST DNS Searchpaths request body example
Signed-off-by: Logan Saso <logansaso@gmail.com>
2022-07-21 17:44:51 -07:00
Maisem Ali
9514ed33d2 go.mod: bump gvisor.dev/gvisor
Pick up https://github.com/google/gvisor/pull/7787

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-21 16:41:18 -07:00
Maisem Ali
1d33157ab9 docs/k8s: use job control in run.sh
This has the benefit of propagating SIGINT to tailscaled, which in turn
can react to the event and logout in case of an ephemeral node.

Also fix missing run.sh in Dockerfile.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-21 15:43:40 -07:00
Maisem Ali
3e06b9ea7a ssh/tailssh: add "ssh" to conn logs
Fixes #5089

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-21 12:42:31 -07:00
Maisem Ali
480fd6c797 ssh/tailssh: handle not-authenticated-yet connections in matchRule
Also make more fields in conn.info thread safe, there was previously a
data race here.

Fixes #5110

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-21 12:42:31 -07:00
Xe Iaso
41e60dae80 cmd/gitops-pusher: use fmt.Println for errors (#5112)
Signed-off-by: Xe <xe@tailscale.com>
2022-07-21 13:02:14 -04:00
Brad Fitzpatrick
43f3a969ca types/views: add SliceContains, View.ContainsFunc, View.IndexFunc
We were starting to write these elsewhere as little unexported copies
in misc places.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-21 08:28:43 -07:00
Brad Fitzpatrick
d8cb5aae17 tailcfg, control/controlclient: add tailcfg.PeersChangedPatch [capver 33]
This adds a lighter mechanism for endpoint updates from control.

Change-Id: If169c26becb76d683e9877dc48cfb35f90cc5f24
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-20 15:05:56 -07:00
Mihai Parparita
b763a12331 cmd/tsconnect: allow building static resources in a different directory
When using tsconnect as a module in another repo, we cannot write to
the ./dist directory (modules directories are read-only by default -
there is a -modcacherw flag for `go get` but we can't count on it).

We add a -distdir flag that is honored by both the build and serve
commands for where to place output in.

Somewhat tedious because esbuild outputs paths relative to the working
directory, so we need to do some extra munging to make them relative
to the output directory.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-20 10:14:43 -07:00
Mihai Parparita
de2dcda2e0 .github/workflows: update module that we try to build in cross-wasm
We now have the actual module that we need to build, so switch to
building it directly instead of its (expected) dependencies.

Also fix a copy/paste error in a jsdeps comment.

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-20 10:13:30 -07:00
Brad Fitzpatrick
b7f1fe7b0d tailcfg: remove old DNS fields
The control plane server doesn't send these to modern clients so we
don't need them in the tree. The server has its own serialization code
to generate legacy MapResponses when needed.

Change-Id: Idd1e5d96ddf9d4306f2da550d20b77f0c252817a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-19 21:23:12 -07:00
Brad Fitzpatrick
9bd3b5b89c types/key: add ControlPrivate.Shared wrapper too
Follow-up to cfdb862673.

Change-Id: Iab610d761f1e6d88e8bcb584d9c02cafe48fc377
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-19 14:49:01 -07:00
Brad Fitzpatrick
cfdb862673 types/key: add naclbox shared key wrapper type + Seal method
So the control plane can stop doing precomputations on each naclbox
message.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-19 14:18:12 -07:00
Mihai Parparita
6f5096fa61 cmd/tsconnect: initial scaffolding for Tailscale Connect browser client
Runs a Tailscale client in the browser (via a WebAssembly build of the
wasm package) and allows SSH access to machines. The wasm package exports
a newIPN function, which returns a simple JS object with methods like
start(), login(), logout() and ssh(). The golang.org/x/crypto/ssh
package is used for the SSH client.

Terminal emulation and QR code renedring is done via NPM packages (xterm
and qrcode respectively), thus we also need a JS toolchain that can
install and bundle them. Yarn is used for installation, and esbuild
handles loading them and bundling for production serving.

Updates #3157

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
2022-07-19 13:43:08 -07:00
Brad Fitzpatrick
2a22ea3e83 util/deephash: generate type-specific hasher funcs
name                old time/op    new time/op    delta
Hash-8                71.1µs ± 2%    71.5µs ± 1%     ~     (p=0.114 n=9+8)
HashPacketFilter-8    8.39µs ± 1%    4.83µs ± 2%  -42.38%  (p=0.000 n=8+9)
HashMapAcyclic-8      56.2µs ± 1%    56.9µs ± 2%   +1.17%  (p=0.035 n=10+9)
TailcfgNode-8         6.49µs ± 2%    3.54µs ± 1%  -45.37%  (p=0.000 n=9+9)
HashArray-8            729ns ± 2%     566ns ± 3%  -22.30%  (p=0.000 n=10+10)

name                old alloc/op   new alloc/op   delta
Hash-8                 24.0B ± 0%     24.0B ± 0%     ~     (all equal)
HashPacketFilter-8     24.0B ± 0%     24.0B ± 0%     ~     (all equal)
HashMapAcyclic-8       0.00B          0.00B          ~     (all equal)
TailcfgNode-8          0.00B          0.00B          ~     (all equal)
HashArray-8            0.00B          0.00B          ~     (all equal)

name                old allocs/op  new allocs/op  delta
Hash-8                  1.00 ± 0%      1.00 ± 0%     ~     (all equal)
HashPacketFilter-8      1.00 ± 0%      1.00 ± 0%     ~     (all equal)
HashMapAcyclic-8        0.00           0.00          ~     (all equal)
TailcfgNode-8           0.00           0.00          ~     (all equal)
HashArray-8             0.00           0.00          ~     (all equal)

Change-Id: I34c4e786e748fe60280646d40cc63a2adb2ea6fe
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-19 11:33:13 -07:00
Maisem Ali
4d0461f721 ipn/ipnlocal: logout ephemeral nodes on shutdown
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-19 11:29:31 -07:00
Tom DNetto
393a229de9 tka: implement synchronization mechanics
This PR implements the synchronization mechanics for TKA: generating a SyncOffer, processing a SyncOffer to find an intersection,
and computing the set of AUMs that should be transmitted to effect convergence.

This is the final PR implementing core mechanics for TKA.

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-07-19 09:58:36 -07:00
Tom DNetto
165c8f898e tka: implement Authority API surface
After this, there should be one final PR to implement the Sync algorithm!

Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-07-19 09:35:29 -07:00
Brad Fitzpatrick
2491fe1afe tailcfg: add missing omitempty annotation to PopBrowserURL
Change-Id: I8e752afd5bf009c17aae1b53650479b37c3232bd
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-19 09:25:06 -07:00
David Anderson
c1cb3efbba net/netcheck: test for OS IPv6 support as well as connectivity.
This lets us distinguish "no IPv6 because the device's ISP doesn't
offer IPv6" from "IPv6 is unavailable/disabled in the OS".

Signed-off-by: David Anderson <danderson@tailscale.com>
2022-07-18 18:02:12 -07:00
Charlotte Brandhorst-Satzkorn
4c0feba38e derp: plumb '/derp' request context through (#5083)
This change is required to implement tracing for derp.

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com>
2022-07-18 15:43:03 -07:00
Denton Gentry
3c892d106c VERSION.txt: this is v1.29.0
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-07-18 10:23:52 -07:00
Maisem Ali
bd4b27753e docs/k8s: set statedir to /tmp when not specified
This makes `tailscale cert` and Taildrop work on k8s and in ephemeral
mode.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-18 10:19:19 -07:00
Brad Fitzpatrick
469c30c33b ipn/localapi: define a cert dir for Synology DSM6
Fixes #4060

Change-Id: I5f145d4f56f6edb14825268e858d419c55918673
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-18 09:51:24 -07:00
Maisem Ali
c6648db333 cmd/tailscale/cli: make cert use localClient
This was seeminlgy missed in 87ba528ae0.

Fixes #5072

Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-17 13:22:29 -07:00
Denton Gentry
9fcda1f0a0 cmd/tailscale/cli/web: add QNAP NAS_SID authentication
QTS 5.0 doesn't always pass a qtoken, in some circumstances
it sends a NAS_SID cookie for us to verify instead.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2022-07-17 12:21:03 -07:00
Brad Fitzpatrick
0d52674a84 net/tstun: diagnose /dev/net/tun fd leak, give better failure message
Updates #5029

Change-Id: Ibee5e0c9076fe764eb5d856d5ef8b09f4d0e2921
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-16 14:21:56 -07:00
Brad Fitzpatrick
931f18b575 derp: add missing docs on clientInfo.Version
It's not the Tailscale version.

Change-Id: Icfbd5ff36300b2125b19cd2fa6caa22876965317
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-15 12:00:19 -07:00
Tom DNetto
4f1374ec9e tka: implement consensus & state computation internals
Signed-off-by: Tom DNetto <tom@tailscale.com>
2022-07-15 10:44:43 -07:00
Maisem Ali
af412e8874 ssh/tailssh: better handling of signals and exits
We were not handling errors occurred while copying data between the subprocess and the connection.
This makes it so that we pass the appropriate signals when to the process and the connection.

This also fixes mosh.

Updates #4919

Co-authored-by: James Tucker <raggi@tailscale.com>
Co-authored-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Signed-off-by: Maisem Ali <maisem@tailscale.com>
2022-07-15 09:06:01 -07:00
Xe Iaso
004f0ca3e0 cmd/gitops-pusher: format HuJSON, enabling exact ACL matches (#5061)
Signed-off-by: Xe <xe@tailscale.com>
2022-07-15 11:09:44 -04:00
Xe Iaso
16c85d0dc5 cmd/gitops-pusher: support GitHub Actions error syntax (#5060)
GitHub Actions lets you annotate lines in files as errors[1]. This syntax
will only fire on syntax errors. Future improvements can extend this to
properly annotate ACL tests too.

[1]: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message

Signed-off-by: Xe <xe@tailscale.com>
2022-07-15 10:20:54 -04:00
Brad Fitzpatrick
7fb6781bda go.toolchain.rev: bump for Go 1.18.4 + runtime timer spin
See https://github.com/tailscale/go/pull/32

Updates #4760
Updates #5030?
Updates #4891?

Change-Id: I066aafddc09fade30a5f3fdee23e6bd200eda9fa
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2022-07-13 20:56:59 -07:00
289 changed files with 10379 additions and 2880 deletions

View File

@@ -25,11 +25,16 @@ jobs:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Wasm build CLI and client modules
- name: Wasm client build
env:
GOOS: js
GOARCH: wasm
run: go build ./cmd/tailscale/cli ./ipn/... ./net/... ./safesocket ./types/... ./wgengine/...
run: go build ./cmd/tsconnect/wasm
- 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 build
- uses: k0kubun/action-slack@v2.0.0
with:

View File

@@ -28,6 +28,11 @@ jobs:
- name: Basic build
run: go build ./cmd/...
- name: Build variants
run: |
go install --tags=ts_include_cli ./cmd/tailscaled
go install --tags=ts_omit_aws ./cmd/tailscaled
- name: Get QEMU
run: |
# The qemu in Ubuntu 20.04 (Focal) is too old; we need 5.x something

View File

@@ -21,6 +21,9 @@ jobs:
- 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 ./...

View File

@@ -72,3 +72,4 @@ FROM alpine:3.16
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables
COPY --from=build-env /go/bin/* /usr/local/bin/
COPY --from=build-env /go/src/tailscale/docs/k8s/run.sh /usr/local/bin/

View File

@@ -1 +1 @@
1.27.0
1.29.0

2
api.md
View File

@@ -1120,7 +1120,7 @@ Replaces the list of searchpaths with the list supplied by the user and returns
`searchPaths` - A list of searchpaths in JSON.
```
{
"searchPaths: ["user1.example.com", "user2.example.com"]
"searchPaths": ["user1.example.com", "user2.example.com"]
}
```

View File

@@ -45,4 +45,25 @@ EOF
exit 0
fi
exec ./tool/go build -ldflags "-X tailscale.com/version.Long=${LONG} -X tailscale.com/version.Short=${SHORT} -X tailscale.com/version.GitCommit=${GIT_HASH}" "$@"
tags=""
ldflags="-X tailscale.com/version.Long=${LONG} -X tailscale.com/version.Short=${SHORT} -X tailscale.com/version.GitCommit=${GIT_HASH}"
# build_dist.sh arguments must precede go build arguments.
while [ "$#" -gt 1 ]; do
case "$1" in
--extra-small)
shift
ldflags="$ldflags -w -s"
tags="${tags:+$tags,}ts_omit_aws"
;;
--box)
shift
tags="${tags:+$tags,}ts_include_cli"
;;
*)
break
;;
esac
done
exec ./tool/go build ${tags:+-tags=$tags} -ldflags "$ldflags" "$@"

View File

@@ -13,8 +13,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"inet.af/netaddr"
"net/netip"
)
// ACLRow defines a rule that grants access by a set of users or groups to a set
@@ -354,7 +353,7 @@ func (c *Client) PreviewACLForUser(ctx context.Context, acl ACL, user string) (r
// Returns ACLPreview on success with matches in a slice. If there are no matches,
// the call is still successful but Matches will be an empty slice.
// Returns error if the provided ACL is invalid.
func (c *Client) PreviewACLForIPPort(ctx context.Context, acl ACL, ipport netaddr.IPPort) (res *ACLPreview, err error) {
func (c *Client) PreviewACLForIPPort(ctx context.Context, acl ACL, ipport netip.AddrPort) (res *ACLPreview, err error) {
// Format return errors to be descriptive.
defer func() {
if err != nil {

View File

@@ -19,6 +19,7 @@ import (
"net"
"net/http"
"net/http/httptrace"
"net/netip"
"net/url"
"os/exec"
"runtime"
@@ -28,7 +29,6 @@ import (
"time"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
@@ -665,7 +665,7 @@ func (lc *LocalClient) ExpandSNIName(ctx context.Context, name string) (fqdn str
// Ping sends a ping of the provided type to the provided IP and waits
// for its response.
func (lc *LocalClient) Ping(ctx context.Context, ip netaddr.IP, pingtype tailcfg.PingType) (*ipnstate.PingResult, error) {
func (lc *LocalClient) Ping(ctx context.Context, ip netip.Addr, pingtype tailcfg.PingType) (*ipnstate.PingResult, error) {
v := url.Values{}
v.Set("ip", ip.String())
v.Set("type", string(pingtype))

View File

@@ -13,15 +13,14 @@ import (
"encoding/json"
"fmt"
"net/http"
"inet.af/netaddr"
"net/netip"
)
// Routes contains the lists of subnet routes that are currently advertised by a device,
// as well as the subnets that are enabled to be routed by the device.
type Routes struct {
AdvertisedRoutes []netaddr.IPPrefix `json:"advertisedRoutes"`
EnabledRoutes []netaddr.IPPrefix `json:"enabledRoutes"`
AdvertisedRoutes []netip.Prefix `json:"advertisedRoutes"`
EnabledRoutes []netip.Prefix `json:"enabledRoutes"`
}
// Routes retrieves the list of subnet routes that have been enabled for a device.
@@ -56,14 +55,14 @@ func (c *Client) Routes(ctx context.Context, deviceID string) (routes *Routes, e
}
type postRoutesParams struct {
Routes []netaddr.IPPrefix `json:"routes"`
Routes []netip.Prefix `json:"routes"`
}
// SetRoutes updates the list of subnets that are enabled for a device.
// Subnets must be parsable by inet.af/netaddr.ParseIPPrefix.
// Subnets must be parsable by net/netip.ParsePrefix.
// Subnets do not have to be currently advertised by a device, they may be pre-enabled.
// Returns the updated list of enabled and advertised subnet routes in a *Routes object.
func (c *Client) SetRoutes(ctx context.Context, deviceID string, subnets []netaddr.IPPrefix) (routes *Routes, err error) {
func (c *Client) SetRoutes(ctx context.Context, deviceID string, subnets []netip.Prefix) (routes *Routes, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("tailscale.SetRoutes: %w", err)

View File

@@ -29,7 +29,6 @@ import (
"tailscale.com/atomicfile"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/logpolicy"
"tailscale.com/metrics"
"tailscale.com/net/stun"
"tailscale.com/tsweb"
@@ -37,16 +36,15 @@ import (
)
var (
dev = flag.Bool("dev", false, "run in localhost development mode")
addr = flag.String("a", ":443", "server HTTPS listen address, in form \":port\", \"ip:port\", or for IPv6 \"[ip]:port\". If the IP is omitted, it defaults to all interfaces.")
httpPort = flag.Int("http-port", 80, "The port on which to serve HTTP. Set to -1 to disable. The listener is bound to the same IP (if any) as specified in the -a flag.")
stunPort = flag.Int("stun-port", 3478, "The UDP port on which to serve STUN. The listener is bound to the same IP (if any) as specified in the -a flag.")
configPath = flag.String("c", "", "config file path")
certMode = flag.String("certmode", "letsencrypt", "mode for getting a cert. possible options: manual, letsencrypt")
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")
logCollection = flag.String("logcollection", "", "If non-empty, logtail collection to log to")
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.")
dev = flag.Bool("dev", false, "run in localhost development mode")
addr = flag.String("a", ":443", "server HTTPS listen address, in form \":port\", \"ip:port\", or for IPv6 \"[ip]:port\". If the IP is omitted, it defaults to all interfaces.")
httpPort = flag.Int("http-port", 80, "The port on which to serve HTTP. Set to -1 to disable. The listener is bound to the same IP (if any) as specified in the -a flag.")
stunPort = flag.Int("stun-port", 3478, "The UDP port on which to serve STUN. The listener is bound to the same IP (if any) as specified in the -a flag.")
configPath = flag.String("c", "", "config file path")
certMode = flag.String("certmode", "letsencrypt", "mode for getting a cert. possible options: manual, letsencrypt")
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.")
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")
@@ -135,7 +133,6 @@ func main() {
flag.Parse()
if *dev {
*logCollection = ""
*addr = ":3340" // above the keys DERP
log.Printf("Running in dev mode.")
tsweb.DevMode = true
@@ -146,12 +143,6 @@ func main() {
log.Fatalf("invalid server address: %v", err)
}
var logPol *logpolicy.Policy
if *logCollection != "" {
logPol = logpolicy.New(*logCollection)
log.SetOutput(logPol.Logtail)
}
cfg := loadConfig()
serveTLS := tsweb.IsProd443(*addr) || *certMode == "manual"

View File

@@ -6,7 +6,6 @@ package main
import (
"bufio"
"context"
"expvar"
"log"
"net/http"
@@ -45,8 +44,8 @@ func addWebSocketSupport(s *derp.Server, base http.Handler) http.Handler {
return
}
counterWebSocketAccepts.Add(1)
wc := websocket.NetConn(context.Background(), c, websocket.MessageBinary)
wc := websocket.NetConn(r.Context(), c, websocket.MessageBinary)
brw := bufio.NewReadWriter(bufio.NewReader(wc), bufio.NewWriter(wc))
s.Accept(wc, brw, r.RemoteAddr)
s.Accept(r.Context(), wc, brw, r.RemoteAddr)
})
}

1
cmd/gitops-pusher/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
version-cache.json

View File

@@ -0,0 +1,67 @@
// 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"
"os"
)
// Cache contains cached information about the last time this tool was run.
//
// This is serialized to a JSON file that should NOT be checked into git.
// It should be managed with either CI cache tools or stored locally somehow. The
// exact mechanism is irrelevant as long as it is consistent.
//
// This allows gitops-pusher to detect external ACL changes. I'm not sure what to
// call this problem, so I've been calling it the "three version problem" in my
// notes. The basic problem is that at any given time we only have two versions
// of the ACL file at any given point. In order to check if there has been
// tampering of the ACL files in the admin panel, we need to have a _third_ version
// to compare against.
//
// In this case I am not storing the old ACL entirely (though that could be a
// reasonable thing to add in the future), but only its sha256sum. This allows
// us to detect if the shasum in control matches the shasum we expect, and if that
// expectation fails, then we can react accordingly.
type Cache struct {
PrevETag string // Stores the previous ETag of the ACL to allow
}
// Save persists the cache to a given file.
func (c *Cache) Save(fname string) error {
os.Remove(fname)
fout, err := os.Create(fname)
if err != nil {
return err
}
defer fout.Close()
return json.NewEncoder(fout).Encode(c)
}
// LoadCache loads the cache from a given file.
func LoadCache(fname string) (*Cache, error) {
var result Cache
fin, err := os.Open(fname)
if err != nil {
return nil, err
}
defer fin.Close()
err = json.NewDecoder(fin).Decode(&result)
if err != nil {
return nil, err
}
return &result, nil
}
// Shuck removes the first and last character of a string, analogous to
// shucking off the husk of an ear of corn.
func Shuck(s string) string {
return s[1 : len(s)-1]
}

View File

@@ -13,24 +13,140 @@ import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"regexp"
"strings"
"time"
"github.com/peterbourgon/ff/v3/ffcli"
"github.com/tailscale/hujson"
)
var (
policyFname = flag.String("policy-file", "./policy.hujson", "filename for policy file")
timeout = flag.Duration("timeout", 5*time.Minute, "timeout for the entire CI run")
rootFlagSet = flag.NewFlagSet("gitops-pusher", flag.ExitOnError)
policyFname = rootFlagSet.String("policy-file", "./policy.hujson", "filename for policy file")
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 main() {
flag.Parse()
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
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)
} 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 {
return func(ctx context.Context, args []string) error {
controlEtag, err := getACLETag(ctx, tailnet, apiKey)
if err != nil {
return err
}
localEtag, err := sumFile(*policyFname)
if err != nil {
return err
}
if cache.PrevETag == "" {
log.Println("no previous etag found, assuming local file is correct and recording that")
cache.PrevETag = localEtag
}
log.Printf("control: %s", controlEtag)
log.Printf("local: %s", localEtag)
log.Printf("cache: %s", cache.PrevETag)
if cache.PrevETag != controlEtag {
modifiedExternallyError()
}
if controlEtag == localEtag {
cache.PrevETag = localEtag
log.Println("no update needed, doing nothing")
return nil
}
if err := applyNewACL(ctx, tailnet, apiKey, *policyFname, controlEtag); err != nil {
return err
}
cache.PrevETag = localEtag
return nil
}
}
func test(cache *Cache, tailnet, apiKey string) func(context.Context, []string) error {
return func(ctx context.Context, args []string) error {
controlEtag, err := getACLETag(ctx, tailnet, apiKey)
if err != nil {
return err
}
localEtag, err := sumFile(*policyFname)
if err != nil {
return err
}
if cache.PrevETag == "" {
log.Println("no previous etag found, assuming local file is correct and recording that")
cache.PrevETag = localEtag
}
log.Printf("control: %s", controlEtag)
log.Printf("local: %s", localEtag)
log.Printf("cache: %s", cache.PrevETag)
if cache.PrevETag != controlEtag {
modifiedExternallyError()
}
if controlEtag == localEtag {
log.Println("no updates found, doing nothing")
return nil
}
if err := testNewACLs(ctx, tailnet, apiKey, *policyFname); err != nil {
return err
}
return nil
}
}
func getChecksums(cache *Cache, tailnet, apiKey string) func(context.Context, []string) error {
return func(ctx context.Context, args []string) error {
controlEtag, err := getACLETag(ctx, tailnet, apiKey)
if err != nil {
return err
}
localEtag, err := sumFile(*policyFname)
if err != nil {
return err
}
if cache.PrevETag == "" {
log.Println("no previous etag found, assuming local file is correct and recording that")
cache.PrevETag = Shuck(localEtag)
}
log.Printf("control: %s", controlEtag)
log.Printf("local: %s", localEtag)
log.Printf("cache: %s", cache.PrevETag)
return nil
}
}
func main() {
tailnet, ok := os.LookupEnv("TS_TAILNET")
if !ok {
log.Fatal("set envvar TS_TAILNET to your tailnet's name")
@@ -39,72 +155,82 @@ func main() {
if !ok {
log.Fatal("set envvar TS_API_KEY to your Tailscale API key")
}
switch flag.Arg(0) {
case "apply":
controlEtag, err := getACLETag(ctx, tailnet, apiKey)
if err != nil {
log.Fatal(err)
cache, err := LoadCache(*cacheFname)
if err != nil {
if os.IsNotExist(err) {
cache = &Cache{}
} else {
log.Fatalf("error loading cache: %v", err)
}
}
defer cache.Save(*cacheFname)
localEtag, err := sumFile(*policyFname)
if err != nil {
log.Fatal(err)
}
applyCmd := &ffcli.Command{
Name: "apply",
ShortUsage: "gitops-pusher [options] apply",
ShortHelp: "Pushes changes to CONTROL",
LongHelp: `Pushes changes to CONTROL`,
Exec: apply(cache, tailnet, apiKey),
}
log.Printf("control: %s", controlEtag)
log.Printf("local: %s", localEtag)
testCmd := &ffcli.Command{
Name: "test",
ShortUsage: "gitops-pusher [options] test",
ShortHelp: "Tests ACL changes",
LongHelp: "Tests ACL changes",
Exec: test(cache, tailnet, apiKey),
}
if controlEtag == localEtag {
log.Println("no update needed, doing nothing")
os.Exit(0)
}
cksumCmd := &ffcli.Command{
Name: "checksum",
ShortUsage: "Shows checksums of ACL files",
ShortHelp: "Fetch checksum of CONTROL's ACL and the local ACL for comparison",
LongHelp: "Fetch checksum of CONTROL's ACL and the local ACL for comparison",
Exec: getChecksums(cache, tailnet, apiKey),
}
if err := applyNewACL(ctx, tailnet, apiKey, *policyFname, controlEtag); err != nil {
log.Fatal(err)
}
root := &ffcli.Command{
ShortUsage: "gitops-pusher [options] <command>",
ShortHelp: "Push Tailscale ACLs to CONTROL using a GitOps workflow",
Subcommands: []*ffcli.Command{applyCmd, cksumCmd, testCmd},
FlagSet: rootFlagSet,
}
case "test":
controlEtag, err := getACLETag(ctx, tailnet, apiKey)
if err != nil {
log.Fatal(err)
}
if err := root.Parse(os.Args[1:]); err != nil {
log.Fatal(err)
}
localEtag, err := sumFile(*policyFname)
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
log.Printf("control: %s", controlEtag)
log.Printf("local: %s", localEtag)
if err := root.Run(ctx); err != nil {
fmt.Println(err)
os.Exit(1)
}
if controlEtag == localEtag {
log.Println("no updates found, doing nothing")
os.Exit(0)
}
if err := testNewACLs(ctx, tailnet, apiKey, *policyFname); err != nil {
log.Fatal(err)
}
default:
log.Fatalf("usage: %s [options] <test|apply>", os.Args[0])
if len(modifiedExternallyFailure) != 0 {
os.Exit(1)
}
}
func sumFile(fname string) (string, error) {
fin, err := os.Open(fname)
data, err := os.ReadFile(fname)
if err != nil {
return "", err
}
formatted, err := hujson.Format(data)
if err != nil {
return "", err
}
defer fin.Close()
h := sha256.New()
_, err = io.Copy(h, fin)
_, err = h.Write(formatted)
if err != nil {
return "", err
}
return fmt.Sprintf("\"%x\"", h.Sum(nil)), nil
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
func applyNewACL(ctx context.Context, tailnet, apiKey, policyFname, oldEtag string) error {
@@ -121,7 +247,7 @@ func applyNewACL(ctx context.Context, tailnet, apiKey, policyFname, oldEtag stri
req.SetBasicAuth(apiKey, "")
req.Header.Set("Content-Type", "application/hujson")
req.Header.Set("If-Match", oldEtag)
req.Header.Set("If-Match", `"`+oldEtag+`"`)
resp, err := http.DefaultClient.Do(req)
if err != nil {
@@ -165,25 +291,27 @@ func testNewACLs(ctx context.Context, tailnet, apiKey, policyFname string) error
}
defer resp.Body.Close()
got := resp.StatusCode
want := http.StatusOK
if got != want {
return fmt.Errorf("wanted HTTP status code %d but got %d", want, got)
}
var ate ACLTestError
err = json.NewDecoder(resp.Body).Decode(&ate)
if err != nil {
return err
}
if len(ate.Data) != 0 {
if len(ate.Message) != 0 || len(ate.Data) != 0 {
return ate
}
got := resp.StatusCode
want := http.StatusOK
if got != want {
return fmt.Errorf("wanted HTTP status code %d but got %d", want, got)
}
return nil
}
var lineColMessageSplit = regexp.MustCompile(`line ([0-9]+), column ([0-9]+): (.*)$`)
type ACLTestError struct {
Message string `json:"message"`
Data []ACLTestErrorDetail `json:"data"`
@@ -192,7 +320,17 @@ type ACLTestError struct {
func (ate ACLTestError) Error() string {
var sb strings.Builder
fmt.Fprintln(&sb, ate.Message)
if *githubSyntax && lineColMessageSplit.MatchString(ate.Message) {
sp := lineColMessageSplit.FindStringSubmatch(ate.Message)
line := sp[1]
col := sp[2]
msg := sp[3]
fmt.Fprintf(&sb, "::error file=%s,line=%s,col=%s::%s", *policyFname, line, col, msg)
} else {
fmt.Fprintln(&sb, ate.Message)
}
fmt.Fprintln(&sb)
for _, data := range ate.Data {
@@ -231,5 +369,5 @@ func getACLETag(ctx context.Context, tailnet, apiKey string) (string, error) {
return "", fmt.Errorf("wanted HTTP status code %d but got %d", want, got)
}
return resp.Header.Get("ETag"), nil
return Shuck(resp.Header.Get("ETag")), nil
}

View File

@@ -135,13 +135,13 @@ func tailscaleIP(who *apitype.WhoIsResponse) string {
return ""
}
for _, nodeIP := range who.Node.Addresses {
if nodeIP.IP().Is4() && nodeIP.IsSingleIP() {
return nodeIP.IP().String()
if nodeIP.Addr().Is4() && nodeIP.IsSingleIP() {
return nodeIP.Addr().String()
}
}
for _, nodeIP := range who.Node.Addresses {
if nodeIP.IsSingleIP() {
return nodeIP.IP().String()
return nodeIP.Addr().String()
}
}
return ""

View File

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

View File

@@ -17,7 +17,6 @@ import (
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/atomicfile"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/version"
)
@@ -28,7 +27,7 @@ var certCmd = &ffcli.Command{
ShortHelp: "get TLS certs",
ShortUsage: "cert [flags] <domain>",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("cert")
fs := flag.NewFlagSet("cert", flag.ExitOnError)
fs.StringVar(&certArgs.certFile, "cert-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.crt if --cert-file and --key-file are both unset")
fs.StringVar(&certArgs.keyFile, "key-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.key if --cert-file and --key-file are both unset")
fs.BoolVar(&certArgs.serve, "serve-demo", false, "if true, serve on port :443 using the cert as a demo, instead of writing out the files to disk")
@@ -46,7 +45,7 @@ func runCert(ctx context.Context, args []string) error {
if certArgs.serve {
s := &http.Server{
TLSConfig: &tls.Config{
GetCertificate: tailscale.GetCertificate,
GetCertificate: localClient.GetCertificate,
},
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.TLS != nil && !strings.Contains(r.Host, ".") && r.Method == "GET" {
@@ -80,7 +79,7 @@ func runCert(ctx context.Context, args []string) error {
domain := args[0]
printf := func(format string, a ...any) {
printf(format, a...)
fmt.Printf(format, a...)
}
if certArgs.certFile == "-" || certArgs.keyFile == "-" {
printf = log.Printf
@@ -90,7 +89,7 @@ func runCert(ctx context.Context, args []string) error {
certArgs.certFile = domain + ".crt"
certArgs.keyFile = domain + ".key"
}
certPEM, keyPEM, err := tailscale.CertPair(ctx, domain)
certPEM, keyPEM, err := localClient.CertPair(ctx, domain)
if err != nil {
return err
}
@@ -139,7 +138,7 @@ func runCert(ctx context.Context, args []string) error {
func writeIfChanged(filename string, contents []byte, mode os.FileMode) (changed bool, err error) {
if filename == "-" {
Stdout.Write(contents)
os.Stdout.Write(contents)
return false, nil
}
if old, err := os.ReadFile(filename); err == nil && bytes.Equal(contents, old) {

View File

@@ -33,22 +33,6 @@ import (
"tailscale.com/version/distro"
)
var Stderr io.Writer = os.Stderr
var Stdout io.Writer = os.Stdout
func printf(format string, a ...any) {
fmt.Fprintf(Stdout, format, a...)
}
// outln is like fmt.Println in the common case, except when Stdout is
// changed (as in js/wasm).
//
// It's not named println because that looks like the Go built-in
// which goes to stderr and formats slightly differently.
func outln(a ...any) {
fmt.Fprintln(Stdout, a...)
}
// ActLikeCLI reports whether a GUI application should act like the
// CLI based on os.Args, GOOS, the context the process is running in
// (pty, parent PID), etc.
@@ -95,16 +79,6 @@ func ActLikeCLI() bool {
return false
}
func newFlagSet(name string) *flag.FlagSet {
onError := flag.ExitOnError
if runtime.GOOS == "js" {
onError = flag.ContinueOnError
}
fs := flag.NewFlagSet(name, onError)
fs.SetOutput(Stderr)
return fs
}
// CleanUpArgs rewrites command line arguments for simplicity and backwards compatibility.
// In particular, it rewrites --authkey to --auth-key.
func CleanUpArgs(args []string) []string {
@@ -138,11 +112,11 @@ func Run(args []string) (err error) {
var warnOnce sync.Once
tailscale.SetVersionMismatchHandler(func(clientVer, serverVer string) {
warnOnce.Do(func() {
fmt.Fprintf(Stderr, "Warning: client version %q != tailscaled server version %q\n", clientVer, serverVer)
fmt.Fprintf(os.Stderr, "Warning: client version %q != tailscaled server version %q\n", clientVer, serverVer)
})
})
rootfs := newFlagSet("tailscale")
rootfs := flag.NewFlagSet("tailscale", flag.ExitOnError)
rootfs.StringVar(&rootArgs.socket, "socket", paths.DefaultTailscaledSocket(), "path to tailscaled's unix socket")
rootCmd := &ffcli.Command{

View File

@@ -9,13 +9,13 @@ import (
"encoding/json"
"flag"
"fmt"
"net/netip"
"reflect"
"strings"
"testing"
qt "github.com/frankban/quicktest"
"github.com/google/go-cmp/cmp"
"inet.af/netaddr"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tstest"
@@ -56,7 +56,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
flags []string // argv to be parsed by FlagSet
curPrefs *ipn.Prefs
curExitNodeIP netaddr.IP
curExitNodeIP netip.Addr
curUser string // os.Getenv("USER") on the client side
goos string // empty means "linux"
distro distro.Distro
@@ -152,10 +152,10 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.0.42.0/24"),
netaddr.MustParseIPPrefix("0.0.0.0/0"),
netaddr.MustParseIPPrefix("::/0"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("10.0.42.0/24"),
netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"),
},
},
want: accidentalUpPrefix + " --advertise-routes=10.0.42.0/24 --advertise-exit-node",
@@ -168,10 +168,10 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.0.42.0/24"),
netaddr.MustParseIPPrefix("0.0.0.0/0"),
netaddr.MustParseIPPrefix("::/0"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("10.0.42.0/24"),
netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"),
},
},
want: "",
@@ -184,10 +184,10 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.0.42.0/24"),
netaddr.MustParseIPPrefix("0.0.0.0/0"),
netaddr.MustParseIPPrefix("::/0"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("10.0.42.0/24"),
netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"),
},
},
want: "",
@@ -212,8 +212,8 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("1.2.0.0/16"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("1.2.0.0/16"),
},
},
want: accidentalUpPrefix + " --advertise-exit-node --advertise-routes=1.2.0.0/16",
@@ -226,10 +226,10 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("0.0.0.0/0"),
netaddr.MustParseIPPrefix("::/0"),
netaddr.MustParseIPPrefix("1.2.0.0/16"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"),
netip.MustParsePrefix("1.2.0.0/16"),
},
},
want: accidentalUpPrefix + " --advertise-exit-node --advertise-routes=1.2.0.0/16",
@@ -255,16 +255,16 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
ControlURL: ipn.DefaultControlURL,
RouteAll: true,
AllowSingleHosts: false,
ExitNodeIP: netaddr.MustParseIP("100.64.5.6"),
ExitNodeIP: netip.MustParseAddr("100.64.5.6"),
CorpDNS: false,
ShieldsUp: true,
AdvertiseTags: []string{"tag:foo", "tag:bar"},
Hostname: "myhostname",
ForceDaemon: true,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.0.0.0/16"),
netaddr.MustParseIPPrefix("0.0.0.0/0"),
netaddr.MustParseIPPrefix("::/0"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("10.0.0.0/16"),
netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"),
},
NetfilterMode: preftype.NetfilterNoDivert,
OperatorUser: "alice",
@@ -280,14 +280,14 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
ControlURL: ipn.DefaultControlURL,
RouteAll: true,
AllowSingleHosts: false,
ExitNodeIP: netaddr.MustParseIP("100.64.5.6"),
ExitNodeIP: netip.MustParseAddr("100.64.5.6"),
CorpDNS: false,
ShieldsUp: true,
AdvertiseTags: []string{"tag:foo", "tag:bar"},
Hostname: "myhostname",
ForceDaemon: true,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.0.0.0/16"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("10.0.0.0/16"),
},
NetfilterMode: preftype.NetfilterNoDivert,
OperatorUser: "alice",
@@ -344,10 +344,10 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("0.0.0.0/0"),
netaddr.MustParseIPPrefix("::/0"),
netaddr.MustParseIPPrefix("1.2.0.0/16"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"),
netip.MustParsePrefix("1.2.0.0/16"),
},
},
want: accidentalUpPrefix + " --operator=expbits --advertise-exit-node --advertise-routes=1.2.0.0/16",
@@ -360,10 +360,10 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
AllowSingleHosts: true,
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("0.0.0.0/0"),
netaddr.MustParseIPPrefix("::/0"),
netaddr.MustParseIPPrefix("1.2.0.0/16"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"),
netip.MustParsePrefix("1.2.0.0/16"),
},
},
want: accidentalUpPrefix + " --advertise-routes=1.2.0.0/16 --operator=expbits --advertise-exit-node",
@@ -391,14 +391,14 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
CorpDNS: true,
NetfilterMode: preftype.NetfilterOn,
ExitNodeIP: netaddr.MustParseIP("100.64.5.4"),
ExitNodeIP: netip.MustParseAddr("100.64.5.4"),
},
want: accidentalUpPrefix + " --hostname=foo --exit-node=100.64.5.4",
},
{
name: "error_exit_node_omit_with_id_pref",
flags: []string{"--hostname=foo"},
curExitNodeIP: netaddr.MustParseIP("100.64.5.7"),
curExitNodeIP: netip.MustParseAddr("100.64.5.7"),
curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL,
AllowSingleHosts: true,
@@ -412,7 +412,7 @@ func TestCheckForAccidentalSettingReverts(t *testing.T) {
{
name: "error_exit_node_and_allow_lan_omit_with_id_pref", // Isue 3480
flags: []string{"--hostname=foo"},
curExitNodeIP: netaddr.MustParseIP("100.2.3.4"),
curExitNodeIP: netip.MustParseAddr("100.2.3.4"),
curPrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL,
AllowSingleHosts: true,
@@ -562,9 +562,9 @@ func TestPrefsFromUpArgs(t *testing.T) {
WantRunning: true,
AllowSingleHosts: true,
CorpDNS: true,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("0.0.0.0/0"),
netaddr.MustParseIPPrefix("::/0"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"),
},
NetfilterMode: preftype.NetfilterOn,
},
@@ -631,7 +631,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
exitNodeIP: "100.105.106.107",
},
st: &ipnstate.Status{
TailscaleIPs: []netaddr.IP{netaddr.MustParseIP("100.105.106.107")},
TailscaleIPs: []netip.Addr{netip.MustParseAddr("100.105.106.107")},
},
wantErr: `cannot use 100.105.106.107 as an exit node as it is a local IP address to this machine; did you mean --advertise-exit-node?`,
},
@@ -671,8 +671,8 @@ func TestPrefsFromUpArgs(t *testing.T) {
want: &ipn.Prefs{
WantRunning: true,
NoSNAT: true,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("fd7a:115c:a1e0:b1a::bb:10.0.0.0/112"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("fd7a:115c:a1e0:b1a::bb:10.0.0.0/112"),
},
},
},
@@ -956,7 +956,7 @@ func TestUpdatePrefs(t *testing.T) {
}
}
var cmpIP = cmp.Comparer(func(a, b netaddr.IP) bool {
var cmpIP = cmp.Comparer(func(a, b netip.Addr) bool {
return a == b
})

View File

@@ -31,7 +31,7 @@ permission to use it.
See: https://tailscale.com/kb/1152/synology-outbound/
`),
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("configure-host")
fs := flag.NewFlagSet("configure-host", flag.ExitOnError)
return fs
})(),
}

View File

@@ -17,6 +17,7 @@ import (
"log"
"net"
"net/http"
"net/netip"
"os"
"runtime"
"strconv"
@@ -24,7 +25,6 @@ import (
"time"
"github.com/peterbourgon/ff/v3/ffcli"
"inet.af/netaddr"
"tailscale.com/control/controlhttp"
"tailscale.com/hostinfo"
"tailscale.com/ipn"
@@ -40,7 +40,7 @@ var debugCmd = &ffcli.Command{
Exec: runDebug,
LongHelp: `"tailscale debug" contains misc debug facilities; it is not a stable interface.`,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("debug")
fs := flag.NewFlagSet("debug", flag.ExitOnError)
fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME")
fs.StringVar(&debugArgs.cpuFile, "cpu-profile", "", "if non-empty, grab a CPU profile for --profile-sec seconds and write it to this file; - for stdout")
fs.StringVar(&debugArgs.memFile, "mem-profile", "", "if non-empty, grab a memory profile and write it to this file; - for stdout")
@@ -63,7 +63,7 @@ var debugCmd = &ffcli.Command{
Exec: runDaemonMetrics,
ShortHelp: "print tailscaled's metrics",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("metrics")
fs := flag.NewFlagSet("metrics", flag.ExitOnError)
fs.BoolVar(&metricsArgs.watch, "watch", false, "print JSON dump of delta values")
return fs
})(),
@@ -103,7 +103,7 @@ var debugCmd = &ffcli.Command{
Exec: runPrefs,
ShortHelp: "print prefs",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("prefs")
fs := flag.NewFlagSet("prefs", flag.ExitOnError)
fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output")
return fs
})(),
@@ -113,7 +113,7 @@ var debugCmd = &ffcli.Command{
Exec: runWatchIPN,
ShortHelp: "subscribe to IPN message bus",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("watch-ipn")
fs := flag.NewFlagSet("watch-ipn", flag.ExitOnError)
fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages")
return fs
})(),
@@ -128,7 +128,7 @@ var debugCmd = &ffcli.Command{
Exec: runTS2021,
ShortHelp: "debug ts2021 protocol connectivity",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("ts2021")
fs := flag.NewFlagSet("ts2021", flag.ExitOnError)
fs.StringVar(&ts2021Args.host, "host", "controlplane.tailscale.com", "hostname of control plane")
fs.IntVar(&ts2021Args.version, "version", int(tailcfg.CurrentCapabilityVersion), "protocol version")
return fs
@@ -146,7 +146,7 @@ var debugArgs struct {
func writeProfile(dst string, v []byte) error {
if dst == "-" {
_, err := Stdout.Write(v)
_, err := os.Stdout.Write(v)
return err
}
return os.WriteFile(dst, v, 0600)
@@ -198,7 +198,7 @@ func runDebug(ctx context.Context, args []string) error {
if err != nil {
fatalf("%v\n", err)
}
e := json.NewEncoder(Stdout)
e := json.NewEncoder(os.Stdout)
e.SetIndent("", "\t")
e.Encode(wfs)
return nil
@@ -212,7 +212,7 @@ func runDebug(ctx context.Context, args []string) error {
return err
}
log.Printf("Size: %v\n", size)
io.Copy(Stdout, rc)
io.Copy(os.Stdout, rc)
return nil
}
if usedFlag {
@@ -226,14 +226,14 @@ func runDebug(ctx context.Context, args []string) error {
func runLocalCreds(ctx context.Context, args []string) error {
port, token, err := safesocket.LocalTCPPortAndToken()
if err == nil {
printf("curl -u:%s http://localhost:%d/localapi/v0/status\n", token, port)
fmt.Printf("curl -u:%s http://localhost:%d/localapi/v0/status\n", token, port)
return nil
}
if runtime.GOOS == "windows" {
printf("curl http://localhost:%v/localapi/v0/status\n", safesocket.WindowsLocalPort)
fmt.Printf("curl http://localhost:%v/localapi/v0/status\n", safesocket.WindowsLocalPort)
return nil
}
printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket())
fmt.Printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket())
return nil
}
@@ -247,10 +247,10 @@ func runPrefs(ctx context.Context, args []string) error {
return err
}
if prefsArgs.pretty {
outln(prefs.Pretty())
fmt.Println(prefs.Pretty())
} else {
j, _ := json.MarshalIndent(prefs, "", "\t")
outln(string(j))
fmt.Println(string(j))
}
return nil
}
@@ -268,7 +268,7 @@ func runWatchIPN(ctx context.Context, args []string) error {
n.NetMap = nil
}
j, _ := json.MarshalIndent(n, "", "\t")
printf("%s\n", j)
fmt.Printf("%s\n", j)
})
bc.RequestEngineStatus()
pump(ctx, bc, c)
@@ -282,7 +282,7 @@ func runDERPMap(ctx context.Context, args []string) error {
"failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err,
)
}
enc := json.NewEncoder(Stdout)
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "\t")
enc.Encode(dm)
return nil
@@ -299,7 +299,7 @@ func localAPIAction(action string) func(context.Context, []string) error {
func runEnv(ctx context.Context, args []string) error {
for _, e := range os.Environ() {
outln(e)
fmt.Println(e)
}
return nil
}
@@ -338,7 +338,7 @@ func runDaemonGoroutines(ctx context.Context, args []string) error {
if err != nil {
return err
}
Stdout.Write(goroutines)
os.Stdout.Write(goroutines)
return nil
}
@@ -354,7 +354,7 @@ func runDaemonMetrics(ctx context.Context, args []string) error {
return err
}
if !metricsArgs.watch {
Stdout.Write(out)
os.Stdout.Write(out)
return nil
}
bs := bufio.NewScanner(bytes.NewReader(out))
@@ -391,9 +391,9 @@ func runDaemonMetrics(ctx context.Context, args []string) error {
if len(changes) > 0 {
format := fmt.Sprintf("%%-%ds %%+5d => %%v\n", maxNameLen)
for _, c := range changes {
fmt.Fprintf(Stdout, format, c.name, c.to-c.from, c.to)
fmt.Fprintf(os.Stdout, format, c.name, c.to-c.from, c.to)
}
io.WriteString(Stdout, "\n")
io.WriteString(os.Stdout, "\n")
}
time.Sleep(time.Second)
}
@@ -404,23 +404,23 @@ func runVia(ctx context.Context, args []string) error {
default:
return errors.New("expect either <site-id> <v4-cidr> or <v6-route>")
case 1:
ipp, err := netaddr.ParseIPPrefix(args[0])
ipp, err := netip.ParsePrefix(args[0])
if err != nil {
return err
}
if !ipp.IP().Is6() {
if !ipp.Addr().Is6() {
return errors.New("with one argument, expect an IPv6 CIDR")
}
if !tsaddr.TailscaleViaRange().Contains(ipp.IP()) {
if !tsaddr.TailscaleViaRange().Contains(ipp.Addr()) {
return errors.New("not a via route")
}
if ipp.Bits() < 96 {
return errors.New("short length, want /96 or more")
}
v4 := tsaddr.UnmapVia(ipp.IP())
a := ipp.IP().As16()
v4 := tsaddr.UnmapVia(ipp.Addr())
a := ipp.Addr().As16()
siteID := binary.BigEndian.Uint32(a[8:12])
fmt.Printf("site %v (0x%x), %v\n", siteID, siteID, netaddr.IPPrefixFrom(v4, ipp.Bits()-96))
fmt.Printf("site %v (0x%x), %v\n", siteID, siteID, netip.PrefixFrom(v4, ipp.Bits()-96))
case 2:
siteID, err := strconv.ParseUint(args[0], 0, 32)
if err != nil {
@@ -429,7 +429,7 @@ func runVia(ctx context.Context, args []string) error {
if siteID > 0xff {
return fmt.Errorf("site-id values over 255 are currently reserved")
}
ipp, err := netaddr.ParseIPPrefix(args[1])
ipp, err := netip.ParsePrefix(args[1])
if err != nil {
return err
}

View File

@@ -8,6 +8,7 @@ import (
"context"
"flag"
"fmt"
"os"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/ipn"
@@ -23,7 +24,7 @@ var downCmd = &ffcli.Command{
}
func newDownFlagSet() *flag.FlagSet {
downf := newFlagSet("down")
downf := flag.NewFlagSet("down", flag.ExitOnError)
registerAcceptRiskFlag(downf)
return downf
}
@@ -44,7 +45,7 @@ func runDown(ctx context.Context, args []string) error {
return fmt.Errorf("error fetching current status: %w", err)
}
if st.BackendState == "Stopped" {
fmt.Fprintf(Stderr, "Tailscale was already stopped.\n")
fmt.Fprintf(os.Stderr, "Tailscale was already stopped.\n")
return nil
}
_, err = localClient.EditPrefs(ctx, &ipn.MaskedPrefs{

View File

@@ -14,6 +14,7 @@ import (
"log"
"mime"
"net/http"
"net/netip"
"os"
"path"
"path/filepath"
@@ -23,7 +24,6 @@ import (
"github.com/peterbourgon/ff/v3/ffcli"
"golang.org/x/time/rate"
"inet.af/netaddr"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/envknob"
"tailscale.com/ipn"
@@ -54,7 +54,7 @@ var fileCpCmd = &ffcli.Command{
ShortHelp: "Copy file(s) to a host",
Exec: runCp,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("cp")
fs := flag.NewFlagSet("cp", flag.ExitOnError)
fs.StringVar(&cpArgs.name, "name", "", "alternate filename to use, especially useful when <file> is \"-\" (stdin)")
fs.BoolVar(&cpArgs.verbose, "verbose", false, "verbose output")
fs.BoolVar(&cpArgs.targets, "targets", false, "list possible file cp targets")
@@ -85,7 +85,7 @@ func runCp(ctx context.Context, args []string) error {
hadBrackets = true
target = strings.TrimSuffix(strings.TrimPrefix(target, "["), "]")
}
if ip, err := netaddr.ParseIP(target); err == nil && ip.Is6() && !hadBrackets {
if ip, err := netip.ParseAddr(target); err == nil && ip.Is6() && !hadBrackets {
return fmt.Errorf("an IPv6 literal must be written as [%s]", ip)
} else if hadBrackets && (err != nil || !ip.Is6()) {
return errors.New("unexpected brackets around target")
@@ -100,7 +100,7 @@ func runCp(ctx context.Context, args []string) error {
return fmt.Errorf("can't send to %s: %v", target, err)
}
if isOffline {
fmt.Fprintf(Stderr, "# warning: %s is offline\n", target)
fmt.Fprintf(os.Stderr, "# warning: %s is offline\n", target)
}
if len(files) > 1 {
@@ -168,7 +168,7 @@ func runCp(ctx context.Context, args []string) error {
}
func getTargetStableID(ctx context.Context, ipStr string) (id tailcfg.StableNodeID, isOffline bool, err error) {
ip, err := netaddr.ParseIP(ipStr)
ip, err := netip.ParseAddr(ipStr)
if err != nil {
return "", false, err
}
@@ -179,7 +179,7 @@ func getTargetStableID(ctx context.Context, ipStr string) (id tailcfg.StableNode
for _, ft := range fts {
n := ft.Node
for _, a := range n.Addresses {
if a.IP() != ip {
if a.Addr() != ip {
continue
}
isOffline = n.Online != nil && !*n.Online
@@ -191,7 +191,7 @@ func getTargetStableID(ctx context.Context, ipStr string) (id tailcfg.StableNode
// fileTargetErrorDetail returns a non-nil error saying why ip is an
// invalid file sharing target.
func fileTargetErrorDetail(ctx context.Context, ip netaddr.IP) error {
func fileTargetErrorDetail(ctx context.Context, ip netip.Addr) error {
found := false
if st, err := localClient.Status(ctx); err == nil && st.Self != nil {
for _, peer := range st.Peer {
@@ -281,7 +281,7 @@ func runCpTargets(ctx context.Context, args []string) error {
if detail != "" {
detail = "\t" + detail
}
printf("%s\t%s%s\n", n.Addresses[0].IP(), n.ComputedName, detail)
fmt.Printf("%s\t%s%s\n", n.Addresses[0].Addr(), n.ComputedName, detail)
}
return nil
}
@@ -315,7 +315,7 @@ var fileGetCmd = &ffcli.Command{
ShortHelp: "Move files out of the Tailscale file inbox",
Exec: runFileGet,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("get")
fs := flag.NewFlagSet("get", flag.ExitOnError)
fs.BoolVar(&getArgs.wait, "wait", false, "wait for a file to arrive if inbox is empty")
fs.BoolVar(&getArgs.loop, "loop", false, "run get in a loop, receiving files as they come in")
fs.BoolVar(&getArgs.verbose, "verbose", false, "verbose output")
@@ -415,7 +415,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
break
}
if getArgs.verbose {
printf("waiting for file...")
fmt.Printf("waiting for file...")
}
if err := waitForFile(ctx); err != nil {
errs = append(errs, err)
@@ -436,7 +436,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
continue
}
if getArgs.verbose {
printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size)
fmt.Printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size)
}
if err = localClient.DeleteWaitingFile(ctx, wf.Name); err != nil {
errs = append(errs, fmt.Errorf("deleting %q from inbox: %v", wf.Name, err))
@@ -448,7 +448,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
// persistently stuck files are basically an error
errs = append(errs, fmt.Errorf("moved %d/%d files", deleted, len(wfs)))
} else if getArgs.verbose {
printf("moved %d/%d files\n", deleted, len(wfs))
fmt.Printf("moved %d/%d files\n", deleted, len(wfs))
}
return errs
}
@@ -471,7 +471,7 @@ func runFileGet(ctx context.Context, args []string) error {
for {
errs := runFileGetOneBatch(ctx, dir)
for _, err := range errs {
outln(err)
fmt.Println(err)
}
if len(errs) > 0 {
// It's possible whatever caused the error(s) (e.g. conflicting target file,
@@ -493,7 +493,7 @@ func runFileGet(ctx context.Context, args []string) error {
return nil
}
for _, err := range errs[:len(errs)-1] {
outln(err)
fmt.Println(err)
}
return errs[len(errs)-1]
}

View File

@@ -9,9 +9,9 @@ import (
"errors"
"flag"
"fmt"
"net/netip"
"github.com/peterbourgon/ff/v3/ffcli"
"inet.af/netaddr"
"tailscale.com/ipn/ipnstate"
)
@@ -22,7 +22,7 @@ var ipCmd = &ffcli.Command{
LongHelp: "Show Tailscale IP addresses for peer. Peer defaults to the current machine.",
Exec: runIP,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("ip")
fs := flag.NewFlagSet("ip", flag.ExitOnError)
fs.BoolVar(&ipArgs.want1, "1", false, "only print one IP address")
fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address")
fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address")
@@ -85,7 +85,7 @@ func runIP(ctx context.Context, args []string) error {
for _, ip := range ips {
if ip.Is4() && v4 || ip.Is6() && v6 {
match = true
outln(ip)
fmt.Println(ip)
}
}
if !match {
@@ -100,7 +100,7 @@ func runIP(ctx context.Context, args []string) error {
}
func peerMatchingIP(st *ipnstate.Status, ipStr string) (ps *ipnstate.PeerStatus, ok bool) {
ip, err := netaddr.ParseIP(ipStr)
ip, err := netip.ParseAddr(ipStr)
if err != nil {
return
}

View File

@@ -29,7 +29,7 @@ func runNC(ctx context.Context, args []string) error {
}
description, ok := isRunningOrStarting(st)
if !ok {
printf("%s\n", description)
fmt.Printf("%s\n", description)
os.Exit(1)
}

View File

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

View File

@@ -11,12 +11,12 @@ import (
"fmt"
"log"
"net"
"net/netip"
"os"
"strings"
"time"
"github.com/peterbourgon/ff/v3/ffcli"
"inet.af/netaddr"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
)
@@ -46,7 +46,7 @@ relay node.
`),
Exec: runPing,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("ping")
fs := flag.NewFlagSet("ping", flag.ExitOnError)
fs.BoolVar(&pingArgs.verbose, "verbose", false, "verbose output")
fs.BoolVar(&pingArgs.untilDirect, "until-direct", true, "stop once a direct path is established")
fs.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through WireGuard, but not either host OS stack)")
@@ -88,7 +88,7 @@ func runPing(ctx context.Context, args []string) error {
}
description, ok := isRunningOrStarting(st)
if !ok {
printf("%s\n", description)
fmt.Printf("%s\n", description)
os.Exit(1)
}
@@ -103,7 +103,7 @@ func runPing(ctx context.Context, args []string) error {
return err
}
if self {
printf("%v is local Tailscale IP\n", ip)
fmt.Printf("%v is local Tailscale IP\n", ip)
return nil
}
@@ -116,11 +116,11 @@ func runPing(ctx context.Context, args []string) error {
for {
n++
ctx, cancel := context.WithTimeout(ctx, pingArgs.timeout)
pr, err := localClient.Ping(ctx, netaddr.MustParseIP(ip), pingType())
pr, err := localClient.Ping(ctx, netip.MustParseAddr(ip), pingType())
cancel()
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
printf("ping %q timed out\n", ip)
fmt.Printf("ping %q timed out\n", ip)
if n == pingArgs.num {
if !anyPong {
return errors.New("no reply")
@@ -133,7 +133,7 @@ func runPing(ctx context.Context, args []string) error {
}
if pr.Err != "" {
if pr.IsLocalIP {
outln(pr.Err)
fmt.Println(pr.Err)
return nil
}
return errors.New(pr.Err)
@@ -149,7 +149,7 @@ func runPing(ctx context.Context, args []string) error {
via = string(pingType())
}
if pingArgs.peerAPI {
printf("hit peerapi of %s (%s) at %s in %s\n", pr.NodeIP, pr.NodeName, pr.PeerAPIURL, latency)
fmt.Printf("hit peerapi of %s (%s) at %s in %s\n", pr.NodeIP, pr.NodeName, pr.PeerAPIURL, latency)
return nil
}
anyPong = true
@@ -157,7 +157,7 @@ func runPing(ctx context.Context, args []string) error {
if pr.PeerAPIPort != 0 {
extra = fmt.Sprintf(", %d", pr.PeerAPIPort)
}
printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency)
fmt.Printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency)
if pingArgs.tsmp || pingArgs.icmp {
return nil
}

View File

@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"log"
"net/netip"
"os"
"os/user"
"path/filepath"
@@ -17,7 +18,6 @@ import (
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
"inet.af/netaddr"
"tailscale.com/envknob"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/tsaddr"
@@ -163,10 +163,10 @@ func nodeDNSNameFromArg(st *ipnstate.Status, arg string) (dnsName string, ok boo
if arg == "" {
return
}
argIP, _ := netaddr.ParseIP(arg)
argIP, _ := netip.ParseAddr(arg)
for _, ps := range st.Peer {
dnsName = ps.DNSName
if !argIP.IsZero() {
if argIP.IsValid() {
for _, ip := range ps.TailscaleIPs {
if ip == argIP {
return dnsName, true
@@ -202,7 +202,7 @@ func isSSHOverTailscale() bool {
if !ok {
return false
}
ip, err := netaddr.ParseIP(ipStr)
ip, err := netip.ParseAddr(ipStr)
if err != nil {
return false
}

View File

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

View File

@@ -1,17 +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.
package cli
import (
"errors"
)
func findSSH() (string, error) {
return "", errors.New("Not implemented")
}
func execSSH(ssh string, argv []string) error {
return errors.New("Not implemented")
}

View File

@@ -13,12 +13,12 @@ import (
"fmt"
"net"
"net/http"
"net/netip"
"os"
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
"github.com/toqueteos/webbrowser"
"inet.af/netaddr"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/interfaces"
@@ -46,7 +46,7 @@ https://github.com/tailscale/tailscale/blob/main/ipn/ipnstate/ipnstate.go
`),
Exec: runStatus,
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("status")
fs := flag.NewFlagSet("status", flag.ExitOnError)
fs.BoolVar(&statusArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
fs.BoolVar(&statusArgs.web, "web", false, "run webserver with HTML showing status")
fs.BoolVar(&statusArgs.active, "active", false, "filter output to only peers with active sessions (not applicable to web mode)")
@@ -92,7 +92,7 @@ func runStatus(ctx context.Context, args []string) error {
if err != nil {
return err
}
printf("%s", j)
fmt.Printf("%s", j)
return nil
}
if statusArgs.web {
@@ -101,7 +101,7 @@ func runStatus(ctx context.Context, args []string) error {
return err
}
statusURL := interfaces.HTTPOfListener(ln)
printf("Serving Tailscale status at %v ...\n", statusURL)
fmt.Printf("Serving Tailscale status at %v ...\n", statusURL)
go func() {
<-ctx.Done()
ln.Close()
@@ -131,16 +131,16 @@ func runStatus(ctx context.Context, args []string) error {
// print health check information prior to checking LocalBackend state as
// it may provide an explanation to the user if we choose to exit early
if len(st.Health) > 0 {
printf("# Health check:\n")
fmt.Printf("# Health check:\n")
for _, m := range st.Health {
printf("# - %s\n", m)
fmt.Printf("# - %s\n", m)
}
outln()
fmt.Println()
}
description, ok := isRunningOrStarting(st)
if !ok {
outln(description)
fmt.Println(description)
os.Exit(1)
}
@@ -213,7 +213,7 @@ func runStatus(ctx context.Context, args []string) error {
printPS(ps)
}
}
Stdout.Write(buf.Bytes())
os.Stdout.Write(buf.Bytes())
return nil
}
@@ -260,7 +260,7 @@ func ownerLogin(st *ipnstate.Status, ps *ipnstate.PeerStatus) string {
return u.LoginName
}
func firstIPString(v []netaddr.IP) string {
func firstIPString(v []netip.Addr) string {
if len(v) == 0 {
return ""
}

View File

@@ -13,6 +13,7 @@ import (
"flag"
"fmt"
"log"
"net/netip"
"os"
"reflect"
"runtime"
@@ -24,7 +25,6 @@ import (
shellquote "github.com/kballard/go-shellquote"
"github.com/peterbourgon/ff/v3/ffcli"
qrcode "github.com/skip2/go-qrcode"
"inet.af/netaddr"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/tsaddr"
@@ -85,7 +85,7 @@ var upFlagSet = newUpFlagSet(effectiveGOOS(), &upArgs)
func inTest() bool { return flag.Lookup("test.v") != nil }
func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet {
upf := newFlagSet("up")
upf := flag.NewFlagSet("up", flag.ExitOnError)
upf.BoolVar(&upArgs.qr, "qr", false, "show QR code for login URLs")
upf.BoolVar(&upArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
@@ -195,22 +195,22 @@ type upOutputJSON struct {
}
func warnf(format string, args ...any) {
printf("Warning: "+format+"\n", args...)
fmt.Printf("Warning: "+format+"\n", args...)
}
var (
ipv4default = netaddr.MustParseIPPrefix("0.0.0.0/0")
ipv6default = netaddr.MustParseIPPrefix("::/0")
ipv4default = netip.MustParsePrefix("0.0.0.0/0")
ipv6default = netip.MustParsePrefix("::/0")
)
func validateViaPrefix(ipp netaddr.IPPrefix) error {
func validateViaPrefix(ipp netip.Prefix) error {
if !tsaddr.IsViaPrefix(ipp) {
return fmt.Errorf("%v is not a 4-in-6 prefix", ipp)
}
if ipp.Bits() < (128 - 32) {
return fmt.Errorf("%v 4-in-6 prefix must be at least a /%v", ipp, 128-32)
}
a := ipp.IP().As16()
a := ipp.Addr().As16()
// The first 64 bits of a are the via prefix.
// The next 32 bits are the "site ID".
// The last 32 bits are the IPv4.
@@ -223,13 +223,13 @@ func validateViaPrefix(ipp netaddr.IPPrefix) error {
return nil
}
func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]netaddr.IPPrefix, error) {
routeMap := map[netaddr.IPPrefix]bool{}
func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]netip.Prefix, error) {
routeMap := map[netip.Prefix]bool{}
if advertiseRoutes != "" {
var default4, default6 bool
advroutes := strings.Split(advertiseRoutes, ",")
for _, s := range advroutes {
ipp, err := netaddr.ParseIPPrefix(s)
ipp, err := netip.ParsePrefix(s)
if err != nil {
return nil, fmt.Errorf("%q is not a valid IP address or CIDR prefix", s)
}
@@ -255,10 +255,10 @@ func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]
}
}
if advertiseDefaultRoute {
routeMap[netaddr.MustParseIPPrefix("0.0.0.0/0")] = true
routeMap[netaddr.MustParseIPPrefix("::/0")] = true
routeMap[netip.MustParsePrefix("0.0.0.0/0")] = true
routeMap[netip.MustParsePrefix("::/0")] = true
}
routes := make([]netaddr.IPPrefix, 0, len(routeMap))
routes := make([]netip.Prefix, 0, len(routeMap))
for r := range routeMap {
routes = append(routes, r)
}
@@ -266,7 +266,7 @@ func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]
if routes[i].Bits() != routes[j].Bits() {
return routes[i].Bits() < routes[j].Bits()
}
return routes[i].IP().Less(routes[j].IP())
return routes[i].Addr().Less(routes[j].Addr())
})
return routes, nil
}
@@ -538,7 +538,7 @@ func runUp(ctx context.Context, args []string) (retErr error) {
if env.upArgs.json {
printUpDoneJSON(ipn.NeedsMachineAuth, "")
} else {
fmt.Fprintf(Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
fmt.Fprintf(os.Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
}
case ipn.Running:
// Done full authentication process
@@ -546,7 +546,7 @@ func runUp(ctx context.Context, args []string) (retErr error) {
printUpDoneJSON(ipn.Running, "")
} else if printed {
// Only need to print an update if we printed the "please click" message earlier.
fmt.Fprintf(Stderr, "Success.\n")
fmt.Fprintf(os.Stderr, "Success.\n")
}
select {
case running <- true:
@@ -575,13 +575,13 @@ func runUp(ctx context.Context, args []string) (retErr error) {
fmt.Println(string(data))
}
} else {
fmt.Fprintf(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
fmt.Fprintf(os.Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
if upArgs.qr {
q, err := qrcode.New(*url, qrcode.Medium)
if err != nil {
log.Printf("QR code error: %v", err)
} else {
fmt.Fprintf(Stderr, "%s\n", q.ToString(false))
fmt.Fprintf(os.Stderr, "%s\n", q.ToString(false))
}
}
}
@@ -695,12 +695,12 @@ func checkSSHUpWarnings(ctx context.Context) {
return
}
if len(st.Health) == 1 && strings.Contains(st.Health[0], "SSH") {
printf("%s\n", st.Health[0])
fmt.Printf("%s\n", st.Health[0])
return
}
printf("# Health check:\n")
fmt.Printf("# Health check:\n")
for _, m := range st.Health {
printf(" - %s\n", m)
fmt.Printf(" - %s\n", m)
}
}
@@ -790,7 +790,7 @@ type upCheckEnv struct {
flagSet *flag.FlagSet
upArgs upArgsT
backendState string
curExitNodeIP netaddr.IP
curExitNodeIP netip.Addr
distro distro.Distro
}
@@ -913,10 +913,10 @@ func prefsToFlags(env upCheckEnv, prefs *ipn.Prefs) (flagVal map[string]any) {
ret := make(map[string]any)
exitNodeIPStr := func() string {
if !prefs.ExitNodeIP.IsZero() {
if prefs.ExitNodeIP.IsValid() {
return prefs.ExitNodeIP.String()
}
if prefs.ExitNodeID.IsZero() || env.curExitNodeIP.IsZero() {
if prefs.ExitNodeID.IsZero() || !env.curExitNodeIP.IsValid() {
return ""
}
return env.curExitNodeIP.String()
@@ -991,13 +991,13 @@ func fmtFlagValueArg(flagName string, val any) string {
return fmt.Sprintf("--%s=%v", flagName, shellquote.Join(fmt.Sprint(val)))
}
func hasExitNodeRoutes(rr []netaddr.IPPrefix) bool {
func hasExitNodeRoutes(rr []netip.Prefix) bool {
var v4, v6 bool
for _, r := range rr {
if r.Bits() == 0 {
if r.IP().Is4() {
if r.Addr().Is4() {
v4 = true
} else if r.IP().Is6() {
} else if r.Addr().Is6() {
v6 = true
}
}
@@ -1008,11 +1008,11 @@ func hasExitNodeRoutes(rr []netaddr.IPPrefix) bool {
// withoutExitNodes returns rr unchanged if it has only 1 or 0 /0
// routes. If it has both IPv4 and IPv6 /0 routes, then it returns
// a copy with all /0 routes removed.
func withoutExitNodes(rr []netaddr.IPPrefix) []netaddr.IPPrefix {
func withoutExitNodes(rr []netip.Prefix) []netip.Prefix {
if !hasExitNodeRoutes(rr) {
return rr
}
var out []netaddr.IPPrefix
var out []netip.Prefix
for _, r := range rr {
if r.Bits() > 0 {
out = append(out, r)
@@ -1023,11 +1023,11 @@ func withoutExitNodes(rr []netaddr.IPPrefix) []netaddr.IPPrefix {
// exitNodeIP returns the exit node IP from p, using st to map
// it from its ID form to an IP address if needed.
func exitNodeIP(p *ipn.Prefs, st *ipnstate.Status) (ip netaddr.IP) {
func exitNodeIP(p *ipn.Prefs, st *ipnstate.Status) (ip netip.Addr) {
if p == nil {
return
}
if !p.ExitNodeIP.IsZero() {
if p.ExitNodeIP.IsValid() {
return p.ExitNodeIP
}
id := p.ExitNodeID

View File

@@ -18,7 +18,7 @@ var versionCmd = &ffcli.Command{
ShortUsage: "version [flags]",
ShortHelp: "Print Tailscale version",
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("version")
fs := flag.NewFlagSet("version", flag.ExitOnError)
fs.BoolVar(&versionArgs.daemon, "daemon", false, "also print local node's daemon version")
return fs
})(),
@@ -34,16 +34,16 @@ func runVersion(ctx context.Context, args []string) error {
return fmt.Errorf("too many non-flag arguments: %q", args)
}
if !versionArgs.daemon {
outln(version.String())
fmt.Println(version.String())
return nil
}
printf("Client: %s\n", version.String())
fmt.Printf("Client: %s\n", version.String())
st, err := localClient.StatusWithoutPeers(ctx)
if err != nil {
return err
}
printf("Daemon: %s\n", st.Version)
fmt.Printf("Daemon: %s\n", st.Version)
return nil
}

View File

@@ -20,6 +20,7 @@ import (
"net"
"net/http"
"net/http/cgi"
"net/netip"
"net/url"
"os"
"os/exec"
@@ -27,7 +28,6 @@ import (
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
"inet.af/netaddr"
"tailscale.com/ipn"
"tailscale.com/tailcfg"
"tailscale.com/types/preftype"
@@ -75,7 +75,7 @@ Tailscale, as opposed to a CLI or a native app.
`),
FlagSet: (func() *flag.FlagSet {
webf := newFlagSet("web")
webf := flag.NewFlagSet("web", flag.ExitOnError)
webf.StringVar(&webArgs.listen, "listen", "localhost:8088", "listen address; use port 0 for automatic")
webf.BoolVar(&webArgs.cgi, "cgi", false, "run as CGI script")
return webf
@@ -208,12 +208,20 @@ func qnapAuthn(r *http.Request) (string, *qnapAuthResponse, error) {
return "", nil, err
}
token, err := r.Cookie("qtoken")
if err != nil {
return "", nil, err
if err == nil {
return qnapAuthnQtoken(r, user.Value, token.Value)
}
sid, err := r.Cookie("NAS_SID")
if err == nil {
return qnapAuthnSid(r, user.Value, sid.Value)
}
return "", nil, fmt.Errorf("not authenticated by any mechanism")
}
func qnapAuthnQtoken(r *http.Request, user, token string) (string, *qnapAuthResponse, error) {
query := url.Values{
"qtoken": []string{token.Value},
"user": []string{user.Value},
"qtoken": []string{token},
"user": []string{user},
}
u := url.URL{
Scheme: r.URL.Scheme,
@@ -221,7 +229,26 @@ func qnapAuthn(r *http.Request) (string, *qnapAuthResponse, error) {
Path: "/cgi-bin/authLogin.cgi",
RawQuery: query.Encode(),
}
resp, err := http.Get(u.String())
return qnapAuthnFinish(user, u.String())
}
func qnapAuthnSid(r *http.Request, user, sid string) (string, *qnapAuthResponse, error) {
query := url.Values{
"sid": []string{sid},
}
u := url.URL{
Scheme: r.URL.Scheme,
Host: r.URL.Host,
Path: "/cgi-bin/authLogin.cgi",
RawQuery: query.Encode(),
}
return qnapAuthnFinish(user, u.String())
}
func qnapAuthnFinish(user, url string) (string, *qnapAuthResponse, error) {
resp, err := http.Get(url)
if err != nil {
return "", nil, err
}
@@ -237,7 +264,7 @@ func qnapAuthn(r *http.Request) (string, *qnapAuthResponse, error) {
if authResp.AuthPassed == 0 {
return "", nil, fmt.Errorf("not authenticated")
}
return user.Value, authResp, nil
return user, authResp, nil
}
func synoAuthn() (string, error) {
@@ -366,8 +393,8 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
Status: st.BackendState,
DeviceName: deviceName,
}
exitNodeRouteV4 := netaddr.MustParseIPPrefix("0.0.0.0/0")
exitNodeRouteV6 := netaddr.MustParseIPPrefix("::/0")
exitNodeRouteV4 := netip.MustParsePrefix("0.0.0.0/0")
exitNodeRouteV6 := netip.MustParsePrefix("::/0")
for _, r := range prefs.AdvertiseRoutes {
if r == exitNodeRouteV4 || r == exitNodeRouteV6 {
data.AdvertiseExitNode = true

View File

@@ -1,9 +1,13 @@
tailscale.com/cmd/tailscale 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/negotiate+
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
@@ -26,11 +30,10 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
💣 go4.org/intern from inet.af/netaddr
github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/derp+
go4.org/unsafe/assume-no-moving-gc from go4.org/intern
go4.org/netipx from tailscale.com/wgengine/filter
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
inet.af/netaddr from tailscale.com/cmd/tailscale/cli+
nhooyr.io/websocket from tailscale.com/derp/derphttp+
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
@@ -54,6 +57,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/net/dnsfallback from tailscale.com/control/controlhttp
tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+
💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscale/cli+
tailscale.com/net/netaddr from tailscale.com/disco+
tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli
tailscale.com/net/neterror from tailscale.com/net/netcheck+
tailscale.com/net/netknob from tailscale.com/net/netns
@@ -69,6 +73,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+
tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
tailscale.com/tka from tailscale.com/types/key
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
@@ -78,6 +83,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/types/key from tailscale.com/derp+
tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+
tailscale.com/types/netmap from tailscale.com/ipn
tailscale.com/types/nettype from tailscale.com/net/netcheck+
tailscale.com/types/opt from tailscale.com/net/netcheck+
tailscale.com/types/pad32 from tailscale.com/derp
tailscale.com/types/persist from tailscale.com/ipn
@@ -96,8 +102,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
tailscale.com/wgengine/filter from tailscale.com/types/netmap
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/blake2s from tailscale.com/control/controlbase
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/control/controlbase+
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+
@@ -155,6 +162,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
embed from tailscale.com/cmd/tailscale/cli+
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+
@@ -175,7 +183,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
image/color from github.com/skip2/go-qrcode+
image/png from github.com/skip2/go-qrcode
io from bufio+
io/fs from crypto/rand+
io/fs from crypto/x509+
io/ioutil from golang.org/x/sys/cpu+
log from expvar+
math from compress/flate+
@@ -190,7 +198,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
net/http/cgi from tailscale.com/cmd/tailscale/cli
net/http/httptrace from github.com/tcnksm/go-httpstat+
net/http/internal from net/http
net/netip from net
net/netip from net+
net/textproto from golang.org/x/net/http/httpguts+
net/url from crypto/x509+
os from crypto/rand+

View File

@@ -20,12 +20,12 @@ import (
"net"
"net/http"
"net/http/httptrace"
"net/netip"
"net/url"
"os"
"strings"
"time"
"inet.af/netaddr"
"tailscale.com/derp/derphttp"
"tailscale.com/envknob"
"tailscale.com/ipn"
@@ -266,11 +266,11 @@ func debugPortmap(ctx context.Context) error {
return err
}
gatewayAndSelfIP := func() (gw, self netaddr.IP, ok bool) {
gatewayAndSelfIP := func() (gw, self netip.Addr, ok bool) {
if v := os.Getenv("TS_DEBUG_GW_SELF"); strings.Contains(v, "/") {
i := strings.Index(v, "/")
gw = netaddr.MustParseIP(v[:i])
self = netaddr.MustParseIP(v[i+1:])
gw = netip.MustParseAddr(v[:i])
self = netip.MustParseAddr(v[i+1:])
return gw, self, true
}
return linkMon.GatewayAndSelfIP()

View File

@@ -1,5 +1,7 @@
tailscale.com/cmd/tailscaled 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
@@ -62,11 +64,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/aws/smithy-go/waiter from github.com/aws/aws-sdk-go-v2/service/ssm
L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router
LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh
github.com/fxamacker/cbor/v2 from tailscale.com/tka
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+
github.com/hdevalence/ed25519consensus from tailscale.com/tka
L github.com/insomniacslk/dhcp/dhcpv4 from tailscale.com/net/tstun
L github.com/insomniacslk/dhcp/iana from github.com/insomniacslk/dhcp/dhcpv4
L github.com/insomniacslk/dhcp/interfaces from github.com/insomniacslk/dhcp/dhcpv4
@@ -113,9 +117,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L github.com/u-root/uio/uio from github.com/insomniacslk/dhcp/dhcpv4+
L 💣 github.com/vishvananda/netlink/nl from github.com/tailscale/netlink
L github.com/vishvananda/netns from github.com/tailscale/netlink+
💣 go4.org/intern from inet.af/netaddr
github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/control/controlbase+
go4.org/unsafe/assume-no-moving-gc from go4.org/intern
go4.org/netipx from tailscale.com/ipn/ipnlocal+
W 💣 golang.zx2c4.com/wintun from golang.zx2c4.com/wireguard/tun
💣 golang.zx2c4.com/wireguard/conn from golang.zx2c4.com/wireguard/device+
W 💣 golang.zx2c4.com/wireguard/conn/winrio from golang.zx2c4.com/wireguard/conn
@@ -129,7 +133,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
💣 golang.zx2c4.com/wireguard/tun from golang.zx2c4.com/wireguard/device+
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/cmd/tailscaled+
gvisor.dev/gvisor/pkg/atomicbitops from gvisor.dev/gvisor/pkg/tcpip+
💣 gvisor.dev/gvisor/pkg/buffer from gvisor.dev/gvisor/pkg/tcpip/stack
gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/bufferv2
💣 gvisor.dev/gvisor/pkg/bufferv2 from gvisor.dev/gvisor/pkg/tcpip+
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs+
💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/state/wire+
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
@@ -143,7 +148,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/linewriter+
gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/header+
gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack
💣 gvisor.dev/gvisor/pkg/tcpip/buffer from gvisor.dev/gvisor/pkg/tcpip/header+
gvisor.dev/gvisor/pkg/tcpip/hash/jenkins from gvisor.dev/gvisor/pkg/tcpip/stack+
gvisor.dev/gvisor/pkg/tcpip/header from gvisor.dev/gvisor/pkg/tcpip/header/parse+
gvisor.dev/gvisor/pkg/tcpip/header/parse from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
@@ -152,6 +156,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
gvisor.dev/gvisor/pkg/tcpip/network/hash from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/internal/fragmentation from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/internal/ip from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/internal/multicast from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/ipv4 from tailscale.com/net/tstun+
gvisor.dev/gvisor/pkg/tcpip/network/ipv6 from tailscale.com/wgengine/netstack
gvisor.dev/gvisor/pkg/tcpip/ports from gvisor.dev/gvisor/pkg/tcpip/stack+
@@ -167,7 +172,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
gvisor.dev/gvisor/pkg/tcpip/transport/tcpconntrack from gvisor.dev/gvisor/pkg/tcpip/stack
gvisor.dev/gvisor/pkg/tcpip/transport/udp from tailscale.com/net/tstun+
gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+
inet.af/netaddr from tailscale.com/control/controlclient+
inet.af/peercred from tailscale.com/ipn/ipnserver
W 💣 inet.af/wf from tailscale.com/wf
nhooyr.io/websocket from tailscale.com/derp/derphttp+
@@ -215,6 +219,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/net/dnsfallback from tailscale.com/control/controlclient+
tailscale.com/net/flowtrack from tailscale.com/net/packet+
💣 tailscale.com/net/interfaces from tailscale.com/control/controlclient+
tailscale.com/net/netaddr from tailscale.com/disco+
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
tailscale.com/net/neterror from tailscale.com/net/dns/resolver+
tailscale.com/net/netknob from tailscale.com/net/netns+
@@ -239,6 +244,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/syncs from tailscale.com/control/controlknobs+
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
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+
@@ -251,7 +257,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/key from tailscale.com/control/controlbase+
tailscale.com/types/logger from tailscale.com/control/controlclient+
tailscale.com/types/netmap from tailscale.com/control/controlclient+
tailscale.com/types/nettype from tailscale.com/wgengine/magicsock
tailscale.com/types/nettype from tailscale.com/wgengine/magicsock+
tailscale.com/types/opt from tailscale.com/control/controlclient+
tailscale.com/types/pad32 from tailscale.com/derp
tailscale.com/types/persist from tailscale.com/control/controlclient+
@@ -268,7 +274,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
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+
tailscale.com/util/netconv from tailscale.com/wgengine/magicsock
tailscale.com/util/osshare from tailscale.com/ipn/ipnlocal+
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
tailscale.com/util/racebuild from tailscale.com/logpolicy
@@ -290,7 +295,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
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
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
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 golang.zx2c4.com/wireguard/device+
LD golang.org/x/crypto/blowfish from golang.org/x/crypto/ssh/internal/bcrypt_pbkdf+
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305+
@@ -361,6 +367,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
embed from tailscale.com+
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+
@@ -377,7 +384,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
hash/maphash from go4.org/mem
html from tailscale.com/ipn/ipnlocal+
io from bufio+
io/fs from crypto/rand+
io/fs from crypto/x509+
io/ioutil from github.com/godbus/dbus/v5+
log from expvar+
LD log/syslog from tailscale.com/ssh/tailssh

View File

@@ -21,6 +21,7 @@ import (
"net"
"net/http"
"net/http/pprof"
"net/netip"
"os"
"os/signal"
"path/filepath"
@@ -29,7 +30,6 @@ import (
"syscall"
"time"
"inet.af/netaddr"
"tailscale.com/cmd/tailscaled/childproc"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
@@ -128,6 +128,8 @@ var subCommands = map[string]*func([]string) error{
"be-child": &beChildFunc,
}
var beCLI func() // non-nil if CLI is linked in
func main() {
printVersion := false
flag.IntVar(&args.verbose, "verbose", 0, "log verbosity level; 0 is default, 1 or higher are increasingly verbose")
@@ -143,6 +145,11 @@ func main() {
flag.StringVar(&args.birdSocketPath, "bird-socket", "", "path of the bird unix socket")
flag.BoolVar(&printVersion, "version", false, "print version information and exit")
if len(os.Args) > 0 && filepath.Base(os.Args[0]) == "tailscale" && beCLI != nil {
beCLI()
return
}
if len(os.Args) > 1 {
sub := os.Args[1]
if fp, ok := subCommands[sub]; ok {
@@ -267,13 +274,6 @@ func ipnServerOpts() (o ipnserver.Options) {
}
switch goos {
case "js":
// The js/wasm client has no state storage so for now
// treat all interactive logins as ephemeral.
// TODO(bradfitz): if we start using browser LocalStorage
// or something, then rethink this.
o.LoginFlags = controlclient.LoginEphemeral
fallthrough
default:
o.SurviveDisconnects = true
o.AutostartStateKey = ipn.GlobalDaemonStateKey
@@ -366,11 +366,11 @@ func run() error {
ns.ProcessSubnets = useNetstack || wrapNetstack
if useNetstack {
dialer.UseNetstackForIP = func(ip netaddr.IP) bool {
dialer.UseNetstackForIP = func(ip netip.Addr) bool {
_, ok := e.PeerForIP(ip)
return ok
}
dialer.NetstackDialTCP = func(ctx context.Context, dst netaddr.IPPort) (net.Conn, error) {
dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
return ns.DialContextTCP(ctx, dst)
}
}
@@ -404,7 +404,6 @@ func run() error {
// want to keep running.
signal.Ignore(syscall.SIGPIPE)
go func() {
defer dialer.Close()
select {
case s := <-interrupt:
logf("tailscaled got signal %v; shutting down", s)
@@ -437,6 +436,7 @@ func run() error {
if err != nil {
return fmt.Errorf("safesocket.Listen: %v", err)
}
defer dialer.Close()
err = srv.Run(ctx, ln)
// Cancelation is not an error: it is the only way to stop ipnserver.
@@ -515,7 +515,7 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer, na
} else {
dev, devName, err := tstun.New(logf, name)
if err != nil {
tstun.Diagnose(logf, name)
tstun.Diagnose(logf, name, err)
return nil, false, fmt.Errorf("tstun.New(%q): %w", name, err)
}
conf.Tun = dev

View File

@@ -25,6 +25,7 @@ import (
"encoding/json"
"fmt"
"log"
"net/netip"
"os"
"time"
@@ -32,7 +33,6 @@ import (
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"inet.af/netaddr"
"tailscale.com/envknob"
"tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store"
@@ -245,7 +245,7 @@ func beFirewallKillswitch() bool {
// is passed in via stdin encoded in json.
dcd := json.NewDecoder(os.Stdin)
for {
var routes []netaddr.IPPrefix
var routes []netip.Prefix
if err := dcd.Decode(&routes); err != nil {
log.Fatalf("parent process died or requested exit, exiting (%v)", err)
}

View File

@@ -0,0 +1,25 @@
// 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.
//go:build ts_include_cli
// +build ts_include_cli
package main
import (
"fmt"
"os"
"tailscale.com/cmd/tailscale/cli"
)
func init() {
beCLI = func() {
args := os.Args[1:]
if err := cli.Run(args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
}

4
cmd/tsconnect/.gitignore vendored Normal file
View File

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

195
cmd/tsconnect/build.go Normal file
View File

@@ -0,0 +1,195 @@
// 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 (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"io/fs"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"github.com/andybalholm/brotli"
esbuild "github.com/evanw/esbuild/pkg/api"
"golang.org/x/sync/errgroup"
)
func runBuild() {
buildOptions, err := commonSetup(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 := cleanDist(); err != nil {
log.Fatalf("Cannot clean %s: %v", *distDir, err)
}
buildOptions.Write = true
buildOptions.MinifyWhitespace = true
buildOptions.MinifyIdentifiers = true
buildOptions.MinifySyntax = true
buildOptions.EntryNames = "[dir]/[name]-[hash]"
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)
}
}
// 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 {
log.Fatalf("Cannot write metadata: %v", err)
}
if er := precompressDist(); err != nil {
log.Fatalf("Cannot precompress resources: %v", er)
}
}
// fixEsbuildMetadataPaths re-keys the esbuild metadata file to use paths
// relative to the dist directory (it normally uses paths relative to the cwd,
// which are akward if we're running with a different cwd at serving time).
func fixEsbuildMetadataPaths(metadataStr string) ([]byte, error) {
var metadata EsbuildMetadata
if err := json.Unmarshal([]byte(metadataStr), &metadata); err != nil {
return nil, fmt.Errorf("Cannot parse metadata: %w", err)
}
distAbsPath, err := filepath.Abs(*distDir)
if err != nil {
return nil, fmt.Errorf("Cannot get absolute path from %s: %w", *distDir, err)
}
for outputPath, output := range metadata.Outputs {
outputAbsPath, err := filepath.Abs(outputPath)
if err != nil {
return nil, fmt.Errorf("Cannot get absolute path from %s: %w", outputPath, err)
}
outputRelPath, err := filepath.Rel(distAbsPath, outputAbsPath)
if err != nil {
return nil, fmt.Errorf("Cannot get relative path from %s: %w", outputRelPath, err)
}
delete(metadata.Outputs, outputPath)
metadata.Outputs[outputRelPath] = output
}
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)
if err != nil {
if os.IsNotExist(err) {
return os.MkdirAll(*distDir, 0755)
}
return err
}
for _, file := range files {
if file.Name() != "placeholder" {
if err := os.Remove(filepath.Join(*distDir, file.Name())); err != nil {
return err
}
}
}
return nil
}
func precompressDist() error {
log.Printf("Pre-compressing files in %s/...\n", *distDir)
var eg errgroup.Group
err := fs.WalkDir(os.DirFS(*distDir), ".", func(p string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if !compressibleExtensions[filepath.Ext(p)] {
return nil
}
p = path.Join(*distDir, p)
log.Printf("Pre-compressing %v\n", p)
eg.Go(func() error {
return precompress(p)
})
return nil
})
if err != nil {
return err
}
return eg.Wait()
}
var compressibleExtensions = map[string]bool{
".js": true,
".css": true,
".wasm": true,
}
func precompress(path string) error {
contents, err := os.ReadFile(path)
if err != nil {
return err
}
fi, err := os.Lstat(path)
if err != nil {
return err
}
err = writeCompressed(contents, func(w io.Writer) (io.WriteCloser, error) {
return gzip.NewWriterLevel(w, gzip.BestCompression)
}, path+".gz", fi.Mode())
if err != nil {
return err
}
return writeCompressed(contents, func(w io.Writer) (io.WriteCloser, error) {
return brotli.NewWriterLevel(w, brotli.BestCompression), nil
}, path+".br", fi.Mode())
}
func writeCompressed(contents []byte, compressedWriterCreator func(io.Writer) (io.WriteCloser, error), outputPath string, outputMode fs.FileMode) error {
var buf bytes.Buffer
compressedWriter, err := compressedWriterCreator(&buf)
if err != nil {
return err
}
if _, err := compressedWriter.Write(contents); err != nil {
return err
}
if err := compressedWriter.Close(); err != nil {
return err
}
return os.WriteFile(outputPath, buf.Bytes(), outputMode)
}

151
cmd/tsconnect/common.go Normal file
View File

@@ -0,0 +1,151 @@
// 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 (
"fmt"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strconv"
"time"
esbuild "github.com/evanw/esbuild/pkg/api"
)
const (
devMode = true
prodMode = false
)
// commonSetup performs setup that is common to both dev and build modes.
func commonSetup(dev bool) (*esbuild.BuildOptions, error) {
// Change cwd to to where this file lives -- that's where all inputs for
// esbuild and other build steps live.
if _, filename, _, ok := runtime.Caller(0); ok {
if err := os.Chdir(path.Dir(filename)); err != nil {
return nil, fmt.Errorf("Cannot change cwd: %w", err)
}
}
if err := buildDeps(dev); err != nil {
return nil, fmt.Errorf("Cannot build deps: %w", err)
}
return &esbuild.BuildOptions{
EntryPoints: []string{"src/index.ts", "src/index.css"},
Loader: map[string]esbuild.Loader{".wasm": esbuild.LoaderFile},
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)
},
}},
}, 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)
}
if err := buildWasm(dev); err != nil {
return fmt.Errorf("Cannot build main.wasm: %w", err)
}
if err := installJSDeps(); err != nil {
return fmt.Errorf("Cannot install JS deps: %w", 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)
if err != nil {
return err
}
return os.WriteFile(wasmExecDstPath, contents, 0600)
}
// 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")
args := []string{"build", "-tags", "tailscale_go,osusergo,netgo,nethttpomithttp2,omitidna,omitpemdecrypt"}
if !dev {
// Omit long paths and debug symbols in release builds, to reduce the
// generated WASM binary size.
args = append(args, "-trimpath", "-ldflags", "-s -w")
}
args = append(args, "-o", "src/main.wasm", "./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()
}
// installJSDeps installs the JavaScript dependencies specified by package.json
func installJSDeps() error {
log.Printf("Installing JS deps...\n")
return runYarn()
}
func runYarn(args ...string) error {
cmd := exec.Command(*yarnPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// EsbuildMetadata is the subset of metadata struct (described by
// https://esbuild.github.io/api/#metafile) that we care about for mapping
// from entry points to hashed file names.
type EsbuildMetadata struct {
Outputs map[string]struct {
EntryPoint string `json:"entryPoint,omitempty"`
} `json:"outputs,omitempty"`
}
func setupEsbuildTailwind(build esbuild.PluginBuild, dev bool) {
build.OnLoad(esbuild.OnLoadOptions{
Filter: "./src/index.css$",
}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
start := time.Now()
yarnArgs := []string{"--silent", "tailwind", "-i", args.Path}
if !dev {
yarnArgs = append(yarnArgs, "--minify")
}
cmd := exec.Command(*yarnPath, yarnArgs...)
tailwindOutput, err := cmd.Output()
log.Printf("Ran tailwind in %v\n", time.Since(start))
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
log.Printf("Tailwind stderr: %s", exitErr.Stderr)
}
return esbuild.OnLoadResult{}, fmt.Errorf("Cannot run tailwind: %w", err)
}
tailwindOutputStr := string(tailwindOutput)
return esbuild.OnLoadResult{
Contents: &tailwindOutputStr,
Loader: esbuild.LoaderCSS,
}, nil
})
}

38
cmd/tsconnect/dev.go Normal file
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.
package main
import (
"log"
"net"
"strconv"
esbuild "github.com/evanw/esbuild/pkg/api"
)
func runDev() {
buildOptions, err := commonSetup(devMode)
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()
}

2
cmd/tsconnect/dist/placeholder vendored Normal file
View File

@@ -0,0 +1,2 @@
This is here to make sure the dist/ directory exists for the go:embed command
in serve.go.

38
cmd/tsconnect/index.html Normal file
View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="dist/index.css" />
</head>
<body class="flex flex-col min-h-screen">
<div class="bg-gray-100 border-b border-gray-200 pt-4 pb-2 mb-6">
<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>
</header>
</div>
<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>
<script src="dist/index.js"></script>
</body>
</html>

View File

@@ -0,0 +1,20 @@
{
"name": "tsconnect",
"version": "0.0.1",
"license": "BSD-3-Clause",
"devDependencies": {
"@types/golang-wasm-exec": "^1.15.0",
"@types/qrcode": "^1.4.2",
"qrcode": "^1.5.0",
"tailwindcss": "^3.1.6",
"typescript": "^4.7.4",
"xterm": "^4.18.0"
},
"scripts": {
"lint": "tsc --noEmit"
},
"prettier": {
"semi": false,
"printWidth": 80
}
}

148
cmd/tsconnect/serve.go Normal file
View File

@@ -0,0 +1,148 @@
// 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 (
"bytes"
"embed"
"encoding/json"
"fmt"
"io"
"io/fs"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"time"
"tailscale.com/tsweb"
)
//go:embed index.html
var embeddedFS embed.FS
//go:embed dist/*
var embeddedDistFS embed.FS
var serveStartTime = time.Now()
func runServe() {
mux := http.NewServeMux()
var distFS fs.FS
if *distDir == "./dist" {
var err error
distFS, err = fs.Sub(embeddedDistFS, "dist")
if err != nil {
log.Fatalf("Could not drop dist/ prefix from embedded FS: %v", err)
}
} else {
distFS = os.DirFS(*distDir)
}
indexBytes, err := generateServeIndex(distFS)
if err != nil {
log.Fatalf("Could not generate index.html: %v", err)
}
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, "index.html", serveStartTime, bytes.NewReader(indexBytes))
}))
mux.Handle("/dist/", http.StripPrefix("/dist/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handleServeDist(w, r, distFS)
})))
tsweb.Debugger(mux)
log.Printf("Listening on %s", *addr)
err = http.ListenAndServe(*addr, mux)
if err != nil {
log.Fatal(err)
}
}
func generateServeIndex(distFS fs.FS) ([]byte, error) {
log.Printf("Generating index.html...\n")
rawIndexBytes, err := embeddedFS.ReadFile("index.html")
if err != nil {
return nil, fmt.Errorf("Could not read index.html: %w", err)
}
esbuildMetadataFile, err := distFS.Open("esbuild-metadata.json")
if err != nil {
return nil, fmt.Errorf("Could not open esbuild-metadata.json: %w", err)
}
defer esbuildMetadataFile.Close()
esbuildMetadataBytes, err := ioutil.ReadAll(esbuildMetadataFile)
if err != nil {
return nil, fmt.Errorf("Could not read esbuild-metadata.json: %w", err)
}
var esbuildMetadata EsbuildMetadata
if err := json.Unmarshal(esbuildMetadataBytes, &esbuildMetadata); err != nil {
return nil, fmt.Errorf("Could not parse esbuild-metadata.json: %w", err)
}
entryPointsToHashedDistPaths := make(map[string]string)
for outputPath, output := range esbuildMetadata.Outputs {
if output.EntryPoint != "" {
entryPointsToHashedDistPaths[output.EntryPoint] = path.Join("dist", outputPath)
}
}
indexBytes := rawIndexBytes
for entryPointPath, defaultDistPath := range entryPointsToDefaultDistPaths {
hashedDistPath := entryPointsToHashedDistPaths[entryPointPath]
if hashedDistPath != "" {
indexBytes = bytes.ReplaceAll(indexBytes, []byte(defaultDistPath), []byte(hashedDistPath))
}
}
return indexBytes, nil
}
var entryPointsToDefaultDistPaths = map[string]string{
"src/index.css": "dist/index.css",
"src/index.ts": "dist/index.js",
}
func handleServeDist(w http.ResponseWriter, r *http.Request, distFS fs.FS) {
path := r.URL.Path
var f fs.File
// Prefer pre-compressed versions generated during the build step.
if tsweb.AcceptsEncoding(r, "br") {
if brotliFile, err := distFS.Open(path + ".br"); err == nil {
f = brotliFile
w.Header().Set("Content-Encoding", "br")
}
}
if f == nil && tsweb.AcceptsEncoding(r, "gzip") {
if gzipFile, err := distFS.Open(path + ".gz"); err == nil {
f = gzipFile
w.Header().Set("Content-Encoding", "gzip")
}
}
if f == nil {
if rawFile, err := distFS.Open(path); err == nil {
f = rawFile
} else {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
}
defer f.Close()
// fs.File does not claim to implement Seeker, but in practice it does.
fSeeker, ok := f.(io.ReadSeeker)
if !ok {
http.Error(w, "Not seekable", http.StatusInternalServerError)
return
}
// Aggressively cache static assets, since we cache-bust our assets with
// hashed filenames.
w.Header().Set("Cache-Control", "public, max-age=31535996")
w.Header().Set("Vary", "Accept-Encoding")
http.ServeContent(w, r, path, serveStartTime, fSeeker)
}

15
cmd/tsconnect/src/esbuild.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
// 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.
/**
* @fileoverview Type definitions for types generated by the esbuild build
* process.
*/
declare module "*.wasm" {
const path: string
export default path
}
declare const DEBUG: boolean

View File

@@ -0,0 +1,75 @@
/* 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;
.link {
@apply text-blue-600;
}
.link:hover {
@apply underline;
}
.button {
@apply font-medium py-1 px-2 rounded-md border border-transparent text-center cursor-pointer;
transition-property: background-color, border-color, color, box-shadow;
transition-duration: 120ms;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
min-width: 80px;
}
.button:focus {
@apply outline-none ring;
}
.button:disabled {
@apply pointer-events-none select-none;
}
.input {
@apply appearance-none leading-tight rounded-md bg-white border border-gray-300 hover:border-gray-400 transition-colors px-3;
height: 2.375rem;
}
.input::placeholder {
@apply text-gray-400;
}
.input:disabled {
@apply border-gray-200;
@apply bg-gray-50;
@apply cursor-not-allowed;
}
.input:focus {
@apply outline-none ring border-transparent;
}
.select {
@apply appearance-none py-2 px-3 leading-tight rounded-md bg-white border border-gray-300;
}
.select-with-arrow {
@apply relative;
}
.select-with-arrow .select {
width: 100%;
}
.select-with-arrow::after {
@apply absolute;
content: "";
top: 50%;
right: 0.5rem;
transform: translate(-0.3em, -0.15em);
width: 0.6em;
height: 0.4em;
opacity: 0.6;
background-color: currentColor;
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
}

View File

@@ -0,0 +1,54 @@
// 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

View File

@@ -0,0 +1,14 @@
// 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.
/** @fileoverview Callbacks used by jsStateStore to persist IPN state. */
export const sessionStateStorage: IPNStateStorage = {
setState(id, value) {
window.sessionStorage[`ipn-state-${id}`] = value
},
getState(id) {
return window.sessionStorage[`ipn-state-${id}`] || ""
},
}

View File

@@ -0,0 +1,73 @@
// 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"
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))
document.body.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

@@ -0,0 +1,65 @@
// 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.getElementById("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)
}

80
cmd/tsconnect/src/ssh.ts Normal file
View File

@@ -0,0 +1,80 @@
// 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"
export function showSSHForm(peers: IPNNetMapPeerNode[], ipn: IPN) {
const formNode = document.getElementById("ssh-form") as HTMLDivElement
const noSSHNode = document.getElementById("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.getElementById("ssh-form") as HTMLDivElement
formNode.classList.add("hidden")
}
function ssh(hostname: string, username: string, ipn: IPN) {
const termContainerNode = document.createElement("div")
termContainerNode.className = "p-3"
document.body.appendChild(termContainerNode)
const term = new Terminal({
cursorBlink: true,
})
term.open(termContainerNode)
// Cancel wheel events from scrolling the page if the terminal has scrollback
termContainerNode.addEventListener("wheel", (e) => {
if (term.buffer.active.baseY > 0) {
e.preventDefault()
}
})
let onDataHook: ((data: string) => void) | undefined
term.onData((e) => {
onDataHook?.(e)
})
term.focus()
ipn.ssh(hostname, username, {
writeFn: (input) => term.write(input),
setReadFn: (hook) => (onDataHook = hook),
rows: term.rows,
cols: term.cols,
onDone: () => {
term.dispose()
termContainerNode.remove()
},
})
}

View File

@@ -0,0 +1,86 @@
// 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.
/**
* @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)
*/
declare global {
function newIPN(config: IPNConfig): IPN
interface IPN {
run(callbacks: IPNCallbacks): void
login(): void
logout(): void
ssh(
host: string,
username: string,
termConfig: {
writeFn: (data: string) => void
setReadFn: (readFn: (data: string) => void) => void
rows: number
cols: number
onDone: () => void
}
): void
}
interface IPNStateStorage {
setState(id: string, value: string): void
getState(id: string): string
}
type IPNConfig = {
stateStorage?: IPNStateStorage
}
type IPNCallbacks = {
notifyState: (state: IPNState) => void
notifyNetMap: (netMapStr: string) => void
notifyBrowseToURL: (url: string) => void
notifyPanicRecover: (err: string) => void
}
type IPNNetMap = {
self: IPNNetMapSelfNode
peers: IPNNetMapPeerNode[]
}
type IPNNetMapNode = {
name: string
addresses: string[]
machineKey: string
nodeKey: string
}
type IPNNetMapSelfNode = IPNNetMapNode & {
machineStatus: IPNMachineStatus
}
type IPNNetMapPeerNode = IPNNetMapNode & {
online?: boolean
tailscaleSSHEnabled: boolean
}
}
/** 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,
}

View File

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

View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2017",
"module": "ES2020",
"moduleResolution": "node",
"isolatedModules": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,62 @@
// 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.
// The tsconnect command builds and serves the static site that is generated for
// the Tailscale Connect JS/WASM client. Can be run in 3 modes:
// - dev: builds the site and serves it. JS and CSS changes can be picked up
// with a reload.
// - build: builds the site and writes it to dist/
// - serve: serves the site from dist/ (embedded in the binary)
package main // import "tailscale.com/cmd/tsconnect"
import (
"flag"
"fmt"
"log"
"os"
)
var (
addr = flag.String("addr", ":9090", "address to listen on")
distDir = flag.String("distdir", "./dist", "path of directory to place build output in")
yarnPath = flag.String("yarnpath", "../../tool/yarn", "path yarn executable used to install JavaScript dependencies")
)
func main() {
flag.Usage = usage
flag.Parse()
if len(flag.Args()) != 1 {
flag.Usage()
}
switch flag.Arg(0) {
case "dev":
runDev()
case "build":
runBuild()
case "serve":
runServe()
default:
log.Printf("Unknown command: %s", flag.Arg(0))
flag.Usage()
}
}
func usage() {
fmt.Fprintf(os.Stderr, `
usage: tsconnect {dev|build|serve}
`[1:])
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, `
tsconnect implements development/build/serving workflows for Tailscale Connect.
It can be invoked with one of three subcommands:
- dev: Run in development mode, allowing JS and CSS changes to be picked up without a rebuilt or restart.
- build: Run in production build mode (generating static assets)
- serve: Run in production serve mode (serving static assets)
`[1:])
os.Exit(2)
}

View File

@@ -0,0 +1,430 @@
// 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.
// The wasm package builds a WebAssembly module that provides a subset of
// Tailscale APIs to JavaScript.
//
// When run in the browser, a newIPN(config) function is added to the global JS
// namespace. When called it returns an ipn object with the methods
// run(callbacks), login(), logout(), and ssh(...).
package main
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"log"
"math/rand"
"net"
"net/netip"
"strings"
"syscall/js"
"time"
"golang.org/x/crypto/ssh"
"tailscale.com/control/controlclient"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store/mem"
"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"
)
func main() {
js.Global().Set("newIPN", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
log.Fatal("Usage: newIPN(config)")
return nil
}
return newIPN(args[0])
}))
// Keep Go runtime alive, otherwise it will be shut down before newIPN gets
// called.
<-make(chan bool)
}
func newIPN(jsConfig js.Value) map[string]any {
netns.SetEnabled(false)
var logf logger.Logf = log.Printf
dialer := new(tsdial.Dialer)
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
Dialer: dialer,
})
if err != nil {
log.Fatal(err)
}
tunDev, magicConn, dnsManager, ok := eng.(wgengine.InternalsGetter).GetInternals()
if !ok {
log.Fatalf("%T is not a wgengine.InternalsGetter", eng)
}
ns, err := netstack.Create(logf, tunDev, eng, magicConn, dialer, dnsManager)
if err != nil {
log.Fatalf("netstack.Create: %v", err)
}
ns.ProcessLocalIPs = true
ns.ProcessSubnets = true
if err := ns.Start(); err != nil {
log.Fatalf("failed to start netstack: %v", err)
}
dialer.UseNetstackForIP = func(ip netip.Addr) bool {
return true
}
dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
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{
SurviveDisconnects: true,
LoginFlags: controlclient.LoginEphemeral,
})
if err != nil {
log.Fatalf("ipnserver.New: %v", err)
}
lb := srv.LocalBackend()
jsIPN := &jsIPN{
dialer: dialer,
srv: srv,
lb: lb,
}
return map[string]any{
"run": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 1 {
log.Fatal(`Usage: run({
notifyState(state: int): void,
notifyNetMap(netMap: object): void,
notifyBrowseToURL(url: string): void,
notifyPanicRecover(err: string): void,
})`)
return nil
}
jsIPN.run(args[0])
return nil
}),
"login": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 0 {
log.Printf("Usage: login()")
return nil
}
jsIPN.login()
return nil
}),
"logout": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 0 {
log.Printf("Usage: logout()")
return nil
}
jsIPN.logout()
return nil
}),
"ssh": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) != 3 {
log.Printf("Usage: ssh(hostname, userName, termConfig)")
return nil
}
go jsIPN.ssh(
args[0].String(),
args[1].String(),
args[2])
return nil
}),
}
}
type jsIPN struct {
dialer *tsdial.Dialer
srv *ipnserver.Server
lb *ipnlocal.LocalBackend
}
func (i *jsIPN) run(jsCallbacks js.Value) {
notifyState := func(state ipn.State) {
jsCallbacks.Call("notifyState", int(state))
}
notifyState(ipn.NoState)
i.lb.SetNotifyCallback(func(n ipn.Notify) {
// Panics in the notify callback are likely due to be due to bugs in
// this bridging module (as opposed to actual bugs in Tailscale) and
// thus may be recoverable. Let the UI know, and allow the user to
// choose if they want to reload the page.
defer func() {
if r := recover(); r != nil {
fmt.Println("Panic recovered:", r)
jsCallbacks.Call("notifyPanicRecover", fmt.Sprint(r))
}
}()
log.Printf("NOTIFY: %+v", n)
if n.State != nil {
notifyState(*n.State)
}
if nm := n.NetMap; nm != nil && i.lb.State() == ipn.Running {
jsNetMap := jsNetMap{
Self: jsNetMapSelfNode{
jsNetMapNode: jsNetMapNode{
Name: nm.Name,
Addresses: mapSlice(nm.Addresses, func(a netip.Prefix) string { return a.Addr().String() }),
NodeKey: nm.NodeKey.String(),
MachineKey: nm.MachineKey.String(),
},
MachineStatus: int(nm.MachineStatus),
},
Peers: mapSlice(nm.Peers, func(p *tailcfg.Node) jsNetMapPeerNode {
name := p.Name
if name == "" {
// In practice this should only happen for Hello.
name = p.Hostinfo.Hostname()
}
return jsNetMapPeerNode{
jsNetMapNode: jsNetMapNode{
Name: name,
Addresses: mapSlice(p.Addresses, func(a netip.Prefix) string { return a.Addr().String() }),
MachineKey: p.Machine.String(),
NodeKey: p.Key.String(),
},
Online: p.Online,
TailscaleSSHEnabled: p.Hostinfo.TailscaleSSHEnabled(),
}
}),
}
if jsonNetMap, err := json.Marshal(jsNetMap); err == nil {
jsCallbacks.Call("notifyNetMap", string(jsonNetMap))
} else {
log.Printf("Could not generate JSON netmap: %v", err)
}
}
if n.BrowseToURL != nil {
jsCallbacks.Call("notifyBrowseToURL", *n.BrowseToURL)
}
})
go func() {
err := i.lb.Start(ipn.Options{
StateKey: "wasm",
UpdatePrefs: &ipn.Prefs{
ControlURL: ipn.DefaultControlURL,
RouteAll: false,
AllowSingleHosts: true,
WantRunning: true,
Hostname: generateHostname(),
},
})
if err != nil {
log.Printf("Start error: %v", err)
}
}()
go func() {
ln, _, err := safesocket.Listen("", 0)
if err != nil {
log.Fatalf("safesocket.Listen: %v", err)
}
err = i.srv.Run(context.Background(), ln)
log.Fatalf("ipnserver.Run exited: %v", err)
}()
}
func (i *jsIPN) login() {
go i.lb.StartLoginInteractive()
}
func (i *jsIPN) logout() {
if i.lb.State() == ipn.NoState {
log.Printf("Backend not running")
}
go i.lb.Logout()
}
func (i *jsIPN) ssh(host, username string, termConfig js.Value) {
writeFn := termConfig.Get("writeFn")
setReadFn := termConfig.Get("setReadFn")
rows := termConfig.Get("rows").Int()
cols := termConfig.Get("cols").Int()
onDone := 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))
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
c, err := i.dialer.UserDial(ctx, "tcp", net.JoinHostPort(host, "22"))
if err != nil {
writeError("Dial", err)
return
}
defer c.Close()
config := &ssh.ClientConfig{
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
User: username,
}
sshConn, _, _, err := ssh.NewClientConn(c, host, config)
if err != nil {
writeError("SSH Connection", err)
return
}
defer sshConn.Close()
write("SSH Connected\r\n")
sshClient := ssh.NewClient(sshConn, nil, nil)
defer sshClient.Close()
session, err := sshClient.NewSession()
if err != nil {
writeError("SSH Session", err)
return
}
write("Session Established\r\n")
defer session.Close()
stdin, err := session.StdinPipe()
if err != nil {
writeError("SSH Stdin", err)
return
}
session.Stdout = termWriter{writeFn}
session.Stderr = termWriter{writeFn}
setReadFn.Invoke(js.FuncOf(func(this js.Value, args []js.Value) interface{} {
input := args[0].String()
_, err := stdin.Write([]byte(input))
if err != nil {
writeError("Write Input", err)
}
return nil
}))
err = session.RequestPty("xterm", rows, cols, ssh.TerminalModes{})
if err != nil {
writeError("Pseudo Terminal", err)
return
}
err = session.Shell()
if err != nil {
writeError("Shell", err)
return
}
err = session.Wait()
if err != nil {
writeError("Exit", err)
return
}
}
type termWriter struct {
f js.Value
}
func (w termWriter) Write(p []byte) (n int, err error) {
r := bytes.Replace(p, []byte("\n"), []byte("\n\r"), -1)
w.f.Invoke(string(r))
return len(p), nil
}
type jsNetMap struct {
Self jsNetMapSelfNode `json:"self"`
Peers []jsNetMapPeerNode `json:"peers"`
}
type jsNetMapNode struct {
Name string `json:"name"`
Addresses []string `json:"addresses"`
MachineKey string `json:"machineKey"`
NodeKey string `json:"nodeKey"`
}
type jsNetMapSelfNode struct {
jsNetMapNode
MachineStatus int `json:"machineStatus"`
}
type jsNetMapPeerNode struct {
jsNetMapNode
Online *bool `json:"online,omitempty"`
TailscaleSSHEnabled bool `json:"tailscaleSSHEnabled"`
}
type jsStateStore struct {
jsStateStorage js.Value
}
func (s *jsStateStore) ReadState(id ipn.StateKey) ([]byte, error) {
jsValue := s.jsStateStorage.Call("getState", string(id))
if jsValue.String() == "" {
return nil, ipn.ErrStateNotExist
}
return hex.DecodeString(jsValue.String())
}
func (s *jsStateStore) WriteState(id ipn.StateKey, bs []byte) error {
s.jsStateStorage.Call("setState", string(id), hex.EncodeToString(bs))
return nil
}
func mapSlice[T any, M any](a []T, f func(T) M) []M {
n := make([]M, len(a))
for i, e := range a {
n[i] = f(e)
}
return n
}
func filterSlice[T any](a []T, f func(T) bool) []T {
n := make([]T, 0, len(a))
for _, e := range a {
if f(e) {
n = append(n, e)
}
}
return n
}
func generateHostname() string {
tails := words.Tails()
scales := words.Scales()
if rand.Int()%2 == 0 {
// JavaScript
tails = filterSlice(tails, func(s string) bool { return strings.HasPrefix(s, "j") })
scales = filterSlice(scales, func(s string) bool { return strings.HasPrefix(s, "s") })
} else {
// WebAssembly
tails = filterSlice(tails, func(s string) bool { return strings.HasPrefix(s, "w") })
scales = filterSlice(scales, func(s string) bool { return strings.HasPrefix(s, "a") })
}
tail := tails[rand.Intn(len(tails))]
scale := scales[rand.Intn(len(scales))]
return fmt.Sprintf("%s-%s", tail, scale)
}

644
cmd/tsconnect/yarn.lock Normal file
View File

@@ -0,0 +1,644 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
dependencies:
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
"@nodelib/fs.walk@^1.2.3":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
dependencies:
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@types/golang-wasm-exec@^1.15.0":
version "1.15.0"
resolved "https://registry.yarnpkg.com/@types/golang-wasm-exec/-/golang-wasm-exec-1.15.0.tgz#d0aafbb2b0dc07eaf45dfb83bfb6cdd5b2b3c55c"
integrity sha512-FrL97mp7WW8LqNinVkzTVKOIQKuYjQqgucnh41+1vRQ+bf1LT8uh++KRf9otZPXsa6H1p8ruIGz1BmCGttOL6Q==
"@types/node@*":
version "18.6.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.1.tgz#828e4785ccca13f44e2fb6852ae0ef11e3e20ba5"
integrity sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==
"@types/qrcode@^1.4.2":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74"
integrity sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ==
dependencies:
"@types/node" "*"
acorn-node@^1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==
dependencies:
acorn "^7.0.0"
acorn-walk "^7.0.0"
xtend "^4.0.2"
acorn-walk@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
acorn@^7.0.0:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^4.0.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
arg@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
braces@^3.0.2, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
camelcase-css@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@^1.1.4, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
defined@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==
detective@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034"
integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==
dependencies:
acorn-node "^1.8.2"
defined "^1.0.0"
minimist "^1.2.6"
didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
dijkstrajs@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257"
integrity sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg==
dlv@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
encode-utf8@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
fast-glob@^3.2.11:
version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.4"
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
dependencies:
reusify "^1.0.4"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1:
version "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:
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==
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob-parent@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
dependencies:
is-glob "^4.0.3"
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
lilconfig@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4"
integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
dependencies:
p-locate "^4.1.0"
merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.4:
version "4.0.5"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
dependencies:
braces "^3.0.2"
picomatch "^2.3.1"
minimist@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
nanoid@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
object-hash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
dependencies:
p-limit "^2.2.0"
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
pngjs@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
postcss-import@^14.1.0:
version "14.1.0"
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0"
integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==
dependencies:
postcss-value-parser "^4.0.0"
read-cache "^1.0.0"
resolve "^1.1.7"
postcss-js@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00"
integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==
dependencies:
camelcase-css "^2.0.1"
postcss-load-config@^3.1.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855"
integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
dependencies:
lilconfig "^2.0.5"
yaml "^1.10.2"
postcss-nested@5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc"
integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==
dependencies:
postcss-selector-parser "^6.0.6"
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.6:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.4.14:
version "8.4.14"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf"
integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==
dependencies:
nanoid "^3.3.4"
picocolors "^1.0.0"
source-map-js "^1.0.2"
qrcode@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b"
integrity sha512-9MgRpgVc+/+47dFvQeD6U2s0Z92EsKzcHogtum4QB+UNd025WOJSHvn/hjk9xmzj7Stj95CyUAs31mrjxliEsQ==
dependencies:
dijkstrajs "^1.0.1"
encode-utf8 "^1.0.3"
pngjs "^5.0.0"
yargs "^15.3.1"
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
quick-lru@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
read-cache@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==
dependencies:
pify "^2.3.0"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
resolve@^1.1.7, resolve@^1.22.1:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
dependencies:
is-core-module "^2.9.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
dependencies:
queue-microtask "^1.2.2"
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
source-map-js@^1.0.2:
version "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:
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==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
tailwindcss@^3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.1.6.tgz#bcb719357776c39e6376a8d84e9834b2b19a49f1"
integrity sha512-7skAOY56erZAFQssT1xkpk+kWt2NrO45kORlxFPXUt3CiGsVPhH1smuH5XoDH6sGPXLyBv+zgCKA2HWBsgCytg==
dependencies:
arg "^5.0.2"
chokidar "^3.5.3"
color-name "^1.1.4"
detective "^5.2.1"
didyoumean "^1.2.2"
dlv "^1.1.3"
fast-glob "^3.2.11"
glob-parent "^6.0.2"
is-glob "^4.0.3"
lilconfig "^2.0.5"
normalize-path "^3.0.0"
object-hash "^3.0.0"
picocolors "^1.0.0"
postcss "^8.4.14"
postcss-import "^14.1.0"
postcss-js "^4.0.0"
postcss-load-config "^3.1.4"
postcss-nested "5.0.6"
postcss-selector-parser "^6.0.10"
postcss-value-parser "^4.2.0"
quick-lru "^5.1.1"
resolve "^1.22.1"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
typescript@^4.7.4:
version "4.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
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"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
xterm@^4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0.tgz#a1f6ab2c330c3918fb094ae5f4c2562987398ea1"
integrity sha512-JQoc1S0dti6SQfI0bK1AZvGnAxH4MVw45ZPFSO6FHTInAiau3Ix77fSxNx3mX4eh9OL4AYa8+4C8f5UvnSfppQ==
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==
yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs@^15.3.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"

View File

@@ -11,3 +11,4 @@
// Its functionality moved into tailscaled.
//
// See https://github.com/tailscale/tailscale/issues/3802
package main

View File

@@ -7,15 +7,14 @@ package tests
import (
"fmt"
"inet.af/netaddr"
"net/netip"
)
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices
type StructWithoutPtrs struct {
Int int
Pfx netaddr.IPPrefix
Pfx netip.Prefix
}
type Map struct {
@@ -54,6 +53,6 @@ type StructWithSlices struct {
Ints []*int
Slice []string
Prefixes []netaddr.IPPrefix
Prefixes []netip.Prefix
Data []byte
}

View File

@@ -7,7 +7,7 @@
package tests
import (
"inet.af/netaddr"
"net/netip"
)
// Clone makes a deep copy of StructWithPtrs.
@@ -50,7 +50,7 @@ func (src *StructWithoutPtrs) Clone() *StructWithoutPtrs {
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _StructWithoutPtrsCloneNeedsRegeneration = StructWithoutPtrs(struct {
Int int
Pfx netaddr.IPPrefix
Pfx netip.Prefix
}{})
// Clone makes a deep copy of Map.
@@ -178,6 +178,6 @@ var _StructWithSlicesCloneNeedsRegeneration = StructWithSlices(struct {
Structs []StructWithPtrs
Ints []*int
Slice []string
Prefixes []netaddr.IPPrefix
Prefixes []netip.Prefix
Data []byte
}{})

View File

@@ -9,9 +9,9 @@ package tests
import (
"encoding/json"
"errors"
"net/netip"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/types/views"
)
@@ -134,13 +134,13 @@ func (v *StructWithoutPtrsView) UnmarshalJSON(b []byte) error {
return nil
}
func (v StructWithoutPtrsView) Int() int { return v.ж.Int }
func (v StructWithoutPtrsView) Pfx() netaddr.IPPrefix { return v.ж.Pfx }
func (v StructWithoutPtrsView) Int() int { return v.ж.Int }
func (v StructWithoutPtrsView) Pfx() netip.Prefix { return v.ж.Pfx }
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _StructWithoutPtrsViewNeedsRegeneration = StructWithoutPtrs(struct {
Int int
Pfx netaddr.IPPrefix
Pfx netip.Prefix
}{})
// View returns a readonly view of Map.
@@ -311,6 +311,6 @@ var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {
Structs []StructWithPtrs
Ints []*int
Slice []string
Prefixes []netaddr.IPPrefix
Prefixes []netip.Prefix
Data []byte
}{})

View File

@@ -177,7 +177,7 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thi
case "byte":
it.Import("go4.org/mem")
writeTemplate("byteSliceField")
case "inet.af/netaddr.IPPrefix":
case "inet.af/netip.Prefix", "net/netip.Prefix":
it.Import("tailscale.com/types/views")
writeTemplate("ipPrefixSliceField")
default:

View File

@@ -16,6 +16,7 @@ import (
"io/ioutil"
"log"
"net/http"
"net/netip"
"net/url"
"os"
"reflect"
@@ -26,7 +27,6 @@ import (
"time"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/control/controlknobs"
"tailscale.com/envknob"
"tailscale.com/health"
@@ -129,7 +129,7 @@ type Options struct {
// Pinger is the LocalBackend.Ping method.
type Pinger interface {
// Ping is a request to do a ping with the peer handling the given IP.
Ping(ctx context.Context, ip netaddr.IP, pingType tailcfg.PingType) (*ipnstate.PingResult, error)
Ping(ctx context.Context, ip netip.Addr, pingType tailcfg.PingType) (*ipnstate.PingResult, error)
}
type Decompressor interface {
@@ -1167,8 +1167,8 @@ func TrimWGConfig() opt.Bool {
// It should not return false positives.
//
// TODO(bradfitz): Change controlclient.Options.SkipIPForwardingCheck into a
// func([]netaddr.IPPrefix) error signature instead.
func ipForwardingBroken(routes []netaddr.IPPrefix, state *interfaces.State) bool {
// func([]netip.Prefix) error signature instead.
func ipForwardingBroken(routes []netip.Prefix, state *interfaces.State) bool {
warn, err := netutil.CheckIPForwarding(routes, state)
if err != nil {
// Oh well, we tried. This is just for debugging.
@@ -1408,7 +1408,7 @@ func (c *Direct) DoNoiseRequest(req *http.Request) (*http.Response, error) {
// doPingerPing sends a Ping to pr.IP using pinger, and sends an http request back to
// pr.URL with ping response data.
func doPingerPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, pinger Pinger, pingType tailcfg.PingType) {
if pr.URL == "" || pr.IP.IsZero() || pinger == nil {
if pr.URL == "" || !pr.IP.IsValid() || pinger == nil {
logf("invalid ping request: missing url, ip or pinger")
return
}

View File

@@ -8,10 +8,10 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/netip"
"testing"
"time"
"inet.af/netaddr"
"tailscale.com/hostinfo"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/tsdial"
@@ -86,7 +86,7 @@ func TestNewDirect(t *testing.T) {
func fakeEndpoints(ports ...uint16) (ret []tailcfg.Endpoint) {
for _, port := range ports {
ret = append(ret, tailcfg.Endpoint{
Addr: netaddr.IPPortFrom(netaddr.IP{}, port),
Addr: netip.AddrPortFrom(netip.Addr{}, port),
})
}
return

View File

@@ -5,10 +5,11 @@
package controlclient
import (
"fmt"
"log"
"net/netip"
"sort"
"inet.af/netaddr"
"tailscale.com/envknob"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
@@ -165,12 +166,6 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
}
ms.addUserProfile(peer.User)
}
if len(resp.DNS) > 0 {
nm.DNS.Nameservers = resp.DNS
}
if len(resp.SearchPaths) > 0 {
nm.DNS.Domains = resp.SearchPaths
}
if Debug.ProxyDNS {
nm.DNS.Proxied = true
}
@@ -244,7 +239,7 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
sortNodes(newFull)
}
if len(mapRes.PeerSeenChange) != 0 || len(mapRes.OnlineChange) != 0 {
if len(mapRes.PeerSeenChange) != 0 || len(mapRes.OnlineChange) != 0 || len(mapRes.PeersChangedPatch) != 0 {
peerByID := make(map[tailcfg.NodeID]*tailcfg.Node, len(newFull))
for _, n := range newFull {
peerByID[n.ID] = n
@@ -265,6 +260,16 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
n.Online = &online
}
}
for _, ec := range mapRes.PeersChangedPatch {
if n, ok := peerByID[ec.NodeID]; ok {
if ec.DERPRegion != 0 {
n.DERP = fmt.Sprintf("%s:%v", tailcfg.DerpMagicIP, ec.DERPRegion)
}
if ec.Endpoints != nil {
n.Endpoints = ec.Endpoints
}
}
}
}
mapRes.Peers = newFull
@@ -298,13 +303,13 @@ func cloneNodes(v1 []*tailcfg.Node) []*tailcfg.Node {
var debugSelfIPv6Only = envknob.Bool("TS_DEBUG_SELF_V6_ONLY")
func filterSelfAddresses(in []netaddr.IPPrefix) (ret []netaddr.IPPrefix) {
func filterSelfAddresses(in []netip.Prefix) (ret []netip.Prefix) {
switch {
default:
return in
case debugSelfIPv6Only:
for _, a := range in {
if a.IP().Is6() {
if a.Addr().Is6() {
ret = append(ret, a)
}
}

View File

@@ -34,6 +34,16 @@ func TestUndeltaPeers(t *testing.T) {
n.LastSeen = &t
}
}
withDERP := func(d string) func(*tailcfg.Node) {
return func(n *tailcfg.Node) {
n.DERP = d
}
}
withEP := func(ep string) func(*tailcfg.Node) {
return func(n *tailcfg.Node) {
n.Endpoints = []string{ep}
}
}
n := func(id tailcfg.NodeID, name string, mod ...func(*tailcfg.Node)) *tailcfg.Node {
n := &tailcfg.Node{ID: id, Name: name}
for _, f := range mod {
@@ -137,7 +147,53 @@ func TestUndeltaPeers(t *testing.T) {
n(2, "bar", seenAt(time.Unix(123, 0))),
),
},
{
name: "ep_change_derp",
prev: peers(n(1, "foo", withDERP("127.3.3.40:3"))),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
DERPRegion: 4,
}},
},
want: peers(n(1, "foo", withDERP("127.3.3.40:4"))),
},
{
name: "ep_change_udp",
prev: peers(n(1, "foo", withEP("1.2.3.4:111"))),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
Endpoints: []string{"1.2.3.4:56"},
}},
},
want: peers(n(1, "foo", withEP("1.2.3.4:56"))),
},
{
name: "ep_change_udp",
prev: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:111"))),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
Endpoints: []string{"1.2.3.4:56"},
}},
},
want: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:56"))),
},
{
name: "ep_change_both",
prev: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:111"))),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
DERPRegion: 2,
Endpoints: []string{"1.2.3.4:56"},
}},
},
want: peers(n(1, "foo", withDERP("127.3.3.40:2"), withEP("1.2.3.4:56"))),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if !tt.curTime.IsZero() {

View File

@@ -11,13 +11,13 @@ import (
"errors"
"fmt"
"io"
"net/netip"
"sync"
"sync/atomic"
"time"
"go4.org/mem"
"golang.org/x/time/rate"
"inet.af/netaddr"
"tailscale.com/types/key"
"tailscale.com/types/logger"
)
@@ -156,6 +156,8 @@ func (c *Client) parseServerInfo(b []byte) (*serverInfo, error) {
}
type clientInfo struct {
// Version is the DERP protocol version that the client was built with.
// See the ProtocolVersion const.
Version int `json:"version,omitempty"`
// MeshKey optionally specifies a pre-shared key used by
@@ -595,17 +597,17 @@ func (c *Client) setSendRateLimiter(sm ServerInfoMessage) {
//
// If the client is broken in some previously detectable way, it
// returns an error.
func (c *Client) LocalAddr() (netaddr.IPPort, error) {
func (c *Client) LocalAddr() (netip.AddrPort, error) {
readErr, _ := c.readErr.Load().(error)
if readErr != nil {
return netaddr.IPPort{}, readErr
return netip.AddrPort{}, readErr
}
if c.nc == nil {
return netaddr.IPPort{}, errors.New("nil conn")
return netip.AddrPort{}, errors.New("nil conn")
}
a := c.nc.LocalAddr()
if a == nil {
return netaddr.IPPort{}, errors.New("nil addr")
return netip.AddrPort{}, errors.New("nil addr")
}
return netaddr.ParseIPPort(a.String())
return netip.ParseAddrPort(a.String())
}

View File

@@ -25,6 +25,7 @@ import (
"math/rand"
"net"
"net/http"
"net/netip"
"os/exec"
"runtime"
"strconv"
@@ -36,7 +37,6 @@ import (
"go4.org/mem"
"golang.org/x/sync/errgroup"
"golang.org/x/time/rate"
"inet.af/netaddr"
"tailscale.com/client/tailscale"
"tailscale.com/disco"
"tailscale.com/envknob"
@@ -162,8 +162,8 @@ type Server struct {
// src.
sentTo map[key.NodePublic]map[key.NodePublic]int64 // src => dst => dst's latest sclient.connNum
// maps from netaddr.IPPort to a client's public key
keyOfAddr map[netaddr.IPPort]key.NodePublic
// maps from netip.AddrPort to a client's public key
keyOfAddr map[netip.AddrPort]key.NodePublic
}
// clientSet represents 1 or more *sclients.
@@ -314,7 +314,7 @@ func NewServer(privateKey key.NodePrivate, logf logger.Logf) *Server {
watchers: map[*sclient]bool{},
sentTo: map[key.NodePublic]map[key.NodePublic]int64{},
avgQueueDuration: new(uint64),
keyOfAddr: map[netaddr.IPPort]key.NodePublic{},
keyOfAddr: map[netip.AddrPort]key.NodePublic{},
}
s.initMetacert()
s.packetsRecvDisco = s.packetsRecvByKind.Get("disco")
@@ -410,7 +410,7 @@ func (s *Server) IsClientConnectedForTest(k key.NodePublic) bool {
// on its own.
//
// Accept closes nc.
func (s *Server) Accept(nc Conn, brw *bufio.ReadWriter, remoteAddr string) {
func (s *Server) Accept(ctx context.Context, nc Conn, brw *bufio.ReadWriter, remoteAddr string) {
closed := make(chan struct{})
s.mu.Lock()
@@ -428,7 +428,7 @@ func (s *Server) Accept(nc Conn, brw *bufio.ReadWriter, remoteAddr string) {
s.mu.Unlock()
}()
if err := s.accept(nc, brw, remoteAddr, connNum); err != nil && !s.isClosed() {
if err := s.accept(ctx, nc, brw, remoteAddr, connNum); err != nil && !s.isClosed() {
s.logf("derp: %s: %v", remoteAddr, err)
}
}
@@ -641,7 +641,7 @@ func (s *Server) addWatcher(c *sclient) {
go c.requestMeshUpdate()
}
func (s *Server) accept(nc Conn, brw *bufio.ReadWriter, remoteAddr string, connNum int64) error {
func (s *Server) accept(ctx context.Context, nc Conn, brw *bufio.ReadWriter, remoteAddr string, connNum int64) error {
br := brw.Reader
nc.SetDeadline(time.Now().Add(10 * time.Second))
bw := &lazyBufioWriter{w: nc, lbw: brw.Writer}
@@ -660,10 +660,10 @@ func (s *Server) accept(nc Conn, brw *bufio.ReadWriter, remoteAddr string, connN
// At this point we trust the client so we don't time out.
nc.SetDeadline(time.Time{})
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(ctx)
defer cancel()
remoteIPPort, _ := netaddr.ParseIPPort(remoteAddr)
remoteIPPort, _ := netip.ParseAddrPort(remoteAddr)
c := &sclient{
connNum: connNum,
@@ -1246,7 +1246,7 @@ type sclient struct {
logf logger.Logf
done <-chan struct{} // closed when connection closes
remoteAddr string // usually ip:port from net.Conn.RemoteAddr().String()
remoteIPPort netaddr.IPPort // zero if remoteAddr is not ip:port.
remoteIPPort netip.AddrPort // zero if remoteAddr is not ip:port.
sendQueue chan pkt // packets queued to this client; never closed
discoSendQueue chan pkt // important packets queued to this client; never closed
sendPongCh chan [8]byte // pong replies to send to the client; never closed
@@ -1759,8 +1759,8 @@ type BytesSentRecv struct {
// parseSSOutput parses the output from the specific call to ss in ServeDebugTraffic.
// Separated out for ease of testing.
func parseSSOutput(raw string) map[netaddr.IPPort]BytesSentRecv {
newState := map[netaddr.IPPort]BytesSentRecv{}
func parseSSOutput(raw string) map[netip.AddrPort]BytesSentRecv {
newState := map[netip.AddrPort]BytesSentRecv{}
// parse every 2 lines and get src and dst ips, and kv pairs
lines := strings.Split(raw, "\n")
for i := 0; i < len(lines); i += 2 {
@@ -1768,7 +1768,7 @@ func parseSSOutput(raw string) map[netaddr.IPPort]BytesSentRecv {
if len(ipInfo) < 5 {
continue
}
src, err := netaddr.ParseIPPort(ipInfo[4])
src, err := netip.ParseAddrPort(ipInfo[4])
if err != nil {
continue
}
@@ -1793,7 +1793,7 @@ func parseSSOutput(raw string) map[netaddr.IPPort]BytesSentRecv {
}
func (s *Server) ServeDebugTraffic(w http.ResponseWriter, r *http.Request) {
prevState := map[netaddr.IPPort]BytesSentRecv{}
prevState := map[netip.AddrPort]BytesSentRecv{}
enc := json.NewEncoder(w)
for r.Context().Err() == nil {
output, err := exec.Command("ss", "-i", "-H", "-t").Output()

View File

@@ -85,8 +85,12 @@ func TestSendRecv(t *testing.T) {
t.Fatal(err)
}
defer cin.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
brwServer := bufio.NewReadWriter(bufio.NewReader(cin), bufio.NewWriter(cin))
go s.Accept(cin, brwServer, fmt.Sprintf("test-client-%d", i))
go s.Accept(ctx, cin, brwServer, fmt.Sprintf("test-client-%d", i))
key := clientPrivateKeys[i]
brw := bufio.NewReadWriter(bufio.NewReader(cout), bufio.NewWriter(cout))
@@ -231,10 +235,10 @@ func TestSendFreeze(t *testing.T) {
// Then cathy stops processing messsages.
// That should not interfere with alice talking to bob.
newClient := func(name string, k key.NodePrivate) (c *Client, clientConn nettest.Conn) {
newClient := func(ctx context.Context, name string, k key.NodePrivate) (c *Client, clientConn nettest.Conn) {
t.Helper()
c1, c2 := nettest.NewConn(name, 1024)
go s.Accept(c1, bufio.NewReadWriter(bufio.NewReader(c1), bufio.NewWriter(c1)), name)
go s.Accept(ctx, c1, bufio.NewReadWriter(bufio.NewReader(c1), bufio.NewWriter(c1)), name)
brw := bufio.NewReadWriter(bufio.NewReader(c2), bufio.NewWriter(c2))
c, err := NewClient(k, c2, brw, t.Logf)
@@ -245,14 +249,17 @@ func TestSendFreeze(t *testing.T) {
return c, c2
}
ctx, clientCtxCancel := context.WithCancel(context.Background())
defer clientCtxCancel()
aliceKey := key.NewNode()
aliceClient, aliceConn := newClient("alice", aliceKey)
aliceClient, aliceConn := newClient(ctx, "alice", aliceKey)
bobKey := key.NewNode()
bobClient, bobConn := newClient("bob", bobKey)
bobClient, bobConn := newClient(ctx, "bob", bobKey)
cathyKey := key.NewNode()
cathyClient, cathyConn := newClient("cathy", cathyKey)
cathyClient, cathyConn := newClient(ctx, "cathy", cathyKey)
var (
aliceCh = make(chan struct{}, 32)
@@ -455,7 +462,7 @@ func (ts *testServer) close(t *testing.T) error {
return nil
}
func newTestServer(t *testing.T) *testServer {
func newTestServer(t *testing.T, ctx context.Context) *testServer {
t.Helper()
logf := logger.WithPrefix(t.Logf, "derp-server: ")
s := NewServer(key.NewNode(), logf)
@@ -475,7 +482,7 @@ func newTestServer(t *testing.T) *testServer {
// TODO: register c in ts so Close also closes it?
go func(i int) {
brwServer := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c))
go s.Accept(c, brwServer, fmt.Sprintf("test-client-%d", i))
go s.Accept(ctx, c, brwServer, fmt.Sprintf("test-client-%d", i))
}(i)
}
}()
@@ -610,7 +617,10 @@ func (c *testClient) close(t *testing.T) {
// TestWatch tests the connection watcher mechanism used by regional
// DERP nodes to mesh up with each other.
func TestWatch(t *testing.T) {
ts := newTestServer(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ts := newTestServer(t, ctx)
defer ts.close(t)
w1 := newTestWatcher(t, ts, "w1")
@@ -1198,7 +1208,10 @@ func benchmarkSendRecvSize(b *testing.B, packetSize int) {
defer connIn.Close()
brwServer := bufio.NewReadWriter(bufio.NewReader(connIn), bufio.NewWriter(connIn))
go s.Accept(connIn, brwServer, "test-client")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go s.Accept(ctx, connIn, brwServer, "test-client")
brw := bufio.NewReadWriter(bufio.NewReader(connOut), bufio.NewWriter(connOut))
client, err := NewClient(k, connOut, brw, logger.Discard)
@@ -1354,7 +1367,10 @@ func TestClientSendRateLimiting(t *testing.T) {
}
func TestServerRepliesToPing(t *testing.T) {
ts := newTestServer(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ts := newTestServer(t, ctx)
defer ts.close(t)
tc := newRegularClient(t, ts, "alice")

View File

@@ -22,6 +22,7 @@ import (
"io/ioutil"
"net"
"net/http"
"net/netip"
"net/url"
"runtime"
"strings"
@@ -30,7 +31,6 @@ import (
"time"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/derp"
"tailscale.com/envknob"
"tailscale.com/net/dnscache"
@@ -579,11 +579,11 @@ func (c *Client) dialContext(ctx context.Context, proto, addr string) (net.Conn,
// address (given in s) is valid. An empty value means to dial, but to
// use DNS. The predicate function reports whether the non-empty
// string s contained a valid IP address of the right family.
func shouldDialProto(s string, pred func(netaddr.IP) bool) bool {
func shouldDialProto(s string, pred func(netip.Addr) bool) bool {
if s == "" {
return true
}
ip, _ := netaddr.ParseIP(s)
ip, _ := netip.ParseAddr(s)
return pred(ip)
}
@@ -651,10 +651,10 @@ func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, e
}
}()
}
if shouldDialProto(n.IPv4, netaddr.IP.Is4) {
if shouldDialProto(n.IPv4, netip.Addr.Is4) {
startDial(n.IPv4, "tcp4")
}
if shouldDialProto(n.IPv6, netaddr.IP.Is6) {
if shouldDialProto(n.IPv6, netip.Addr.Is6) {
startDial(n.IPv6, "tcp6")
}
if nwait == 0 {
@@ -839,15 +839,15 @@ func (c *Client) SendPing(data [8]byte) error {
// LocalAddr reports c's local TCP address, without any implicit
// connect or reconnect.
func (c *Client) LocalAddr() (netaddr.IPPort, error) {
func (c *Client) LocalAddr() (netip.AddrPort, error) {
c.mu.Lock()
closed, client := c.closed, c.client
c.mu.Unlock()
if closed {
return netaddr.IPPort{}, ErrClientClosed
return netip.AddrPort{}, ErrClientClosed
}
if client == nil {
return netaddr.IPPort{}, errors.New("client not connected")
return netip.AddrPort{}, errors.New("client not connected")
}
return client.LocalAddr()
}

View File

@@ -56,6 +56,6 @@ func Handler(s *derp.Server) http.Handler {
pubKey.UntypedHexString())
}
s.Accept(netConn, conn, netConn.RemoteAddr().String())
s.Accept(r.Context(), netConn, conn, netConn.RemoteAddr().String())
})
}

View File

@@ -24,9 +24,10 @@ import (
"errors"
"fmt"
"net"
"net/netip"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/net/netaddr"
"tailscale.com/types/key"
)
@@ -172,7 +173,7 @@ type CallMeMaybe struct {
// in this field, but might not yet be in control's endpoints.
// (And in the future, control will stop distributing endpoints
// when clients are suitably new.)
MyNumber []netaddr.IPPort
MyNumber []netip.AddrPort
}
const epLength = 16 + 2 // 16 byte IP address + 2 byte port
@@ -180,7 +181,7 @@ const epLength = 16 + 2 // 16 byte IP address + 2 byte port
func (m *CallMeMaybe) AppendMarshal(b []byte) []byte {
ret, p := appendMsgHeader(b, TypeCallMeMaybe, v0, epLength*len(m.MyNumber))
for _, ipp := range m.MyNumber {
a := ipp.IP().As16()
a := ipp.Addr().As16()
copy(p[:], a[:])
binary.BigEndian.PutUint16(p[16:], ipp.Port())
p = p[epLength:]
@@ -193,11 +194,11 @@ func parseCallMeMaybe(ver uint8, p []byte) (m *CallMeMaybe, err error) {
if len(p)%epLength != 0 || ver != 0 || len(p) == 0 {
return m, nil
}
m.MyNumber = make([]netaddr.IPPort, 0, len(p)/epLength)
m.MyNumber = make([]netip.AddrPort, 0, len(p)/epLength)
for len(p) > 0 {
var a [16]byte
copy(a[:], p)
m.MyNumber = append(m.MyNumber, netaddr.IPPortFrom(
m.MyNumber = append(m.MyNumber, netip.AddrPortFrom(
netaddr.IPFrom16(a),
binary.BigEndian.Uint16(p[16:18])))
p = p[epLength:]
@@ -211,7 +212,7 @@ func parseCallMeMaybe(ver uint8, p []byte) (m *CallMeMaybe, err error) {
// STUN response.
type Pong struct {
TxID [12]byte
Src netaddr.IPPort // 18 bytes (16+2) on the wire; v4-mapped ipv6 for IPv4
Src netip.AddrPort // 18 bytes (16+2) on the wire; v4-mapped ipv6 for IPv4
}
const pongLen = 12 + 16 + 2
@@ -219,7 +220,7 @@ const pongLen = 12 + 16 + 2
func (m *Pong) AppendMarshal(b []byte) []byte {
ret, d := appendMsgHeader(b, TypePong, v0, pongLen)
d = d[copy(d, m.TxID[:]):]
ip16 := m.Src.IP().As16()
ip16 := m.Src.Addr().As16()
d = d[copy(d, ip16[:]):]
binary.BigEndian.PutUint16(d, m.Src.Port())
return ret
@@ -236,7 +237,7 @@ func parsePong(ver uint8, p []byte) (m *Pong, err error) {
srcIP, _ := netaddr.FromStdIP(net.IP(p[:16]))
p = p[16:]
port := binary.BigEndian.Uint16(p)
m.Src = netaddr.IPPortFrom(srcIP, port)
m.Src = netip.AddrPortFrom(srcIP, port)
return m, nil
}

View File

@@ -6,12 +6,12 @@ package disco
import (
"fmt"
"net/netip"
"reflect"
"strings"
"testing"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/types/key"
)
@@ -60,9 +60,9 @@ func TestMarshalAndParse(t *testing.T) {
{
name: "call_me_maybe_endpoints",
m: &CallMeMaybe{
MyNumber: []netaddr.IPPort{
netaddr.MustParseIPPort("1.2.3.4:567"),
netaddr.MustParseIPPort("[2001::3456]:789"),
MyNumber: []netip.AddrPort{
netip.MustParseAddrPort("1.2.3.4:567"),
netip.MustParseAddrPort("[2001::3456]:789"),
},
},
want: "03 00 00 00 00 00 00 00 00 00 00 00 ff ff 01 02 03 04 02 37 20 01 00 00 00 00 00 00 00 00 00 00 00 00 34 56 03 15",
@@ -93,8 +93,8 @@ func TestMarshalAndParse(t *testing.T) {
}
}
func mustIPPort(s string) netaddr.IPPort {
ipp, err := netaddr.ParseIPPort(s)
func mustIPPort(s string) netip.AddrPort {
ipp, err := netip.ParseAddrPort(s)
if err != nil {
panic(err)
}

View File

@@ -1,7 +1,11 @@
# Overview
There are quite a few ways of running Tailscale inside a Kubernetes Cluster, some of the common ones are covered in this doc.
## Instructions
### Setup
1. (Optional) Create the following secret which will automate login.<br>
You will need to get an [auth key](https://tailscale.com/kb/1085/auth-keys/) from [Tailscale Admin Console](https://login.tailscale.com/admin/authkeys).<br>
If you don't provide the key, you can still authenticate using the url in the logs.
@@ -12,12 +16,13 @@ There are quite a few ways of running Tailscale inside a Kubernetes Cluster, som
metadata:
name: tailscale-auth
stringData:
AUTH_KEY: tskey-...
TS_AUTH_KEY: tskey-...
```
1. Tailscale (v1.16+) supports storing state inside a Kubernetes Secret.
Configure RBAC to allow the Tailscale pod to read/write the `tailscale` secret.
```bash
export SA_NAME=tailscale
export TS_KUBE_SECRET=tailscale-auth
@@ -25,6 +30,7 @@ There are quite a few ways of running Tailscale inside a Kubernetes Cluster, som
```
### Sample Sidecar
Running as a sidecar allows you to directly expose a Kubernetes pod over Tailscale. This is particularly useful if you do not wish to expose a service on the public internet. This method allows bi-directional connectivity between the pod and other devices on the Tailnet. You can use [ACLs](https://tailscale.com/kb/1018/acls/) to control traffic flow.
1. Create and login to the sample nginx pod with a Tailscale sidecar
@@ -40,12 +46,15 @@ Running as a sidecar allows you to directly expose a Kubernetes pod over Tailsca
```bash
curl http://nginx
```
Or, if you have [MagicDNS](https://tailscale.com/kb/1081/magicdns/) disabled:
```bash
curl "http://$(tailscale ip -4 nginx)"
```
#### Userspace Sidecar
You can also run the sidecar in userspace mode. The obvious benefit is reducing the amount of permissions Tailscale needs to run, the downside is that for outbound connectivity from the pod to the Tailnet you would need to use either the [SOCKS proxy](https://tailscale.com/kb/1112/userspace-networking) or HTTP proxy.
1. Create and login to the sample nginx pod with a Tailscale sidecar
@@ -61,23 +70,29 @@ You can also run the sidecar in userspace mode. The obvious benefit is reducing
```bash
curl http://nginx
```
Or, if you have [MagicDNS](https://tailscale.com/kb/1081/magicdns/) disabled:
```bash
curl "http://$(tailscale ip -4 nginx)"
```
### Sample Proxy
Running a Tailscale proxy allows you to provide inbound connectivity to a Kubernetes Service.
1. Provide the `ClusterIP` of the service you want to reach by either:
**Creating a new deployment**
```bash
kubectl create deployment nginx --image nginx
kubectl expose deployment nginx --port 80
export TS_DEST_IP="$(kubectl get svc nginx -o=jsonpath='{.spec.clusterIP}')"
```
**Using an existing service**
```bash
export TS_DEST_IP="$(kubectl get svc <SVC_NAME> -o=jsonpath='{.spec.clusterIP}')"
```
@@ -107,7 +122,7 @@ Running a Tailscale proxy allows you to provide inbound connectivity to a Kubern
Running a Tailscale [subnet router](https://tailscale.com/kb/1019/subnets/) allows you to access
the entire Kubernetes cluster network (assuming NetworkPolicies allow) over Tailscale.
1. Identify the Pod/Service CIDRs that cover your Kubernetes cluster. These will vary depending on [which CNI](https://kubernetes.io/docs/concepts/cluster-administration/networking/) you are using and on the Cloud Provider you are using. Add these to the `TS_ROUTES` variable as comma-separated values.
1. Identify the Pod/Service CIDRs that cover your Kubernetes cluster. These will vary depending on [which CNI](https://kubernetes.io/docs/concepts/cluster-administration/networking/) you are using and on the Cloud Provider you are using. Add these to the `TS_ROUTES` variable as comma-separated values.
```bash
SERVICE_CIDR=10.20.0.0/16
@@ -124,7 +139,7 @@ the entire Kubernetes cluster network (assuming NetworkPolicies allow) over Tail
```
1. In the [Tailscale admin console](https://login.tailscale.com/admin/machines), ensure that the
routes for the subnet-router are enabled.
routes for the subnet-router are enabled.
1. Make sure that any client you want to connect from has `--accept-routes` enabled.
@@ -133,8 +148,8 @@ routes for the subnet-router are enabled.
```bash
# Get the Service IP
INTERNAL_IP="$(kubectl get svc <SVC_NAME> -o=jsonpath='{.spec.clusterIP}')"
# or, the Pod IP
# INTERNAL_IP="$(kubectl get po <POD_NAME> -o=jsonpath='{.status.podIP}')"
INTERNAL_PORT=8080
# or, the Pod IP
# INTERNAL_IP="$(kubectl get po <POD_NAME> -o=jsonpath='{.status.podIP}')"
INTERNAL_PORT=8080
curl http://$INTERNAL_IP:$INTERNAL_PORT
```

View File

@@ -4,6 +4,8 @@
#! /bin/sh
set -m # enable job control
export PATH=$PATH:/tailscale/bin
TS_AUTH_KEY="${TS_AUTH_KEY:-}"
@@ -23,11 +25,11 @@ set -e
TAILSCALED_ARGS="--socket=/tmp/tailscaled.sock"
if [[ ! -z "${KUBERNETES_SERVICE_HOST}" ]]; then
TAILSCALED_ARGS="${TAILSCALED_ARGS} --state=kube:${TS_KUBE_SECRET}"
TAILSCALED_ARGS="${TAILSCALED_ARGS} --state=kube:${TS_KUBE_SECRET} --statedir=${TS_STATE_DIR:-/tmp}"
elif [[ ! -z "${TS_STATE_DIR}" ]]; then
TAILSCALED_ARGS="${TAILSCALED_ARGS} --statedir=${TS_STATE_DIR}"
else
TAILSCALED_ARGS="${TAILSCALED_ARGS} --state=mem:"
TAILSCALED_ARGS="${TAILSCALED_ARGS} --state=mem: --statedir=/tmp"
fi
if [[ "${TS_USERSPACE}" == "true" ]]; then
@@ -60,7 +62,6 @@ fi
echo "Starting tailscaled"
tailscaled ${TAILSCALED_ARGS} &
PID=$!
UP_ARGS="--accept-dns=${TS_ACCEPT_DNS}"
if [[ ! -z "${TS_ROUTES}" ]]; then
@@ -81,4 +82,4 @@ if [[ ! -z "${TS_DEST_IP}" ]]; then
iptables -t nat -I PREROUTING -d "$(tailscale --socket=/tmp/tailscaled.sock ip -4)" -j DNAT --to-destination "${TS_DEST_IP}"
fi
wait ${PID}
fg

16
go.mod
View File

@@ -6,6 +6,7 @@ require (
filippo.io/mkcert v1.4.3
github.com/akutz/memconn v0.1.0
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
github.com/andybalholm/brotli v1.0.3
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
github.com/aws/aws-sdk-go-v2 v1.11.2
github.com/aws/aws-sdk-go-v2/config v1.11.0
@@ -16,13 +17,16 @@ require (
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/creack/pty v1.1.17
github.com/dave/jennifer v1.4.1
github.com/evanw/esbuild v0.14.39
github.com/frankban/quicktest v1.14.0
github.com/fxamacker/cbor/v2 v2.4.0
github.com/go-ole/go-ole v1.2.6
github.com/godbus/dbus/v5 v5.0.6
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/google/go-cmp v0.5.8
github.com/google/uuid v1.3.0
github.com/goreleaser/nfpm v1.10.3
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3
github.com/iancoleman/strcase v0.2.0
github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b
@@ -42,7 +46,7 @@ require (
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
github.com/tailscale/golang-x-crypto v0.0.0-20220428210705-0b941c09a5e1
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
github.com/tailscale/hujson v0.0.0-20220506202205-92b4b88a9e17
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f
github.com/tailscale/mkctr v0.0.0-20220601142259-c0b937af2e89
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85
github.com/tcnksm/go-httpstat v0.2.0
@@ -50,6 +54,7 @@ require (
github.com/u-root/u-root v0.8.0
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
go4.org/mem v0.0.0-20210711025021-927187094b94
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
golang.org/x/net v0.0.0-20220607020251-c690dde0001d
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
@@ -59,11 +64,10 @@ require (
golang.org/x/tools v0.1.11
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
golang.zx2c4.com/wireguard/windows v0.4.10
gvisor.dev/gvisor v0.0.0-20220407223209-21871174d445
gvisor.dev/gvisor v0.0.0-20220801230058-850e42eb4444
honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83
inet.af/netaddr v0.0.0-20220617031823-097006376321
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
inet.af/wf v0.0.0-20211204062712-86aaea0a7310
inet.af/wf v0.0.0-20220728202103-50d96caab2f6
nhooyr.io/websocket v1.8.7
)
@@ -122,7 +126,6 @@ require (
github.com/fatih/color v1.13.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/fzipp/gocyclo v0.3.1 // indirect
github.com/gliderlabs/ssh v0.3.3 // indirect
github.com/go-critic/go-critic v0.6.1 // indirect
@@ -164,7 +167,6 @@ require (
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
@@ -261,8 +263,6 @@ require (
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/yeya24/promlinter v0.1.0 // indirect
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
golang.org/x/exp/typeparams v0.0.0-20220328175248-053ad81199eb // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/text v0.3.7 // indirect

37
go.sum
View File

@@ -112,6 +112,7 @@ github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pO
github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
@@ -264,7 +265,6 @@ github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56
github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -280,6 +280,8 @@ github.com/esimonov/ifshort v1.0.3 h1:JD6x035opqGec5fZ0TLjXeROD2p5H7oLGn8MKfy9HT
github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE=
github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw=
github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
github.com/evanw/esbuild v0.14.39 h1:1TMZtCXOY4ctAbGY4QT9sjT203I/cQ16vXt2F9rLT58=
github.com/evanw/esbuild v0.14.39/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY=
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
@@ -304,6 +306,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
@@ -343,9 +346,11 @@ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
@@ -380,9 +385,11 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -839,6 +846,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
@@ -911,7 +920,6 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/peterbourgon/ff/v3 v3.0.0/go.mod h1:UILIFjRH5a/ar8TjXYLTkIvSvekZqPm5Eb/qbGk6CT0=
github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP6JM=
github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE=
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA=
@@ -1100,8 +1108,8 @@ github.com/tailscale/golang-x-crypto v0.0.0-20220428210705-0b941c09a5e1 h1:vsFV6
github.com/tailscale/golang-x-crypto v0.0.0-20220428210705-0b941c09a5e1/go.mod h1:95n9fbUCixVSI4QXLEvdKJjnYK2eUlkTx9+QwLPXFKU=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
github.com/tailscale/hujson v0.0.0-20220506202205-92b4b88a9e17 h1:QaQrUggZ7U2lE3HhoPx6bDK7fO385FR7pHRYSPEv70Q=
github.com/tailscale/hujson v0.0.0-20220506202205-92b4b88a9e17/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f h1:n4r/sJ92cBSBHK8n9lR1XLFr0OiTVeGfN5TR+9LaN7E=
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/mkctr v0.0.0-20220601142259-c0b937af2e89 h1:7xU7AFQE83h0wz/dIMvD0t77g0FxFfZIQjghDQxyG2U=
github.com/tailscale/mkctr v0.0.0-20220601142259-c0b937af2e89/go.mod h1:OGMqrTzDqmJkGumUTtOv44Rp3/4xS+QFbE8Rn0AGlaU=
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 h1:zrsUcqrG2uQSPhaUPjUQwozcRdDdSxxqhNgNZ3drZFk=
@@ -1147,6 +1155,7 @@ github.com/u-root/uio v0.0.0-20210528151154-e40b768296a7/go.mod h1:LpEX5FO/cB+WF
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
@@ -1232,16 +1241,12 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/mem v0.0.0-20210711025021-927187094b94 h1:OAAkygi2Js191AJP1Ds42MhJRgeofeKGjuoUqNp1QC4=
go4.org/mem v0.0.0-20210711025021-927187094b94/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf h1:IdwJUzqoIo5lkr2EOyKoe5qipUaEjbOKKY5+fzPBZ3A=
go4.org/netipx v0.0.0-20220725152314-7e7bdc8411bf/go.mod h1:+QXzaoURFd0rGDIjDNpyIkv+F9R7EmeKorvlKRnhqgA=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -1509,6 +1514,7 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1845,8 +1851,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gvisor.dev/gvisor v0.0.0-20220407223209-21871174d445 h1:pLNQCtMzh4O6rdhoUeWHuutt4yMft+B9Cgw/bezWchE=
gvisor.dev/gvisor v0.0.0-20220407223209-21871174d445/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
gvisor.dev/gvisor v0.0.0-20220801230058-850e42eb4444 h1:0d3ygmOM5RgQB8rmsZNeAY/7Q98fKt1HrGO2XIp4pDI=
gvisor.dev/gvisor v0.0.0-20220801230058-850e42eb4444/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1861,13 +1867,10 @@ honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83/go.mod h1:vlRD9XEr
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
inet.af/netaddr v0.0.0-20210515010201-ad03edc7c841/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20220617031823-097006376321 h1:B4dC8ySKTQXasnjDTMsoCMf1sQG4WsMej0WXaHxunmU=
inet.af/netaddr v0.0.0-20220617031823-097006376321/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg=
inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
inet.af/wf v0.0.0-20211204062712-86aaea0a7310 h1:0jKHTf+W75kYRyg5bto1UT+r18QmAz2u/5pAs/fx4zo=
inet.af/wf v0.0.0-20211204062712-86aaea0a7310/go.mod h1:ViGMZRA6+RA318D7GCncrjv5gHUrPYrNDejjU12tikA=
inet.af/wf v0.0.0-20220728202103-50d96caab2f6 h1:BfgDtKnWJTeu+xI1aOEweXdPwqOhB3IbQUDj1XuftcY=
inet.af/wf v0.0.0-20220728202103-50d96caab2f6/go.mod h1:bSAQ38BYbY68uwpasXOTZo22dKGy9SNvI6PZFeKomZE=
mvdan.cc/gofumpt v0.0.0-20200802201014-ab5a8192947d/go.mod h1:bzrjFmaD6+xqohD3KYP0H2FEuxknnBmyyOxdhLdaIws=
mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475/go.mod h1:E4LOcu9JQEtnYXtB1Y51drqh2Qr2Ngk9J3YrRCwcbd0=
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=

View File

@@ -1 +1 @@
tailscale.go1.18
tailscale.go1.19

View File

@@ -1 +1 @@
04d67b90d8cfd6f822664220f79e0e69cacb6b5c
effe2d16a9ed6c4fd97361e8090bb22acc221075

View File

@@ -48,7 +48,17 @@ func linuxDeviceModel() string {
return ""
}
func getQnapQtsVersion(versionInfo string) string {
for _, field := range strings.Fields(versionInfo) {
if suffix := strings.TrimPrefix(field, "QTSFW_"); suffix != field {
return "QTS " + suffix
}
}
return ""
}
func osVersionLinux() string {
// TODO(bradfitz,dgentry): cache this, or make caller(s) cache it.
dist := distro.Get()
propFile := "/etc/os-release"
switch dist {
@@ -59,6 +69,9 @@ func osVersionLinux() string {
case distro.WDMyCloud:
slurp, _ := ioutil.ReadFile("/etc/version")
return fmt.Sprintf("%s", string(bytes.TrimSpace(slurp)))
case distro.QNAP:
slurp, _ := ioutil.ReadFile("/etc/version_info")
return getQnapQtsVersion(string(slurp))
}
m := map[string]string{}

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.
//go:build linux && !android
// +build linux,!android
package hostinfo
import (
"testing"
)
func TestQnap(t *testing.T) {
version_info := `commit 2910d3a594b068024ed01a64a0fe4168cb001a12
Date: 2022-05-30 16:08:45 +0800
================================================
* QTSFW_5.0.0
remotes/origin/QTSFW_5.0.0`
got := getQnapQtsVersion(version_info)
want := "QTS 5.0.0"
if got != want {
t.Errorf("got %q; want %q", got, want)
}
got = getQnapQtsVersion("")
want = ""
if got != want {
t.Errorf("got %q; want %q", got, want)
}
got = getQnapQtsVersion("just a bunch of junk")
want = ""
if got != want {
t.Errorf("got %q; want %q", got, want)
}
}

View File

@@ -2,8 +2,12 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build for_go_mod_tidy_only
// +build for_go_mod_tidy_only
package tooldeps
import (
_ "github.com/tailscale/depaware/depaware"
_ "golang.org/x/tools/cmd/goimports"
)

View File

@@ -5,10 +5,10 @@
package ipn
import (
"net/netip"
"sync"
"time"
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
@@ -126,7 +126,7 @@ func (h *Handle) EngineStatus() EngineStatus {
return h.engineStatusCache
}
func (h *Handle) LocalAddrs() []netaddr.IPPrefix {
func (h *Handle) LocalAddrs() []netip.Prefix {
h.mu.Lock()
defer h.mu.Unlock()
@@ -134,7 +134,7 @@ func (h *Handle) LocalAddrs() []netaddr.IPPrefix {
if nm != nil {
return nm.Addresses
}
return []netaddr.IPPrefix{}
return []netip.Prefix{}
}
func (h *Handle) NetMap() *netmap.NetworkMap {

View File

@@ -7,7 +7,8 @@
package ipn
import (
"inet.af/netaddr"
"net/netip"
"tailscale.com/tailcfg"
"tailscale.com/types/persist"
"tailscale.com/types/preftype"
@@ -36,7 +37,7 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct {
RouteAll bool
AllowSingleHosts bool
ExitNodeID tailcfg.StableNodeID
ExitNodeIP netaddr.IP
ExitNodeIP netip.Addr
ExitNodeAllowLANAccess bool
CorpDNS bool
RunSSH bool
@@ -47,7 +48,7 @@ var _PrefsCloneNeedsRegeneration = Prefs(struct {
Hostname string
NotepadURLs bool
ForceDaemon bool
AdvertiseRoutes []netaddr.IPPrefix
AdvertiseRoutes []netip.Prefix
NoSNAT bool
NetfilterMode preftype.NetfilterMode
OperatorUser string

View File

@@ -6,10 +6,10 @@ package ipnlocal
import (
"encoding/json"
"net/netip"
"reflect"
"testing"
"inet.af/netaddr"
"tailscale.com/ipn"
"tailscale.com/net/dns"
"tailscale.com/tailcfg"
@@ -20,20 +20,20 @@ import (
"tailscale.com/util/dnsname"
)
func ipps(ippStrs ...string) (ipps []netaddr.IPPrefix) {
func ipps(ippStrs ...string) (ipps []netip.Prefix) {
for _, s := range ippStrs {
if ip, err := netaddr.ParseIP(s); err == nil {
ipps = append(ipps, netaddr.IPPrefixFrom(ip, ip.BitLen()))
if ip, err := netip.ParseAddr(s); err == nil {
ipps = append(ipps, netip.PrefixFrom(ip, ip.BitLen()))
continue
}
ipps = append(ipps, netaddr.MustParseIPPrefix(s))
ipps = append(ipps, netip.MustParsePrefix(s))
}
return
}
func ips(ss ...string) (ips []netaddr.IP) {
func ips(ss ...string) (ips []netip.Addr) {
for _, s := range ss {
ips = append(ips, netaddr.MustParseIP(s))
ips = append(ips, netip.MustParseAddr(s))
}
return
}
@@ -54,7 +54,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
prefs: &ipn.Prefs{},
want: &dns.Config{
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
Hosts: map[dnsname.FQDN][]netaddr.IP{},
Hosts: map[dnsname.FQDN][]netip.Addr{},
},
},
{
@@ -80,7 +80,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
prefs: &ipn.Prefs{},
want: &dns.Config{
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
Hosts: map[dnsname.FQDN][]netaddr.IP{
Hosts: map[dnsname.FQDN][]netip.Addr{
"b.net.": ips("100.102.0.1", "100.102.0.2"),
"myname.net.": ips("100.101.101.101"),
"peera.net.": ips("100.102.0.1", "100.102.0.2"),
@@ -115,7 +115,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
want: &dns.Config{
OnlyIPv6: true,
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
Hosts: map[dnsname.FQDN][]netaddr.IP{
Hosts: map[dnsname.FQDN][]netip.Addr{
"b.net.": ips("fe75::2"),
"myname.net.": ips("fe75::1"),
"peera.net.": ips("fe75::1001"),
@@ -139,7 +139,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
prefs: &ipn.Prefs{},
want: &dns.Config{
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
Hosts: map[dnsname.FQDN][]netaddr.IP{
Hosts: map[dnsname.FQDN][]netip.Addr{
"myname.net.": ips("100.101.101.101"),
"foo.com.": ips("1.2.3.4"),
"bar.com.": ips("1::6"),
@@ -159,7 +159,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
CorpDNS: true,
},
want: &dns.Config{
Hosts: map[dnsname.FQDN][]netaddr.IP{},
Hosts: map[dnsname.FQDN][]netip.Addr{},
Routes: map[dnsname.FQDN][]*dnstype.Resolver{
"0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.": nil,
"100.100.in-addr.arpa.": nil,
@@ -259,7 +259,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
CorpDNS: true,
},
want: &dns.Config{
Hosts: map[dnsname.FQDN][]netaddr.IP{},
Hosts: map[dnsname.FQDN][]netip.Addr{},
DefaultResolvers: []*dnstype.Resolver{
{Addr: "8.8.8.8"},
},
@@ -282,7 +282,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
ExitNodeID: "some-id",
},
want: &dns.Config{
Hosts: map[dnsname.FQDN][]netaddr.IP{},
Hosts: map[dnsname.FQDN][]netip.Addr{},
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
DefaultResolvers: []*dnstype.Resolver{
{Addr: "8.8.4.4"},
@@ -302,7 +302,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
CorpDNS: true,
},
want: &dns.Config{
Hosts: map[dnsname.FQDN][]netaddr.IP{},
Hosts: map[dnsname.FQDN][]netip.Addr{},
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
},
},

View File

@@ -11,6 +11,7 @@ import (
"io"
"net"
"net/http"
"net/netip"
"os"
"os/user"
"path/filepath"
@@ -22,7 +23,7 @@ import (
"sync/atomic"
"time"
"inet.af/netaddr"
"go4.org/netipx"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
@@ -128,7 +129,7 @@ type LocalBackend struct {
shutdownCalled bool // if Shutdown has been called
filterAtomic atomic.Value // of *filter.Filter
containsViaIPFuncAtomic atomic.Value // of func(netaddr.IP) bool
containsViaIPFuncAtomic atomic.Value // of func(netip.Addr) bool
// The mutex protects the following elements.
mu sync.Mutex
@@ -150,7 +151,7 @@ type LocalBackend struct {
hostinfo *tailcfg.Hostinfo
// netMap is not mutated in-place once set.
netMap *netmap.NetworkMap
nodeByAddr map[netaddr.IP]*tailcfg.Node
nodeByAddr map[netip.Addr]*tailcfg.Node
activeLogin string // last logged LoginName from netMap
engineStatus ipn.EngineStatus
endpoints []tailcfg.Endpoint
@@ -174,11 +175,11 @@ type LocalBackend struct {
// same as the Network Extension lifetime and we can thus avoid
// double-copying files by writing them to the right location
// immediately.
// It's also used on Synology & TrueNAS, but in that case DoFinalRename
// is also set true, which moves the *.partial file to its final
// name on completion.
// It's also used on several NAS platforms (Synology, TrueNAS, etc)
// but in that case DoFinalRename is also set true, which moves the
// *.partial file to its final name on completion.
directFileRoot string
directFileDoFinalRename bool // false on macOS, true on Synology & TrueNAS
directFileDoFinalRename bool // false on macOS, true on several NAS platforms
// statusLock must be held before calling statusChanged.Wait() or
// statusChanged.Broadcast().
@@ -231,7 +232,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, diale
}
// Default filter blocks everything and logs nothing, until Start() is called.
b.setFilter(filter.NewAllowNone(logf, &netaddr.IPSet{}))
b.setFilter(filter.NewAllowNone(logf, &netipx.IPSet{}))
b.statusChanged = sync.NewCond(&b.statusLock)
b.e.SetStatusCallback(b.setWgengineStatus)
@@ -342,7 +343,19 @@ func (b *LocalBackend) onHealthChange(sys health.Subsystem, err error) {
// can no longer be used after Shutdown returns.
func (b *LocalBackend) Shutdown() {
b.mu.Lock()
if b.shutdownCalled {
b.mu.Unlock()
return
}
b.shutdownCalled = true
if b.loginFlags&controlclient.LoginEphemeral != 0 {
b.mu.Unlock()
ctx, cancel := context.WithTimeout(b.ctx, 5*time.Second)
defer cancel()
b.LogoutSync(ctx) // best effort
b.mu.Lock()
}
cc := b.cc
if b.sshServer != nil {
b.sshServer.Shutdown()
@@ -484,13 +497,13 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
if p.LastSeen != nil {
lastSeen = *p.LastSeen
}
var tailscaleIPs = make([]netaddr.IP, 0, len(p.Addresses))
var tailscaleIPs = make([]netip.Addr, 0, len(p.Addresses))
for _, addr := range p.Addresses {
if addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP()) {
tailscaleIPs = append(tailscaleIPs, addr.IP())
if addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.Addr()) {
tailscaleIPs = append(tailscaleIPs, addr.Addr())
}
}
exitNodeOption := tsaddr.PrefixesContainsFunc(p.AllowedIPs, func(r netaddr.IPPrefix) bool {
exitNodeOption := tsaddr.PrefixesContainsFunc(p.AllowedIPs, func(r netip.Prefix) bool {
return r.Bits() == 0
})
var tags *views.Slice[string]
@@ -528,12 +541,12 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
// WhoIs reports the node and user who owns the node with the given IP:port.
// If the IP address is a Tailscale IP, the provided port may be 0.
// If ok == true, n and u are valid.
func (b *LocalBackend) WhoIs(ipp netaddr.IPPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) {
func (b *LocalBackend) WhoIs(ipp netip.AddrPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) {
b.mu.Lock()
defer b.mu.Unlock()
n, ok = b.nodeByAddr[ipp.IP()]
n, ok = b.nodeByAddr[ipp.Addr()]
if !ok {
var ip netaddr.IP
var ip netip.Addr
if ipp.Port() != 0 {
ip, ok = b.e.WhoIsIPPort(ipp)
}
@@ -554,7 +567,7 @@ func (b *LocalBackend) WhoIs(ipp netaddr.IPPort) (n *tailcfg.Node, u tailcfg.Use
// PeerCaps returns the capabilities that remote src IP has to
// ths current node.
func (b *LocalBackend) PeerCaps(src netaddr.IP) []string {
func (b *LocalBackend) PeerCaps(src netip.Addr) []string {
b.mu.Lock()
defer b.mu.Unlock()
if b.netMap == nil {
@@ -568,9 +581,9 @@ func (b *LocalBackend) PeerCaps(src netaddr.IP) []string {
if !a.IsSingleIP() {
continue
}
dstIP := a.IP()
dstIP := a.Addr()
if dstIP.BitLen() == src.BitLen() {
return filt.AppendCaps(nil, src, a.IP())
return filt.AppendCaps(nil, src, a.Addr())
}
}
return nil
@@ -738,7 +751,7 @@ func (b *LocalBackend) findExitNodeIDLocked(nm *netmap.NetworkMap) (prefsChanged
// If we have a desired IP on file, try to find the corresponding
// node.
if b.prefs.ExitNodeIP.IsZero() {
if !b.prefs.ExitNodeIP.IsValid() {
return false
}
@@ -750,13 +763,13 @@ func (b *LocalBackend) findExitNodeIDLocked(nm *netmap.NetworkMap) (prefsChanged
for _, peer := range nm.Peers {
for _, addr := range peer.Addresses {
if !addr.IsSingleIP() || addr.IP() != b.prefs.ExitNodeIP {
if !addr.IsSingleIP() || addr.Addr() != b.prefs.ExitNodeIP {
continue
}
// Found the node being referenced, upgrade prefs to
// reference it directly for next time.
b.prefs.ExitNodeID = peer.StableID
b.prefs.ExitNodeIP = netaddr.IP{}
b.prefs.ExitNodeIP = netip.Addr{}
return true
}
}
@@ -1109,10 +1122,10 @@ func (b *LocalBackend) updateFilterLocked(netMap *netmap.NetworkMap, prefs *ipn.
// quite hard to debug, so save yourself the trouble.
var (
haveNetmap = netMap != nil
addrs []netaddr.IPPrefix
addrs []netip.Prefix
packetFilter []filter.Match
localNetsB netaddr.IPSetBuilder
logNetsB netaddr.IPSetBuilder
localNetsB netipx.IPSetBuilder
logNetsB netipx.IPSetBuilder
shieldsUp = prefs == nil || prefs.ShieldsUp // Be conservative when not ready
)
// Log traffic for Tailscale IPs.
@@ -1192,21 +1205,21 @@ func (b *LocalBackend) setFilter(f *filter.Filter) {
b.e.SetFilter(f)
}
var removeFromDefaultRoute = []netaddr.IPPrefix{
var removeFromDefaultRoute = []netip.Prefix{
// RFC1918 LAN ranges
netaddr.MustParseIPPrefix("192.168.0.0/16"),
netaddr.MustParseIPPrefix("172.16.0.0/12"),
netaddr.MustParseIPPrefix("10.0.0.0/8"),
netip.MustParsePrefix("192.168.0.0/16"),
netip.MustParsePrefix("172.16.0.0/12"),
netip.MustParsePrefix("10.0.0.0/8"),
// IPv4 link-local
netaddr.MustParseIPPrefix("169.254.0.0/16"),
netip.MustParsePrefix("169.254.0.0/16"),
// IPv4 multicast
netaddr.MustParseIPPrefix("224.0.0.0/4"),
netip.MustParsePrefix("224.0.0.0/4"),
// Tailscale IPv4 range
tsaddr.CGNATRange(),
// IPv6 Link-local addresses
netaddr.MustParseIPPrefix("fe80::/10"),
netip.MustParsePrefix("fe80::/10"),
// IPv6 multicast
netaddr.MustParseIPPrefix("ff00::/8"),
netip.MustParsePrefix("ff00::/8"),
// Tailscale IPv6 range
tsaddr.TailscaleULARange(),
}
@@ -1218,7 +1231,7 @@ var removeFromDefaultRoute = []netaddr.IPPrefix{
//
// Given that "internal" routes don't leave the device, we choose to
// trust them more, allowing access to them when an Exit Node is enabled.
func internalAndExternalInterfaces() (internal, external []netaddr.IPPrefix, err error) {
func internalAndExternalInterfaces() (internal, external []netip.Prefix, err error) {
il, err := interfaces.GetList()
if err != nil {
return nil, nil, err
@@ -1226,12 +1239,12 @@ func internalAndExternalInterfaces() (internal, external []netaddr.IPPrefix, err
return internalAndExternalInterfacesFrom(il, runtime.GOOS)
}
func internalAndExternalInterfacesFrom(il interfaces.List, goos string) (internal, external []netaddr.IPPrefix, err error) {
func internalAndExternalInterfacesFrom(il interfaces.List, goos string) (internal, external []netip.Prefix, err error) {
// We use an IPSetBuilder here to canonicalize the prefixes
// and to remove any duplicate entries.
var internalBuilder, externalBuilder netaddr.IPSetBuilder
if err := il.ForeachInterfaceAddress(func(iface interfaces.Interface, pfx netaddr.IPPrefix) {
if tsaddr.IsTailscaleIP(pfx.IP()) {
var internalBuilder, externalBuilder netipx.IPSetBuilder
if err := il.ForeachInterfaceAddress(func(iface interfaces.Interface, pfx netip.Prefix) {
if tsaddr.IsTailscaleIP(pfx.Addr()) {
return
}
if pfx.IsSingleIP() {
@@ -1272,16 +1285,16 @@ func internalAndExternalInterfacesFrom(il interfaces.List, goos string) (interna
return iSet.Prefixes(), eSet.Prefixes(), nil
}
func interfaceRoutes() (ips *netaddr.IPSet, hostIPs []netaddr.IP, err error) {
var b netaddr.IPSetBuilder
if err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netaddr.IPPrefix) {
if tsaddr.IsTailscaleIP(pfx.IP()) {
func interfaceRoutes() (ips *netipx.IPSet, hostIPs []netip.Addr, err error) {
var b netipx.IPSetBuilder
if err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netip.Prefix) {
if tsaddr.IsTailscaleIP(pfx.Addr()) {
return
}
if pfx.IsSingleIP() {
return
}
hostIPs = append(hostIPs, pfx.IP())
hostIPs = append(hostIPs, pfx.Addr())
b.AddPrefix(pfx)
}); err != nil {
return nil, nil, err
@@ -1294,8 +1307,8 @@ func interfaceRoutes() (ips *netaddr.IPSet, hostIPs []netaddr.IP, err error) {
// shrinkDefaultRoute returns an IPSet representing the IPs in route,
// minus those in removeFromDefaultRoute and localInterfaceRoutes,
// plus the IPs in hostIPs.
func shrinkDefaultRoute(route netaddr.IPPrefix, localInterfaceRoutes *netaddr.IPSet, hostIPs []netaddr.IP) (*netaddr.IPSet, error) {
var b netaddr.IPSetBuilder
func shrinkDefaultRoute(route netip.Prefix, localInterfaceRoutes *netipx.IPSet, hostIPs []netip.Addr) (*netipx.IPSet, error) {
var b netipx.IPSetBuilder
// Add the default route.
b.AddPrefix(route)
// Remove the local interface routes.
@@ -1321,7 +1334,7 @@ func shrinkDefaultRoute(route netaddr.IPPrefix, localInterfaceRoutes *netaddr.IP
// dnsCIDRsEqual determines whether two CIDR lists are equal
// for DNS map construction purposes (that is, only the first entry counts).
func dnsCIDRsEqual(newAddr, oldAddr []netaddr.IPPrefix) bool {
func dnsCIDRsEqual(newAddr, oldAddr []netip.Prefix) bool {
if len(newAddr) != len(oldAddr) {
return false
}
@@ -1719,7 +1732,7 @@ func (b *LocalBackend) StartLoginInteractive() {
}
}
func (b *LocalBackend) Ping(ctx context.Context, ip netaddr.IP, pingType tailcfg.PingType) (*ipnstate.PingResult, error) {
func (b *LocalBackend) Ping(ctx context.Context, ip netip.Addr, pingType tailcfg.PingType) (*ipnstate.PingResult, error) {
if pingType == tailcfg.PingPeerAPI {
t0 := time.Now()
node, base, err := b.pingPeerAPI(ctx, ip)
@@ -1756,7 +1769,7 @@ func (b *LocalBackend) Ping(ctx context.Context, ip netaddr.IP, pingType tailcfg
}
}
func (b *LocalBackend) pingPeerAPI(ctx context.Context, ip netaddr.IP) (peer *tailcfg.Node, peerBase string, err error) {
func (b *LocalBackend) pingPeerAPI(ctx context.Context, ip netip.Addr) (peer *tailcfg.Node, peerBase string, err error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
nm := b.NetMap()
@@ -1865,6 +1878,9 @@ func (b *LocalBackend) checkSSHPrefsLocked(p *ipn.Prefs) error {
if distro.Get() == distro.Synology && !envknob.UseWIPCode() {
return errors.New("The Tailscale SSH server does not run on Synology.")
}
if distro.Get() == distro.QNAP && !envknob.UseWIPCode() {
return errors.New("The Tailscale SSH server does not run on QNAP.")
}
// otherwise okay
case "darwin":
// okay only in tailscaled mode for now.
@@ -2052,7 +2068,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) {
// GetPeerAPIPort returns the port number for the peerapi server
// running on the provided IP.
func (b *LocalBackend) GetPeerAPIPort(ip netaddr.IP) (port uint16, ok bool) {
func (b *LocalBackend) GetPeerAPIPort(ip netip.Addr) (port uint16, ok bool) {
b.mu.Lock()
defer b.mu.Unlock()
for _, pln := range b.peerAPIListeners {
@@ -2070,11 +2086,11 @@ func (b *LocalBackend) GetPeerAPIPort(ip netaddr.IP) (port uint16, ok bool) {
// or IPv6 IP and the peerapi port for that address).
//
// The connection will be closed by ServePeerAPIConnection.
func (b *LocalBackend) ServePeerAPIConnection(remote, local netaddr.IPPort, c net.Conn) {
func (b *LocalBackend) ServePeerAPIConnection(remote, local netip.AddrPort, c net.Conn) {
b.mu.Lock()
defer b.mu.Unlock()
for _, pln := range b.peerAPIListeners {
if pln.ip == local.IP() {
if pln.ip == local.Addr() {
go pln.ServeConn(remote, c)
return
}
@@ -2272,7 +2288,7 @@ func shouldUseOneCGNATRoute(nm *netmap.NetworkMap, logf logger.Logf, versionOS s
func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Logf, versionOS string) *dns.Config {
dcfg := &dns.Config{
Routes: map[dnsname.FQDN][]*dnstype.Resolver{},
Hosts: map[dnsname.FQDN][]netaddr.IP{},
Hosts: map[dnsname.FQDN][]netip.Addr{},
}
// selfV6Only is whether we only have IPv6 addresses ourselves.
@@ -2285,7 +2301,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log
// isn't configured to make MagicDNS resolution truly
// magic. Details in
// https://github.com/tailscale/tailscale/issues/1886.
set := func(name string, addrs []netaddr.IPPrefix) {
set := func(name string, addrs []netip.Prefix) {
if len(addrs) == 0 || name == "" {
return
}
@@ -2294,11 +2310,11 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log
return // TODO: propagate error?
}
have4 := tsaddr.PrefixesContainsFunc(addrs, tsaddr.PrefixIs4)
var ips []netaddr.IP
var ips []netip.Addr
for _, addr := range addrs {
if selfV6Only {
if addr.IP().Is6() {
ips = append(ips, addr.IP())
if addr.Addr().Is6() {
ips = append(ips, addr.Addr())
}
continue
}
@@ -2310,10 +2326,10 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log
// https://github.com/tailscale/tailscale/issues/1152
// tracks adding the right capability reporting to
// enable AAAA in MagicDNS.
if addr.IP().Is6() && have4 {
if addr.Addr().Is6() && have4 {
continue
}
ips = append(ips, addr.IP())
ips = append(ips, addr.Addr())
}
dcfg.Hosts[fqdn] = ips
}
@@ -2329,7 +2345,7 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log
// TODO: more
continue
}
ip, err := netaddr.ParseIP(rec.Value)
ip, err := netip.ParseAddr(rec.Value)
if err != nil {
// Ignore.
continue
@@ -2505,7 +2521,7 @@ func (b *LocalBackend) initPeerAPIListener() {
if len(b.netMap.Addresses) == len(b.peerAPIListeners) {
allSame := true
for i, pln := range b.peerAPIListeners {
if pln.ip != b.netMap.Addresses[i].IP() {
if pln.ip != b.netMap.Addresses[i].Addr() {
allSame = false
break
}
@@ -2548,20 +2564,20 @@ func (b *LocalBackend) initPeerAPIListener() {
var err error
skipListen := i > 0 && isNetstack
if !skipListen {
ln, err = ps.listen(a.IP(), b.prevIfState)
ln, err = ps.listen(a.Addr(), b.prevIfState)
if err != nil {
if peerAPIListenAsync {
// Expected. But we fix it later in linkChange
// ("peerAPIListeners too low").
continue
}
b.logf("[unexpected] peerapi listen(%q) error: %v", a.IP(), err)
b.logf("[unexpected] peerapi listen(%q) error: %v", a.Addr(), err)
continue
}
}
pln := &peerAPIListener{
ps: ps,
ip: a.IP(),
ip: a.Addr(),
ln: ln, // nil for 2nd+ on netstack
lb: b,
}
@@ -2570,7 +2586,7 @@ func (b *LocalBackend) initPeerAPIListener() {
} else {
pln.port = ln.Addr().(*net.TCPAddr).Port
}
pln.urlStr = "http://" + net.JoinHostPort(a.IP().String(), strconv.Itoa(pln.port))
pln.urlStr = "http://" + net.JoinHostPort(a.Addr().String(), strconv.Itoa(pln.port))
b.logf("peerapi: serving on %s", pln.urlStr)
go pln.serve()
b.peerAPIListeners = append(b.peerAPIListeners, pln)
@@ -2605,30 +2621,30 @@ func magicDNSRootDomains(nm *netmap.NetworkMap) []dnsname.FQDN {
}
var (
ipv4Default = netaddr.MustParseIPPrefix("0.0.0.0/0")
ipv6Default = netaddr.MustParseIPPrefix("::/0")
ipv4Default = netip.MustParsePrefix("0.0.0.0/0")
ipv6Default = netip.MustParsePrefix("::/0")
)
// peerRoutes returns the routerConfig.Routes to access peers.
// If there are over cgnatThreshold CGNAT routes, one big CGNAT route
// is used instead.
func peerRoutes(peers []wgcfg.Peer, cgnatThreshold int) (routes []netaddr.IPPrefix) {
func peerRoutes(peers []wgcfg.Peer, cgnatThreshold int) (routes []netip.Prefix) {
tsULA := tsaddr.TailscaleULARange()
cgNAT := tsaddr.CGNATRange()
var didULA bool
var cgNATIPs []netaddr.IPPrefix
var cgNATIPs []netip.Prefix
for _, peer := range peers {
for _, aip := range peer.AllowedIPs {
aip = unmapIPPrefix(aip)
// Only add the Tailscale IPv6 ULA once, if we see anybody using part of it.
if aip.IP().Is6() && aip.IsSingleIP() && tsULA.Contains(aip.IP()) {
if aip.Addr().Is6() && aip.IsSingleIP() && tsULA.Contains(aip.Addr()) {
if !didULA {
didULA = true
routes = append(routes, tsULA)
}
continue
}
if aip.IsSingleIP() && cgNAT.Contains(aip.IP()) {
if aip.IsSingleIP() && cgNAT.Contains(aip.Addr()) {
cgNATIPs = append(cgNATIPs, aip)
} else {
routes = append(routes, aip)
@@ -2648,11 +2664,11 @@ func peerRoutes(peers []wgcfg.Peer, cgnatThreshold int) (routes []netaddr.IPPref
return routes
}
func ipPrefixLess(ri, rj netaddr.IPPrefix) bool {
if ri.IP() == rj.IP() {
func ipPrefixLess(ri, rj netip.Prefix) bool {
if ri.Addr() == rj.Addr() {
return ri.Bits() < rj.Bits()
}
return ri.IP().Less(rj.IP())
return ri.Addr().Less(rj.Addr())
}
// routerConfig produces a router.Config from a wireguard config and IPN prefs.
@@ -2680,7 +2696,7 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs, oneCGNA
// likely to break some functionality, but if the user expressed a
// preference for routing remotely, we want to avoid leaking
// traffic at the expense of functionality.
if prefs.ExitNodeID != "" || !prefs.ExitNodeIP.IsZero() {
if prefs.ExitNodeID != "" || prefs.ExitNodeIP.IsValid() {
var default4, default6 bool
for _, route := range rs.Routes {
switch route {
@@ -2719,17 +2735,17 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs, oneCGNA
}
if tsaddr.PrefixesContainsFunc(rs.LocalAddrs, tsaddr.PrefixIs4) {
rs.Routes = append(rs.Routes, netaddr.IPPrefixFrom(tsaddr.TailscaleServiceIP(), 32))
rs.Routes = append(rs.Routes, netip.PrefixFrom(tsaddr.TailscaleServiceIP(), 32))
}
return rs
}
func unmapIPPrefix(ipp netaddr.IPPrefix) netaddr.IPPrefix {
return netaddr.IPPrefixFrom(ipp.IP().Unmap(), ipp.Bits())
func unmapIPPrefix(ipp netip.Prefix) netip.Prefix {
return netip.PrefixFrom(ipp.Addr().Unmap(), ipp.Bits())
}
func unmapIPPrefixes(ippsList ...[]netaddr.IPPrefix) (ret []netaddr.IPPrefix) {
func unmapIPPrefixes(ippsList ...[]netip.Prefix) (ret []netip.Prefix) {
for _, ipps := range ippsList {
for _, ipp := range ipps {
ret = append(ret, unmapIPPrefix(ipp))
@@ -2813,7 +2829,7 @@ func (b *LocalBackend) enterState(newState ipn.State) {
case ipn.Running:
var addrs []string
for _, addr := range netMap.Addresses {
addrs = append(addrs, addr.IP().String())
addrs = append(addrs, addr.Addr().String())
}
systemd.Status("Connected; %s; %s", activeLogin, strings.Join(addrs, " "))
default:
@@ -2972,8 +2988,8 @@ func (b *LocalBackend) ShouldRunSSH() bool { return b.sshAtomicBool.Get() && can
// ShouldHandleViaIP reports whether whether ip is an IPv6 address in the
// Tailscale ULA's v6 "via" range embedding an IPv4 address to be forwarded to
// by Tailscale.
func (b *LocalBackend) ShouldHandleViaIP(ip netaddr.IP) bool {
if f, ok := b.containsViaIPFuncAtomic.Load().(func(netaddr.IP) bool); ok {
func (b *LocalBackend) ShouldHandleViaIP(ip netip.Addr) bool {
if f, ok := b.containsViaIPFuncAtomic.Load().(func(netip.Addr) bool); ok {
return f(ip)
}
return false
@@ -3090,7 +3106,7 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
// Update the nodeByAddr index.
if b.nodeByAddr == nil {
b.nodeByAddr = map[netaddr.IP]*tailcfg.Node{}
b.nodeByAddr = map[netip.Addr]*tailcfg.Node{}
}
// First pass, mark everything unwanted.
for k := range b.nodeByAddr {
@@ -3099,7 +3115,7 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
addNode := func(n *tailcfg.Node) {
for _, ipp := range n.Addresses {
if ipp.IsSingleIP() {
b.nodeByAddr[ipp.IP()] = n
b.nodeByAddr[ipp.Addr()] = n
}
}
}
@@ -3279,9 +3295,9 @@ func peerAPIBase(nm *netmap.NetworkMap, peer *tailcfg.Node) string {
continue
}
switch {
case a.IP().Is4():
case a.Addr().Is4():
have4 = true
case a.IP().Is6():
case a.Addr().Is6():
have6 = true
}
}
@@ -3296,26 +3312,26 @@ func peerAPIBase(nm *netmap.NetworkMap, peer *tailcfg.Node) string {
p6 = s.Port
}
}
var ipp netaddr.IPPort
var ipp netip.AddrPort
switch {
case have4 && p4 != 0:
ipp = netaddr.IPPortFrom(nodeIP(peer, netaddr.IP.Is4), p4)
ipp = netip.AddrPortFrom(nodeIP(peer, netip.Addr.Is4), p4)
case have6 && p6 != 0:
ipp = netaddr.IPPortFrom(nodeIP(peer, netaddr.IP.Is6), p6)
ipp = netip.AddrPortFrom(nodeIP(peer, netip.Addr.Is6), p6)
}
if ipp.IP().IsZero() {
if !ipp.Addr().IsValid() {
return ""
}
return fmt.Sprintf("http://%v", ipp)
}
func nodeIP(n *tailcfg.Node, pred func(netaddr.IP) bool) netaddr.IP {
func nodeIP(n *tailcfg.Node, pred func(netip.Addr) bool) netip.Addr {
for _, a := range n.Addresses {
if a.IsSingleIP() && pred(a.IP()) {
return a.IP()
if a.IsSingleIP() && pred(a.Addr()) {
return a.Addr()
}
}
return netaddr.IP{}
return netip.Addr{}
}
func (b *LocalBackend) CheckIPForwarding() error {
@@ -3354,9 +3370,9 @@ func (b *LocalBackend) OfferingExitNode() bool {
if r.Bits() != 0 {
continue
}
if r.IP().Is4() {
if r.Addr().Is4() {
def4 = true
} else if r.IP().Is6() {
} else if r.Addr().Is6() {
def6 = true
}
}
@@ -3528,7 +3544,7 @@ func (b *LocalBackend) handleQuad100Port80Conn(w http.ResponseWriter, r *http.Re
}
io.WriteString(w, "<p>Local addresses:</p><ul>\n")
for _, ipp := range b.netMap.Addresses {
fmt.Fprintf(w, "<li>%v</li>\n", ipp.IP())
fmt.Fprintf(w, "<li>%v</li>\n", ipp.Addr())
}
io.WriteString(w, "</ul>\n")
}

View File

@@ -8,11 +8,12 @@ import (
"fmt"
"net"
"net/http"
"net/netip"
"reflect"
"testing"
"time"
"inet.af/netaddr"
"go4.org/netipx"
"tailscale.com/ipn"
"tailscale.com/ipn/store/mem"
"tailscale.com/net/interfaces"
@@ -25,17 +26,17 @@ import (
)
func TestNetworkMapCompare(t *testing.T) {
prefix1, err := netaddr.ParseIPPrefix("192.168.0.0/24")
prefix1, err := netip.ParsePrefix("192.168.0.0/24")
if err != nil {
t.Fatal(err)
}
node1 := &tailcfg.Node{Addresses: []netaddr.IPPrefix{prefix1}}
node1 := &tailcfg.Node{Addresses: []netip.Prefix{prefix1}}
prefix2, err := netaddr.ParseIPPrefix("10.0.0.0/8")
prefix2, err := netip.ParsePrefix("10.0.0.0/8")
if err != nil {
t.Fatal(err)
}
node2 := &tailcfg.Node{Addresses: []netaddr.IPPrefix{prefix2}}
node2 := &tailcfg.Node{Addresses: []netip.Prefix{prefix2}}
tests := []struct {
name string
@@ -131,7 +132,7 @@ func TestNetworkMapCompare(t *testing.T) {
}
}
func inRemove(ip netaddr.IP) bool {
func inRemove(ip netip.Addr) bool {
for _, pfx := range removeFromDefaultRoute {
if pfx.Contains(ip) {
return true
@@ -145,7 +146,7 @@ func TestShrinkDefaultRoute(t *testing.T) {
route string
in []string
out []string
localIPFn func(netaddr.IP) bool // true if this machine's local IP address should be "in" after shrinking.
localIPFn func(netip.Addr) bool // true if this machine's local IP address should be "in" after shrinking.
}{
{
route: "0.0.0.0/0",
@@ -165,7 +166,7 @@ func TestShrinkDefaultRoute(t *testing.T) {
"fe80::",
"2601::1",
},
localIPFn: func(ip netaddr.IP) bool { return !inRemove(ip) && ip.Is4() },
localIPFn: func(ip netip.Addr) bool { return !inRemove(ip) && ip.Is4() },
},
{
route: "::/0",
@@ -173,47 +174,47 @@ func TestShrinkDefaultRoute(t *testing.T) {
out: []string{
"fe80::1",
"ff00::1",
tsaddr.TailscaleULARange().IP().String(),
tsaddr.TailscaleULARange().Addr().String(),
},
localIPFn: func(ip netaddr.IP) bool { return !inRemove(ip) && ip.Is6() },
localIPFn: func(ip netip.Addr) bool { return !inRemove(ip) && ip.Is6() },
},
}
// Construct a fake local network environment to make this test hermetic.
// localInterfaceRoutes and hostIPs would normally come from calling interfaceRoutes,
// and localAddresses would normally come from calling interfaces.LocalAddresses.
var b netaddr.IPSetBuilder
var b netipx.IPSetBuilder
for _, c := range []string{"127.0.0.0/8", "192.168.9.0/24", "fe80::/32"} {
p := netaddr.MustParseIPPrefix(c)
p := netip.MustParsePrefix(c)
b.AddPrefix(p)
}
localInterfaceRoutes, err := b.IPSet()
if err != nil {
t.Fatal(err)
}
hostIPs := []netaddr.IP{
netaddr.MustParseIP("127.0.0.1"),
netaddr.MustParseIP("192.168.9.39"),
netaddr.MustParseIP("fe80::1"),
netaddr.MustParseIP("fe80::437d:feff:feca:49a7"),
hostIPs := []netip.Addr{
netip.MustParseAddr("127.0.0.1"),
netip.MustParseAddr("192.168.9.39"),
netip.MustParseAddr("fe80::1"),
netip.MustParseAddr("fe80::437d:feff:feca:49a7"),
}
localAddresses := []netaddr.IP{
netaddr.MustParseIP("192.168.9.39"),
localAddresses := []netip.Addr{
netip.MustParseAddr("192.168.9.39"),
}
for _, test := range tests {
def := netaddr.MustParseIPPrefix(test.route)
def := netip.MustParsePrefix(test.route)
got, err := shrinkDefaultRoute(def, localInterfaceRoutes, hostIPs)
if err != nil {
t.Fatalf("shrinkDefaultRoute(%q): %v", test.route, err)
}
for _, ip := range test.in {
if !got.Contains(netaddr.MustParseIP(ip)) {
if !got.Contains(netip.MustParseAddr(ip)) {
t.Errorf("shrink(%q).Contains(%v) = false, want true", test.route, ip)
}
}
for _, ip := range test.out {
if got.Contains(netaddr.MustParseIP(ip)) {
if got.Contains(netip.MustParseAddr(ip)) {
t.Errorf("shrink(%q).Contains(%v) = true, want false", test.route, ip)
}
}
@@ -227,22 +228,22 @@ func TestShrinkDefaultRoute(t *testing.T) {
}
func TestPeerRoutes(t *testing.T) {
pp := netaddr.MustParseIPPrefix
pp := netip.MustParsePrefix
tests := []struct {
name string
peers []wgcfg.Peer
want []netaddr.IPPrefix
want []netip.Prefix
}{
{
name: "small_v4",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
AllowedIPs: []netip.Prefix{
pp("100.101.102.103/32"),
},
},
},
want: []netaddr.IPPrefix{
want: []netip.Prefix{
pp("100.101.102.103/32"),
},
},
@@ -250,14 +251,14 @@ func TestPeerRoutes(t *testing.T) {
name: "big_v4",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
AllowedIPs: []netip.Prefix{
pp("100.101.102.103/32"),
pp("100.101.102.104/32"),
pp("100.101.102.105/32"),
},
},
},
want: []netaddr.IPPrefix{
want: []netip.Prefix{
pp("100.64.0.0/10"),
},
},
@@ -265,12 +266,12 @@ func TestPeerRoutes(t *testing.T) {
name: "has_1_v6",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
AllowedIPs: []netip.Prefix{
pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b240/128"),
},
},
},
want: []netaddr.IPPrefix{
want: []netip.Prefix{
pp("fd7a:115c:a1e0::/48"),
},
},
@@ -278,13 +279,13 @@ func TestPeerRoutes(t *testing.T) {
name: "has_2_v6",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
AllowedIPs: []netip.Prefix{
pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b240/128"),
pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b241/128"),
},
},
},
want: []netaddr.IPPrefix{
want: []netip.Prefix{
pp("fd7a:115c:a1e0::/48"),
},
},
@@ -292,7 +293,7 @@ func TestPeerRoutes(t *testing.T) {
name: "big_v4_big_v6",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
AllowedIPs: []netip.Prefix{
pp("100.101.102.103/32"),
pp("100.101.102.104/32"),
pp("100.101.102.105/32"),
@@ -301,7 +302,7 @@ func TestPeerRoutes(t *testing.T) {
},
},
},
want: []netaddr.IPPrefix{
want: []netip.Prefix{
pp("100.64.0.0/10"),
pp("fd7a:115c:a1e0::/48"),
},
@@ -310,19 +311,19 @@ func TestPeerRoutes(t *testing.T) {
name: "output-should-be-sorted",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
AllowedIPs: []netip.Prefix{
pp("100.64.0.2/32"),
pp("10.0.0.0/16"),
},
},
{
AllowedIPs: []netaddr.IPPrefix{
AllowedIPs: []netip.Prefix{
pp("100.64.0.1/32"),
pp("10.0.0.0/8"),
},
},
},
want: []netaddr.IPPrefix{
want: []netip.Prefix{
pp("10.0.0.0/8"),
pp("10.0.0.0/16"),
pp("100.64.0.1/32"),
@@ -361,14 +362,14 @@ func TestPeerAPIBase(t *testing.T) {
{
name: "self_only_4_them_both",
nm: &netmap.NetworkMap{
Addresses: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.1/32"),
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.1.1/32"),
},
},
peer: &tailcfg.Node{
Addresses: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.2/32"),
netaddr.MustParseIPPrefix("fe70::2/128"),
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.1.2/32"),
netip.MustParsePrefix("fe70::2/128"),
},
Hostinfo: (&tailcfg.Hostinfo{
Services: []tailcfg.Service{
@@ -382,14 +383,14 @@ func TestPeerAPIBase(t *testing.T) {
{
name: "self_only_6_them_both",
nm: &netmap.NetworkMap{
Addresses: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("fe70::1/128"),
Addresses: []netip.Prefix{
netip.MustParsePrefix("fe70::1/128"),
},
},
peer: &tailcfg.Node{
Addresses: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.2/32"),
netaddr.MustParseIPPrefix("fe70::2/128"),
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.1.2/32"),
netip.MustParsePrefix("fe70::2/128"),
},
Hostinfo: (&tailcfg.Hostinfo{
Services: []tailcfg.Service{
@@ -403,15 +404,15 @@ func TestPeerAPIBase(t *testing.T) {
{
name: "self_both_them_only_4",
nm: &netmap.NetworkMap{
Addresses: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.1/32"),
netaddr.MustParseIPPrefix("fe70::1/128"),
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.1.1/32"),
netip.MustParsePrefix("fe70::1/128"),
},
},
peer: &tailcfg.Node{
Addresses: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.2/32"),
netaddr.MustParseIPPrefix("fe70::2/128"),
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.1.2/32"),
netip.MustParsePrefix("fe70::2/128"),
},
Hostinfo: (&tailcfg.Hostinfo{
Services: []tailcfg.Service{
@@ -424,15 +425,15 @@ func TestPeerAPIBase(t *testing.T) {
{
name: "self_both_them_only_6",
nm: &netmap.NetworkMap{
Addresses: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.1/32"),
netaddr.MustParseIPPrefix("fe70::1/128"),
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.1.1/32"),
netip.MustParsePrefix("fe70::1/128"),
},
},
peer: &tailcfg.Node{
Addresses: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.2/32"),
netaddr.MustParseIPPrefix("fe70::2/128"),
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.1.2/32"),
netip.MustParsePrefix("fe70::2/128"),
},
Hostinfo: (&tailcfg.Hostinfo{
Services: []tailcfg.Service{
@@ -445,15 +446,15 @@ func TestPeerAPIBase(t *testing.T) {
{
name: "self_both_them_no_peerapi_service",
nm: &netmap.NetworkMap{
Addresses: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.1/32"),
netaddr.MustParseIPPrefix("fe70::1/128"),
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.1.1/32"),
netip.MustParsePrefix("fe70::1/128"),
},
},
peer: &tailcfg.Node{
Addresses: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("100.64.1.2/32"),
netaddr.MustParseIPPrefix("fe70::2/128"),
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.1.2/32"),
netip.MustParsePrefix("fe70::2/128"),
},
},
want: "",
@@ -541,10 +542,10 @@ func TestFileTargets(t *testing.T) {
func TestInternalAndExternalInterfaces(t *testing.T) {
type interfacePrefix struct {
i interfaces.Interface
pfx netaddr.IPPrefix
pfx netip.Prefix
}
masked := func(ips ...interfacePrefix) (pfxs []netaddr.IPPrefix) {
masked := func(ips ...interfacePrefix) (pfxs []netip.Prefix) {
for _, ip := range ips {
pfxs = append(pfxs, ip.pfx.Masked())
}
@@ -557,11 +558,11 @@ func TestInternalAndExternalInterfaces(t *testing.T) {
return il
}
newInterface := func(name, pfx string, wsl2, loopback bool) interfacePrefix {
ippfx := netaddr.MustParseIPPrefix(pfx)
ippfx := netip.MustParsePrefix(pfx)
ip := interfaces.Interface{
Interface: &net.Interface{},
AltAddrs: []net.Addr{
ippfx.IPNet(),
netipx.PrefixIPNet(ippfx),
},
}
if loopback {
@@ -583,8 +584,8 @@ func TestInternalAndExternalInterfaces(t *testing.T) {
name string
goos string
il interfaces.List
wantInt []netaddr.IPPrefix
wantExt []netaddr.IPPrefix
wantInt []netip.Prefix
wantExt []netip.Prefix
}{
{
name: "single-interface",

View File

@@ -16,6 +16,7 @@ import (
"io/fs"
"net"
"net/http"
"net/netip"
"net/url"
"os"
"path"
@@ -31,7 +32,6 @@ import (
"github.com/kortschak/wol"
"golang.org/x/net/dns/dnsmessage"
"inet.af/netaddr"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/health"
"tailscale.com/hostinfo"
@@ -39,6 +39,7 @@ import (
"tailscale.com/logtail/backoff"
"tailscale.com/net/dns/resolver"
"tailscale.com/net/interfaces"
"tailscale.com/net/netaddr"
"tailscale.com/net/netutil"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
@@ -47,7 +48,7 @@ import (
"tailscale.com/wgengine/filter"
)
var initListenConfig func(*net.ListenConfig, netaddr.IP, *interfaces.State, string) error
var initListenConfig func(*net.ListenConfig, netip.Addr, *interfaces.State, string) error
// addH2C is non-nil on platforms where we want to add H2C
// ("cleartext" HTTP/2) support to the peerAPI.
@@ -386,7 +387,7 @@ func (s *peerAPIServer) OpenFile(baseName string) (rc io.ReadCloser, size int64,
return f, fi.Size(), nil
}
func (s *peerAPIServer) listen(ip netaddr.IP, ifState *interfaces.State) (ln net.Listener, err error) {
func (s *peerAPIServer) listen(ip netip.Addr, ifState *interfaces.State) (ln net.Listener, err error) {
// Android for whatever reason often has problems creating the peerapi listener.
// But since we started intercepting it with netstack, it's not even important that
// we have a real kernel-level listener. So just create a dummy listener on Android
@@ -450,7 +451,7 @@ func (s *peerAPIServer) listen(ip netaddr.IP, ifState *interfaces.State) (ln net
type peerAPIListener struct {
ps *peerAPIServer
ip netaddr.IP
ip netip.Addr
lb *LocalBackend
// ln is the Listener. It can be nil in netstack mode if there are more than
@@ -502,7 +503,7 @@ func (pln *peerAPIListener) serve() {
}
}
func (pln *peerAPIListener) ServeConn(src netaddr.IPPort, c net.Conn) {
func (pln *peerAPIListener) ServeConn(src netip.AddrPort, c net.Conn) {
logf := pln.lb.logf
peerNode, peerUser, ok := pln.lb.WhoIs(src)
if !ok {
@@ -529,7 +530,7 @@ func (pln *peerAPIListener) ServeConn(src netaddr.IPPort, c net.Conn) {
// peerAPIHandler serves the Peer API for a source specific client.
type peerAPIHandler struct {
ps *peerAPIServer
remoteAddr netaddr.IPPort
remoteAddr netip.AddrPort
isSelf bool // whether peerNode is owned by same user as this node
peerNode *tailcfg.Node // peerNode is who's making the request
peerUser tailcfg.UserProfile // profile of peerNode
@@ -577,7 +578,7 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
<body>
<h1>Hello, %s (%v)</h1>
This is my Tailscale device. Your device is %v.
`, html.EscapeString(who), h.remoteAddr.IP(), html.EscapeString(h.peerNode.ComputedName))
`, html.EscapeString(who), h.remoteAddr.Addr(), html.EscapeString(h.peerNode.ComputedName))
if h.isSelf {
fmt.Fprintf(w, "<p>You are the owner of this node.\n")
@@ -608,7 +609,7 @@ func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Re
fmt.Fprintf(w, "<th>%v</th> ", v)
}
fmt.Fprint(w, "</tr>\n")
i.ForeachInterface(func(iface interfaces.Interface, ipps []netaddr.IPPrefix) {
i.ForeachInterface(func(iface interfaces.Interface, ipps []netip.Prefix) {
fmt.Fprint(w, "<tr>")
for _, v := range []any{iface.Index, iface.Name, iface.MTU, iface.Flags, ipps} {
fmt.Fprintf(w, "<td>%v</td> ", v)
@@ -693,7 +694,7 @@ func (h *peerAPIHandler) canWakeOnLAN() bool {
}
func (h *peerAPIHandler) peerHasCap(wantCap string) bool {
for _, hasCap := range h.ps.b.PeerCaps(h.remoteAddr.IP()) {
for _, hasCap := range h.ps.b.PeerCaps(h.remoteAddr.Addr()) {
if hasCap == wantCap {
return true
}
@@ -801,7 +802,7 @@ func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
}
d := time.Since(t0).Round(time.Second / 10)
h.logf("got put of %s in %v from %v/%v", approxSize(finalSize), d, h.remoteAddr.IP, h.peerNode.ComputedName)
h.logf("got put of %s in %v from %v/%v", approxSize(finalSize), d, h.remoteAddr.Addr(), h.peerNode.ComputedName)
// TODO: set modtime
// TODO: some real response
@@ -925,12 +926,11 @@ func (h *peerAPIHandler) handleWakeOnLAN(w http.ResponseWriter, r *http.Request)
}
for ifName, ips := range st.InterfaceIPs {
for _, ip := range ips {
if ip.IP().IsLoopback() || ip.IP().Is6() {
if ip.Addr().IsLoopback() || ip.Addr().Is6() {
continue
}
ipa := ip.IP().IPAddr()
local := &net.UDPAddr{
IP: ipa.IP,
IP: ip.Addr().AsSlice(),
Port: 0,
}
remote := &net.UDPAddr{
@@ -982,11 +982,11 @@ func (h *peerAPIHandler) replyToDNSQueries() bool {
// arbitrary. DNS runs over TCP and UDP, so sure... we check
// TCP.
dstIP := netaddr.IPv4(0, 0, 0, 0)
remoteIP := h.remoteAddr.IP()
remoteIP := h.remoteAddr.Addr()
if remoteIP.Is6() {
// autogroup:internet for IPv6 is defined to start with 2000::/3,
// so use 2000::0 as the probe "the internet" address.
dstIP = netaddr.MustParseIP("2000::")
dstIP = netip.MustParseAddr("2000::")
}
verdict := f.CheckTCP(remoteIP, dstIP, 53)
return verdict == filter.Accept
@@ -1169,9 +1169,9 @@ func writePrettyDNSReply(w io.Writer, res []byte) (err error) {
// it's listening on the provided IP address and on TCP port 1.
//
// See docs on fakePeerAPIListener.
func newFakePeerAPIListener(ip netaddr.IP) net.Listener {
func newFakePeerAPIListener(ip netip.Addr) net.Listener {
return &fakePeerAPIListener{
addr: netaddr.IPPortFrom(ip, 1).TCPAddr(),
addr: net.TCPAddrFromAddrPort(netip.AddrPortFrom(ip, 1)),
closed: make(chan struct{}),
}
}

View File

@@ -11,8 +11,8 @@ package ipnlocal
import (
"fmt"
"net"
"net/netip"
"inet.af/netaddr"
"tailscale.com/net/interfaces"
"tailscale.com/net/netns"
)
@@ -24,7 +24,7 @@ func init() {
// initListenConfigNetworkExtension configures nc for listening on IP
// through the iOS/macOS Network/System Extension (Packet Tunnel
// Provider) sandbox.
func initListenConfigNetworkExtension(nc *net.ListenConfig, ip netaddr.IP, st *interfaces.State, tunIfName string) error {
func initListenConfigNetworkExtension(nc *net.ListenConfig, ip netip.Addr, st *interfaces.State, tunIfName string) error {
tunIf, ok := st.Interface[tunIfName]
if !ok {
return fmt.Errorf("no interface with name %q", tunIfName)

View File

@@ -13,13 +13,14 @@ import (
"math/rand"
"net/http"
"net/http/httptest"
"net/netip"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"inet.af/netaddr"
"go4.org/netipx"
"tailscale.com/ipn"
"tailscale.com/tailcfg"
"tailscale.com/tstest"
@@ -582,7 +583,7 @@ func TestPeerAPIReplyToDNSQueries(t *testing.T) {
t.Errorf("for isSelf = false; want true")
}
h.isSelf = false
h.remoteAddr = netaddr.MustParseIPPort("100.150.151.152:12345")
h.remoteAddr = netip.MustParseAddrPort("100.150.151.152:12345")
eng, _ := wgengine.NewFakeUserspaceEngine(logger.Discard, 0)
h.ps = &peerAPIServer{
@@ -594,9 +595,9 @@ func TestPeerAPIReplyToDNSQueries(t *testing.T) {
t.Fatal("unexpectedly offering exit node")
}
h.ps.b.prefs = &ipn.Prefs{
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("0.0.0.0/0"),
netaddr.MustParseIPPrefix("::/0"),
AdvertiseRoutes: []netip.Prefix{
netip.MustParsePrefix("0.0.0.0/0"),
netip.MustParsePrefix("::/0"),
},
}
if !h.ps.b.OfferingExitNode() {
@@ -607,7 +608,7 @@ func TestPeerAPIReplyToDNSQueries(t *testing.T) {
t.Errorf("unexpectedly doing DNS without filter")
}
h.ps.b.setFilter(filter.NewAllowNone(logger.Discard, new(netaddr.IPSet)))
h.ps.b.setFilter(filter.NewAllowNone(logger.Discard, new(netipx.IPSet)))
if h.replyToDNSQueries() {
t.Errorf("unexpectedly doing DNS without filter")
}
@@ -620,7 +621,7 @@ func TestPeerAPIReplyToDNSQueries(t *testing.T) {
}
// Also test IPv6.
h.remoteAddr = netaddr.MustParseIPPort("[fe70::1]:12345")
h.remoteAddr = netip.MustParseAddrPort("[fe70::1]:12345")
if !h.replyToDNSQueries() {
t.Errorf("unexpectedly IPv6 deny; wanted to be a DNS server")
}

View File

@@ -8,6 +8,7 @@
package ipnlocal
import (
"bytes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
@@ -25,6 +26,7 @@ import (
"github.com/tailscale/golang-x-crypto/ssh"
"tailscale.com/envknob"
"tailscale.com/util/mak"
)
var useHostKeys = envknob.Bool("TS_USE_SYSTEM_SSH_HOST_KEYS")
@@ -35,34 +37,39 @@ var useHostKeys = envknob.Bool("TS_USE_SYSTEM_SSH_HOST_KEYS")
var keyTypes = []string{"rsa", "ecdsa", "ed25519"}
func (b *LocalBackend) GetSSH_HostKeys() (keys []ssh.Signer, err error) {
var existing map[string]ssh.Signer
if os.Geteuid() == 0 {
keys, err = b.getSystemSSH_HostKeys()
if err != nil || len(keys) > 0 {
return keys, err
}
// Otherwise, perhaps they don't have OpenSSH etc installed.
// Generate our own keys...
existing = b.getSystemSSH_HostKeys()
}
return b.getTailscaleSSH_HostKeys()
return b.getTailscaleSSH_HostKeys(existing)
}
func (b *LocalBackend) getTailscaleSSH_HostKeys() (keys []ssh.Signer, err error) {
root := b.TailscaleVarRoot()
if root == "" {
return nil, errors.New("no var root for ssh keys")
}
keyDir := filepath.Join(root, "ssh")
if err := os.MkdirAll(keyDir, 0700); err != nil {
return nil, err
}
// getTailscaleSSH_HostKeys returns the three (rsa, ecdsa, ed25519) SSH host
// keys, reusing the provided ones in existing if present in the map.
func (b *LocalBackend) getTailscaleSSH_HostKeys(existing map[string]ssh.Signer) (keys []ssh.Signer, err error) {
var keyDir string // lazily initialized $TAILSCALE_VAR/ssh dir.
for _, typ := range keyTypes {
if s, ok := existing[typ]; ok {
keys = append(keys, s)
continue
}
if keyDir == "" {
root := b.TailscaleVarRoot()
if root == "" {
return nil, errors.New("no var root for ssh keys")
}
keyDir = filepath.Join(root, "ssh")
if err := os.MkdirAll(keyDir, 0700); err != nil {
return nil, err
}
}
hostKey, err := b.hostKeyFileOrCreate(keyDir, typ)
if err != nil {
return nil, err
return nil, fmt.Errorf("error creating SSH host key type %q in %q: %w", typ, keyDir, err)
}
signer, err := ssh.ParsePrivateKey(hostKey)
if err != nil {
return nil, err
return nil, fmt.Errorf("error parsing SSH host key type %q from %q: %w", typ, keyDir, err)
}
keys = append(keys, signer)
}
@@ -114,23 +121,21 @@ func (b *LocalBackend) hostKeyFileOrCreate(keyDir, typ string) ([]byte, error) {
return pemGen, err
}
func (b *LocalBackend) getSystemSSH_HostKeys() (ret []ssh.Signer, err error) {
// TODO(bradfitz): cache this?
func (b *LocalBackend) getSystemSSH_HostKeys() (ret map[string]ssh.Signer) {
for _, typ := range keyTypes {
hostKey, err := ioutil.ReadFile("/etc/ssh/ssh_host_" + typ + "_key")
if os.IsNotExist(err) {
filename := "/etc/ssh/ssh_host_" + typ + "_key"
hostKey, err := ioutil.ReadFile(filename)
if err != nil || len(bytes.TrimSpace(hostKey)) == 0 {
continue
}
if err != nil {
return nil, err
}
signer, err := ssh.ParsePrivateKey(hostKey)
if err != nil {
return nil, err
b.logf("warning: error reading host key %s: %v (generating one instead)", filename, err)
continue
}
ret = append(ret, signer)
mak.Set(&ret, typ, signer)
}
return ret, nil
return ret
}
func (b *LocalBackend) getSSHHostKeyPublicStrings() (ret []string) {

View File

@@ -15,7 +15,7 @@ import (
func TestSSHKeyGen(t *testing.T) {
dir := t.TempDir()
lb := &LocalBackend{varRoot: dir}
keys, err := lb.getTailscaleSSH_HostKeys()
keys, err := lb.getTailscaleSSH_HostKeys(nil)
if err != nil {
t.Fatal(err)
}
@@ -32,7 +32,7 @@ func TestSSHKeyGen(t *testing.T) {
t.Fatalf("keys = %v; want %v", got, want)
}
keys2, err := lb.getTailscaleSSH_HostKeys()
keys2, err := lb.getTailscaleSSH_HostKeys(nil)
if err != nil {
t.Fatal(err)
}

View File

@@ -16,6 +16,7 @@ import (
"log"
"net"
"net/http"
"net/netip"
"os"
"os/exec"
"os/signal"
@@ -29,7 +30,6 @@ import (
"time"
"go4.org/mem"
"inet.af/netaddr"
"inet.af/peercred"
"tailscale.com/control/controlclient"
"tailscale.com/envknob"
@@ -146,15 +146,15 @@ func (s *Server) getConnIdentity(c net.Conn) (ci connIdentity, err error) {
ci.Creds, _ = peercred.Get(c)
return ci, nil
}
la, err := netaddr.ParseIPPort(c.LocalAddr().String())
la, err := netip.ParseAddrPort(c.LocalAddr().String())
if err != nil {
return ci, fmt.Errorf("parsing local address: %w", err)
}
ra, err := netaddr.ParseIPPort(c.RemoteAddr().String())
ra, err := netip.ParseAddrPort(c.RemoteAddr().String())
if err != nil {
return ci, fmt.Errorf("parsing local remote: %w", err)
}
if !la.IP().IsLoopback() || !ra.IP().IsLoopback() {
if !la.Addr().IsLoopback() || !ra.Addr().IsLoopback() {
return ci, errors.New("non-loopback connection")
}
tab, err := netstat.Get()
@@ -772,7 +772,7 @@ func New(logf logger.Logf, logid string, store ipn.StateStore, eng wgengine.Engi
dg := distro.Get()
switch dg {
case distro.Synology, distro.TrueNAS:
case distro.Synology, distro.TrueNAS, distro.QNAP:
// See if they have a "Taildrop" share.
// See https://github.com/tailscale/tailscale/issues/2179#issuecomment-982821319
path, err := findTaildropDir(dg)
@@ -1065,7 +1065,7 @@ func (s *Server) ServeHTMLStatus(w http.ResponseWriter, r *http.Request) {
st.WriteHTML(w)
}
func peerPid(entries []netstat.Entry, la, ra netaddr.IPPort) int {
func peerPid(entries []netstat.Entry, la, ra netip.AddrPort) int {
for _, e := range entries {
if e.Local == ra && e.Remote == la {
return e.Pid
@@ -1123,6 +1123,8 @@ func findTaildropDir(dg distro.Distro) (string, error) {
return findSynologyTaildropDir(name)
case distro.TrueNAS:
return findTrueNASTaildropDir(name)
case distro.QNAP:
return findQnapTaildropDir(name)
}
return "", fmt.Errorf("%s is an unsupported distro for Taildrop dir", dg)
}
@@ -1163,6 +1165,28 @@ func findTrueNASTaildropDir(name string) (dir string, err error) {
return "", fmt.Errorf("shared folder %q not found", name)
}
// findQnapTaildropDir checks if a Shared Folder named "Taildrop" exists.
func findQnapTaildropDir(name string) (string, error) {
dir := fmt.Sprintf("/share/%s", name)
fi, err := os.Stat(dir)
if err != nil {
return "", fmt.Errorf("shared folder %q not found", name)
}
if fi.IsDir() {
return dir, nil
}
// share/Taildrop is usually a symlink to CACHEDEV1_DATA/Taildrop/ or some such.
fullpath, err := filepath.EvalSymlinks(dir)
if err != nil {
return "", fmt.Errorf("symlink to shared folder %q not found", name)
}
if fi, err = os.Stat(fullpath); err == nil && fi.IsDir() {
return dir, nil // return the symlink, how QNAP set it up
}
return "", fmt.Errorf("shared folder %q not found", name)
}
func loadExtraEnv() (env []string, err error) {
if runtime.GOOS != "windows" {
return nil, nil

View File

@@ -12,12 +12,12 @@ import (
"html"
"io"
"log"
"net/netip"
"sort"
"strings"
"sync"
"time"
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/views"
@@ -35,7 +35,7 @@ type Status struct {
BackendState string
AuthURL string // current URL provided by control to authorize client
TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
TailscaleIPs []netip.Addr // Tailscale IP(s) assigned to this node
Self *PeerStatus
// ExitNodeStatus describes the current exit node.
@@ -94,7 +94,7 @@ type ExitNodeStatus struct {
Online bool
// TailscaleIPs are the exit node's IP addresses assigned to the node.
TailscaleIPs []netaddr.IPPrefix
TailscaleIPs []netip.Prefix
}
func (s *Status) Peers() []key.NodePublic {
@@ -124,7 +124,7 @@ type PeerStatus struct {
DNSName string
OS string // HostInfo.OS
UserID tailcfg.UserID
TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
TailscaleIPs []netip.Addr // Tailscale IP(s) assigned to this node
// Tags are the list of ACL tags applied to this node.
// See tailscale.com/tailcfg#Node.Tags for more information.
@@ -240,7 +240,7 @@ func (sb *StatusBuilder) AddUser(id tailcfg.UserID, up tailcfg.UserProfile) {
}
// AddIP adds a Tailscale IP address to the status.
func (sb *StatusBuilder) AddTailscaleIP(ip netaddr.IP) {
func (sb *StatusBuilder) AddTailscaleIP(ip netip.Addr) {
sb.mu.Lock()
defer sb.mu.Unlock()
if sb.locked {

View File

@@ -29,6 +29,7 @@ import (
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
@@ -37,6 +38,7 @@ import (
"tailscale.com/envknob"
"tailscale.com/ipn/ipnstate"
"tailscale.com/types/logger"
"tailscale.com/version/distro"
)
// Process-wide cache. (A new *Handler is created per connection,
@@ -53,6 +55,13 @@ var (
func (h *Handler) certDir() (string, error) {
d := h.b.TailscaleVarRoot()
// As a workaround for Synology DSM6 not having a "var" directory, use the
// app's "etc" directory (on a small partition) to hold certs at least.
// See https://github.com/tailscale/tailscale/issues/4060#issuecomment-1186592251
if d == "" && runtime.GOOS == "linux" && distro.Get() == distro.Synology && distro.DSMVersion() == 6 {
d = "/var/packages/Tailscale/etc" // base; we append "certs" below
}
if d == "" {
return "", errors.New("no TailscaleVarRoot")
}

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