Compare commits

..

91 Commits

Author SHA1 Message Date
Christina Wen
77f1591d68 API.md: revise documentation to be more consistent
Signed-off-by: Christina Wen <christina@tailscale.com>
2021-01-05 14:28:14 -05:00
Christina Wen
309d113f93 API.md: release API documentation
Co-authored-by: Daniel Chung <daniel@tailscale.com>
Signed-off-by: Christina Wen <christina@tailscale.com>
2021-01-05 13:37:11 -05:00
David Anderson
8fc11d582d go.sum: update to match wireguard-go version update.
Signed-off-by: David Anderson <dave@natulte.net>
2021-01-02 16:27:06 -08:00
Josh Bleecher Snyder
14af677332 go.mod: update wireguard-go version
To pick up netaddr deps change
2020-12-30 17:41:14 -08:00
David Anderson
86fe22a1b1 Update netaddr, and adjust wgengine/magicsock due to API change.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-30 17:36:03 -08:00
Josh Bleecher Snyder
56a7652dc9 wgkey: new package
This is a replacement for the key-related parts
of the wireguard-go wgcfg package.

This is almost a straight copy/paste from the wgcfg package.
I have slightly changed some of the exported functions and types
to avoid stutter, added and tweaked some comments,
and removed some now-unused code.

To avoid having wireguard-go depend on this new package,
wgcfg will keep its key types.

We translate into and out of those types at the last minute.
These few remaining uses will be eliminated alongside
the rest of the wgcfg package.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-30 17:33:02 -08:00
Brad Fitzpatrick
13b554fed9 version: bump for the last time in 2020 2020-12-30 12:07:25 -08:00
Brad Fitzpatrick
c2edb2865b go.sum: update 2020-12-30 12:07:25 -08:00
Christine Dodrill
70f14af21e add nix-shell boilerplate (#1028)
This enables users of nix-shell to automagically have the correct 
development environment by simply changing directory into a
checkout of this repo. For more information on this see the following
links:

- https://christine.website/blog/how-i-start-nix-2020-03-08
- https://direnv.net/
2020-12-29 12:17:03 -05:00
Brad Fitzpatrick
0d94fe5f69 wgengine/router: disable IPv6 on Linux if ip rule -6 fails (#1074)
Updates #562
Fixes #973

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-29 08:26:17 -08:00
Josh Bleecher Snyder
1e88050403 net/tsaddr: add ChromeOS contains tests
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-28 16:48:43 -08:00
Josh Bleecher Snyder
cf2ac2d123 go.mod: upgrade inet.af/netaddr
To pick up IPPrefix.Contains fix.
2020-12-28 15:46:46 -08:00
Josh Bleecher Snyder
2fe770ed72 all: replace wgcfg.IP and wgcfg.CIDR with netaddr types
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-28 13:00:42 -08:00
Charlotte Brandhorst-Satzkorn
ff2b3d02e6 Fix typo in cmd/tailscale/cli/cli.go (#1069)
Remove duplicate 'to connect' in error message.

Fixes #1068

Signed-off-by: Charlotte Brandhorst-Satzkorn <charlotte@catzkorn.dev>
2020-12-25 07:32:37 -08:00
Frederik “Freso” S. Olesen
a9a80ab372 tailscaled.service: Harden systemd unit somewhat (#1062)
While not a full capability lockdown of the systemd unit, this still
improves sandboxing and security of the running process a good deal.

Signed-off-by: Frederik “Freso” S. Olesen <freso.dk@gmail.com>
2020-12-24 16:14:58 -08:00
Matt Layher
1a42cef3a2 cmd/tailscale*: make updatedeps
Signed-off-by: Matt Layher <mdlayher@gmail.com>
2020-12-23 08:08:02 -08:00
Matt Layher
bfbd6b9241 go.mod: bump github.com/mdlayher/netlink to v1.2.0
Signed-off-by: Matt Layher <mdlayher@gmail.com>
2020-12-23 08:08:02 -08:00
Brad Fitzpatrick
80c94168ae wgengine: finish updating isTrimmablePeer
I accidentally merged Dave's change with the XXXX DO NOT SUBMIT comment
in it.
2020-12-22 14:48:24 -08:00
David Anderson
cb96b14bf4 net/packet: remove the custom IP4/IP6 types in favor of netaddr.IP.
Upstream netaddr has a change that makes it alloc-free, so it's safe to
use in hot codepaths. This gets rid of one of the many IP types in our
codebase.

Performance is currently worse across the board. This is likely due in
part to netaddr.IP being a larger value type (4b -> 24b for IPv4,
16b -> 24b for IPv6), and in other part due to missing low-hanging fruit
optimizations in netaddr. However, the regression is less bad than
it looks at first glance, because we'd micro-optimized packet.IP* in
the past few weeks. This change drops us back to roughly where we
were at the 1.2 release, but with the benefit of a significant
code and architectural simplification.

name                   old time/op    new time/op    delta
pkg:tailscale.com/net/packet goos:linux goarch:amd64
Decode/tcp4-8            12.2ns ± 5%    29.7ns ± 2%  +142.32%  (p=0.008 n=5+5)
Decode/tcp6-8            12.6ns ± 3%    65.1ns ± 2%  +418.47%  (p=0.008 n=5+5)
Decode/udp4-8            11.8ns ± 3%    30.5ns ± 2%  +157.94%  (p=0.008 n=5+5)
Decode/udp6-8            27.1ns ± 1%    65.7ns ± 2%  +142.36%  (p=0.016 n=4+5)
Decode/icmp4-8           24.6ns ± 2%    30.5ns ± 2%   +23.65%  (p=0.016 n=4+5)
Decode/icmp6-8           22.9ns ±51%    65.5ns ± 2%  +186.19%  (p=0.008 n=5+5)
Decode/igmp-8            18.1ns ±44%    30.2ns ± 1%   +66.89%  (p=0.008 n=5+5)
Decode/unknown-8         20.8ns ± 1%    10.6ns ± 9%   -49.11%  (p=0.016 n=4+5)
pkg:tailscale.com/wgengine/filter goos:linux goarch:amd64
Filter/icmp4-8           30.5ns ± 1%    77.9ns ± 3%  +155.01%  (p=0.008 n=5+5)
Filter/tcp4_syn_in-8     43.7ns ± 3%   123.0ns ± 3%  +181.72%  (p=0.008 n=5+5)
Filter/tcp4_syn_out-8    24.5ns ± 2%    45.7ns ± 6%   +86.22%  (p=0.008 n=5+5)
Filter/udp4_in-8         64.8ns ± 1%   210.0ns ± 2%  +223.87%  (p=0.008 n=5+5)
Filter/udp4_out-8         119ns ± 0%     278ns ± 0%  +133.78%  (p=0.016 n=4+5)
Filter/icmp6-8           40.3ns ± 2%   204.4ns ± 4%  +407.70%  (p=0.008 n=5+5)
Filter/tcp6_syn_in-8     35.3ns ± 3%   199.2ns ± 2%  +464.95%  (p=0.008 n=5+5)
Filter/tcp6_syn_out-8    32.8ns ± 2%    81.0ns ± 2%  +147.10%  (p=0.008 n=5+5)
Filter/udp6_in-8          106ns ± 2%     290ns ± 2%  +174.48%  (p=0.008 n=5+5)
Filter/udp6_out-8         184ns ± 2%     314ns ± 3%   +70.43%  (p=0.016 n=4+5)
pkg:tailscale.com/wgengine/tstun goos:linux goarch:amd64
Write-8                  9.02ns ± 3%    8.92ns ± 1%      ~     (p=0.421 n=5+5)

name                   old alloc/op   new alloc/op   delta
pkg:tailscale.com/net/packet goos:linux goarch:amd64
Decode/tcp4-8             0.00B          0.00B           ~     (all equal)
Decode/tcp6-8             0.00B          0.00B           ~     (all equal)
Decode/udp4-8             0.00B          0.00B           ~     (all equal)
Decode/udp6-8             0.00B          0.00B           ~     (all equal)
Decode/icmp4-8            0.00B          0.00B           ~     (all equal)
Decode/icmp6-8            0.00B          0.00B           ~     (all equal)
Decode/igmp-8             0.00B          0.00B           ~     (all equal)
Decode/unknown-8          0.00B          0.00B           ~     (all equal)
pkg:tailscale.com/wgengine/filter goos:linux goarch:amd64
Filter/icmp4-8            0.00B          0.00B           ~     (all equal)
Filter/tcp4_syn_in-8      0.00B          0.00B           ~     (all equal)
Filter/tcp4_syn_out-8     0.00B          0.00B           ~     (all equal)
Filter/udp4_in-8          0.00B          0.00B           ~     (all equal)
Filter/udp4_out-8         16.0B ± 0%     64.0B ± 0%  +300.00%  (p=0.008 n=5+5)
Filter/icmp6-8            0.00B          0.00B           ~     (all equal)
Filter/tcp6_syn_in-8      0.00B          0.00B           ~     (all equal)
Filter/tcp6_syn_out-8     0.00B          0.00B           ~     (all equal)
Filter/udp6_in-8          0.00B          0.00B           ~     (all equal)
Filter/udp6_out-8         48.0B ± 0%     64.0B ± 0%   +33.33%  (p=0.008 n=5+5)

name                   old allocs/op  new allocs/op  delta
pkg:tailscale.com/net/packet goos:linux goarch:amd64
Decode/tcp4-8              0.00           0.00           ~     (all equal)
Decode/tcp6-8              0.00           0.00           ~     (all equal)
Decode/udp4-8              0.00           0.00           ~     (all equal)
Decode/udp6-8              0.00           0.00           ~     (all equal)
Decode/icmp4-8             0.00           0.00           ~     (all equal)
Decode/icmp6-8             0.00           0.00           ~     (all equal)
Decode/igmp-8              0.00           0.00           ~     (all equal)
Decode/unknown-8           0.00           0.00           ~     (all equal)
pkg:tailscale.com/wgengine/filter goos:linux goarch:amd64
Filter/icmp4-8             0.00           0.00           ~     (all equal)
Filter/tcp4_syn_in-8       0.00           0.00           ~     (all equal)
Filter/tcp4_syn_out-8      0.00           0.00           ~     (all equal)
Filter/udp4_in-8           0.00           0.00           ~     (all equal)
Filter/udp4_out-8          1.00 ± 0%      1.00 ± 0%      ~     (all equal)
Filter/icmp6-8             0.00           0.00           ~     (all equal)
Filter/tcp6_syn_in-8       0.00           0.00           ~     (all equal)
Filter/tcp6_syn_out-8      0.00           0.00           ~     (all equal)
Filter/udp6_in-8           0.00           0.00           ~     (all equal)
Filter/udp6_out-8          1.00 ± 0%      1.00 ± 0%      ~     (all equal)

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-22 14:44:36 -08:00
Brad Fitzpatrick
d0baece5fa go.mod: bump inet.af/netaddr to non-allocating version 2020-12-22 14:25:32 -08:00
Brad Fitzpatrick
ef15096a7d control/controlclient, version/distro: detect NixOS explicitly
The fallthrough happened to work in controlclient already due to the
/etc/os-release PRETTY_NAME default, but make it explicit so it
doesn't look like an accident.

Also add it to version/distro, even though nothing needs it yet.
2020-12-21 21:03:04 -08:00
David Crawshaw
2b2a16d9a2 wgengine/router/dns: reduce windows registry key open timeout
The windows key timeout is longer than the wgengine watchdog timeout,
which means we never reach the timeout, instead the process exits.
Reduce the timeout so if we do hit it, at least the process continues.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-12-21 17:24:58 -05:00
David Crawshaw
b4f70d8232 wgengine/router/dns: use the correct interface GUID
On Win10, there's a hardcoded GUID and this works.
On Win7, this GUID changes and we need to ask the tun for its
LUID and convert that from the GUID.

This commit uses the computed GUID that is placed in InterfaceName.

Diagnosed by Jason Donnenfeld. (Thanks!)
2020-12-21 16:43:24 -05:00
Brad Fitzpatrick
15c064f76f wgengine/router/dns: remove unsafe endianness detection on Linux 2020-12-21 13:11:09 -08:00
Brad Fitzpatrick
f9659323df wgengine/router/dns: fix typo in comment 2020-12-21 13:07:30 -08:00
Brad Fitzpatrick
053a1d1340 all: annotate log verbosity levels on most egregiously spammy log prints
Fixes #924
Fixes #282

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-21 12:59:33 -08:00
Brad Fitzpatrick
57dd247376 cmd/tailscaled, logpolicy, logtail: support log levels
Log levels can now be specified with "[v1] " or "[v2] " substrings
that are then stripped and filtered at the final logger. This follows
our existing "[unexpected]" etc convention and doesn't require a
wholesale reworking of our logging at the moment.

cmd/tailscaled then gets a new --verbose=N flag to take a log level
that controls what gets logged to stderr (and thus systemd, syslog,
etc). Logtail is unaffected by --verbose.

This commit doesn't add annotations to any existing log prints. That
is in the next commit.

Updates #924
Updates #282

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-21 12:59:33 -08:00
Brad Fitzpatrick
d97ee12179 logtail, logpolicy: remove an unidiomatic use of an interface 2020-12-21 09:03:39 -08:00
Brad Fitzpatrick
83f45ae2dd version: bump date 2020-12-21 08:33:46 -08:00
Brad Fitzpatrick
c348fb554f control/controlclient: clarify a comment 2020-12-21 08:33:05 -08:00
Brad Fitzpatrick
90c8519765 go.sum: update 2020-12-21 08:32:51 -08:00
David Anderson
ca676ea645 tailcfg: introduce map version 8, for clients that support v6 node config.
For now, the server will only send v6 configuration to mapversion 8 clients
as part of an early-adopter program, while we verify that the functionality
is robust.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 18:28:27 -08:00
David Anderson
03a039d48d go.mod: bump wireguard-go version.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 17:26:05 -08:00
David Anderson
f5e33ad761 go.mod: update inet.af/netaddr, go mod tidy.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 14:01:43 -08:00
David Anderson
89be4037bb control/controlclient: report broken routing for v4 and v6.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 13:50:44 -08:00
David Anderson
baa7937998 net/interfaces: return IPv6 addresses from LocalAddresses.
In practice, we already provide IPv6 endpoint addresses via netcheck,
and that address is likely to match a local address anyway (i.e. no NAT66).
The comment at that piece of the code mentions needing to figure out a
good priority ordering, but that only applies to non-active-discovery
clients, who already don't do anything with IPv6 addresses.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 13:50:44 -08:00
David Anderson
294ceb513c ipn, wgengine/magicsock: fix tailscale status display.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 13:50:44 -08:00
David Anderson
891110e64c wgengine: expand lazy config to work with dual-stacked peers.
Lazy wg configuration now triggers if a peer has only endpoint
addresses (/32 for IPv4, /128 for IPv6). Subnet routers still
trigger eager configuration to avoid the need for a CIDR match
in the hot packet path.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 13:50:44 -08:00
David Anderson
aa353b8d0f net/packet: add an IP6 constructor from a raw byte array.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-19 13:50:44 -08:00
Smitty
f0b0a62873 Clarify that raw format strings are intentional
This caused some confusion in issue #460, since usually raw format
strings aren't printed directly. Hopefully by directly logging that
they are intended to be raw format strings, this will be more clear.
Rate limited format strings now look like:

  [RATE LIMITED] format string "control: sendStatus: %s: %v"

Closes #460.

Signed-off-by: Smitty <me@smitop.com>
2020-12-19 13:49:14 -08:00
David Anderson
c8c493f3d9 wgengine/magicsock: make ReceiveIPv4 a little easier to follow.
The previous code used a lot of whole-function variables and shared
behavior that only triggered based on prior action from a single codepath.
Instead of that, move the small amounts of "shared" code into each switch
case.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
David Anderson
0ad109f63d wgengine/magicsock: move legacy endpoint creation into legacy.go.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
David Anderson
f873da5b16 wgengine/magicsock: move more legacy endpoint handling.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
David Anderson
58fcd103c4 wgengine/magicsock: move legacy sending code to legacy.go.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
David Anderson
65ae66260f wgengine/magicsock: unexport AddrSet.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
David Anderson
c9b9afd761 wgengine/magicsock: move most legacy nat traversal bits to another file.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-18 01:15:53 -08:00
Brad Fitzpatrick
5f07da4854 util/systemd: don't log warnings when not running under systemd
It caused our integration tests to fail, which prohibit logging to
os.Stderr for test cleanliness reasons.
2020-12-17 12:59:05 -08:00
Brad Fitzpatrick
741c513e51 wgengine/tsdns: fix error response marshaling, improve bad query logs
Updates #995

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-17 12:46:08 -08:00
David Anderson
554a20becb wgengine/magicsock: only log about lazy config when actually doing lazy config.
Before, tailscaled would log every 10 seconds when the periodic noteRecvActivity
call happens. This is noisy, but worse it's misleading, because the message
suggests that the disco code is starting a lazy config run for a missing peer,
whereas in fact it's just an internal piece of keepalive logic.

With this change, we still log when going from 0->1 tunnel for the peer, but
not every 10s thereafter.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-17 12:11:36 -08:00
Brad Fitzpatrick
da1bad51cd tailcfg: document new OmitPeers endpoint updating functionality 2020-12-15 12:16:15 -08:00
Brad Fitzpatrick
fa412c8760 wgengine/filter, wgengine/magicsock: use new IP.BitLen to simplify some code 2020-12-15 12:12:56 -08:00
Brad Fitzpatrick
afcf134812 wgengine/filter, tailcfg: support CIDRs+ranges in PacketFilter (mapver 7)
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-15 11:00:37 -08:00
Christine Dodrill
0681c6da49 Merge pull request #967 from Xe/report-status-systemd
ipn/ipnserver: enable systemd-notify support
2020-12-15 11:44:20 -05:00
Christine Dodrill
2485faf69a Merge branch 'main' into report-status-systemd 2020-12-15 08:40:46 -05:00
Christine Dodrill
7ea809897d ipn/ipnserver: enable systemd-notify support
Addresses #964

Still to be done:
- Figure out the correct logging lines in util/systemd
- Figure out if we need to slip the systemd.Status function anywhere
  else
- Log util/systemd errors? (most of the errors are of the "you cannot do
  anything about this, but it might be a bad idea to crash the program if
  it errors" kind)

Assistance in getting this over the finish line would help a lot.

Signed-off-by: Christine Dodrill <me@christine.website>

util/systemd: rename the nonlinux file to appease the magic

Signed-off-by: Christine Dodrill <me@christine.website>

util/systemd: fix package name

Signed-off-by: Christine Dodrill <me@christine.website>

util/systemd: fix review feedback from @mdlayher

Signed-off-by: Christine Dodrill <me@christine.website>

cmd/tailscale{,d}: update depaware manifests

Signed-off-by: Christine Dodrill <me@christine.website>

util/systemd: use sync.Once instead of func init

Signed-off-by: Christine Dodrill <me@christine.website>

control/controlclient: minor review feedback fixes

Signed-off-by: Christine Dodrill <me@christine.website>

{control,ipn,systemd}: fix review feedback

Signed-off-by: Christine Dodrill <me@christine.website>

review feedback fixes

Signed-off-by: Christine Dodrill <me@christine.website>

ipn: fix sprintf call

Signed-off-by: Christine Dodrill <me@christine.website>

ipn: make staticcheck less sad

Signed-off-by: Christine Dodrill <me@christine.website>

ipn: print IP address in connected status

Signed-off-by: Christine Dodrill <me@christine.website>

ipn: review feedback

Signed-off-by: Christine Dodrill <me@christine.website>

final fixups

Signed-off-by: Christine Dodrill <me@christine.website>
2020-12-15 08:39:06 -05:00
David Anderson
9cee0bfa8c wgengine/magicsock: sprinkle more docstrings.
Magicsock is too damn big, but this might help me page it back
in faster next time.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-14 23:59:17 -08:00
Josh Bleecher Snyder
34a0292433 depaware.txt: update
Upgrading staticcheck upgraded golang.org/x/sync
(one downside of mixing our tools in with our regular go.mod),
which introduced a new dependency via
https://go-review.googlesource.com/c/sync/+/251677

That CL could and probably should be written without runtime/debug,
but it's not clear to me that that is better at this moment
than simply accepting the additional package as a dependency.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-14 14:01:29 -08:00
Josh Bleecher Snyder
ce4d68b416 go.mod: upgrade depaware version
Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-14 14:01:29 -08:00
Josh Bleecher Snyder
a6cad71fb2 go.mod: upgrade staticcheck to 0.1.0
Also run go.mod and fix some staticcheck warnings.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-12-14 14:01:29 -08:00
Brad Fitzpatrick
a0a8b9d76a control/controlclient: don't spin when starting up when node key is expired
Fixes #1018

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2020-12-14 11:51:04 -08:00
Smitty
b895bf853a Require at least Go 1.15
This was actually required before this commit, this just updates
go.mod with that fact.

Signed-off-by: Smitty <me@smitop.com>
2020-12-13 16:36:25 -08:00
Smitty
8a57f920ae Remove unused .gitignore lines
These ignore built files that don't exist anymore, and just serve
to clutter up the .gitignore file. (I was initially confused when
I saw those lines, since I (correctly) thought that the only
Tailscale binaries were tailscale and tailscaled):

- taillogin was removed in d052586
- relaynode was removed in a56e853

Signed-off-by: Smitty <me@smitop.com>
2020-12-12 16:11:58 -08:00
Josh Bleecher Snyder
6db9c4a173 wgenginer/router/dns: use constant from golang.org/x/sys/windows
Made available in https://golang.org/cl/277153
2020-12-10 17:23:01 -08:00
Aleksandar Pesic
0dc295a640 Isolate WireGuard code into a separate file with appropriate copyright info in header.
Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2020-12-11 01:08:41 +01:00
Aleksandar Pesic
d854fe95d2 Trivial change in function description.
Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2020-12-11 00:55:37 +01:00
Aleksandar Pesic
4749a96a5b Update depaware.txt files.
Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2020-12-11 00:45:31 +01:00
Aleksandar Pesic
338fd44657 Replace registry-access code, update wireguard-go and x/sys/windows.
Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2020-12-11 00:37:24 +01:00
Aleksandar Pesic
274d32d0aa Prepare for the new wireguard-go API.
Signed-off-by: Aleksandar Pesic <peske.nis@gmail.com>
2020-12-11 00:08:28 +01:00
Adrian Dewhurst
943860fde7 version: relax git detection logic (again)
This is a repeat of commit 3aa68cd397
which was lost in a rework of version.sh.

git worktrees have a .git file rather than a .git directory, so building
in a worktree caused version.sh to generate an error.

Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>
2020-12-09 21:55:41 -05:00
Brad Fitzpatrick
bce865b61b logpolicy: migrate from x/crypto/ssh/terminal to x/term 2020-12-09 15:28:31 -08:00
David Anderson
57cd7738c2 tsweb: add an endpoint to manually trigger a GC.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-08 16:49:42 -08:00
Brad Fitzpatrick
9cb6ee3777 go.mod, go.sum: update 2020-12-08 15:23:56 -08:00
Brad Fitzpatrick
08f94b3b50 net/netcheck: fix offset of unspecified address in PCP request packet
Fixes #810
2020-12-08 15:22:26 -08:00
Brad Fitzpatrick
442d1873ec go.mod: bump tailscale/wireguard-go 2020-12-07 14:02:05 -08:00
Brad Fitzpatrick
19c2c6403d Update go.sum 2020-12-07 14:00:53 -08:00
Brad Fitzpatrick
b3c7b631c2 tailcfg, control/controlclient: make nil PacketFilter mean unchanged (mapver 6)
After mapver 5's incremental netmap updates & user profiles, much of
the remaining bandwidth for streamed MapResponses were redundant,
unchanged PacketFilters. So make MapRequest.Version 6 mean that nil
means unchanged from the previous value.
2020-12-07 09:17:42 -08:00
Brad Fitzpatrick
05e5233e07 net/netcheck: don't send flood of PCP unmap requests to router
Updates #810
2020-12-06 19:46:11 -08:00
Brad Fitzpatrick
9503be083d tailcfg: update comments a bit 2020-12-03 12:16:10 -08:00
Brad Fitzpatrick
88179121e3 version: bump date 2020-12-03 12:08:07 -08:00
Brad Fitzpatrick
7b92f8e718 wgengine/magicsock: add start of magicsock benchmarks (Conn.ReceiveIPv4 for now)
And only single-threaded for now. Will get fancier later.

Updates #414
2020-12-02 20:26:54 -08:00
Brad Fitzpatrick
713cbe84c1 wgengine/magicsock: use net.JoinHostPort when host might have colons (udp6)
Only affected tests. (where it just generated log spam)
2020-12-02 20:19:28 -08:00
David Anderson
be6fe393c5 wgengine: don't try pinging IPv6 addresses in legacy pinger.
Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-01 20:09:32 -08:00
David Anderson
dfbde3d3aa ipn: pass through the prefix length from control.
Control sets this to /32 for IPv4 and /128 for IPv6.

Signed-off-by: David Anderson <danderson@tailscale.com>
2020-12-01 20:09:16 -08:00
David Anderson
4c8ccd6dd6 tailcfg: document new debug flag. 2020-12-01 18:17:09 -08:00
Brad Fitzpatrick
c0af7deb86 tailcfg, cmd/tailscale: add Hostinfo.ShareeNode, hide in "tailscale status" 2020-12-01 15:29:18 -08:00
Brad Fitzpatrick
ab482118ad tailcfg: add some missing json omitempty
Noticed these in MapResponses to clients.

MachineAuthorized was set true, but once we fix the coordination server
to zero out that field, then it can be omittted.
2020-11-25 10:27:01 -08:00
Dmytro Tananayskiy
c431382720 Fix receiver in order to be consistent: syncs.WaitGroupChan
Signed-off-by: Dmytro Tananayskiy <dmitriyminer@gmail.com>
2020-11-24 17:20:34 -08:00
Josh Bleecher Snyder
3a7402aa2d logtail: help the server be more efficient
Add content length hints to headers.
The server can use these hints to more efficiently select buffers.

Stop attempting to compress tiny requests.
The bandwidth savings are negligible (and sometimes negative!),
and it makes extra work for the server.

Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>
2020-11-24 12:00:32 -08:00
Brad Fitzpatrick
cd6099113f ipn: add a comment about skipping files with null bytes
Updates #954
2020-11-24 11:07:49 -08:00
Alex Brainman
72e082aaf5 ipn: make LoadPrefs return os.ErrNotExist when reading corrupted files
It appears some users have corrupted pref.conf files. Have LoadPrefs
treat these files as non-existent. This way tailscale will make user
login, and not crash.

Fixes #954

Signed-off-by: Alex Brainman <alex.brainman@gmail.com>
2020-11-24 11:05:42 -08:00
David Crawshaw
2c48b4ee14 tailcfg: remove outdated comments about Clone methods
The cloner tool adds static checks that the Clone methods are up to
date, so failing to update Clone causes a compiler error.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-11-24 13:16:21 -05:00
85 changed files with 3278 additions and 2045 deletions

2
.gitignore vendored
View File

@@ -6,8 +6,6 @@
*.so
*.dylib
cmd/relaynode/relaynode
cmd/taillogin/taillogin
cmd/tailscale/tailscale
cmd/tailscaled/tailscaled

661
api.md Normal file
View File

@@ -0,0 +1,661 @@
# Tailscale API
The Tailscale API is a (mostly) RESTful API. Typically, POST bodies should be JSON encoded and responses will be JSON encoded.
# Authentication
Currently based on {some authentication method}. Visit the [admin panel](https://api.tailscale.com/admin) and navigate to the `Keys` page. Generate an API Key and keep it safe. Provide the key as the user key in basic auth when making calls to Tailscale API endpoints.
# APIS
## Device
<!-- TODO: description about what devices are -->
#### `GET /api/v2/device/:deviceid` - lists the details for a device
Returns the details for the specified device.
Supply the device of interest in the path using its ID.
Use the `fields` query parameter to explicitly indicate which fields are returned.
##### Parameters
##### Query Parameters
`fields` - Controls which fields will be included in the returned response.
Currently, supported options are:
* `all`: returns all fields in the response.
* `default`: return all fields except:
* `enabledRoutes`
* `advertisedRoutes`
* `clientConnectivity` (which contains the following fields: `mappingVariesByDestIP`, `derp`, `endpoints`, `latency`, and `clientSupports`)
Use commas to separate multiple options.
If more than one option is indicated, then the union is used.
For example, for `fields=default,all`, all fields are returned.
If the `fields` parameter is not provided, then the default option is used.
##### Example
```
GET /api/v2/device/12345
curl https://api.tailscale.com/api/v2/device/12345?fields=all \
-u "tskey-yourapikey123:"
```
Response
```
{
"addresses":[
"100.105.58.116"
],
"id":"12345",
"user":"user1@example.com",
"name":"user1-device.example.com",
"hostname":"User1-Device",
"clientVersion":"date.20201107",
"updateAvailable":false,
"os":"macOS",
"created":"2020-11-20T20:56:49Z",
"lastSeen":"2020-11-20T16:15:55-05:00",
"keyExpiryDisabled":false,
"expires":"2021-05-19T20:56:49Z",
"authorized":true,
"isExternal":false,
"machineKey":"mkey:user1-machine-key",
"nodeKey":"nodekey:user1-node-key",
"blocksIncomingConnections":false,
"enabledRoutes":[
],
"advertisedRoutes":[
],
"clientConnectivity": {
"endpoints":[
"209.195.87.231:59128",
"192.168.0.173:59128"
],
"derp":"",
"mappingVariesByDestIP":false,
"latency":{
"Dallas":{
"latencyMs":60.463043
},
"New York City":{
"preferred":true,
"latencyMs":31.323811
},
"San Francisco":{
"latencyMs":81.313389
}
},
"clientSupports":{
"hairPinning":false,
"ipv6":false,
"pcp":false,
"pmp":false,
"udp":true,
"upnp":false
}
}
}
```
#### `GET /api/v2/device/:deviceID/routes` - fetch subnet routes that are advertised and enabled for a device
Retrieves the list of subnet routes that a device is advertising, as well as those that are enabled for it. Enabled routes are not necessarily advertised (e.g. for pre-enabling), and likewise, advertised routes are not necessarily enabled.
##### Parameters
No parameters.
##### Example
```
curl https://api.tailscale.com/api/v2/device/11055/routes \
-u "tskey-yourapikey123:"
```
Response
```
{
"advertisedRoutes" : [
"10.0.1.0/24",
"1.2.0.0/16",
"2.0.0.0/24"
],
"enabledRoutes" : []
}
```
#### `POST /api/v2/device/:deviceID/routes` - set the subnet routes that are enabled for a device
Sets which subnet routes are enabled to be routed by a device by replacing the existing list of subnet routes with the supplied parameters. Routes can be enabled without a device advertising them (e.g. for preauth). Returns a list of enabled subnet routes and a list of advertised subnet routes for a device.
##### Parameters
###### POST Body
`routes` - The new list of enabled subnet routes in JSON.
```
{
"routes": ["10.0.1.0/24", "1.2.0.0/16", "2.0.0.0/24"]
}
```
##### Example
```
curl https://api.tailscale.com/api/v2/device/11055/routes \
-u "tskey-yourapikey123:" \
--data-binary '{"routes": ["10.0.1.0/24", "1.2.0.0/16", "2.0.0.0/24"]}'
```
Response
```
{
"advertisedRoutes" : [
"10.0.1.0/24",
"1.2.0.0/16",
"2.0.0.0/24"
],
"enabledRoutes" : [
"10.0.1.0/24",
"1.2.0.0/16",
"2.0.0.0/24"
]
}
```
## Domain
<!---
TODO: ctrl+f domain, replace with {workgroup/tailnet/other}
Domain is a top level resource. ACL is an example of a resource that is tied to a top level domain.
--->
### ACL
#### `GET /api/v2/domain/:domain/acl` - fetch ACL for a domain
Retrieves the ACL that is currently set for the given domain. Supply the domain of interest in the path. This endpoint can send back either the HuJSON of the ACL or a parsed JSON, depending on the `Accept` header.
##### Parameters
###### Headers
`Accept` - Response is parsed `JSON` if `application/json` is explicitly named, otherwise HuJSON will be returned.
##### Returns
Returns the ACL HuJSON by default. Returns a parsed JSON of the ACL (sans comments) if the `Accept` type is explicitly set to `application/json`. An `ETag` header is also sent in the response, which can be optionally used in POST requests to avoid missed updates.
<!-- TODO (chungdaniel): define error types and a set of docs for them -->
##### Example
###### Requesting a HuJSON response:
```
GET /api/v2/domain/example.com/acl
curl https://api.tailscale.com/api/v2/domain/example.com/acl \
-u "tskey-yourapikey123:" \
-H "Accept: application/hujson" \
-v
```
Response
```
...
Content-Type: application/hujson
Etag: "e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c"
...
// Example/default ACLs for unrestricted connections.
{
"Tests": [],
// Declare static groups of users beyond those in the identity service.
"Groups": {
"group:example": [
"user1@example.com",
"user2@example.com"
],
},
// Declare convenient hostname aliases to use in place of IP addresses.
"Hosts": {
"example-host-1": "100.100.100.100",
},
// Access control lists.
"ACLs": [
// Match absolutely everything. Comment out this section if you want
// to define specific ACL restrictions.
{
"Action": "accept",
"Users": [
"*"
],
"Ports": [
"*:*"
]
},
]
}
```
###### Requesting a JSON response:
```
GET /api/v2/domain/example.com/acl
curl https://api.tailscale.com/api/v2/domain/example.com/acl \
-u "tskey-yourapikey123:" \
-H "Accept: application/json" \
-v
```
Response
```
...
Content-Type: application/json
Etag: "e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c"
...
{
"acls" : [
{
"action" : "accept",
"ports" : [
"*:*"
],
"users" : [
"*"
]
}
],
"groups" : {
"group:example" : [
"user1@example.com",
"user2@example.com"
]
},
"hosts" : {
"example-host-1" : "100.100.100.100"
}
}
```
#### `POST /api/v2/domain/:domain/acl` - set ACL for a domain
Sets the ACL for the given domain. HuJSON and JSON are both accepted inputs. An `If-Match` header can be set to avoid missed updates.
Returns error for invalid ACLs.
Returns error if using an `If-Match` header and the ETag does not match.
##### Parameters
###### Headers
`If-Match` - A request header. Set this value to the ETag header provided in an `ACL GET` request to avoid missed updates.
`Accept` - Sets the return type of the updated ACL. Response is parsed `JSON` if `application/json` is explicitly named, otherwise HuJSON will be returned.
###### POST Body
ACL JSON or HuJSON (see https://tailscale.com/kb/1018/acls)
##### Example
```
POST /api/v2/domain/example.com/acl
curl https://api.tailscale.com/api/v2/domain/example.com/acl \
-u "tskey-yourapikey123:" \
-H "If-Match: \"e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c\""
--data-binary '// Example/default ACLs for unrestricted connections.
{
// Declare tests to check functionality of ACL rules. User must be a valid user with registered machines.
"Tests": [
// {"User": "user1@example.com", "Allow": ["example-host-1:22"], "Deny": ["example-host-2:100"]},
],
// Declare static groups of users beyond those in the identity service.
"Groups": {
"group:example": [ "user1@example.com", "user2@example.com" ],
},
// Declare convenient hostname aliases to use in place of IP addresses.
"Hosts": {
"example-host-1": "100.100.100.100",
},
// Access control lists.
"ACLs": [
// Match absolutely everything. Comment out this section if you want
// to define specific ACL restrictions.
{ "Action": "accept", "Users": ["*"], "Ports": ["*:*"] },
]
}'
```
Response
```
// Example/default ACLs for unrestricted connections.
{
// Declare tests to check functionality of ACL rules. User must be a valid user with registered machines.
"Tests": [
// {"User": "user1@example.com", "Allow": ["example-host-1:22"], "Deny": ["example-host-2:100"]},
],
// Declare static groups of users beyond those in the identity service.
"Groups": {
"group:example": [ "user1@example.com", "user2@example.com" ],
},
// Declare convenient hostname aliases to use in place of IP addresses.
"Hosts": {
"example-host-1": "100.100.100.100",
},
// Access control lists.
"ACLs": [
// Match absolutely everything. Comment out this section if you want
// to define specific ACL restrictions.
{ "Action": "accept", "Users": ["*"], "Ports": ["*:*"] },
]
}
```
#### `POST /api/v2/domain/:domain/acl/preview` - preview rule matches on an ACL for a resource
Determines what rules match for a user on an ACL without saving the ACL to the server.
##### Parameters
###### Query Parameters
`user` - A user's email. The provided ACL is queried with this user to determine which rules match.
###### POST Body
ACL JSON or HuJSON (see https://tailscale.com/kb/1018/acls)
##### Example
```
POST /api/v2/domain/example.com/acl/preiew
curl https://api.tailscale.com/api/v2/domain/example.com/acl?user=user1@example.com \
-u "tskey-yourapikey123:" \
--data-binary '// Example/default ACLs for unrestricted connections.
{
// Declare tests to check functionality of ACL rules. User must be a valid user with registered machines.
"Tests": [
// {"User": "user1@example.com", "Allow": ["example-host-1:22"], "Deny": ["example-host-2:100"]},
],
// Declare static groups of users beyond those in the identity service.
"Groups": {
"group:example": [ "user1@example.com", "user2@example.com" ],
},
// Declare convenient hostname aliases to use in place of IP addresses.
"Hosts": {
"example-host-1": "100.100.100.100",
},
// Access control lists.
"ACLs": [
// Match absolutely everything. Comment out this section if you want
// to define specific ACL restrictions.
{ "Action": "accept", "Users": ["*"], "Ports": ["*:*"] },
]
}'
```
Response
```
{"matches":[{"users":["*"],"ports":["*:*"],"lineNumber":19}],"user":"user1@example.com"}
```
### Devices
#### `GET /api/v2/domain/:domain/devices` - list the devices for a domain
Lists the devices for a domain.
Supply the domain of interest in the path.
Use the `fields` query parameter to explicitly indicate which fields are returned.
##### Parameters
###### Query Parameters
`fields` - Controls which fields will be included in the returned response.
Currently, supported options are:
* `all`: Returns all fields in the response.
* `default`: return all fields except:
* `enabledRoutes`
* `advertisedRoutes`
* `clientConnectivity` (which contains the following fields: `mappingVariesByDestIP`, `derp`, `endpoints`, `latency`, and `clientSupports`)
Use commas to separate multiple options.
If more than one option is indicated, then the union is used.
For example, for `fields=default,all`, all fields are returned.
If the `fields` parameter is not provided, then the default option is used.
##### Example
```
GET /api/v2/domain/example.com/devices
curl https://api.tailscale.com/api/v2/domain/example.com/devices \
-u "tskey-yourapikey123:"
```
Response
```
{
"devices":[
{
"addresses":[
"100.68.203.125"
],
"clientVersion":"date.20201107",
"os":"macOS",
"name":"user1-device.example.com",
"created":"2020-11-30T22:20:04Z",
"lastSeen":"2020-11-30T17:20:04-05:00",
"hostname":"User1-Device",
"machineKey":"mkey:user1-node-key",
"nodeKey":"nodekey:user1-node-key",
"id":"12345",
"user":"user1@example.com",
"expires":"2021-05-29T22:20:04Z",
"keyExpiryDisabled":false,
"authorized":false,
"isExternal":false,
"updateAvailable":false,
"blocksIncomingConnections":false,
},
{
"addresses":[
"100.111.63.90"
],
"clientVersion":"date.20201107",
"os":"macOS",
"name":"user2-device.example.com",
"created":"2020-11-30T22:21:03Z",
"lastSeen":"2020-11-30T17:21:03-05:00",
"hostname":"User2-Device",
"machineKey":"mkey:user2-machine-key",
"nodeKey":"nodekey:user2-node-key",
"id":"48810",
"user":"user2@example.com",
"expires":"2021-05-29T22:21:03Z",
"keyExpiryDisabled":false,
"authorized":false,
"isExternal":false,
"updateAvailable":false,
"blocksIncomingConnections":false,
}
]
}
```
### DNS
#### `GET /api/v2/domain/:domain/dns/nameservers` - list the DNS nameservers for a domain
Lists the DNS nameservers for a domain.
Supply the domain of interest in the path.
##### Parameters
No parameters.
##### Example
```
GET /api/v2/domain/example.com/dns/nameservers
curl https://api.tailscale.com/api/v2/domain/example.com/dns/nameservers \
-u "tskey-yourapikey123:"
```
Response
```
{
"dns": ["8.8.8.8"],
}
```
#### `POST /api/v2/domain/:domain/dns/nameservers` - replaces the list of DNS nameservers for a domain
Replaces the list of DNS nameservers for the given domain with the list supplied by the user.
Supply the domain of interest in the path.
Note that changing the list of DNS nameservers may also affect the status of MagicDNS (if MagicDNS is on).
##### Parameters
###### POST Body
`dns` - The new list of DNS nameservers in JSON.
```
{
"dns":["8.8.8.8"]
}
```
##### Returns
Returns the new list of nameservers and the status of MagicDNS.
If all nameservers have been removed, MagicDNS will be automatically disabled (until explicitly turned back on by the user).
##### Example
###### Adding DNS nameservers with the MagicDNS on:
```
POST /api/v2/domain/example.com/dns/nameservers
curl -X POST 'https://api.tailscale.com/api/v2/domain/example.com/dns/nameservers' \
-u "tskey-yourapikey123:" \
--data-binary '{"dns": ["8.8.8.8"]}'
```
Response:
```
{
"dns":["8.8.8.8"],
"magicDNS":true,
}
```
###### Removing all DNS nameservers with the MagicDNS on:
```
POST /api/v2/domain/example.com/dns/nameservers
curl -X POST 'https://api.tailscale.com/api/v2/domain/example.com/dns/nameservers' \
-u "tskey-yourapikey123:" \
--data-binary '{"dns": []}'
```
Response:
```
{
"dns":[],
"magicDNS": false,
}
```
#### `GET /api/v2/domain/:domain/dns/preferences` - retrieves the DNS preferences for a domain
Retrieves the DNS preferences that are currently set for the given domain.
Supply the domain of interest in the path.
##### Parameters
No parameters.
##### Example
```
GET /api/v2/domain/example.com/dns/preferences
curl 'https://api.tailscale.com/api/v2/domain/example.com/dns/preferences' \
-u "tskey-yourapikey123:"
```
Response:
```
{
"magicDNS":false,
}
```
#### `POST /api/v2/domain/:domain/dns/preferences` - replaces the DNS preferences for a domain
Replaces the DNS preferences for a domain, specifically, the MagicDNS setting.
Note that MagicDNS is dependent on DNS servers.
If there is at least one DNS server, then MagicDNS can be enabled.
Otherwise, it returns an error.
Note that removing all nameservers will turn off MagicDNS.
To reenable it, nameservers must be added back, and MagicDNS must be explicity turned on.
##### Parameters
###### POST Body
The DNS preferences in JSON. Currently, MagicDNS is the only setting available.
`magicDNS` - Automatically registers DNS names for devices in your network.
```
{
"magicDNS": true
}
```
##### Example
```
POST /api/v2/domain/example.com/dns/preferences
curl -X POST 'https://api.tailscale.com/api/v2/domain/example.com/dns/preferences' \
-u "tskey-yourapikey123:" \
--data-binary '{"magicDNS": true}'
```
Response:
If there are no DNS servers, it returns an error message:
```
{
"message":"need at least one nameserver to enable MagicDNS"
}
```
If there are DNS servers:
```
{
"magicDNS":true,
}
```
#### `GET /api/v2/domain/:domain/dns/searchpaths` - retrieves the search paths for a domain
Retrieves the list of search paths that is currently set for the given domain.
Supply the domain of interest in the path.
##### Parameters
No parameters.
##### Example
```
GET /api/v2/domain/example.com/dns/searchpaths
curl 'https://api.tailscale.com/api/v2/domain/example.com/dns/searchpaths' \
-u "tskey-yourapikey123:"
```
Response:
```
{
"searchPaths": ["user1.example.com"],
}
```
#### `POST /api/v2/domain/:domain/dns/searchpaths` - replaces the search paths for a domain
Replaces the list of search paths with the list supplied by the user and returns an error otherwise.
##### Parameters
###### POST Body
`searchPaths` - A list of searchpaths in JSON format.
```
{
"searchPaths: ["user1.example.com", "user2.example.com"]
}
```
##### Example
```
POST /api/v2/domain/example.com/dns/searchpaths
curl -X POST 'https://api.tailscale.com/api/v2/domain/example.com/dns/searchpaths' \
-u "tskey-yourapikey123:" \
--data-binary '{"searchPaths": ["user1.example.com", "user2.example.com"]}'
```
Response:
```
{
"searchPaths": ["user1.example.com", "user2.example.com"],
}
```

View File

@@ -25,7 +25,6 @@ import (
"strings"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/crypto/acme/autocert"
"tailscale.com/atomicfile"
"tailscale.com/derp"
@@ -35,6 +34,7 @@ import (
"tailscale.com/net/stun"
"tailscale.com/tsweb"
"tailscale.com/types/key"
"tailscale.com/types/wgkey"
"tailscale.com/version"
)
@@ -51,7 +51,7 @@ var (
)
type config struct {
PrivateKey wgcfg.PrivateKey
PrivateKey wgkey.Private
}
func loadConfig() config {
@@ -77,8 +77,8 @@ func loadConfig() config {
}
}
func mustNewKey() wgcfg.PrivateKey {
key, err := wgcfg.NewPrivateKey()
func mustNewKey() wgkey.Private {
key, err := wgkey.NewPrivate()
if err != nil {
log.Fatal(err)
}

View File

@@ -99,7 +99,7 @@ func connect(ctx context.Context) (net.Conn, *ipn.BackendClient, context.Context
if runtime.GOOS != "windows" && rootArgs.socket == "" {
fatalf("--socket cannot be empty")
}
fatalf("Failed to connect to connect to tailscaled. (safesocket.Connect: %v)\n", err)
fatalf("Failed to connect to tailscaled. (safesocket.Connect: %v)\n", err)
}
clientToServer := func(b []byte) {
ipn.WriteMsg(c, b)

View File

@@ -169,6 +169,9 @@ func runStatus(ctx context.Context, args []string) error {
}
for _, peer := range st.Peers() {
ps := st.Peer[peer]
if ps.ShareeNode {
continue
}
active := peerActive(ps)
if statusArgs.active && !active {
continue

View File

@@ -19,7 +19,6 @@ import (
"sync"
"github.com/peterbourgon/ff/v2/ffcli"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/ipn"
"tailscale.com/tailcfg"
@@ -85,29 +84,6 @@ var upArgs struct {
hostname string
}
// parseIPOrCIDR parses an IP address or a CIDR prefix. If the input
// is an IP address, it is returned in CIDR form with a /32 mask for
// IPv4 or a /128 mask for IPv6.
func parseIPOrCIDR(s string) (wgcfg.CIDR, bool) {
if strings.Contains(s, "/") {
ret, err := wgcfg.ParseCIDR(s)
if err != nil {
return wgcfg.CIDR{}, false
}
return ret, true
}
ip, ok := wgcfg.ParseIP(s)
if !ok {
return wgcfg.CIDR{}, false
}
if ip.Is4() {
return wgcfg.CIDR{IP: ip, Mask: 32}, true
} else {
return wgcfg.CIDR{IP: ip, Mask: 128}, true
}
}
func isBSD(s string) bool {
return s == "dragonfly" || s == "freebsd" || s == "netbsd" || s == "openbsd"
}
@@ -162,19 +138,18 @@ func runUp(ctx context.Context, args []string) error {
}
}
var routes []wgcfg.CIDR
var routes []netaddr.IPPrefix
if upArgs.advertiseRoutes != "" {
advroutes := strings.Split(upArgs.advertiseRoutes, ",")
for _, s := range advroutes {
cidr, ok := parseIPOrCIDR(s)
ipp, err := netaddr.ParseIPPrefix(s) // parse it with other pawith both packages
if !ok || err != nil {
ipp, err := netaddr.ParseIPPrefix(s)
if err != nil {
fatalf("%q is not a valid IP address or CIDR prefix", s)
}
if ipp != ipp.Masked() {
fatalf("%s has non-address bits set; expected %s", ipp, ipp.Masked())
}
routes = append(routes, cidr)
routes = append(routes, ipp)
}
checkIPForwarding()
}

View File

@@ -10,10 +10,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
L 💣 github.com/godbus/dbus/v5 from tailscale.com/wgengine/router/dns
github.com/golang/groupcache/lru from tailscale.com/wgengine/filter+
L github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
@@ -27,15 +28,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/iphlpapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/namespaceapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/nci from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/registry from github.com/tailscale/wireguard-go/tun/wintun+
W 💣 github.com/tailscale/wireguard-go/tun/wintun/setupapi from github.com/tailscale/wireguard-go/tun/wintun
github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/conn+
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
💣 go4.org/intern from inet.af/netaddr
💣 go4.org/mem from tailscale.com/control/controlclient+
go4.org/unsafe/assume-no-moving-gc from go4.org/intern
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
inet.af/netaddr from tailscale.com/cmd/tailscale/cli+
rsc.io/goversion/version from tailscale.com/version
@@ -75,8 +73,10 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/types/opt from tailscale.com/control/controlclient+
tailscale.com/types/strbuilder from tailscale.com/net/packet
tailscale.com/types/structs from tailscale.com/control/controlclient+
W tailscale.com/util/endian from tailscale.com/net/netns
tailscale.com/types/wgkey from tailscale.com/control/controlclient+
LW tailscale.com/util/endian from tailscale.com/net/netns+
tailscale.com/util/lineread from tailscale.com/control/controlclient+
tailscale.com/util/systemd from tailscale.com/control/controlclient+
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
tailscale.com/wgengine from tailscale.com/ipn
@@ -84,12 +84,12 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
tailscale.com/wgengine/magicsock from tailscale.com/wgengine
💣 tailscale.com/wgengine/monitor from tailscale.com/cmd/tailscale/cli+
tailscale.com/wgengine/router from tailscale.com/cmd/tailscale/cli+
💣 tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/tsdns from tailscale.com/ipn+
tailscale.com/wgengine/tstun from tailscale.com/wgengine
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
@@ -118,11 +118,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+
W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+
W golang.org/x/sys/windows/registry from github.com/tailscale/wireguard-go/tun/wintun+
W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
golang.org/x/text/unicode/norm from github.com/tailscale/wireguard-go/tun/wintun+
golang.org/x/text/unicode/norm from golang.org/x/net/idna
golang.org/x/time/rate from tailscale.com/types/logger+
bufio from compress/flate+
bytes from bufio+
@@ -197,6 +197,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
reflect from crypto/x509+
regexp from github.com/coreos/go-iptables/iptables+
regexp/syntax from regexp
runtime/debug from golang.org/x/sync/singleflight
runtime/pprof from tailscale.com/log/logheap+
sort from compress/flate+
strconv from compress/flate+

View File

@@ -10,7 +10,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
L 💣 github.com/godbus/dbus/v5 from tailscale.com/wgengine/router/dns
github.com/golang/groupcache/lru from tailscale.com/wgengine/filter+
L github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
github.com/klauspost/compress/fse from github.com/klauspost/compress/huff0
github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd
@@ -19,6 +19,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+
💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine+
github.com/tailscale/wireguard-go/device/tokenbucket from github.com/tailscale/wireguard-go/device
@@ -30,14 +31,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/iphlpapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/namespaceapi from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/nci from github.com/tailscale/wireguard-go/tun/wintun
W 💣 github.com/tailscale/wireguard-go/tun/wintun/registry from github.com/tailscale/wireguard-go/tun/wintun+
W 💣 github.com/tailscale/wireguard-go/tun/wintun/setupapi from github.com/tailscale/wireguard-go/tun/wintun
github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/conn+
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
💣 go4.org/intern from inet.af/netaddr
💣 go4.org/mem from tailscale.com/control/controlclient+
go4.org/unsafe/assume-no-moving-gc from go4.org/intern
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
inet.af/netaddr from tailscale.com/control/controlclient+
rsc.io/goversion/version from tailscale.com/version
@@ -83,10 +81,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/types/opt from tailscale.com/control/controlclient+
tailscale.com/types/strbuilder from tailscale.com/net/packet
tailscale.com/types/structs from tailscale.com/control/controlclient+
W tailscale.com/util/endian from tailscale.com/net/netns+
tailscale.com/types/wgkey from tailscale.com/control/controlclient+
LW tailscale.com/util/endian from tailscale.com/net/netns+
tailscale.com/util/lineread from tailscale.com/control/controlclient+
tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver
tailscale.com/util/racebuild from tailscale.com/logpolicy
tailscale.com/util/systemd from tailscale.com/control/controlclient+
tailscale.com/version from tailscale.com/cmd/tailscaled+
tailscale.com/version/distro from tailscale.com/control/controlclient+
tailscale.com/wgengine from tailscale.com/cmd/tailscaled+
@@ -94,12 +94,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/magicsock from tailscale.com/cmd/tailscaled+
💣 tailscale.com/wgengine/monitor from tailscale.com/wgengine
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
💣 tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/router/dns from tailscale.com/ipn+
tailscale.com/wgengine/tsdns from tailscale.com/ipn+
tailscale.com/wgengine/tstun from tailscale.com/wgengine
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/chacha20poly1305 from crypto/tls+
golang.org/x/crypto/cryptobyte from crypto/ecdsa+
@@ -110,7 +110,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box
golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device+
golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+
golang.org/x/crypto/ssh/terminal from tailscale.com/logpolicy
golang.org/x/net/bpf from github.com/mdlayher/netlink+
golang.org/x/net/context/ctxhttp from golang.org/x/oauth2/internal
golang.org/x/net/dns/dnsmessage from net+
@@ -129,11 +128,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+
W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+
W golang.org/x/sys/windows/registry from github.com/tailscale/wireguard-go/tun/wintun+
W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
golang.org/x/term from tailscale.com/logpolicy
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
golang.org/x/text/unicode/norm from github.com/tailscale/wireguard-go/tun/wintun+
golang.org/x/text/unicode/norm from golang.org/x/net/idna
golang.org/x/time/rate from tailscale.com/types/logger+
bufio from compress/flate+
bytes from bufio+

View File

@@ -64,6 +64,7 @@ var args struct {
port uint16
statepath string
socketpath string
verbose int
}
func main() {
@@ -76,6 +77,7 @@ func main() {
}
printVersion := false
flag.IntVar(&args.verbose, "verbose", 0, "log verbosity level; 0 is default, 1 or higher are increasingly verbose")
flag.BoolVar(&args.cleanup, "cleanup", false, "clean up system state and exit")
flag.BoolVar(&args.fake, "fake", false, "use userspace fake tunnel+routing instead of kernel TUN interface")
flag.StringVar(&args.debug, "debug", "", "listen address ([ip]:port) of optional debug server")
@@ -118,6 +120,7 @@ func run() error {
var err error
pol := logpolicy.New("tailnode.log.tailscale.io")
pol.SetVerbosityLevel(args.verbose)
defer func() {
// Finish uploading logs after closing everything else.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)

View File

@@ -18,6 +18,18 @@ StateDirectory=tailscale
StateDirectoryMode=0750
CacheDirectory=tailscale
CacheDirectoryMode=0750
Type=notify
LockPersonality=true
MemoryDenyWriteExecute=true
PrivateTmp=true
ProtectControlGroups=true
ProtectHome=true
ProtectKernelTunables=true
ProtectSystem=strict
ReadWritePaths=/etc/
RestrictSUIDSGID=true
SystemCallArchitectures=native
[Install]
WantedBy=multi-user.target

View File

@@ -17,13 +17,13 @@ import (
"sync"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/oauth2"
"tailscale.com/logtail/backoff"
"tailscale.com/tailcfg"
"tailscale.com/types/empty"
"tailscale.com/types/logger"
"tailscale.com/types/structs"
"tailscale.com/types/wgkey"
)
// State is the high-level state of the client. It is used only in
@@ -182,7 +182,8 @@ func (c *Client) SetPaused(paused bool) {
}
c.paused = paused
if paused {
// Just cancel the map routine. The auth routine isn't expensive.
// Only cancel the map routine. (The auth routine isn't expensive
// so it's fine to keep it running.)
c.cancelMapLocked()
} else {
for _, ch := range c.unpauseWaiters {
@@ -230,7 +231,7 @@ func (c *Client) cancelMapSafely() {
c.mu.Lock()
defer c.mu.Unlock()
c.logf("cancelMapSafely: synced=%v", c.synced)
c.logf("[v1] cancelMapSafely: synced=%v", c.synced)
if c.inPollNetMap {
// received at least one netmap since the last
@@ -252,12 +253,12 @@ func (c *Client) cancelMapSafely() {
// request.
select {
case c.newMapCh <- struct{}{}:
c.logf("cancelMapSafely: wrote to channel")
c.logf("[v1] cancelMapSafely: wrote to channel")
default:
// if channel write failed, then there was already
// an outstanding newMapCh request. One is enough,
// since it'll always use the latest endpoints.
c.logf("cancelMapSafely: channel was full")
c.logf("[v1] cancelMapSafely: channel was full")
}
}
}
@@ -268,22 +269,24 @@ func (c *Client) authRoutine() {
for {
c.mu.Lock()
c.logf("authRoutine: %s", c.state)
expiry := c.expiry
goal := c.loginGoal
ctx := c.authCtx
synced := c.synced
if goal != nil {
c.logf("authRoutine: %s; wantLoggedIn=%v", c.state, goal.wantLoggedIn)
} else {
c.logf("authRoutine: %s; goal=nil", c.state)
}
c.mu.Unlock()
select {
case <-c.quit:
c.logf("authRoutine: quit")
c.logf("[v1] authRoutine: quit")
return
default:
}
report := func(err error, msg string) {
c.logf("%s: %v", msg, err)
c.logf("[v1] %s: %v", msg, err)
err = fmt.Errorf("%s: %v", msg, err)
// don't send status updates for context errors,
// since context cancelation is always on purpose.
@@ -293,51 +296,13 @@ func (c *Client) authRoutine() {
}
if goal == nil {
// Wait for something interesting to happen
var exp <-chan time.Time
var expTimer *time.Timer
if expiry != nil && !expiry.IsZero() {
// if expiry is in the future, don't delay
// past that time.
// If it's in the past, then it's already
// being handled by someone, so no need to
// wake ourselves up again.
now := c.timeNow()
if expiry.Before(now) {
delay := expiry.Sub(now)
if delay > 5*time.Second {
delay = time.Second
}
expTimer = time.NewTimer(delay)
exp = expTimer.C
}
}
select {
case <-ctx.Done():
if expTimer != nil {
expTimer.Stop()
}
c.logf("authRoutine: context done.")
case <-exp:
// Unfortunately the key expiry isn't provided
// by the control server until mapRequest.
// So we have to do some hackery with c.expiry
// in here.
// TODO(apenwarr): add a key expiry field in RegisterResponse.
c.logf("authRoutine: key expiration check.")
if synced && expiry != nil && !expiry.IsZero() && expiry.Before(c.timeNow()) {
c.logf("Key expired; setting loggedIn=false.")
// Wait for user to Login or Logout.
<-ctx.Done()
c.logf("[v1] authRoutine: context done.")
continue
}
c.mu.Lock()
c.loginGoal = &LoginGoal{
wantLoggedIn: c.loggedIn,
}
c.loggedIn = false
c.expiry = nil
c.mu.Unlock()
}
}
} else if !goal.wantLoggedIn {
if !goal.wantLoggedIn {
err := c.direct.TryLogout(ctx)
if err != nil {
report(err, "TryLogout")
@@ -469,7 +434,7 @@ func (c *Client) mapRoutine() {
}
report := func(err error, msg string) {
c.logf("%s: %v", msg, err)
c.logf("[v1] %s: %v", msg, err)
err = fmt.Errorf("%s: %v", msg, err)
// don't send status updates for context errors,
// since context cancelation is always on purpose.
@@ -504,7 +469,7 @@ func (c *Client) mapRoutine() {
select {
case <-c.newMapCh:
c.logf("mapRoutine: new map request during PollNetMap. canceling.")
c.logf("[v1] mapRoutine: new map request during PollNetMap. canceling.")
c.cancelMapLocked()
// Don't emit this netmap; we're
@@ -526,7 +491,7 @@ func (c *Client) mapRoutine() {
c.mu.Unlock()
c.logf("mapRoutine: netmap received: %s", state)
c.logf("[v1] mapRoutine: netmap received: %s", state)
if stillAuthed {
c.sendStatus("mapRoutine-got-netmap", nil, "", nm)
}
@@ -607,7 +572,7 @@ func (c *Client) sendStatus(who string, err error, url string, nm *NetworkMap) {
c.inSendStatus++
c.mu.Unlock()
c.logf("sendStatus: %s: %v", who, state)
c.logf("[v1] sendStatus: %s: %v", who, state)
var p *Persist
var fin *empty.Message
@@ -700,7 +665,7 @@ func (c *Client) Shutdown() {
// NodePublicKey returns the node public key currently in use. This is
// used exclusively in tests.
func (c *Client) TestOnlyNodePublicKey() wgcfg.Key {
func (c *Client) TestOnlyNodePublicKey() wgkey.Key {
priv := c.direct.GetPersist()
return priv.PrivateNodeKey.Public()
}

View File

@@ -31,7 +31,6 @@ import (
"sync/atomic"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/crypto/nacl/box"
"golang.org/x/oauth2"
"inet.af/netaddr"
@@ -44,7 +43,10 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
"tailscale.com/types/wgkey"
"tailscale.com/util/systemd"
"tailscale.com/version"
"tailscale.com/wgengine/filter"
)
type Persist struct {
@@ -59,10 +61,10 @@ type Persist struct {
// needed. This field should be considered read-only from GUI
// frontends. The real value should not be written back in
// this field, lest the frontend persist it to disk.
LegacyFrontendPrivateMachineKey wgcfg.PrivateKey `json:"PrivateMachineKey"`
LegacyFrontendPrivateMachineKey wgkey.Private `json:"PrivateMachineKey"`
PrivateNodeKey wgcfg.PrivateKey
OldPrivateNodeKey wgcfg.PrivateKey // needed to request key rotation
PrivateNodeKey wgkey.Private
OldPrivateNodeKey wgkey.Private // needed to request key rotation
Provider string
LoginName string
}
@@ -83,7 +85,7 @@ func (p *Persist) Equals(p2 *Persist) bool {
}
func (p *Persist) Pretty() string {
var mk, ok, nk wgcfg.Key
var mk, ok, nk wgkey.Key
if !p.LegacyFrontendPrivateMachineKey.IsZero() {
mk = p.LegacyFrontendPrivateMachineKey.Public()
}
@@ -93,7 +95,7 @@ func (p *Persist) Pretty() string {
if !p.PrivateNodeKey.IsZero() {
nk = p.PrivateNodeKey.Public()
}
ss := func(k wgcfg.Key) string {
ss := func(k wgkey.Key) string {
if k.IsZero() {
return ""
}
@@ -113,14 +115,14 @@ type Direct struct {
keepAlive bool
logf logger.Logf
discoPubKey tailcfg.DiscoKey
machinePrivKey wgcfg.PrivateKey
machinePrivKey wgkey.Private
debugFlags []string
mu sync.Mutex // mutex guards the following fields
serverKey wgcfg.Key
serverKey wgkey.Key
persist Persist
authKey string
tryingNewKey wgcfg.PrivateKey
tryingNewKey wgkey.Private
expiry *time.Time
// hostinfo is mutated in-place while mu is held.
hostinfo *tailcfg.Hostinfo // always non-nil
@@ -131,7 +133,7 @@ type Direct struct {
type Options struct {
Persist Persist // initial persistent data
MachinePrivateKey wgcfg.PrivateKey // the machine key to use
MachinePrivateKey wgkey.Private // the machine key to use
ServerURL string // URL of the tailcontrol server
AuthKey string // optional node auth key for auto registration
TimeNow func() time.Time // time.Now implementation used by Client
@@ -308,6 +310,7 @@ func (c *Direct) doLoginOrRegen(ctx context.Context, t *oauth2.Token, flags Logi
if mustregen {
_, url, err = c.doLogin(ctx, t, flags, true, url)
}
return url, err
}
@@ -328,6 +331,7 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
if expired {
c.logf("Old key expired -> regen=true")
systemd.Status("key expired; run 'tailscale up' to authenticate")
regen = true
}
if (flags & LoginInteractive) != 0 {
@@ -336,7 +340,7 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
}
c.logf("doLogin(regen=%v, hasUrl=%v)", regen, url != "")
if serverKey == (wgcfg.Key{}) {
if serverKey.IsZero() {
var err error
serverKey, err = loadServerKey(ctx, c.httpc, c.serverURL)
if err != nil {
@@ -348,12 +352,12 @@ func (c *Direct) doLogin(ctx context.Context, t *oauth2.Token, flags LoginFlags,
c.mu.Unlock()
}
var oldNodeKey wgcfg.Key
var oldNodeKey wgkey.Key
if url != "" {
} else if regen || persist.PrivateNodeKey.IsZero() {
c.logf("Generating a new nodekey.")
persist.OldPrivateNodeKey = persist.PrivateNodeKey
key, err := wgcfg.NewPrivateKey()
key, err := wgkey.NewPrivate()
if err != nil {
c.logf("login keygen: %v", err)
return regen, url, err
@@ -533,15 +537,17 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
}
allowStream := maxPolls != 1
c.logf("PollNetMap: stream=%v :%v ep=%v", allowStream, localPort, ep)
c.logf("[v1] PollNetMap: stream=%v :%v ep=%v", allowStream, localPort, ep)
vlogf := logger.Discard
if Debug.NetMap {
// TODO(bradfitz): update this to use "[v2]" prefix perhaps? but we don't
// want to upload it always.
vlogf = c.logf
}
request := tailcfg.MapRequest{
Version: 5,
Version: 8,
KeepAlive: c.keepAlive,
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
DiscoKey: c.discoPubKey,
@@ -636,6 +642,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
var lastDERPMap *tailcfg.DERPMap
var lastUserProfile = map[tailcfg.UserID]tailcfg.UserProfile{}
var lastParsedPacketFilter []filter.Match
// If allowStream, then the server will use an HTTP long poll to
// return incremental results. There is always one response right
@@ -676,7 +683,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
case timeoutReset <- struct{}{}:
vlogf("netmap: sent timer reset")
case <-ctx.Done():
c.logf("netmap: not resetting timer; context done: %v", ctx.Err())
c.logf("[v1] netmap: not resetting timer; context done: %v", ctx.Err())
return ctx.Err()
}
if resp.KeepAlive {
@@ -713,6 +720,10 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
resp.Peers = filtered
}
if pf := resp.PacketFilter; pf != nil {
lastParsedPacketFilter = c.parsePacketFilter(pf)
}
nm := &NetworkMap{
NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()),
PrivateKey: persist.PrivateNodeKey,
@@ -727,7 +738,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
Domain: resp.Domain,
DNS: resp.DNSConfig,
Hostinfo: resp.Node.Hostinfo,
PacketFilter: c.parsePacketFilter(resp.PacketFilter),
PacketFilter: lastParsedPacketFilter,
DERPMap: lastDERPMap,
Debug: resp.Debug,
}
@@ -750,7 +761,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
nm.MachineStatus = tailcfg.MachineUnauthorized
}
if len(resp.DNS) > 0 {
nm.DNS.Nameservers = wgIPToNetaddr(resp.DNS)
nm.DNS.Nameservers = resp.DNS
}
if len(resp.SearchPaths) > 0 {
nm.DNS.Domains = resp.SearchPaths
@@ -767,7 +778,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
now := c.timeNow()
if now.Sub(c.lastPrintMap) >= 5*time.Minute {
c.lastPrintMap = now
c.logf("new network map[%d]:\n%s", i, nm.Concise())
c.logf("[v1] new network map[%d]:\n%s", i, nm.Concise())
}
c.mu.Lock()
@@ -782,7 +793,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM
return nil
}
func decode(res *http.Response, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) error {
func decode(res *http.Response, v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) error {
defer res.Body.Close()
msg, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
if err != nil {
@@ -837,7 +848,7 @@ func (c *Direct) decodeMsg(msg []byte, v interface{}) error {
}
func decodeMsg(msg []byte, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) error {
func decodeMsg(msg []byte, v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) error {
decrypted, err := decryptMsg(msg, serverKey, mkey)
if err != nil {
return err
@@ -851,7 +862,7 @@ func decodeMsg(msg []byte, v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.Priv
return nil
}
func decryptMsg(msg []byte, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byte, error) {
func decryptMsg(msg []byte, serverKey *wgkey.Key, mkey *wgkey.Private) ([]byte, error) {
var nonce [24]byte
if len(msg) < len(nonce)+1 {
return nil, fmt.Errorf("response missing nonce, len=%d", len(msg))
@@ -867,7 +878,7 @@ func decryptMsg(msg []byte, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byt
return decrypted, nil
}
func encode(v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byte, error) {
func encode(v interface{}, serverKey *wgkey.Key, mkey *wgkey.Private) ([]byte, error) {
b, err := json.Marshal(v)
if err != nil {
return nil, err
@@ -886,42 +897,31 @@ func encode(v interface{}, serverKey *wgcfg.Key, mkey *wgcfg.PrivateKey) ([]byte
return msg, nil
}
func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (wgcfg.Key, error) {
func loadServerKey(ctx context.Context, httpc *http.Client, serverURL string) (wgkey.Key, error) {
req, err := http.NewRequest("GET", serverURL+"/key", nil)
if err != nil {
return wgcfg.Key{}, fmt.Errorf("create control key request: %v", err)
return wgkey.Key{}, fmt.Errorf("create control key request: %v", err)
}
req = req.WithContext(ctx)
res, err := httpc.Do(req)
if err != nil {
return wgcfg.Key{}, fmt.Errorf("fetch control key: %v", err)
return wgkey.Key{}, fmt.Errorf("fetch control key: %v", err)
}
defer res.Body.Close()
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<16))
if err != nil {
return wgcfg.Key{}, fmt.Errorf("fetch control key response: %v", err)
return wgkey.Key{}, fmt.Errorf("fetch control key response: %v", err)
}
if res.StatusCode != 200 {
return wgcfg.Key{}, fmt.Errorf("fetch control key: %d: %s", res.StatusCode, string(b))
return wgkey.Key{}, fmt.Errorf("fetch control key: %d: %s", res.StatusCode, string(b))
}
key, err := wgcfg.ParseHexKey(string(b))
key, err := wgkey.ParseHex(string(b))
if err != nil {
return wgcfg.Key{}, fmt.Errorf("fetch control key: %v", err)
return wgkey.Key{}, fmt.Errorf("fetch control key: %v", err)
}
return key, nil
}
func wgIPToNetaddr(ips []wgcfg.IP) (ret []netaddr.IP) {
for _, ip := range ips {
nip, ok := netaddr.FromStdIP(ip.IP())
if !ok {
panic(fmt.Sprintf("conversion of %s from wgcfg to netaddr IP failed", ip))
}
ret = append(ret, nip.Unmap())
}
return ret
}
// Debug contains temporary internal-only debug knobs.
// They're unexported to not draw attention to them.
var Debug = initDebug()
@@ -1080,11 +1080,12 @@ func TrimWGConfig() opt.Bool {
// and will definitely not work for the routes provided.
//
// It should not return false positives.
func ipForwardingBroken(routes []wgcfg.CIDR) bool {
func ipForwardingBroken(routes []netaddr.IPPrefix) bool {
if len(routes) == 0 {
// Nothing to route, so no need to warn.
return false
}
if runtime.GOOS != "linux" {
// We only do subnet routing on Linux for now.
// It might work on darwin/macOS when building from source, so
@@ -1092,17 +1093,57 @@ func ipForwardingBroken(routes []wgcfg.CIDR) bool {
// already in the admin panel.
return false
}
out, err := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward")
if err != nil {
// Try another way.
out, err = exec.Command("sysctl", "-n", "net.ipv4.ip_forward").Output()
v4Routes, v6Routes := false, false
for _, r := range routes {
if r.IP.Is4() {
v4Routes = true
} else {
v6Routes = true
}
}
if err != nil {
// Oh well, we tried. This is just for debugging.
// We don't want false positives.
// TODO: maybe we want a different warning for inability to check?
return false
if v4Routes {
out, err := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward")
if err != nil {
// Try another way.
out, err = exec.Command("sysctl", "-n", "net.ipv4.ip_forward").Output()
}
if err != nil {
// Oh well, we tried. This is just for debugging.
// We don't want false positives.
// TODO: maybe we want a different warning for inability to check?
return false
}
if strings.TrimSpace(string(out)) == "0" {
return true
}
}
return strings.TrimSpace(string(out)) == "0"
// TODO: also check IPv6 if 'routes' contains any IPv6 routes
if v6Routes {
// Note: you might be wondering why we check only the state of
// conf.all.forwarding, rather than per-interface forwarding
// configuration. According to kernel documentation, it seems
// that to actually forward packets, you need to enable
// forwarding globally, and the per-interface forwarding
// setting only alters other things such as how router
// advertisements are handled. The kernel itself warns that
// enabling forwarding per-interface and not globally will
// probably not work, so I feel okay calling those configs
// broken until we have proof otherwise.
out, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/forwarding")
if err != nil {
out, err = exec.Command("sysctl", "-n", "net.ipv6.conf.all.forwarding").Output()
}
if err != nil {
// Oh well, we tried. This is just for debugging.
// We don't want false positives.
// TODO: maybe we want a different warning for inability to check?
return false
}
if strings.TrimSpace(string(out)) == "0" {
return true
}
}
return false
}

View File

@@ -73,7 +73,7 @@ func osVersionLinux() string {
return fmt.Sprintf("%s%s", bytes.TrimSpace(cr), attr)
}
fallthrough
case "fedora", "rhel", "alpine":
case "fedora", "rhel", "alpine", "nixos":
// Their PRETTY_NAME is fine as-is for all versions I tested.
fallthrough
default:

View File

@@ -14,8 +14,10 @@ import (
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/wgkey"
"tailscale.com/wgengine/filter"
)
@@ -23,11 +25,11 @@ type NetworkMap struct {
// Core networking
NodeKey tailcfg.NodeKey
PrivateKey wgcfg.PrivateKey
PrivateKey wgkey.Private
Expiry time.Time
// Name is the DNS name assigned to this node.
Name string
Addresses []wgcfg.CIDR
Addresses []netaddr.IPPrefix
LocalPort uint16 // used for debugging
MachineStatus tailcfg.MachineStatus
MachineKey tailcfg.MachineKey
@@ -240,7 +242,7 @@ const EndpointDiscoSuffix = ".disco.tailscale:12345"
func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Config, error) {
cfg := &wgcfg.Config{
Name: "tailscale",
PrivateKey: nm.PrivateKey,
PrivateKey: wgcfg.PrivateKey(nm.PrivateKey),
Addresses: nm.Addresses,
ListenPort: nm.LocalPort,
Peers: make([]wgcfg.Peer, 0, len(nm.Peers)),
@@ -278,14 +280,14 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi
}
}
for _, allowedIP := range peer.AllowedIPs {
if allowedIP.Mask == 0 {
if allowedIP.Bits == 0 {
if (flags & AllowDefaultRoute) == 0 {
logf("wgcfg: %v skipping default route", peer.Key.ShortString())
logf("[v1] wgcfg: %v skipping default route", peer.Key.ShortString())
continue
}
} else if cidrIsSubnet(peer, allowedIP) {
if (flags & AllowSubnetRoutes) == 0 {
logf("wgcfg: %v skipping subnet route", peer.Key.ShortString())
logf("[v1] wgcfg: %v skipping subnet route", peer.Key.ShortString())
continue
}
}
@@ -298,17 +300,11 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi
// cidrIsSubnet reports whether cidr is a non-default-route subnet
// exported by node that is not one of its own self addresses.
func cidrIsSubnet(node *tailcfg.Node, cidr wgcfg.CIDR) bool {
if cidr.Mask == 0 {
func cidrIsSubnet(node *tailcfg.Node, cidr netaddr.IPPrefix) bool {
if cidr.Bits == 0 {
return false
}
if cidr.Mask < 32 {
// Fast path for IPv4, to avoid loop below.
//
// TODO: if cidr.IP is an IPv6 address, we could do "< 128"
// to avoid the range over node.Addresses. Or we could
// just remove this fast path and unconditionally do the range
// loop.
if !cidr.IsSingleIP() {
return true
}
for _, selfCIDR := range node.Addresses {
@@ -351,7 +347,7 @@ func eqStringsIgnoreNil(a, b []string) bool {
// eqCIDRsIgnoreNil reports whether a and b have the same length and
// contents, but ignore whether a or b are nil.
func eqCIDRsIgnoreNil(a, b []wgcfg.CIDR) bool {
func eqCIDRsIgnoreNil(a, b []netaddr.IPPrefix) bool {
if len(a) != len(b) {
return false
}

View File

@@ -6,9 +6,10 @@ package controlclient
import (
"encoding/hex"
"encoding/json"
"testing"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/tailcfg"
)
@@ -250,7 +251,7 @@ func TestConciseDiffFrom(t *testing.T) {
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:41641", "1.1.1.1:41641"},
DiscoKey: testDiscoKey("f00f00f00f"),
AllowedIPs: []wgcfg.CIDR{{IP: wgcfg.IPv4(100, 102, 103, 104), Mask: 32}},
AllowedIPs: []netaddr.IPPrefix{{IP: netaddr.IPv4(100, 102, 103, 104), Bits: 32}},
},
},
},
@@ -263,7 +264,7 @@ func TestConciseDiffFrom(t *testing.T) {
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:41641", "1.1.1.1:41641"},
DiscoKey: testDiscoKey("ba4ba4ba4b"),
AllowedIPs: []wgcfg.CIDR{{IP: wgcfg.IPv4(100, 102, 103, 104), Mask: 32}},
AllowedIPs: []netaddr.IPPrefix{{IP: netaddr.IPv4(100, 102, 103, 104), Bits: 32}},
},
},
},
@@ -282,3 +283,15 @@ func TestConciseDiffFrom(t *testing.T) {
})
}
}
func TestNewHostinfo(t *testing.T) {
hi := NewHostinfo()
if hi == nil {
t.Fatal("no Hostinfo")
}
j, err := json.MarshalIndent(hi, " ", "")
if err != nil {
t.Fatal(err)
}
t.Logf("Got: %s", j)
}

View File

@@ -8,7 +8,7 @@ import (
"reflect"
"testing"
"github.com/tailscale/wireguard-go/wgcfg"
"tailscale.com/types/wgkey"
)
func TestPersistEqual(t *testing.T) {
@@ -18,8 +18,8 @@ func TestPersistEqual(t *testing.T) {
have, persistHandles)
}
newPrivate := func() wgcfg.PrivateKey {
k, err := wgcfg.NewPrivateKey()
newPrivate := func() wgkey.Private {
k, err := wgkey.NewPrivate()
if err != nil {
panic(err)
}

30
go.mod
View File

@@ -1,6 +1,6 @@
module tailscale.com
go 1.14
go 1.15
require (
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5
@@ -13,29 +13,33 @@ require (
github.com/go-ole/go-ole v1.2.4
github.com/godbus/dbus/v5 v5.0.3
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
github.com/google/go-cmp v0.4.0
github.com/google/go-cmp v0.5.4
github.com/goreleaser/nfpm v1.1.10
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391
github.com/klauspost/compress v1.10.10
github.com/kr/pty v1.1.1
github.com/mdlayher/netlink v1.1.0
github.com/mdlayher/netlink v1.2.0
github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a
github.com/miekg/dns v1.1.30
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
github.com/peterbourgon/ff/v2 v2.0.0
github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be
github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
github.com/tailscale/wireguard-go v0.0.0-20201228234719-da0d2727455d
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
go4.org/intern v0.0.0-20201223061701-969c7e87e7cb // indirect
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392
golang.org/x/net v0.0.0-20201216054612-986b41b23924
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8
honnef.co/go/tools v0.0.1-2020.1.4
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98
honnef.co/go/tools v0.1.0
inet.af/netaddr v0.0.0-20201231012616-c5dc91d2a016
rsc.io/goversion v1.2.0
)

117
go.sum
View File

@@ -1,12 +1,8 @@
cloud.google.com/go v0.0.0-20170206221025-ce650573d812/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo=
github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14=
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/aclements/go-gg v0.0.0-20170118225347-6dbb4e4fefb0/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes=
github.com/aclements/go-moremath v0.0.0-20161014184102-0ff62e0875ff/go.mod h1:idZL3yvz4kzx1dsBOAC+oYv6L92P1oFEhUXUB1A/lwQ=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
@@ -27,6 +23,7 @@ github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmeka
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dvyukov/go-fuzz v0.0.0-20201127111758-49e582c6c23d/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
@@ -35,27 +32,24 @@ github.com/go-multierror/multierror v1.0.2 h1:AwsKbEXkmf49ajdFJgcFXqSG0aLo0HEyAE
github.com/go-multierror/multierror v1.0.2/go.mod h1:U7SZR/D9jHgt2nkSj8XcbCWdmVM2igraCHQ3HC1HiKY=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc=
github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg=
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks=
github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A=
github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0 h1:BW6OvS3kpT5UEPbCZ+KyX/OB4Ks9/MNMhWjqPPkZxsE=
github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg=
github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/goreleaser/nfpm v1.1.10 h1:0nwzKUJTcygNxTzVKq2Dh9wpVP1W2biUH6SNKmoxR3w=
github.com/goreleaser/nfpm v1.1.10/go.mod h1:oOcoGRVwvKIODz57NUfiRwFWGfn00NXdgnn6MrYtO5k=
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
@@ -63,6 +57,9 @@ github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4 h1:nwOc1YaOrYJ37sEBrtWZrdqzK22hiJs3GpDmP3sR2Yw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391 h1:Dqu/4JhMV1vpXHDjzQCuDCEsjNi0xfuSmQlMOyqayKA=
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
@@ -76,13 +73,17 @@ github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1/go.mod h1:E23UucZGqpuUANJ
github.com/lxn/walk v0.0.0-20201110160827-18ea5e372cdb/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA=
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/mattn/go-sqlite3 v0.0.0-20161215041557-2d44decb4941/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkfg=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.2.0 h1:zPolhRjfuabdf8ofZsl56eoU+92cvSlAn13lw4veCZ0=
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a h1:wMv2mvcHRH4jqIxaVL5t6gSq1hjPiaWH7TOcA0Z+uNo=
github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a/go.mod h1:HtjVsQfsrBm1GDcDTUFn4ZXhftxTwO/hxrvEiRc61U4=
github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo=
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
@@ -106,14 +107,15 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be h1:ZKe3kVGbu/goUVxXcaCPbQ4b0STQ5NsCpG90CG6mw/c=
github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be/go.mod h1:jissDaJNHiyV2tFdr3QyNEfsZrax/i2yQiSO+CljThI=
github.com/tailscale/wireguard-go v0.0.0-20200921221757-11a958a67bdd h1:yEWpro9EdxGgkt24NInVnONIJxRLURH5c37Ki5+06EE=
github.com/tailscale/wireguard-go v0.0.0-20200921221757-11a958a67bdd/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4=
github.com/tailscale/wireguard-go v0.0.0-20201008164108-2c83f43a9859 h1:Z7bXXCYRg/8sjSyKTk0V8Yso/gQjNvPb10DBemKuz+A=
github.com/tailscale/wireguard-go v0.0.0-20201008164108-2c83f43a9859/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4=
github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f h1:KMx58dbn2YCutzOvjNHgmvbwQH7nGE8H+J42Nenjl/M=
github.com/tailscale/wireguard-go v0.0.0-20201021041318-a6168fd06b3f/go.mod h1:WXq+IkSOJGIgfF1XW+4z4oW+LX/TXzU9DcKlT5EZLi4=
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBWY6aGilF+IRlQIdmhzLrsEmF6JgN+Ryw=
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/tailscale/wireguard-go v0.0.0-20201220011020-db78fad0bebf h1:HuBwLWbDNIh/G72KSImSEx+dnd7FPGFI1e60LMJtLjU=
github.com/tailscale/wireguard-go v0.0.0-20201220011020-db78fad0bebf/go.mod h1:9PbAnF5CAklkURoO0uQhm+YUjDmm9T9oCyTGlCHuTPQ=
github.com/tailscale/wireguard-go v0.0.0-20201228234719-da0d2727455d h1:ha3qx0YBsEYM1VpLoAxVyLsz74H2a/Kv/id+2Bo/WLU=
github.com/tailscale/wireguard-go v0.0.0-20201228234719-da0d2727455d/go.mod h1:FEGDKc5yHNWtTS5ugWnHMNF0d9LlaHv/zQwOrVogo2U=
github.com/tailscale/wireguard-go v0.0.20201119-0.20201228205120-066446d1733a h1:RUJeuZlAm1DT6Mhk9UTsaHrDeDZhPrbKfNsaEtKF6+0=
github.com/tailscale/wireguard-go v0.0.20201119-0.20201228205120-066446d1733a/go.mod h1:UIAx57STfAZOrNVj8QGP2zG3ovWPMTD4DDubFHqMlYI=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
@@ -122,27 +124,36 @@ github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go4.org/mem v0.0.0-20200706164138-185c595c3ecc h1:paujszgN6SpsO/UsXC7xax3gQAKz/XQKCYZLQdU34Tw=
go4.org/intern v0.0.0-20201223054237-ef8cbcb8edd7 h1:yeDrXaQ3VRXbTN7lHj70DxW4LdPow83MVwPPRjpP70U=
go4.org/intern v0.0.0-20201223054237-ef8cbcb8edd7/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/intern v0.0.0-20201223061701-969c7e87e7cb h1:yuqO0E4bHRsTPUocDpRKXfLE40lwWplVxENQ2WOV7Gc=
go4.org/intern v0.0.0-20201223061701-969c7e87e7cb/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/mem v0.0.0-20200706164138-185c595c3ecc/go.mod h1:NEYvpHWemiG/E5UWfaN5QAIGZeT1sa0Z2UNk6oeMb/k=
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 h1:vSug/WNOi2+4jrKdivxayTN/zd8EA1UrStjpWvvo1jk=
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e h1:ExUmGi0ZsQmiVo9giDQqXkr7vreeXPMkOGIusfsfbzI=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -150,26 +161,27 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20170207211851-4464e7848382/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY=
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20200918155509-d949658356f9 h1:yVBHF5pcQLKR9B+y+dOJ6y68nqJBDWaZ9DhB1Ohg0qE=
golang.org/x/perf v0.0.0-20200918155509-d949658356f9/go.mod h1:FrqOtQDO3iMDVUtw5nNTDFpR1HUCGh00M3kj2wiSzLQ=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -178,22 +190,29 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d h1:QQrM/CCYEzTs91GZylDCQjGHudbPTxF/1fvXdVh5lMo=
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 h1:+CBz4km/0KPU3RGTwARGh/noP3bEwtHcq+0YcBQM2JQ=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b h1:a0ErnNnPKmhDyIXQvdZr+Lq8dc8xpMeqkF8y5PgQU4Q=
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -203,38 +222,46 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d h1:vWQvJ/Z0Lu+9/8oQ/pAYXNzbc7CMnBl+tULGVHOy3oE=
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 h1:1Bs6RVeBFtLZ8Yi1Hk07DiOqzvwLD/4hln4iahvFlag=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.20200321-0.20200715051853-507f148e1c42 h1:SrR1hmxGKKarHEEDvaHxatwnqE3uT+7jvMcin6SHOkw=
golang.zx2c4.com/wireguard v0.0.20200321-0.20200715051853-507f148e1c42/go.mod h1:GJvYs5O24/ASlwPiRklVnjMx2xQzrOic0DuU6GvYJL4=
golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9 h1:qowcZ56hhpeoESmWzI4Exhx4Y78TpCyXUJur4/c0CoE=
golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9/go.mod h1:LMeNfjlcPZTrBC1juwgbQyA4Zy2XVcsrdO/fIJxwyuA=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201004085714-dd60d0447f81 h1:cT2oWlz8v9g7bjFZclT362akxJJfGv9d7ccKu6GQUbA=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201004085714-dd60d0447f81/go.mod h1:GaK5zcgr5XE98WaRzIDilumDBp5/yP8j2kG/LCDnvAM=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8 h1:nlXPqGA98n+qcq1pwZ28KjM5EsFQvamKS00A+VUeVjs=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8/go.mod h1:psva4yDnAHLuh7lUzOK7J7bLYxNFfo0iKWz+mi9gzkA=
google.golang.org/api v0.0.0-20170206182103-3d017632ea10/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/grpc v0.0.0-20170208002647-2a6bf6142e96/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98 h1:bWyWDZP0l6VnQ1TDKf6yNwuiEDV6Q3q1Mv34m+lzT1I=
honnef.co/go/tools v0.1.0 h1:AWNL1W1i7f0wNZ8VwOKNJ0sliKvOF/adn0EHenfUh+c=
honnef.co/go/tools v0.1.0/go.mod h1:XtegFAyX/PfluP4921rXU5IkjkqBCDnUq4W8VCIoKvM=
inet.af/netaddr v0.0.0-20200810144936-56928fe48a98/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww=
inet.af/netaddr v0.0.0-20201218162718-658fec415e52/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww=
inet.af/netaddr v0.0.0-20201223185330-97d366981fac h1:aqMW8vft7VmOIhtQhsTWhAuZzOBGYBv+Otyvwj+VGSU=
inet.af/netaddr v0.0.0-20201223185330-97d366981fac/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o=
inet.af/netaddr v0.0.0-20201224214825-a55841caa437 h1:Li2QBwaT/hU3wE7GdyoqaX+TzIlI+V1zs/CuWrjX8e4=
inet.af/netaddr v0.0.0-20201224214825-a55841caa437/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o=
inet.af/netaddr v0.0.0-20201226233944-2d1876c01610 h1:9Nnw3NS9SL4SlFtBWSdv7onMbdY+B8nflRNZvhgxuMY=
inet.af/netaddr v0.0.0-20201226233944-2d1876c01610/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o=
inet.af/netaddr v0.0.0-20201228234250-33d0a924ebbf h1:0eHZ8v6j5wIiOVyoYPd70ueZ/RPEQtRlzi60uneDbRU=
inet.af/netaddr v0.0.0-20201228234250-33d0a924ebbf/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o=
inet.af/netaddr v0.0.0-20201231012616-c5dc91d2a016 h1:CEeeAJW60aRKE6gGJC5krs2xC/uM2l8SasvgeDXFN5Q=
inet.af/netaddr v0.0.0-20201231012616-c5dc91d2a016/go.mod h1:lbePDLSB5c45kkUmF7ETNE5X9z/yuQvWJIv1hhb5rFI=
rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=
rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
tailscale.com v1.2.10/go.mod h1:JEJiCce3MHtPCTdX2ahLc4tcnxZ7b5etish1Yt0B6+w=

View File

@@ -37,7 +37,7 @@ func getVal() []interface{} {
return []interface{}{
&wgcfg.Config{
Name: "foo",
Addresses: []wgcfg.CIDR{{Mask: 5, IP: wgcfg.IP{Addr: [16]byte{3: 3}}}},
Addresses: []netaddr.IPPrefix{{Bits: 5, IP: netaddr.IPFrom16([16]byte{3: 3})}},
ListenPort: 5,
Peers: []wgcfg.Peer{
{

View File

@@ -8,8 +8,8 @@ import (
"sync"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"golang.org/x/oauth2"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/types/logger"
)
@@ -118,7 +118,7 @@ func (h *Handle) EngineStatus() EngineStatus {
return h.engineStatusCache
}
func (h *Handle) LocalAddrs() []wgcfg.CIDR {
func (h *Handle) LocalAddrs() []netaddr.IPPrefix {
h.mu.Lock()
defer h.mu.Unlock()
@@ -126,7 +126,7 @@ func (h *Handle) LocalAddrs() []wgcfg.CIDR {
if nm != nil {
return nm.Addresses
}
return []wgcfg.CIDR{}
return []netaddr.IPPrefix{}
}
func (h *Handle) NetMap() *controlclient.NetworkMap {

View File

@@ -34,6 +34,7 @@ import (
"tailscale.com/smallzstd"
"tailscale.com/types/logger"
"tailscale.com/util/pidowner"
"tailscale.com/util/systemd"
"tailscale.com/version"
"tailscale.com/wgengine"
)
@@ -265,12 +266,14 @@ func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
}
defer s.removeAndCloseConn(c)
logf("incoming control connection")
logf("[v1] incoming control connection")
for ctx.Err() == nil {
msg, err := ipn.ReadMsg(br)
if err != nil {
if ctx.Err() == nil {
if errors.Is(err, io.EOF) {
logf("[v1] ReadMsg: %v", err)
} else if ctx.Err() == nil {
logf("ReadMsg: %v", err)
}
return
@@ -589,6 +592,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
})
}
systemd.Ready()
for i := 1; ctx.Err() == nil; i++ {
var c net.Conn
var err error

View File

@@ -64,6 +64,12 @@ type PeerStatus struct {
LastHandshake time.Time // with local wireguard
KeepAlive bool
// ShareeNode indicates this node exists in the netmap because
// it's owned by a shared-to user and that node might connect
// to us. These nodes should be hidden by "tailscale status"
// etc by default.
ShareeNode bool `json:",omitempty"`
// InNetworkMap means that this peer was seen in our latest network map.
// In theory, all of InNetworkMap and InMagicSock and InEngine should all be true.
InNetworkMap bool
@@ -218,6 +224,9 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
if st.KeepAlive {
e.KeepAlive = true
}
if st.ShareeNode {
e.ShareeNode = true
}
}
type StatusUpdater interface {

View File

@@ -29,6 +29,8 @@ import (
"tailscale.com/types/empty"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/wgkey"
"tailscale.com/util/systemd"
"tailscale.com/version"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
@@ -82,7 +84,7 @@ type LocalBackend struct {
userID string // current controlling user ID (for Windows, primarily)
prefs *Prefs
inServerMode bool
machinePrivKey wgcfg.PrivateKey
machinePrivKey wgkey.Private
state State
// hostinfo is mutated in-place while mu is held.
hostinfo *tailcfg.Hostinfo
@@ -208,8 +210,14 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
lastSeen = *p.LastSeen
}
var tailAddr string
if len(p.Addresses) > 0 {
tailAddr = strings.TrimSuffix(p.Addresses[0].String(), "/32")
for _, addr := range p.Addresses {
// The peer struct currently only allows a single
// Tailscale IP address. For compatibility with the
// old display, make sure it's the IPv4 address.
if addr.IP.Is4() && addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.IP) {
tailAddr = addr.IP.String()
break
}
}
sb.AddPeer(key.Public(p.Key), &ipnstate.PeerStatus{
InNetworkMap: true,
@@ -221,6 +229,7 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
KeepAlive: p.KeepAlive,
Created: p.Created,
LastSeen: lastSeen,
ShareeNode: p.Hostinfo.ShareeNode,
})
}
}
@@ -243,7 +252,11 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// The following do not depend on any data for which we need to lock b.
if st.Err != "" {
// TODO(crawshaw): display in the UI.
b.logf("Received error: %v", st.Err)
if st.Err == "EOF" {
b.logf("[v1] Received error: EOF")
} else {
b.logf("Received error: %v", st.Err)
}
return
}
if st.LoginFinished != nil {
@@ -307,7 +320,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
if netMap != nil {
diff := st.NetMap.ConciseDiffFrom(netMap)
if strings.TrimSpace(diff) == "" {
b.logf("netmap diff: (none)")
b.logf("[v1] netmap diff: (none)")
} else {
b.logf("netmap diff:\n%v", diff)
}
@@ -522,9 +535,9 @@ func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *Pre
// quite hard to debug, so save yourself the trouble.
var (
haveNetmap = netMap != nil
addrs []wgcfg.CIDR
addrs []netaddr.IPPrefix
packetFilter []filter.Match
advRoutes []wgcfg.CIDR
advRoutes []netaddr.IPPrefix
shieldsUp = prefs == nil || prefs.ShieldsUp // Be conservative when not ready
)
if haveNetmap {
@@ -546,7 +559,7 @@ func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *Pre
return
}
localNets := wgCIDRsToNetaddr(netMap.Addresses, advRoutes)
localNets := unmapIPPrefixes(netMap.Addresses, advRoutes)
if shieldsUp {
b.logf("netmap packet filter: (shields up)")
@@ -560,7 +573,7 @@ func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *Pre
// dnsCIDRsEqual determines whether two CIDR lists are equal
// for DNS map construction purposes (that is, only the first entry counts).
func dnsCIDRsEqual(newAddr, oldAddr []wgcfg.CIDR) bool {
func dnsCIDRsEqual(newAddr, oldAddr []netaddr.IPPrefix) bool {
if len(newAddr) != len(oldAddr) {
return false
}
@@ -614,11 +627,11 @@ func (b *LocalBackend) updateDNSMap(netMap *controlclient.NetworkMap) {
}
nameToIP := make(map[string]netaddr.IP)
set := func(name string, addrs []wgcfg.CIDR) {
set := func(name string, addrs []netaddr.IPPrefix) {
if len(addrs) == 0 || name == "" {
return
}
nameToIP[name] = netaddr.IPFrom16(addrs[0].IP.Addr)
nameToIP[name] = addrs[0].IP
}
for _, peer := range netMap.Peers {
@@ -725,7 +738,7 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
return nil
}
var legacyMachineKey wgcfg.PrivateKey
var legacyMachineKey wgkey.Private
if b.prefs.Persist != nil {
legacyMachineKey = b.prefs.Persist.LegacyFrontendPrivateMachineKey
}
@@ -760,7 +773,7 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) {
} else {
b.logf("generating new machine key")
var err error
b.machinePrivKey, err = wgcfg.NewPrivateKey()
b.machinePrivKey, err = wgkey.NewPrivate()
if err != nil {
return fmt.Errorf("initializing new machine key: %w", err)
}
@@ -995,8 +1008,8 @@ func (b *LocalBackend) parseWgStatusLocked(s *wgengine.Status) (ret EngineStatus
// [GRINDER STATS LINES] - please don't remove (used for log parsing)
if peerStats.Len() > 0 {
b.keyLogf("peer keys: %s", strings.TrimSpace(peerKeys.String()))
b.statsLogf("v%v peers: %v", version.Long, strings.TrimSpace(peerStats.String()))
b.keyLogf("[v1] peer keys: %s", strings.TrimSpace(peerKeys.String()))
b.statsLogf("[v1] v%v peers: %v", version.Long, strings.TrimSpace(peerStats.String()))
}
return ret
}
@@ -1052,7 +1065,7 @@ func (b *LocalBackend) SetPrefs(newp *Prefs) {
oldHi := b.hostinfo
newHi := oldHi.Clone()
newHi.RoutableIPs = append([]wgcfg.CIDR(nil), b.prefs.AdvertiseRoutes...)
newHi.RoutableIPs = append([]netaddr.IPPrefix(nil), b.prefs.AdvertiseRoutes...)
applyPrefsToHostinfo(newHi, newp)
b.hostinfo = newHi
hostInfoChanged := !oldHi.Equal(newHi)
@@ -1219,7 +1232,7 @@ func (b *LocalBackend) authReconfig() {
if err == wgengine.ErrNoChanges {
return
}
b.logf("authReconfig: ra=%v dns=%v 0x%02x: %v", uc.RouteAll, uc.CorpDNS, flags, err)
b.logf("[v1] authReconfig: ra=%v dns=%v 0x%02x: %v", uc.RouteAll, uc.CorpDNS, flags, err)
}
// domainsForProxying produces a list of search domains for proxied DNS.
@@ -1252,23 +1265,15 @@ func domainsForProxying(nm *controlclient.NetworkMap) []string {
// routerConfig produces a router.Config from a wireguard config and IPN prefs.
func routerConfig(cfg *wgcfg.Config, prefs *Prefs) *router.Config {
var addrs []wgcfg.CIDR
for _, addr := range cfg.Addresses {
addrs = append(addrs, wgcfg.CIDR{
IP: addr.IP,
Mask: 32,
})
}
rs := &router.Config{
LocalAddrs: wgCIDRsToNetaddr(addrs),
SubnetRoutes: wgCIDRsToNetaddr(prefs.AdvertiseRoutes),
LocalAddrs: unmapIPPrefixes(cfg.Addresses),
SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes),
SNATSubnetRoutes: !prefs.NoSNAT,
NetfilterMode: prefs.NetfilterMode,
}
for _, peer := range cfg.Peers {
rs.Routes = append(rs.Routes, wgCIDRsToNetaddr(peer.AllowedIPs)...)
rs.Routes = append(rs.Routes, unmapIPPrefixes(peer.AllowedIPs)...)
}
rs.Routes = append(rs.Routes, netaddr.IPPrefix{
@@ -1279,15 +1284,10 @@ func routerConfig(cfg *wgcfg.Config, prefs *Prefs) *router.Config {
return rs
}
func wgCIDRsToNetaddr(cidrLists ...[]wgcfg.CIDR) (ret []netaddr.IPPrefix) {
for _, cidrs := range cidrLists {
for _, cidr := range cidrs {
ncidr, ok := netaddr.FromStdIPNet(cidr.IPNet())
if !ok {
panic(fmt.Sprintf("conversion of %s from wgcfg to netaddr IPNet failed", cidr))
}
ncidr.IP = ncidr.IP.Unmap()
ret = append(ret, ncidr)
func unmapIPPrefixes(ippsList ...[]netaddr.IPPrefix) (ret []netaddr.IPPrefix) {
for _, ipps := range ippsList {
for _, ipp := range ipps {
ret = append(ret, netaddr.IPPrefix{IP: ipp.IP.Unmap(), Bits: ipp.Bits})
}
}
return ret
@@ -1321,6 +1321,8 @@ func (b *LocalBackend) enterState(newState State) {
notify := b.notify
bc := b.c
networkUp := b.prevIfState.AnyInterfaceUp()
activeLogin := b.activeLogin
authURL := b.authURL
b.mu.Unlock()
if state == newState {
@@ -1338,6 +1340,7 @@ func (b *LocalBackend) enterState(newState State) {
switch newState {
case NeedsLogin:
systemd.Status("Needs login: %s", authURL)
b.blockEngineUpdates(true)
fallthrough
case Stopped:
@@ -1345,12 +1348,20 @@ func (b *LocalBackend) enterState(newState State) {
if err != nil {
b.logf("Reconfig(down): %v", err)
}
if authURL == "" {
systemd.Status("Stopped; run 'tailscale up' to log in")
}
case Starting, NeedsMachineAuth:
b.authReconfig()
// Needed so that UpdateEndpoints can run
b.e.RequestStatus()
case Running:
break
var addrs []string
for _, addr := range b.netMap.Addresses {
addrs = append(addrs, addr.IP.String())
}
systemd.Status("Connected; %s; %s", activeLogin, strings.Join(addrs, " "))
default:
b.logf("[unexpected] unknown newState %#v", newState)
}
@@ -1549,7 +1560,6 @@ func (b *LocalBackend) TestOnlyPublicKeys() (machineKey tailcfg.MachineKey, node
// 1.0.x. But eventually we want to stop sending the machine key to
// clients. We can't do that until 1.0.x is no longer supported.
func temporarilySetMachineKeyInPersist() bool {
//lint:ignore S1008 for comments
switch runtime.GOOS {
case "darwin", "ios", "android":
// iOS, macOS, Android users can't downgrade anyway.

View File

@@ -23,8 +23,8 @@ import (
func TestLocalLogLines(t *testing.T) {
logListen := tstest.NewLogLineTracker(t.Logf, []string{
"SetPrefs: %v",
"peer keys: %s",
"v%v peers: %v",
"[v1] peer keys: %s",
"[v1] v%v peers: %v",
})
logid := func(hex byte) logtail.PublicID {
@@ -71,7 +71,7 @@ func TestLocalLogLines(t *testing.T) {
prefs.Persist = persist
lb.SetPrefs(prefs)
t.Run("after_prefs", testWantRemain("peer keys: %s", "v%v peers: %v"))
t.Run("after_prefs", testWantRemain("[v1] peer keys: %s", "[v1] v%v peers: %v"))
// log peers, peer keys
status := &wgengine.Status{

View File

@@ -5,6 +5,7 @@
package ipn
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
@@ -14,7 +15,7 @@ import (
"runtime"
"strings"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/atomicfile"
"tailscale.com/control/controlclient"
"tailscale.com/wgengine/router"
@@ -99,7 +100,7 @@ type Prefs struct {
// AdvertiseRoutes specifies CIDR prefixes to advertise into the
// Tailscale network as reachable through the current
// node.
AdvertiseRoutes []wgcfg.CIDR
AdvertiseRoutes []netaddr.IPPrefix
// NoSNAT specifies whether to source NAT traffic going to
// destinations in AdvertiseRoutes. The default is to apply source
@@ -205,12 +206,12 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.Persist.Equals(p2.Persist)
}
func compareIPNets(a, b []wgcfg.CIDR) bool {
func compareIPNets(a, b []netaddr.IPPrefix) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !a[i].IP.Equal(b[i].IP) || a[i].Mask != b[i].Mask {
if a[i] != b[i] {
return false
}
}
@@ -276,6 +277,14 @@ func LoadPrefs(filename string) (*Prefs, error) {
if err != nil {
return nil, fmt.Errorf("LoadPrefs open: %w", err) // err includes path
}
if bytes.Contains(data, jsonEscapedZero) {
// Tailscale 1.2.0 - 1.2.8 on Windows had a memory corruption bug
// in the backend process that ended up sending NULL bytes over JSON
// to the frontend which wrote them out to JSON files on disk.
// So if we see one, treat is as corrupt and the user will need
// to log in again. (better than crashing)
return nil, os.ErrNotExist
}
p, err := PrefsFromBytes(data, false)
if err != nil {
return nil, fmt.Errorf("LoadPrefs(%q) decode: %w", filename, err)

View File

@@ -7,7 +7,7 @@
package ipn
import (
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/wgengine/router"
)
@@ -44,7 +44,7 @@ var _PrefsNeedsRegeneration = Prefs(struct {
DeviceModel string
NotepadURLs bool
ForceDaemon bool
AdvertiseRoutes []wgcfg.CIDR
AdvertiseRoutes []netaddr.IPPrefix
NoSNAT bool
NetfilterMode router.NetfilterMode
Persist *controlclient.Persist

View File

@@ -7,14 +7,16 @@ package ipn
import (
"errors"
"fmt"
"io/ioutil"
"os"
"reflect"
"testing"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/control/controlclient"
"tailscale.com/tstest"
"tailscale.com/types/wgkey"
"tailscale.com/wgengine/router"
)
@@ -34,9 +36,9 @@ func TestPrefsEqual(t *testing.T) {
have, prefsHandles)
}
nets := func(strs ...string) (ns []wgcfg.CIDR) {
nets := func(strs ...string) (ns []netaddr.IPPrefix) {
for _, s := range strs {
n, err := wgcfg.ParseCIDR(s)
n, err := netaddr.ParseIPPrefix(s)
if err != nil {
panic(err)
}
@@ -165,12 +167,12 @@ func TestPrefsEqual(t *testing.T) {
{
&Prefs{AdvertiseRoutes: nil},
&Prefs{AdvertiseRoutes: []wgcfg.CIDR{}},
&Prefs{AdvertiseRoutes: []netaddr.IPPrefix{}},
true,
},
{
&Prefs{AdvertiseRoutes: []wgcfg.CIDR{}},
&Prefs{AdvertiseRoutes: []wgcfg.CIDR{}},
&Prefs{AdvertiseRoutes: []netaddr.IPPrefix{}},
&Prefs{AdvertiseRoutes: []netaddr.IPPrefix{}},
true,
},
{
@@ -346,7 +348,7 @@ func TestPrefsPretty(t *testing.T) {
{
Prefs{
Persist: &controlclient.Persist{
PrivateNodeKey: wgcfg.PrivateKey{1: 1},
PrivateNodeKey: wgkey.Private{1: 1},
},
},
"linux",
@@ -371,3 +373,25 @@ func TestLoadPrefsNotExist(t *testing.T) {
}
t.Fatalf("unexpected prefs=%#v, err=%v", p, err)
}
// TestLoadPrefsFileWithZeroInIt verifies that LoadPrefs hanldes corrupted input files.
// See issue #954 for details.
func TestLoadPrefsFileWithZeroInIt(t *testing.T) {
f, err := ioutil.TempFile("", "TestLoadPrefsFileWithZeroInIt")
if err != nil {
t.Fatal(err)
}
path := f.Name()
if _, err := f.Write(jsonEscapedZero); err != nil {
t.Fatal(err)
}
f.Close()
defer os.Remove(path)
p, err := LoadPrefs(path)
if errors.Is(err, os.ErrNotExist) {
// expected.
return
}
t.Fatalf("unexpected prefs=%#v, err=%v", p, err)
}

View File

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

View File

@@ -25,7 +25,7 @@ import (
"strings"
"time"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/term"
"tailscale.com/atomicfile"
"tailscale.com/logtail"
"tailscale.com/logtail/filch"
@@ -49,7 +49,7 @@ type Config struct {
// Policy is a logger and its public ID.
type Policy struct {
// Logtail is the logger.
Logtail logtail.Logger
Logtail *logtail.Logger
// PublicID is the logger's instance identifier.
PublicID logtail.PublicID
}
@@ -311,7 +311,7 @@ func tryFixLogStateLocation(dir, cmdname string) {
// given collection name.
func New(collection string) *Policy {
var lflags int
if terminal.IsTerminal(2) || runtime.GOOS == "windows" {
if term.IsTerminal(2) || runtime.GOOS == "windows" {
lflags = 0
} else {
lflags = log.LstdFlags
@@ -391,7 +391,7 @@ func New(collection string) *Policy {
if filchBuf != nil {
c.Buffer = filchBuf
}
lw := logtail.Log(c, log.Printf)
lw := logtail.NewLogger(c, log.Printf)
log.SetFlags(0) // other logflags are set on console, not here
log.SetOutput(lw)
@@ -413,6 +413,15 @@ func New(collection string) *Policy {
}
}
// SetVerbosityLevel controls the verbosity level that should be
// written to stderr. 0 is the default (not verbose). Levels 1 or higher
// are increasingly verbose.
//
// It should not be changed concurrently with log writes.
func (p *Policy) SetVerbosityLevel(level int) {
p.Logtail.SetVerbosityLevel(level)
}
// Close immediately shuts down the logger.
func (p *Policy) Close() {
ctx, cancel := context.WithCancel(context.Background())

View File

@@ -31,7 +31,7 @@ func main() {
log.Fatalf("logtail: bad -privateid: %v", err)
}
logger := logtail.Log(logtail.Config{
logger := logtail.NewLogger(logtail.Config{
Collection: *collection,
PrivateID: id,
}, log.Printf)

View File

@@ -14,6 +14,7 @@ import (
"io/ioutil"
"net/http"
"os"
"strconv"
"time"
"tailscale.com/logtail/backoff"
@@ -24,34 +25,6 @@ import (
// Config.BaseURL isn't provided.
const DefaultHost = "log.tailscale.io"
type Logger interface {
// Write logs an encoded JSON blob.
//
// If the []byte passed to Write is not an encoded JSON blob,
// then contents is fit into a JSON blob and written.
//
// This is intended as an interface for the stdlib "log" package.
Write([]byte) (int, error)
// Flush uploads all logs to the server.
// It blocks until complete or there is an unrecoverable error.
Flush() error
// Shutdown gracefully shuts down the logger while completing any
// remaining uploads.
//
// It will block, continuing to try and upload unless the passed
// context object interrupts it by being done.
// If the shutdown is interrupted, an error is returned.
Shutdown(context.Context) error
// Close shuts down this logger object, the background log uploader
// process, and any associated goroutines.
//
// DEPRECATED: use Shutdown
Close()
}
type Encoder interface {
EncodeAll(src, dst []byte) []byte
Close() error
@@ -66,6 +39,7 @@ type Config struct {
LowMemory bool // if true, logtail minimizes memory use
TimeNow func() time.Time // if set, subsitutes uses of time.Now
Stderr io.Writer // if set, logs are sent here instead of os.Stderr
StderrLevel int // max verbosity level to write to stderr; 0 means the non-verbose messages only
Buffer Buffer // temp storage, if nil a MemoryBuffer
NewZstdEncoder func() Encoder // if set, used to compress logs for transmission
@@ -74,7 +48,7 @@ type Config struct {
DrainLogs <-chan struct{}
}
func Log(cfg Config, logf tslogger.Logf) Logger {
func NewLogger(cfg Config, logf tslogger.Logf) *Logger {
if cfg.BaseURL == "" {
cfg.BaseURL = "https://" + DefaultHost
}
@@ -94,8 +68,9 @@ func Log(cfg Config, logf tslogger.Logf) Logger {
}
cfg.Buffer = NewMemoryBuffer(pendingSize)
}
l := &logger{
l := &Logger{
stderr: cfg.Stderr,
stderrLevel: cfg.StderrLevel,
httpc: cfg.HTTPC,
url: cfg.BaseURL + "/c/" + cfg.Collection + "/" + cfg.PrivateID.String(),
lowMem: cfg.LowMemory,
@@ -122,8 +97,11 @@ func Log(cfg Config, logf tslogger.Logf) Logger {
return l
}
type logger struct {
// Logger writes logs, splitting them as configured between local
// logging facilities and uploading to a log server.
type Logger struct {
stderr io.Writer
stderrLevel int
httpc *http.Client
url string
lowMem bool
@@ -141,7 +119,22 @@ type logger struct {
shutdownDone chan struct{} // closd when shutdown complete
}
func (l *logger) Shutdown(ctx context.Context) error {
// SetVerbosityLevel controls the verbosity level that should be
// written to stderr. 0 is the default (not verbose). Levels 1 or higher
// are increasingly verbose.
//
// It should not be changed concurrently with log writes.
func (l *Logger) SetVerbosityLevel(level int) {
l.stderrLevel = level
}
// Shutdown gracefully shuts down the logger while completing any
// remaining uploads.
//
// It will block, continuing to try and upload unless the passed
// context object interrupts it by being done.
// If the shutdown is interrupted, an error is returned.
func (l *Logger) Shutdown(ctx context.Context) error {
done := make(chan struct{})
go func() {
select {
@@ -163,7 +156,11 @@ func (l *logger) Shutdown(ctx context.Context) error {
return nil
}
func (l *logger) Close() {
// Close shuts down this logger object, the background log uploader
// process, and any associated goroutines.
//
// Deprecated: use Shutdown
func (l *Logger) Close() {
l.Shutdown(context.Background())
}
@@ -174,7 +171,7 @@ func (l *logger) Close() {
//
// If the caller provides a DrainLogs channel, then unblock-drain-on-Write
// is disabled, and it is up to the caller to trigger unblock the drain.
func (l *logger) drainBlock() (shuttingDown bool) {
func (l *Logger) drainBlock() (shuttingDown bool) {
if l.drainLogs == nil {
select {
case <-l.shutdownStart:
@@ -193,7 +190,7 @@ func (l *logger) drainBlock() (shuttingDown bool) {
// drainPending drains and encodes a batch of logs from the buffer for upload.
// If no logs are available, drainPending blocks until logs are available.
func (l *logger) drainPending() (res []byte) {
func (l *Logger) drainPending() (res []byte) {
buf := new(bytes.Buffer)
entries := 0
@@ -254,13 +251,22 @@ func (l *logger) drainPending() (res []byte) {
}
// This is the goroutine that repeatedly uploads logs in the background.
func (l *logger) uploading(ctx context.Context) {
func (l *Logger) uploading(ctx context.Context) {
defer close(l.shutdownDone)
for {
body := l.drainPending()
if l.zstdEncoder != nil {
body = l.zstdEncoder.EncodeAll(body, nil)
origlen := -1 // sentinel value: uncompressed
// Don't attempt to compress tiny bodies; not worth the CPU cycles.
if l.zstdEncoder != nil && len(body) > 256 {
zbody := l.zstdEncoder.EncodeAll(body, nil)
// Only send it compressed if the bandwidth savings are sufficient.
// Just the extra headers associated with enabling compression
// are 50 bytes by themselves.
if len(body)-len(zbody) > 64 {
origlen = len(body)
body = zbody
}
}
for len(body) > 0 {
@@ -269,7 +275,7 @@ func (l *logger) uploading(ctx context.Context) {
return
default:
}
uploaded, err := l.upload(ctx, body)
uploaded, err := l.upload(ctx, body, origlen)
if err != nil {
fmt.Fprintf(l.stderr, "logtail: upload: %v\n", err)
}
@@ -287,7 +293,10 @@ func (l *logger) uploading(ctx context.Context) {
}
}
func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err error) {
// upload uploads body to the log server.
// origlen indicates the pre-compression body length.
// origlen of -1 indicates that the body is not compressed.
func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded bool, err error) {
req, err := http.NewRequest("POST", l.url, bytes.NewReader(body))
if err != nil {
// I know of no conditions under which this could fail.
@@ -295,8 +304,9 @@ func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err er
// TODO record logs to disk
panic("logtail: cannot build http request: " + err.Error())
}
if l.zstdEncoder != nil {
if origlen != -1 {
req.Header.Add("Content-Encoding", "zstd")
req.Header.Add("Orig-Content-Length", strconv.Itoa(origlen))
}
req.Header["User-Agent"] = nil // not worth writing one; save some bytes
@@ -306,7 +316,7 @@ func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err er
req = req.WithContext(ctx)
compressedNote := "not-compressed"
if l.zstdEncoder != nil {
if origlen != -1 {
compressedNote = "compressed"
}
@@ -333,11 +343,13 @@ func (l *logger) upload(ctx context.Context, body []byte) (uploaded bool, err er
return true, nil
}
func (l *logger) Flush() error {
// Flush uploads all logs to the server.
// It blocks until complete or there is an unrecoverable error.
func (l *Logger) Flush() error {
return nil
}
func (l *logger) send(jsonBlob []byte) (int, error) {
func (l *Logger) send(jsonBlob []byte) (int, error) {
n, err := l.buffer.Write(jsonBlob)
if l.drainLogs == nil {
select {
@@ -350,7 +362,7 @@ func (l *logger) send(jsonBlob []byte) (int, error) {
// TODO: instead of allocating, this should probably just append
// directly into the output log buffer.
func (l *logger) encodeText(buf []byte, skipClientTime bool) []byte {
func (l *Logger) encodeText(buf []byte, skipClientTime bool) []byte {
now := l.timeNow()
// Factor in JSON encoding overhead to try to only do one alloc
@@ -406,7 +418,7 @@ func (l *logger) encodeText(buf []byte, skipClientTime bool) []byte {
return b
}
func (l *logger) encode(buf []byte) []byte {
func (l *Logger) encode(buf []byte) []byte {
if buf[0] != '{' {
return l.encodeText(buf, l.skipClientTime) // text fast-path
}
@@ -447,11 +459,18 @@ func (l *logger) encode(buf []byte) []byte {
return b
}
func (l *logger) Write(buf []byte) (int, error) {
// Write logs an encoded JSON blob.
//
// If the []byte passed to Write is not an encoded JSON blob,
// then contents is fit into a JSON blob and written.
//
// This is intended as an interface for the stdlib "log" package.
func (l *Logger) Write(buf []byte) (int, error) {
if len(buf) == 0 {
return 0, nil
}
if l.stderr != nil && l.stderr != ioutil.Discard {
level, buf := parseAndRemoveLogLevel(buf)
if l.stderr != nil && l.stderr != ioutil.Discard && level <= l.stderrLevel {
if buf[len(buf)-1] == '\n' {
l.stderr.Write(buf)
} else {
@@ -465,3 +484,23 @@ func (l *logger) Write(buf []byte) (int, error) {
_, err := l.send(b)
return len(buf), err
}
var (
openBracketV = []byte("[v")
v1 = []byte("[v1] ")
v2 = []byte("[v2] ")
)
// level 0 is normal (or unknown) level; 1+ are increasingly verbose
func parseAndRemoveLogLevel(buf []byte) (level int, cleanBuf []byte) {
if len(buf) == 0 || buf[0] == '{' || !bytes.Contains(buf, openBracketV) {
return 0, buf
}
if bytes.Contains(buf, v1) {
return 1, bytes.ReplaceAll(buf, v1, nil)
}
if bytes.Contains(buf, v2) {
return 2, bytes.ReplaceAll(buf, v2, nil)
}
return 0, buf
}

View File

@@ -14,7 +14,7 @@ func TestFastShutdown(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
l := Log(Config{
l := NewLogger(Config{
BaseURL: "http://localhost:1234",
}, t.Logf)
l.Shutdown(ctx)
@@ -23,7 +23,7 @@ func TestFastShutdown(t *testing.T) {
var sink []byte
func TestLoggerEncodeTextAllocs(t *testing.T) {
lg := &logger{timeNow: time.Now}
lg := &Logger{timeNow: time.Now}
inBuf := []byte("some text to encode")
n := testing.AllocsPerRun(1000, func() {
sink = lg.encodeText(inBuf, false)
@@ -34,7 +34,7 @@ func TestLoggerEncodeTextAllocs(t *testing.T) {
}
func TestLoggerWriteLength(t *testing.T) {
lg := &logger{
lg := &Logger{
timeNow: time.Now,
buffer: NewMemoryBuffer(1024),
}

View File

@@ -94,11 +94,6 @@ func LocalAddresses() (regular, loopback []string, err error) {
if !ok {
continue
}
if ip.Is6() {
// TODO(crawshaw): IPv6 support.
// Easy to do here, but we need good endpoint ordering logic.
continue
}
// TODO(apenwarr): don't special case cgNAT.
// In the general wireguard case, it might
// very well be something we can route to

View File

@@ -64,11 +64,11 @@ func likelyHomeRouterIPDarwinExec() (ret netaddr.IP, ok bool) {
if err == nil && isPrivateIP(ip) {
ret = ip
// We've found what we're looking for.
return stopReadingNetstatTable
return errStopReadingNetstatTable
}
return nil
})
return ret, !ret.IsZero()
}
var stopReadingNetstatTable = errors.New("found private gateway")
var errStopReadingNetstatTable = errors.New("found private gateway")

View File

@@ -692,7 +692,7 @@ func (rs *reportState) probePortMapServices() {
port1900 := netaddr.IPPort{IP: gw, Port: 1900}.UDPAddr()
port5351 := netaddr.IPPort{IP: gw, Port: 5351}.UDPAddr()
rs.c.logf("probePortMapServices: me %v -> gw %v", myIP, gw)
rs.c.logf("[v1] probePortMapServices: me %v -> gw %v", myIP, gw)
// Create a UDP4 socket used just for querying for UPnP, NAT-PMP, and PCP.
uc, err := netns.Listener().ListenPacket(context.Background(), "udp4", ":0")
@@ -710,6 +710,7 @@ func (rs *reportState) probePortMapServices() {
uc.WriteTo(pcpPacket(myIP, tempPort, false), port5351)
res := make([]byte, 1500)
sentPCPDelete := false
for {
n, addr, err := uc.ReadFrom(res)
if err != nil {
@@ -727,11 +728,14 @@ func (rs *reportState) probePortMapServices() {
if n == 60 && res[0] == 0x02 { // right length and version 2
rs.setOptBool(&rs.report.PCP, true)
// And now delete the mapping.
// (PCP is the only protocol of the three that requires
// we cause a side effect to detect whether it's present,
// so we need to redo that side effect now.)
uc.WriteTo(pcpPacket(myIP, tempPort, true), port5351)
if !sentPCPDelete {
sentPCPDelete = true
// And now delete the mapping.
// (PCP is the only protocol of the three that requires
// we cause a side effect to detect whether it's present,
// so we need to redo that side effect now.)
uc.WriteTo(pcpPacket(myIP, tempPort, true), port5351)
}
}
}
}
@@ -747,6 +751,7 @@ var uPnPPacket = []byte("M-SEARCH * HTTP/1.1\r\n" +
var v4unspec, _ = netaddr.ParseIP("0.0.0.0")
// pcpPacket generates a PCP packet with a MAP opcode.
func pcpPacket(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte {
const udpProtoNumber = 17
lifetimeSeconds := uint32(1)
@@ -754,17 +759,24 @@ func pcpPacket(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte {
lifetimeSeconds = 0
}
const opMap = 1
// 24 byte header + 36 byte map opcode
pkt := make([]byte, (32+32+128)/8+(96+8+24+16+16+128)/8)
// The header (https://tools.ietf.org/html/rfc6887#section-7.1)
pkt[0] = 2 // version
pkt[1] = opMap
binary.BigEndian.PutUint32(pkt[4:8], lifetimeSeconds)
myIP16 := myIP.As16()
copy(pkt[8:], myIP16[:])
rand.Read(pkt[24 : 24+12])
pkt[36] = udpProtoNumber
binary.BigEndian.PutUint16(pkt[40:], uint16(mapToLocalPort))
// The map opcode body (https://tools.ietf.org/html/rfc6887#section-11.1)
mapOp := pkt[24:]
rand.Read(mapOp[:12]) // 96 bit mappping nonce
mapOp[12] = udpProtoNumber
binary.BigEndian.PutUint16(mapOp[16:], uint16(mapToLocalPort))
v4unspec16 := v4unspec.As16()
copy(pkt[40:], v4unspec16[:])
copy(mapOp[20:], v4unspec16[:])
return pkt
}
@@ -830,7 +842,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
ifState, err := interfaces.GetState()
if err != nil {
c.logf("interfaces: %v", err)
c.logf("[v1] interfaces: %v", err)
return nil, err
}
@@ -939,7 +951,7 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (*Report, e
go func(reg *tailcfg.DERPRegion) {
defer wg.Done()
if d, ip, err := c.measureHTTPSLatency(ctx, reg); err != nil {
c.logf("netcheck: measuring HTTPS latency of %v (%d): %v", reg.RegionCode, reg.RegionID, err)
c.logf("[v1] netcheck: measuring HTTPS latency of %v (%d): %v", reg.RegionCode, reg.RegionID, err)
} else {
rs.mu.Lock()
rs.report.RegionLatency[reg.RegionID] = d
@@ -1033,7 +1045,7 @@ func (c *Client) measureHTTPSLatency(ctx context.Context, reg *tailcfg.DERPRegio
}
func (c *Client) logConciseReport(r *Report, dm *tailcfg.DERPMap) {
c.logf("%v", logger.ArgWriter(func(w *bufio.Writer) {
c.logf("[v1] report: %v", logger.ArgWriter(func(w *bufio.Writer) {
fmt.Fprintf(w, "udp=%v", r.UDP)
if !r.IPv4 {
fmt.Fprintf(w, " v4=%v", r.IPv4)

View File

@@ -554,7 +554,7 @@ func TestLogConciseReport(t *testing.T) {
var buf bytes.Buffer
c := &Client{Logf: func(f string, a ...interface{}) { fmt.Fprintf(&buf, f, a...) }}
c.logConciseReport(tt.r, dm)
if got := buf.String(); got != tt.want {
if got := strings.TrimPrefix(buf.String(), "[v1] report: "); got != tt.want {
t.Errorf("unexpected result.\n got: %#q\nwant: %#q\n", got, tt.want)
}
})

View File

@@ -6,47 +6,11 @@ package packet
import (
"encoding/binary"
"fmt"
"errors"
"inet.af/netaddr"
)
// IP4 is an IPv4 address.
type IP4 uint32
// IPFromNetaddr converts a netaddr.IP to an IP4. Panics if !ip.Is4.
func IP4FromNetaddr(ip netaddr.IP) IP4 {
ipbytes := ip.As4()
return IP4(binary.BigEndian.Uint32(ipbytes[:]))
}
// Netaddr converts ip to a netaddr.IP.
func (ip IP4) Netaddr() netaddr.IP {
return netaddr.IPv4(byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
}
func (ip IP4) String() string {
return fmt.Sprintf("%d.%d.%d.%d", byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
}
// IsMulticast returns whether ip is a multicast address.
func (ip IP4) IsMulticast() bool {
return byte(ip>>24)&0xf0 == 0xe0
}
// IsLinkLocalUnicast returns whether ip is a link-local unicast
// address.
func (ip IP4) IsLinkLocalUnicast() bool {
return byte(ip>>24) == 169 && byte(ip>>16) == 254
}
// IsMostLinkLocalUnicast returns whether ip is a link-local unicast
// address other than the magical "169.254.169.254" address used by
// GCP DNS.
func (ip IP4) IsMostLinkLocalUnicast() bool {
return ip.IsLinkLocalUnicast() && ip != 0xA9FEA9FE
}
// ip4HeaderLength is the length of an IPv4 header with no IP options.
const ip4HeaderLength = 20
@@ -54,8 +18,8 @@ const ip4HeaderLength = 20
type IP4Header struct {
IPProto IPProto
IPID uint16
SrcIP IP4
DstIP IP4
Src netaddr.IP
Dst netaddr.IP
}
// Len implements Header.
@@ -63,6 +27,8 @@ func (h IP4Header) Len() int {
return ip4HeaderLength
}
var errWrongFamily = errors.New("wrong address family for src/dst IP")
// Marshal implements Header.
func (h IP4Header) Marshal(buf []byte) error {
if len(buf) < h.Len() {
@@ -71,6 +37,9 @@ func (h IP4Header) Marshal(buf []byte) error {
if len(buf) > maxPacketLength {
return errLargePacket
}
if !h.Src.Is4() || !h.Dst.Is4() {
return errWrongFamily
}
buf[0] = 0x40 | (byte(h.Len() >> 2)) // IPv4 + IHL
buf[1] = 0x00 // DSCP + ECN
@@ -83,8 +52,10 @@ func (h IP4Header) Marshal(buf []byte) error {
// it later, because the checksum computation runs over these
// bytes and expects them to be zero.
binary.BigEndian.PutUint16(buf[10:12], 0)
binary.BigEndian.PutUint32(buf[12:16], uint32(h.SrcIP)) // Src
binary.BigEndian.PutUint32(buf[16:20], uint32(h.DstIP)) // Dst
src := h.Src.As4()
dst := h.Dst.As4()
copy(buf[12:16], src[:])
copy(buf[16:20], dst[:])
binary.BigEndian.PutUint16(buf[10:12], ip4Checksum(buf[0:20])) // Checksum
@@ -93,7 +64,7 @@ func (h IP4Header) Marshal(buf []byte) error {
// ToResponse implements Header.
func (h *IP4Header) ToResponse() {
h.SrcIP, h.DstIP = h.DstIP, h.SrcIP
h.Src, h.Dst = h.Dst, h.Src
// Flip the bits in the IPID. If incoming IPIDs are distinct, so are these.
h.IPID = ^h.IPID
}
@@ -135,8 +106,9 @@ func (h IP4Header) marshalPseudo(buf []byte) error {
}
length := len(buf) - h.Len()
binary.BigEndian.PutUint32(buf[8:12], uint32(h.SrcIP))
binary.BigEndian.PutUint32(buf[12:16], uint32(h.DstIP))
src, dst := h.Src.As4(), h.Dst.As4()
copy(buf[8:12], src[:])
copy(buf[12:16], dst[:])
buf[16] = 0x0
buf[17] = uint8(h.IPProto)
binary.BigEndian.PutUint16(buf[18:20], uint16(length))

View File

@@ -6,45 +6,10 @@ package packet
import (
"encoding/binary"
"fmt"
"inet.af/netaddr"
)
// IP6 is an IPv6 address.
type IP6 struct {
Hi, Lo uint64
}
// IP6FromNetaddr converts a netaddr.IP to an IP6. Panics if !ip.Is6.
func IP6FromNetaddr(ip netaddr.IP) IP6 {
if !ip.Is6() {
panic(fmt.Sprintf("IP6FromNetaddr called with non-v6 addr %q", ip))
}
b := ip.As16()
return IP6{binary.BigEndian.Uint64(b[:8]), binary.BigEndian.Uint64(b[8:])}
}
// Netaddr converts ip to a netaddr.IP.
func (ip IP6) Netaddr() netaddr.IP {
var b [16]byte
binary.BigEndian.PutUint64(b[:8], ip.Hi)
binary.BigEndian.PutUint64(b[8:], ip.Lo)
return netaddr.IPFrom16(b)
}
func (ip IP6) String() string {
return ip.Netaddr().String()
}
func (ip IP6) IsMulticast() bool {
return (ip.Hi >> 56) == 0xFF
}
func (ip IP6) IsLinkLocalUnicast() bool {
return (ip.Hi >> 48) == 0xFE80
}
// ip6HeaderLength is the length of an IPv6 header with no IP options.
const ip6HeaderLength = 40
@@ -52,8 +17,8 @@ const ip6HeaderLength = 40
type IP6Header struct {
IPProto IPProto
IPID uint32 // only lower 20 bits used
SrcIP IP6
DstIP IP6
Src netaddr.IP
Dst netaddr.IP
}
// Len implements Header.
@@ -75,17 +40,16 @@ func (h IP6Header) Marshal(buf []byte) error {
binary.BigEndian.PutUint16(buf[4:6], uint16(len(buf)-ip6HeaderLength)) // Total length
buf[6] = uint8(h.IPProto) // Inner protocol
buf[7] = 64 // TTL
binary.BigEndian.PutUint64(buf[8:16], h.SrcIP.Hi)
binary.BigEndian.PutUint64(buf[16:24], h.SrcIP.Lo)
binary.BigEndian.PutUint64(buf[24:32], h.DstIP.Hi)
binary.BigEndian.PutUint64(buf[32:40], h.DstIP.Lo)
src, dst := h.Src.As16(), h.Dst.As16()
copy(buf[8:24], src[:])
copy(buf[24:40], dst[:])
return nil
}
// ToResponse implements Header.
func (h *IP6Header) ToResponse() {
h.SrcIP, h.DstIP = h.DstIP, h.SrcIP
h.Src, h.Dst = h.Dst, h.Src
// Flip the bits in the IPID. If incoming IPIDs are distinct, so are these.
h.IPID = (^h.IPID) & 0x000FFFFF
}
@@ -100,10 +64,9 @@ func (h IP6Header) marshalPseudo(buf []byte) error {
return errLargePacket
}
binary.BigEndian.PutUint64(buf[:8], h.SrcIP.Hi)
binary.BigEndian.PutUint64(buf[8:16], h.SrcIP.Lo)
binary.BigEndian.PutUint64(buf[16:24], h.DstIP.Hi)
binary.BigEndian.PutUint64(buf[24:32], h.DstIP.Lo)
src, dst := h.Src.As16(), h.Dst.As16()
copy(buf[:16], src[:])
copy(buf[16:32], dst[:])
binary.BigEndian.PutUint32(buf[32:36], uint32(len(buf)-h.Len()))
buf[36] = 0
buf[37] = 0

View File

@@ -7,8 +7,10 @@ package packet
import (
"encoding/binary"
"fmt"
"net"
"strings"
"inet.af/netaddr"
"tailscale.com/types/strbuilder"
)
@@ -38,64 +40,50 @@ type Parsed struct {
IPVersion uint8
// IPProto is the IP subprotocol (UDP, TCP, etc.). Valid iff IPVersion != 0.
IPProto IPProto
// SrcIP4 is the IPv4 source address. Valid iff IPVersion == 4.
SrcIP4 IP4
// DstIP4 is the IPv4 destination address. Valid iff IPVersion == 4.
DstIP4 IP4
// SrcIP6 is the IPv6 source address. Valid iff IPVersion == 6.
SrcIP6 IP6
// DstIP6 is the IPv6 destination address. Valid iff IPVersion == 6.
DstIP6 IP6
// SrcPort is the TCP/UDP source port. Valid iff IPProto == TCP || IPProto == UDP.
SrcPort uint16
// DstPort is the TCP/UDP source port. Valid iff IPProto == TCP || IPProto == UDP.
DstPort uint16
// SrcIP4 is the source address. Family matches IPVersion. Port is
// valid iff IPProto == TCP || IPProto == UDP.
Src netaddr.IPPort
// DstIP4 is the destination address. Family matches IPVersion.
Dst netaddr.IPPort
// TCPFlags is the packet's TCP flag bigs. Valid iff IPProto == TCP.
TCPFlags uint8
}
func (p *Parsed) String() string {
switch p.IPVersion {
case 4:
sb := strbuilder.Get()
sb.WriteString(p.IPProto.String())
sb.WriteByte('{')
writeIP4Port(sb, p.SrcIP4, p.SrcPort)
sb.WriteString(" > ")
writeIP4Port(sb, p.DstIP4, p.DstPort)
sb.WriteByte('}')
return sb.String()
case 6:
sb := strbuilder.Get()
sb.WriteString(p.IPProto.String())
sb.WriteByte('{')
writeIP6Port(sb, p.SrcIP6, p.SrcPort)
sb.WriteString(" > ")
writeIP6Port(sb, p.DstIP6, p.DstPort)
sb.WriteByte('}')
return sb.String()
default:
if p.IPVersion != 4 && p.IPVersion != 6 {
return "Unknown{???}"
}
sb := strbuilder.Get()
sb.WriteString(p.IPProto.String())
sb.WriteByte('{')
writeIPPort(sb, p.Src)
sb.WriteString(" > ")
writeIPPort(sb, p.Dst)
sb.WriteByte('}')
return sb.String()
}
func writeIP4Port(sb *strbuilder.Builder, ip IP4, port uint16) {
sb.WriteUint(uint64(byte(ip >> 24)))
sb.WriteByte('.')
sb.WriteUint(uint64(byte(ip >> 16)))
sb.WriteByte('.')
sb.WriteUint(uint64(byte(ip >> 8)))
sb.WriteByte('.')
sb.WriteUint(uint64(byte(ip)))
sb.WriteByte(':')
sb.WriteUint(uint64(port))
}
func writeIP6Port(sb *strbuilder.Builder, ip IP6, port uint16) {
sb.WriteByte('[')
sb.WriteString(ip.Netaddr().String()) // TODO: faster?
sb.WriteString("]:")
sb.WriteUint(uint64(port))
// writeIPPort writes ipp.String() into sb, with fewer allocations.
//
// TODO: make netaddr more efficient in this area, and retire this func.
func writeIPPort(sb *strbuilder.Builder, ipp netaddr.IPPort) {
if ipp.IP.Is4() {
raw := ipp.IP.As4()
sb.WriteUint(uint64(raw[0]))
sb.WriteByte('.')
sb.WriteUint(uint64(raw[1]))
sb.WriteByte('.')
sb.WriteUint(uint64(raw[2]))
sb.WriteByte('.')
sb.WriteUint(uint64(raw[3]))
sb.WriteByte(':')
} else {
sb.WriteByte('[')
sb.WriteString(ipp.IP.String()) // TODO: faster?
sb.WriteString("]:")
}
sb.WriteUint(uint64(ipp.Port))
}
// Decode extracts data from the packet in b into q.
@@ -140,8 +128,8 @@ func (q *Parsed) decode4(b []byte) {
}
// If it's valid IPv4, then the IP addresses are valid
q.SrcIP4 = IP4(binary.BigEndian.Uint32(b[12:16]))
q.DstIP4 = IP4(binary.BigEndian.Uint32(b[16:20]))
q.Src.IP = netaddr.IPv4(b[12], b[13], b[14], b[15])
q.Dst.IP = netaddr.IPv4(b[16], b[17], b[18], b[19])
q.subofs = int((b[0] & 0x0F) << 2)
if q.subofs > q.length {
@@ -183,8 +171,8 @@ func (q *Parsed) decode4(b []byte) {
q.IPProto = Unknown
return
}
q.SrcPort = 0
q.DstPort = 0
q.Src.Port = 0
q.Dst.Port = 0
q.dataofs = q.subofs + icmp4HeaderLength
return
case IGMP:
@@ -196,8 +184,8 @@ func (q *Parsed) decode4(b []byte) {
q.IPProto = Unknown
return
}
q.SrcPort = binary.BigEndian.Uint16(sub[0:2])
q.DstPort = binary.BigEndian.Uint16(sub[2:4])
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.TCPFlags = sub[13] & 0x3F
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
@@ -207,8 +195,8 @@ func (q *Parsed) decode4(b []byte) {
q.IPProto = Unknown
return
}
q.SrcPort = binary.BigEndian.Uint16(sub[0:2])
q.DstPort = binary.BigEndian.Uint16(sub[2:4])
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.dataofs = q.subofs + udpHeaderLength
return
default:
@@ -249,10 +237,10 @@ func (q *Parsed) decode6(b []byte) {
return
}
q.SrcIP6.Hi = binary.BigEndian.Uint64(b[8:16])
q.SrcIP6.Lo = binary.BigEndian.Uint64(b[16:24])
q.DstIP6.Hi = binary.BigEndian.Uint64(b[24:32])
q.DstIP6.Lo = binary.BigEndian.Uint64(b[32:40])
// okay to ignore `ok` here, because IPs pulled from packets are
// always well-formed stdlib IPs.
q.Src.IP, _ = netaddr.FromStdIP(net.IP(b[8:24]))
q.Dst.IP, _ = netaddr.FromStdIP(net.IP(b[24:40]))
// We don't support any IPv6 extension headers. Don't try to
// be clever. Therefore, the IP subprotocol always starts at
@@ -276,16 +264,16 @@ func (q *Parsed) decode6(b []byte) {
q.IPProto = Unknown
return
}
q.SrcPort = 0
q.DstPort = 0
q.Src.Port = 0
q.Dst.Port = 0
q.dataofs = q.subofs + icmp6HeaderLength
case TCP:
if len(sub) < tcpHeaderLength {
q.IPProto = Unknown
return
}
q.SrcPort = binary.BigEndian.Uint16(sub[0:2])
q.DstPort = binary.BigEndian.Uint16(sub[2:4])
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.TCPFlags = sub[13] & 0x3F
headerLength := (sub[12] & 0xF0) >> 2
q.dataofs = q.subofs + int(headerLength)
@@ -295,8 +283,8 @@ func (q *Parsed) decode6(b []byte) {
q.IPProto = Unknown
return
}
q.SrcPort = binary.BigEndian.Uint16(sub[0:2])
q.DstPort = binary.BigEndian.Uint16(sub[2:4])
q.Src.Port = binary.BigEndian.Uint16(sub[0:2])
q.Dst.Port = binary.BigEndian.Uint16(sub[2:4])
q.dataofs = q.subofs + udpHeaderLength
default:
q.IPProto = Unknown
@@ -312,8 +300,8 @@ func (q *Parsed) IP4Header() IP4Header {
return IP4Header{
IPID: ipid,
IPProto: q.IPProto,
SrcIP: q.SrcIP4,
DstIP: q.DstIP4,
Src: q.Src.IP,
Dst: q.Dst.IP,
}
}
@@ -334,8 +322,8 @@ func (q *Parsed) UDP4Header() UDP4Header {
}
return UDP4Header{
IP4Header: q.IP4Header(),
SrcPort: q.SrcPort,
DstPort: q.DstPort,
SrcPort: q.Src.Port,
DstPort: q.Dst.Port,
}
}

View File

@@ -12,54 +12,12 @@ import (
"inet.af/netaddr"
)
func mustIP4(s string) IP4 {
ip, err := netaddr.ParseIP(s)
func mustIPPort(s string) netaddr.IPPort {
ipp, err := netaddr.ParseIPPort(s)
if err != nil {
panic(err)
}
return IP4FromNetaddr(ip)
}
func mustIP6(s string) IP6 {
ip, err := netaddr.ParseIP(s)
if err != nil {
panic(err)
}
return IP6FromNetaddr(ip)
}
func TestIP4String(t *testing.T) {
const str = "1.2.3.4"
ip := mustIP4(str)
var got string
allocs := testing.AllocsPerRun(1000, func() {
got = ip.String()
})
if got != str {
t.Errorf("got %q; want %q", got, str)
}
if allocs != 1 {
t.Errorf("allocs = %v; want 1", allocs)
}
}
func TestIP6String(t *testing.T) {
const str = "2607:f8b0:400a:809::200e"
ip := mustIP6(str)
var got string
allocs := testing.AllocsPerRun(1000, func() {
got = ip.String()
})
if got != str {
t.Errorf("got %q; want %q", got, str)
}
if allocs != 2 {
t.Errorf("allocs = %v; want 1", allocs)
}
return ipp
}
var icmp4RequestBuffer = []byte{
@@ -83,10 +41,8 @@ var icmp4RequestDecode = Parsed{
IPVersion: 4,
IPProto: ICMPv4,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
SrcPort: 0,
DstPort: 0,
Src: mustIPPort("1.2.3.4:0"),
Dst: mustIPPort("5.6.7.8:0"),
}
var icmp4ReplyBuffer = []byte{
@@ -109,10 +65,8 @@ var icmp4ReplyDecode = Parsed{
IPVersion: 4,
IPProto: ICMPv4,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
SrcPort: 0,
DstPort: 0,
Src: mustIPPort("1.2.3.4:0"),
Dst: mustIPPort("5.6.7.8:0"),
}
// ICMPv6 Router Solicitation
@@ -132,8 +86,8 @@ var icmp6PacketDecode = Parsed{
length: len(icmp6PacketBuffer),
IPVersion: 6,
IPProto: ICMPv6,
SrcIP6: mustIP6("fe80::fb57:1dea:9c39:8fb7"),
DstIP6: mustIP6("ff02::2"),
Src: mustIPPort("[fe80::fb57:1dea:9c39:8fb7]:0"),
Dst: mustIPPort("[ff02::2]:0"),
}
// This is a malformed IPv4 packet.
@@ -170,10 +124,8 @@ var tcp4PacketDecode = Parsed{
IPVersion: 4,
IPProto: TCP,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
SrcPort: 123,
DstPort: 567,
Src: mustIPPort("1.2.3.4:123"),
Dst: mustIPPort("5.6.7.8:567"),
TCPFlags: TCPSynAck,
}
@@ -198,10 +150,8 @@ var tcp6RequestDecode = Parsed{
IPVersion: 6,
IPProto: TCP,
SrcIP6: mustIP6("2001:559:bc13:5400:1749:4628:3934:e1b"),
DstIP6: mustIP6("2607:f8b0:400a:809::200e"),
SrcPort: 42080,
DstPort: 80,
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:42080"),
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:80"),
TCPFlags: TCPSyn,
}
@@ -226,10 +176,8 @@ var udp4RequestDecode = Parsed{
IPVersion: 4,
IPProto: UDP,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
SrcPort: 123,
DstPort: 567,
Src: mustIPPort("1.2.3.4:123"),
Dst: mustIPPort("5.6.7.8:567"),
}
var invalid4RequestBuffer = []byte{
@@ -250,8 +198,8 @@ var invalid4RequestDecode = Parsed{
IPVersion: 4,
IPProto: Unknown,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
Src: mustIPPort("1.2.3.4:0"),
Dst: mustIPPort("5.6.7.8:0"),
}
var udp6RequestBuffer = []byte{
@@ -275,10 +223,8 @@ var udp6RequestDecode = Parsed{
IPVersion: 6,
IPProto: UDP,
SrcIP6: mustIP6("2001:559:bc13:5400:1749:4628:3934:e1b"),
DstIP6: mustIP6("2607:f8b0:400a:809::200e"),
SrcPort: 54276,
DstPort: 443,
Src: mustIPPort("[2001:559:bc13:5400:1749:4628:3934:e1b]:54276"),
Dst: mustIPPort("[2607:f8b0:400a:809::200e]:443"),
}
var udp4ReplyBuffer = []byte{
@@ -301,10 +247,8 @@ var udp4ReplyDecode = Parsed{
length: len(udp4ReplyBuffer),
IPProto: UDP,
SrcIP4: mustIP4("1.2.3.4"),
DstIP4: mustIP4("5.6.7.8"),
SrcPort: 567,
DstPort: 123,
Src: mustIPPort("1.2.3.4:567"),
Dst: mustIPPort("5.6.7.8:123"),
}
var igmpPacketBuffer = []byte{
@@ -326,8 +270,8 @@ var igmpPacketDecode = Parsed{
IPVersion: 4,
IPProto: IGMP,
SrcIP4: mustIP4("192.168.1.82"),
DstIP4: mustIP4("224.0.0.251"),
Src: mustIPPort("192.168.1.82:0"),
Dst: mustIPPort("224.0.0.251:0"),
}
func TestParsed(t *testing.T) {

View File

@@ -4,7 +4,32 @@
package tsaddr
import "testing"
import (
"testing"
"inet.af/netaddr"
)
func TestInCrostiniRange(t *testing.T) {
tests := []struct {
ip netaddr.IP
want bool
}{
{netaddr.IPv4(192, 168, 0, 1), false},
{netaddr.IPv4(100, 101, 102, 103), false},
{netaddr.IPv4(100, 115, 92, 0), true},
{netaddr.IPv4(100, 115, 92, 5), true},
{netaddr.IPv4(100, 115, 92, 255), true},
{netaddr.IPv4(100, 115, 93, 40), true},
{netaddr.IPv4(100, 115, 94, 1), false},
}
for _, test := range tests {
if got := ChromeOSVMRange().Contains(test.ip); got != test.want {
t.Errorf("inCrostiniRange(%q) = %v, want %v", test.ip, got, test.want)
}
}
}
func TestChromeOSVMRange(t *testing.T) {
if got, want := ChromeOSVMRange().String(), "100.115.92.0/23"; got != want {

View File

@@ -33,7 +33,7 @@ func NewWaitGroupChan() *WaitGroupChan {
}
// DoneChan returns a channel that's closed on completion.
func (c *WaitGroupChan) DoneChan() <-chan struct{} { return c.done }
func (wg *WaitGroupChan) DoneChan() <-chan struct{} { return wg.done }
// Add adds delta, which may be negative, to the WaitGroupChan
// counter. If the counter becomes zero, all goroutines blocked on
@@ -46,10 +46,10 @@ func (c *WaitGroupChan) DoneChan() <-chan struct{} { return c.done }
// than zero, may happen at any time. Typically this means the calls
// to Add should execute before the statement creating the goroutine
// or other event to be waited for.
func (c *WaitGroupChan) Add(delta int) {
n := atomic.AddInt64(&c.n, int64(delta))
func (wg *WaitGroupChan) Add(delta int) {
n := atomic.AddInt64(&wg.n, int64(delta))
if n == 0 {
close(c.done)
close(wg.done)
}
}

View File

@@ -14,7 +14,6 @@ import (
"strings"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"go4.org/mem"
"golang.org/x/oauth2"
"inet.af/netaddr"
@@ -112,8 +111,6 @@ type User struct {
Logins []LoginID
Roles []RoleID
Created time.Time
// Note: be sure to update Clone when adding new fields
}
type Login struct {
@@ -145,20 +142,17 @@ type Node struct {
KeyExpiry time.Time
Machine MachineKey
DiscoKey DiscoKey
Addresses []wgcfg.CIDR // IP addresses of this Node directly
AllowedIPs []wgcfg.CIDR // range of IP addresses to route to this node
Endpoints []string `json:",omitempty"` // IP+port (public via STUN, and local LANs)
DERP string `json:",omitempty"` // DERP-in-IP:port ("127.3.3.40:N") endpoint
Addresses []netaddr.IPPrefix // IP addresses of this Node directly
AllowedIPs []netaddr.IPPrefix // range of IP addresses to route to this node
Endpoints []string `json:",omitempty"` // IP+port (public via STUN, and local LANs)
DERP string `json:",omitempty"` // DERP-in-IP:port ("127.3.3.40:N") endpoint
Hostinfo Hostinfo
Created time.Time
LastSeen *time.Time `json:",omitempty"`
KeepAlive bool // open and keep open a connection to this peer
KeepAlive bool `json:",omitempty"` // open and keep open a connection to this peer
MachineAuthorized bool // TODO(crawshaw): replace with MachineStatus
// NOTE: any new fields containing pointers in this type
// require changes to Node.Clone.
MachineAuthorized bool `json:",omitempty"` // TODO(crawshaw): replace with MachineStatus
}
type MachineStatus int
@@ -275,9 +269,6 @@ type Service struct {
Description string `json:",omitempty"` // text description of service
// TODO(apenwarr): allow advertising services on subnet IPs?
// TODO(apenwarr): add "tags" here for each service?
// NOTE: any new fields containing pointers in this type
// require changes to Hostinfo.Clone.
}
// Hostinfo contains a summary of a Tailscale host.
@@ -287,22 +278,23 @@ type Service struct {
type Hostinfo struct {
// TODO(crawshaw): mark all these fields ",omitempty" when all the
// iOS apps are updated with the latest swift version of this struct.
IPNVersion string // version of this code
FrontendLogID string `json:",omitempty"` // logtail ID of frontend instance
BackendLogID string `json:",omitempty"` // logtail ID of backend instance
OS string // operating system the client runs on (a version.OS value)
OSVersion string `json:",omitempty"` // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041")
DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone 11 Pro")
Hostname string // name of the host the client runs on
ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections
GoArch string `json:",omitempty"` // the host's GOARCH value (of the running binary)
RoutableIPs []wgcfg.CIDR `json:",omitempty"` // set of IP ranges this client can route
RequestTags []string `json:",omitempty"` // set of ACL tags this node wants to claim
Services []Service `json:",omitempty"` // services advertised by this machine
NetInfo *NetInfo `json:",omitempty"`
IPNVersion string `json:",omitempty"` // version of this code
FrontendLogID string `json:",omitempty"` // logtail ID of frontend instance
BackendLogID string `json:",omitempty"` // logtail ID of backend instance
OS string // operating system the client runs on (a version.OS value)
OSVersion string `json:",omitempty"` // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041")
DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone 11 Pro")
Hostname string // name of the host the client runs on
ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections
ShareeNode bool `json:",omitempty"` // indicates this node exists in netmap because it's owned by a shared-to user
GoArch string `json:",omitempty"` // the host's GOARCH value (of the running binary)
RoutableIPs []netaddr.IPPrefix `json:",omitempty"` // set of IP ranges this client can route
RequestTags []string `json:",omitempty"` // set of ACL tags this node wants to claim
Services []Service `json:",omitempty"` // services advertised by this machine
NetInfo *NetInfo `json:",omitempty"`
// NOTE: any new fields containing pointers in this type
// require changes to Hostinfo.Clone and Hostinfo.Equal.
// require changes to Hostinfo.Equal.
}
// NetInfo contains information about the host's network state.
@@ -354,7 +346,7 @@ type NetInfo struct {
// the control plane.
DERPLatency map[string]float64 `json:",omitempty"`
// Update Clone and BasicallyEqual when adding fields.
// Update BasicallyEqual when adding fields.
}
func (ni *NetInfo) String() string {
@@ -482,7 +474,10 @@ type MapRequest struct {
// History of versions:
// 3: implicit compression, keep-alives
// 4: opt-in keep-alives via KeepAlive field, opt-in compression via Compress
// 5: 2020-10-19, implies IncludeIPv6, DeltaPeers/DeltaUserProfiles, supports MagicDNS
// 5: 2020-10-19, implies IncludeIPv6, delta Peers/UserProfiles, supports MagicDNS
// 6: 2020-12-07: means MapResponse.PacketFilter nil means unchanged
// 7: 2020-12-15: FilterRule.SrcIPs accepts CIDRs+ranges, doesn't warn about 0.0.0.0/::
// 8: 2020-12-19: client can receive IPv6 addresses and routes if beta enabled server-side
Version int
Compress string // "zstd" or "" (no compression)
KeepAlive bool // whether server should send keep-alives back to us
@@ -505,6 +500,10 @@ type MapRequest struct {
// OmitPeers is whether the client is okay with the Peers list
// being omitted in the response. (For example, a client on
// start up using ReadOnly to get the DERP map.)
//
// If OmitPeers is true, Stream is false, and ReadOnly is false,
// then the server will let clients update their endpoints without
// breaking existing long-polling (Stream == true) connections.
OmitPeers bool `json:",omitempty"`
// DebugFlags is a list of strings specifying debugging and
@@ -519,6 +518,8 @@ type MapRequest struct {
// router but their IP forwarding is broken.
// * "v6-overlay": IPv6 development flag to have control send
// v6 node addrs
// * "minimize-netmap": have control minimize the netmap, removing
// peers that are unreachable per ACLS.
DebugFlags []string `json:",omitempty"`
}
@@ -530,11 +531,11 @@ type PortRange struct {
var PortRangeAny = PortRange{0, 65535}
// NetPortRange represents a single subnet:portrange.
// NetPortRange represents a range of ports that's allowed for one or more IPs.
type NetPortRange struct {
_ structs.Incomparable
IP string // "*" means all
Bits *int // backward compatibility: if missing, means "all" bits
IP string // IP, CIDR, Range, or "*" (same formats as FilterRule.SrcIPs)
Bits *int // deprecated; the old way to turn IP into a CIDR
Ports PortRange
}
@@ -545,18 +546,25 @@ type NetPortRange struct {
// allowed if a source IP is mathces of those CIDRs.
type FilterRule struct {
// SrcIPs are the source IPs/networks to match.
// The special value "*" means to match all.
//
// It may take the following forms:
// * an IP address (IPv4 or IPv6)
// * the string "*" to match everything (both IPv4 & IPv6)
// * a CIDR (e.g. "192.168.0.0/16")
// * a range of two IPs, inclusive, separated by hyphen ("2eff::1-2eff::0800")
SrcIPs []string
// SrcBits values correspond to the SrcIPs above.
// SrcBits is deprecated; it's the old way to specify a CIDR
// prior to MapRequest.Version 7. Its values correspond to the
// SrcIPs above.
//
// If present at the same index, it changes the SrcIP above to
// be a network with /n CIDR bits. If the slice is nil or
// insufficiently long, the default value (for an IPv4
// address) for a position is 32, as if the SrcIPs above were
// a /32 mask. For a "*" SrcIPs value, the corresponding
// SrcBits value is ignored.
// TODO: for IPv6, clarify default bits length.
// If an entry of SrcBits is present for the same index as a
// SrcIPs entry, it changes the SrcIP above to be a network
// with /n CIDR bits. If the slice is nil or insufficiently
// long, the default value (for an IPv4 address) for a
// position is 32, as if the SrcIPs above were a /32 mask. For
// a "*" SrcIPs value, the corresponding SrcBits value is
// ignored.
SrcBits []int
// DstPorts are the port ranges to allow once a source IP
@@ -601,14 +609,15 @@ type MapResponse struct {
// Peers, if non-empty, is the complete list of peers.
// It will be set in the first MapResponse for a long-polled request/response.
// Subsequent responses will be delta-encoded if DeltaPeers was set in the request.
// Subsequent responses will be delta-encoded if MapRequest.Version >= 5 and server
// chooses, in which case Peers will be nil or zero length.
// If Peers is non-empty, PeersChanged and PeersRemoved should
// be ignored (and should be empty).
// Peers is always returned sorted by Node.ID.
Peers []*Node `json:",omitempty"`
// PeersChanged are the Nodes (identified by their ID) that
// have changed or been added since the past update on the
// HTTP response. It's only set if MapRequest.DeltaPeers was true.
// HTTP response. It's not used by the server if MapRequest.Version < 5.
// PeersChanged is always returned sorted by Node.ID.
PeersChanged []*Node `json:",omitempty"`
// PeersRemoved are the NodeIDs that are no longer in the peer list.
@@ -617,18 +626,32 @@ type MapResponse struct {
// DNS is the same as DNSConfig.Nameservers.
//
// TODO(dmytro): should be sent in DNSConfig.Nameservers once clients have updated.
DNS []wgcfg.IP `json:",omitempty"`
DNS []netaddr.IP `json:",omitempty"`
// SearchPaths are the same as DNSConfig.Domains.
//
// TODO(dmytro): should be sent in DNSConfig.Domains once clients have updated.
SearchPaths []string `json:",omitempty"`
DNSConfig DNSConfig `json:",omitempty"`
// ACLs
Domain string
// Domain is the name of the network that this node is
// in. It's either of the form "example.com" (for user
// foo@example.com, for multi-user networks) or
// "foo@gmail.com" (for siloed users on shared email
// providers). Its exact form should not be depended on; new
// forms are coming later.
Domain string
// PacketFilter are the firewall rules.
//
// For MapRequest.Version >= 6, a nil value means the most
// previously streamed non-nil MapResponse.PacketFilter within
// the same HTTP response. A non-nil but empty list always means
// no PacketFilter (that is, to block everything).
PacketFilter []FilterRule
UserProfiles []UserProfile // as of 1.1.541: may be new or updated user profiles only
UserProfiles []UserProfile // as of 1.1.541 (mapver 5): may be new or updated user profiles only
Roles []Role // deprecated; clients should not rely on Roles
// TODO: Groups []Group
// TODO: Capabilities []Capability
@@ -756,7 +779,7 @@ func eqStrings(a, b []string) bool {
return true
}
func eqCIDRs(a, b []wgcfg.CIDR) bool {
func eqCIDRs(a, b []netaddr.IPPrefix) bool {
if len(a) != len(b) || ((a == nil) != (b == nil)) {
return false
}

View File

@@ -7,7 +7,6 @@
package tailcfg
import (
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/types/opt"
"tailscale.com/types/structs"
@@ -69,8 +68,8 @@ var _NodeNeedsRegeneration = Node(struct {
KeyExpiry time.Time
Machine MachineKey
DiscoKey DiscoKey
Addresses []wgcfg.CIDR
AllowedIPs []wgcfg.CIDR
Addresses []netaddr.IPPrefix
AllowedIPs []netaddr.IPPrefix
Endpoints []string
DERP string
Hostinfo Hostinfo
@@ -106,8 +105,9 @@ var _HostinfoNeedsRegeneration = Hostinfo(struct {
DeviceModel string
Hostname string
ShieldsUp bool
ShareeNode bool
GoArch string
RoutableIPs []wgcfg.CIDR
RoutableIPs []netaddr.IPPrefix
RequestTags []string
Services []Service
NetInfo *NetInfo

View File

@@ -11,7 +11,8 @@ import (
"testing"
"time"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/types/wgkey"
)
func fieldsOf(t reflect.Type) (fields []string) {
@@ -23,18 +24,21 @@ func fieldsOf(t reflect.Type) (fields []string) {
func TestHostinfoEqual(t *testing.T) {
hiHandles := []string{
"IPNVersion", "FrontendLogID", "BackendLogID", "OS", "OSVersion",
"DeviceModel", "Hostname", "ShieldsUp", "GoArch", "RoutableIPs",
"RequestTags", "Services", "NetInfo",
"IPNVersion", "FrontendLogID", "BackendLogID",
"OS", "OSVersion", "DeviceModel", "Hostname",
"ShieldsUp", "ShareeNode",
"GoArch",
"RoutableIPs", "RequestTags",
"Services", "NetInfo",
}
if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) {
t.Errorf("Hostinfo.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
have, hiHandles)
}
nets := func(strs ...string) (ns []wgcfg.CIDR) {
nets := func(strs ...string) (ns []netaddr.IPPrefix) {
for _, s := range strs {
n, err := wgcfg.ParseCIDR(s)
n, err := netaddr.ParseIPPrefix(s)
if err != nil {
panic(err)
}
@@ -169,6 +173,11 @@ func TestHostinfoEqual(t *testing.T) {
&Hostinfo{Services: []Service{Service{Proto: TCP, Port: 1234, Description: "foo"}}},
true,
},
{
&Hostinfo{ShareeNode: true},
&Hostinfo{},
false,
},
}
for i, tt := range tests {
got := tt.a.Equal(tt.b)
@@ -185,9 +194,9 @@ func TestNodeEqual(t *testing.T) {
have, nodeHandles)
}
newPublicKey := func(t *testing.T) wgcfg.Key {
newPublicKey := func(t *testing.T) wgkey.Key {
t.Helper()
k, err := wgcfg.NewPrivateKey()
k, err := wgkey.NewPrivate()
if err != nil {
t.Fatal(err)
}
@@ -256,23 +265,23 @@ func TestNodeEqual(t *testing.T) {
true,
},
{
&Node{Addresses: []wgcfg.CIDR{}},
&Node{Addresses: []netaddr.IPPrefix{}},
&Node{Addresses: nil},
false,
},
{
&Node{Addresses: []wgcfg.CIDR{}},
&Node{Addresses: []wgcfg.CIDR{}},
&Node{Addresses: []netaddr.IPPrefix{}},
&Node{Addresses: []netaddr.IPPrefix{}},
true,
},
{
&Node{AllowedIPs: []wgcfg.CIDR{}},
&Node{AllowedIPs: []netaddr.IPPrefix{}},
&Node{AllowedIPs: nil},
false,
},
{
&Node{Addresses: []wgcfg.CIDR{}},
&Node{Addresses: []wgcfg.CIDR{}},
&Node{Addresses: []netaddr.IPPrefix{}},
&Node{Addresses: []netaddr.IPPrefix{}},
true,
},
{
@@ -421,8 +430,8 @@ func TestCloneNode(t *testing.T) {
}{
{"nil_fields", &Node{}},
{"zero_fields", &Node{
Addresses: make([]wgcfg.CIDR, 0),
AllowedIPs: make([]wgcfg.CIDR, 0),
Addresses: make([]netaddr.IPPrefix, 0),
AllowedIPs: make([]netaddr.IPPrefix, 0),
Endpoints: make([]string, 0),
}},
}

View File

@@ -44,11 +44,12 @@ func AddHandlers(mux *http.ServeMux) {
func Cmdline(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, strings.Join(os.Args, "\x00"))
fmt.Fprint(w, strings.Join(os.Args, "\x00"))
}
func sleep(w http.ResponseWriter, d time.Duration) {
var clientGone <-chan bool
//lint:ignore SA1019 CloseNotifier is deprecated but it functions and this is a temporary fork
if cn, ok := w.(http.CloseNotifier); ok {
clientGone = cn.CloseNotify()
}

View File

@@ -44,6 +44,16 @@ func registerCommonDebug(mux *http.ServeMux) {
mux.Handle("/debug/pprof/", Protected(http.DefaultServeMux)) // to net/http/pprof
mux.Handle("/debug/vars", Protected(http.DefaultServeMux)) // to expvar
mux.Handle("/debug/varz", Protected(http.HandlerFunc(varzHandler)))
mux.Handle("/debug/gc", Protected(http.HandlerFunc(gcHandler)))
}
func gcHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("running GC...\n"))
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
runtime.GC()
w.Write([]byte("Done.\n"))
}
func DefaultCertDir(leafDir string) string {

View File

@@ -7,7 +7,7 @@ package key
import (
"testing"
"github.com/tailscale/wireguard-go/wgcfg"
"tailscale.com/types/wgkey"
)
func TestTextUnmarshal(t *testing.T) {
@@ -28,10 +28,10 @@ func TestTextUnmarshal(t *testing.T) {
func TestClamping(t *testing.T) {
t.Run("NewPrivate", func(t *testing.T) { testClamping(t, NewPrivate) })
// Also test the wgcfg package, as their behavior should match.
t.Run("wgcfg", func(t *testing.T) {
// Also test the wgkey package, as their behavior should match.
t.Run("wgkey", func(t *testing.T) {
testClamping(t, func() Private {
k, err := wgcfg.NewPrivateKey()
k, err := wgkey.NewPrivate()
if err != nil {
t.Fatal(err)
}

View File

@@ -132,7 +132,7 @@ func RateLimitedFn(logf Logf, f time.Duration, burst int, maxCache int) Logf {
logf(format, args...)
case warn:
// For the warning, log the specific format string
logf("[RATE LIMITED] %s", format)
logf("[RATE LIMITED] format string \"%s\"", format)
}
}
}

View File

@@ -45,8 +45,8 @@ func TestRateLimiter(t *testing.T) {
"templated format string no. 0",
"boring string with constant formatting (constant)",
"templated format string no. 1",
"[RATE LIMITED] boring string with constant formatting %s",
"[RATE LIMITED] templated format string no. %d",
"[RATE LIMITED] format string \"boring string with constant formatting %s\"",
"[RATE LIMITED] format string \"templated format string no. %d\"",
"Make sure this string makes it through the rest (that are blocked) 4",
"4 shouldn't get filtered.",
}

241
types/wgkey/key.go Normal file
View File

@@ -0,0 +1,241 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package wgkey contains types and helpers for WireGuard keys.
// It is very similar to package tailscale.com/types/key,
// which is also used for curve25519 keys.
// These keys are used for WireGuard clients;
// those keys are used in other curve25519 clients.
package wgkey
import (
"bytes"
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"strings"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/curve25519"
)
// Size is the number of bytes in a curve25519 key.
const Size = 32
// A Key is a curve25519 key.
// It is used by WireGuard to represent public keys.
type Key [Size]byte
// NewPreshared generates a new random Key.
func NewPreshared() (*Key, error) {
var k [Size]byte
_, err := rand.Read(k[:])
if err != nil {
return nil, err
}
return (*Key)(&k), nil
}
func Parse(b64 string) (*Key, error) { return parseBase64(base64.StdEncoding, b64) }
func ParseHex(s string) (Key, error) {
b, err := hex.DecodeString(s)
if err != nil {
return Key{}, fmt.Errorf("invalid hex key (%q): %w", s, err)
}
if len(b) != Size {
return Key{}, fmt.Errorf("invalid hex key (%q): length=%d, want %d", s, len(b), Size)
}
var key Key
copy(key[:], b)
return key, nil
}
func ParsePrivateHex(v string) (Private, error) {
k, err := ParseHex(v)
if err != nil {
return Private{}, err
}
pk := Private(k)
if pk.IsZero() {
// Do not clamp a zero key, pass the zero through
// (much like NaN propagation) so that IsZero reports
// a useful result.
return pk, nil
}
pk.clamp()
return pk, nil
}
func (k Key) Base64() string { return base64.StdEncoding.EncodeToString(k[:]) }
func (k Key) String() string { return k.ShortString() }
func (k Key) HexString() string { return hex.EncodeToString(k[:]) }
func (k Key) Equal(k2 Key) bool { return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 }
func (k *Key) ShortString() string {
long := k.Base64()
return "[" + long[0:5] + "]"
}
func (k *Key) IsZero() bool {
if k == nil {
return true
}
var zeros Key
return subtle.ConstantTimeCompare(zeros[:], k[:]) == 1
}
func (k *Key) MarshalJSON() ([]byte, error) {
if k == nil {
return []byte("null"), nil
}
// TODO(josharian): use encoding/hex instead?
buf := new(bytes.Buffer)
fmt.Fprintf(buf, `"%x"`, k[:])
return buf.Bytes(), nil
}
func (k *Key) UnmarshalJSON(b []byte) error {
if k == nil {
return errors.New("wgkey.Key: UnmarshalJSON on nil pointer")
}
if len(b) < 3 || b[0] != '"' || b[len(b)-1] != '"' {
return errors.New("wgkey.Key: UnmarshalJSON not given a string")
}
b = b[1 : len(b)-1]
key, err := ParseHex(string(b))
if err != nil {
return fmt.Errorf("wgkey.Key: UnmarshalJSON: %v", err)
}
copy(k[:], key[:])
return nil
}
func (a *Key) LessThan(b *Key) bool {
for i := range a {
if a[i] < b[i] {
return true
} else if a[i] > b[i] {
return false
}
}
return false
}
// A Private is a curve25519 key.
// It is used by WireGuard to represent private keys.
type Private [Size]byte
// NewPrivate generates a new curve25519 secret key.
// It conforms to the format described on https://cr.yp.to/ecdh.html.
func NewPrivate() (Private, error) {
k, err := NewPreshared()
if err != nil {
return Private{}, err
}
k[0] &= 248
k[31] = (k[31] & 127) | 64
return (Private)(*k), nil
}
func ParsePrivate(b64 string) (*Private, error) {
k, err := parseBase64(base64.StdEncoding, b64)
return (*Private)(k), err
}
func (k *Private) String() string { return base64.StdEncoding.EncodeToString(k[:]) }
func (k *Private) HexString() string { return hex.EncodeToString(k[:]) }
func (k *Private) Equal(k2 Private) bool { return subtle.ConstantTimeCompare(k[:], k2[:]) == 1 }
func (k *Private) IsZero() bool {
pk := Key(*k)
return pk.IsZero()
}
func (k *Private) clamp() {
k[0] &= 248
k[31] = (k[31] & 127) | 64
}
// Public computes the public key matching this curve25519 secret key.
func (k *Private) Public() Key {
pk := Key(*k)
if pk.IsZero() {
panic("Tried to generate emptyPrivate.Public()")
}
var p [Size]byte
curve25519.ScalarBaseMult(&p, (*[Size]byte)(k))
return (Key)(p)
}
func (k Private) MarshalText() ([]byte, error) {
// TODO(josharian): use encoding/hex instead?
buf := new(bytes.Buffer)
fmt.Fprintf(buf, `privkey:%x`, k[:])
return buf.Bytes(), nil
}
func (k *Private) UnmarshalText(b []byte) error {
s := string(b)
if !strings.HasPrefix(s, `privkey:`) {
return errors.New("wgkey.Private: UnmarshalText not given a private-key string")
}
s = strings.TrimPrefix(s, `privkey:`)
key, err := ParseHex(s)
if err != nil {
return fmt.Errorf("wgkey.Private: UnmarshalText: %v", err)
}
copy(k[:], key[:])
return nil
}
func parseBase64(enc *base64.Encoding, s string) (*Key, error) {
k, err := enc.DecodeString(s)
if err != nil {
return nil, fmt.Errorf("invalid key (%q): %w", s, err)
}
if len(k) != Size {
return nil, fmt.Errorf("invalid key (%q): length=%d, want %d", s, len(k), Size)
}
var key Key
copy(key[:], k)
return &key, nil
}
func ParseSymmetric(b64 string) (Symmetric, error) {
k, err := parseBase64(base64.StdEncoding, b64)
if err != nil {
return Symmetric{}, err
}
return Symmetric(*k), nil
}
func ParseSymmetricHex(s string) (Symmetric, error) {
b, err := hex.DecodeString(s)
if err != nil {
return Symmetric{}, fmt.Errorf("invalid symmetric hex key (%q): %w", s, err)
}
if len(b) != chacha20poly1305.KeySize {
return Symmetric{}, fmt.Errorf("invalid symmetric hex key length (%q): length=%d, want %d", s, len(b), chacha20poly1305.KeySize)
}
var key Symmetric
copy(key[:], b)
return key, nil
}
// Symmetric is a chacha20poly1305 key.
// It is used by WireGuard to represent pre-shared symmetric keys.
type Symmetric [chacha20poly1305.KeySize]byte
func (k Symmetric) Base64() string { return base64.StdEncoding.EncodeToString(k[:]) }
func (k Symmetric) String() string { return "sym:" + k.Base64()[:8] }
func (k Symmetric) HexString() string { return hex.EncodeToString(k[:]) }
func (k Symmetric) IsZero() bool { return k.Equal(Symmetric{}) }
func (k Symmetric) Equal(k2 Symmetric) bool {
return subtle.ConstantTimeCompare(k[:], k2[:]) == 1
}

111
types/wgkey/key_test.go Normal file
View File

@@ -0,0 +1,111 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package wgkey
import (
"bytes"
"testing"
)
func TestKeyBasics(t *testing.T) {
k1, err := NewPreshared()
if err != nil {
t.Fatal(err)
}
b, err := k1.MarshalJSON()
if err != nil {
t.Fatal(err)
}
t.Run("JSON round-trip", func(t *testing.T) {
// should preserve the keys
k2 := new(Key)
if err := k2.UnmarshalJSON(b); err != nil {
t.Fatal(err)
}
if !bytes.Equal(k1[:], k2[:]) {
t.Fatalf("k1 %v != k2 %v", k1[:], k2[:])
}
if b1, b2 := k1.String(), k2.String(); b1 != b2 {
t.Fatalf("base64-encoded keys do not match: %s, %s", b1, b2)
}
})
t.Run("JSON incompatible with PrivateKey", func(t *testing.T) {
k2 := new(Private)
if err := k2.UnmarshalText(b); err == nil {
t.Fatalf("successfully decoded key as private key")
}
})
t.Run("second key", func(t *testing.T) {
// A second call to NewPreshared should make a new key.
k3, err := NewPreshared()
if err != nil {
t.Fatal(err)
}
if bytes.Equal(k1[:], k3[:]) {
t.Fatalf("k1 %v == k3 %v", k1[:], k3[:])
}
// Check for obvious comparables to make sure we are not generating bad strings somewhere.
if b1, b2 := k1.String(), k3.String(); b1 == b2 {
t.Fatalf("base64-encoded keys match: %s, %s", b1, b2)
}
})
}
func TestPrivateKeyBasics(t *testing.T) {
pri, err := NewPrivate()
if err != nil {
t.Fatal(err)
}
b, err := pri.MarshalText()
if err != nil {
t.Fatal(err)
}
t.Run("JSON round-trip", func(t *testing.T) {
// should preserve the keys
pri2 := new(Private)
if err := pri2.UnmarshalText(b); err != nil {
t.Fatal(err)
}
if !bytes.Equal(pri[:], pri2[:]) {
t.Fatalf("pri %v != pri2 %v", pri[:], pri2[:])
}
if b1, b2 := pri.String(), pri2.String(); b1 != b2 {
t.Fatalf("base64-encoded keys do not match: %s, %s", b1, b2)
}
if pub1, pub2 := pri.Public().String(), pri2.Public().String(); pub1 != pub2 {
t.Fatalf("base64-encoded public keys do not match: %s, %s", pub1, pub2)
}
})
t.Run("JSON incompatible with Key", func(t *testing.T) {
k2 := new(Key)
if err := k2.UnmarshalJSON(b); err == nil {
t.Fatalf("successfully decoded private key as key")
}
})
t.Run("second key", func(t *testing.T) {
// A second call to New should make a new key.
pri3, err := NewPrivate()
if err != nil {
t.Fatal(err)
}
if bytes.Equal(pri[:], pri3[:]) {
t.Fatalf("pri %v == pri3 %v", pri[:], pri3[:])
}
// Check for obvious comparables to make sure we are not generating bad strings somewhere.
if b1, b2 := pri.String(), pri3.String(); b1 == b2 {
t.Fatalf("base64-encoded keys match: %s, %s", b1, b2)
}
if pub1, pub2 := pri.Public().String(), pri3.Public().String(); pub1 == pub2 {
t.Fatalf("base64-encoded public keys match: %s, %s", pub1, pub2)
}
})
}

14
util/systemd/doc.go Normal file
View File

@@ -0,0 +1,14 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package systemd contains a minimal wrapper around systemd-notify to enable
applications to signal readiness and status to systemd.
This package will only have effect on Linux systems running Tailscale in a
systemd unit with the Type=notify flag set. On other operating systems (or
when running in a Linux distro without being run from inside systemd) this
package will become a no-op.
*/
package systemd

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package systemd
import (
"log"
"os"
"sync"
"github.com/mdlayher/sdnotify"
)
var getNotifyOnce struct {
sync.Once
v *sdnotify.Notifier
}
type logOnce struct {
sync.Once
}
func (l *logOnce) logf(format string, args ...interface{}) {
l.Once.Do(func() {
log.Printf(format, args...)
})
}
var (
readyOnce = &logOnce{}
statusOnce = &logOnce{}
)
func notifier() *sdnotify.Notifier {
getNotifyOnce.Do(func() {
sock := os.Getenv(sdnotify.Socket)
if sock == "" {
// Not running under systemd probably. Bail out before logging.
return
}
var err error
getNotifyOnce.v, err = sdnotify.Open(sock)
if err != nil {
log.Printf("systemd: systemd-notifier error: %v", err)
}
})
return getNotifyOnce.v
}
// Ready signals readiness to systemd. This will unblock service dependents from starting.
func Ready() {
err := notifier().Notify(sdnotify.Ready)
if err != nil {
readyOnce.logf("systemd: error notifying: %v", err)
}
}
// Status sends a single line status update to systemd so that information shows up
// in systemctl output. For example:
//
// $ systemctl status tailscale
// ● tailscale.service - Tailscale client daemon
// Loaded: loaded (/nix/store/qc312qcy907wz80fqrgbbm8a9djafmlg-unit-tailscale.service/tailscale.service; enabled; vendor preset: enabled)
// Active: active (running) since Tue 2020-11-24 17:54:07 EST; 13h ago
// Main PID: 26741 (.tailscaled-wra)
// Status: "Connected; user@host.domain.tld; 100.101.102.103"
// IP: 0B in, 0B out
// Tasks: 22 (limit: 4915)
// Memory: 30.9M
// CPU: 2min 38.469s
// CGroup: /system.slice/tailscale.service
// └─26741 /nix/store/sv6cj4mw2jajm9xkbwj07k29dj30lh0n-tailscale-date.20200727/bin/tailscaled --port 41641
func Status(format string, args ...interface{}) {
err := notifier().Notify(sdnotify.Statusf(format, args...))
if err != nil {
statusOnce.logf("systemd: error notifying: %v", err)
}
}

View File

@@ -0,0 +1,10 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !linux
package systemd
func Ready() {}
func Status(string, ...interface{}) {}

View File

@@ -17,6 +17,7 @@ const (
Arch = Distro("arch")
Synology = Distro("synology")
OpenWrt = Distro("openwrt")
NixOS = Distro("nixos")
)
// Get returns the current distro, or the empty string if unknown.
@@ -27,18 +28,28 @@ func Get() Distro {
return ""
}
func have(file string) bool {
_, err := os.Stat(file)
return err == nil
}
func haveDir(file string) bool {
fi, err := os.Stat(file)
return err == nil && fi.IsDir()
}
func linuxDistro() Distro {
if fi, err := os.Stat("/usr/syno"); err == nil && fi.IsDir() {
switch {
case haveDir("usr/syno"):
return Synology
}
if _, err := os.Stat("/etc/debian_version"); err == nil {
case have("/etc/debian_version"):
return Debian
}
if _, err := os.Stat("/etc/arch-release"); err == nil {
case have("/etc/arch-release"):
return Arch
}
if _, err := os.Stat("/etc/openwrt_version"); err == nil {
case have("/etc/openwrt_version"):
return OpenWrt
case have("/run/current-system/sw/bin/nixos-version"):
return NixOS
}
return ""
}

View File

@@ -10,7 +10,7 @@ package version
// Long is a full version number for this build, of the form
// "x.y.z-commithash", or "date.yyyymmdd" if no actual version was
// provided.
const Long = "date.20201107"
const Long = "date.20201230"
// Short is a short version number for this build, of the form
// "x.y.z", or "date.yyyymmdd" if no actual version was provided.

View File

@@ -31,7 +31,7 @@ case $# in
if [ -z "$extra_hash_or_dir" ]; then
# Nothing, empty extra hash is fine.
extra_hash=""
elif [ -d "$extra_hash_or_dir/.git" ]; then
elif [ -e "$extra_hash_or_dir/.git" ]; then
extra_hash=$(git_hash_dirty "$extra_hash_or_dir" HEAD)
elif ! expr "$extra_hash_or_dir" : "^[0-9a-f]*$"; then
echo "Invalid extra hash '$extra_hash_or_dir', must be a git commit or path to a git repo" >&2

View File

@@ -25,45 +25,33 @@ type Filter struct {
// tailscale must have a destination within local4 or local6,
// regardless of the policy filter below. Zero values reject
// all incoming traffic.
local4 []net4
local6 []net6
local4 []netaddr.IPPrefix
local6 []netaddr.IPPrefix
// matches4 and matches6 are lists of match->action rules
// applied to all packets arriving over tailscale
// tunnels. Matches are checked in order, and processing stops
// at the first matching rule. The default policy if no rules
// match is to drop the packet.
matches4 matches4
matches6 matches6
matches4 matches
matches6 matches
// state is the connection tracking state attached to this
// filter. It is used to allow incoming traffic that is a response
// to an outbound connection that this node made, even if those
// incoming packets don't get accepted by matches above.
state4 *filterState
state6 *filterState
state *filterState
}
// tuple4 is a 4-tuple of source and destination IPv4 and port. It's
// used as a lookup key in filterState.
type tuple4 struct {
SrcIP packet.IP4
DstIP packet.IP4
SrcPort uint16
DstPort uint16
}
// tuple6 is a 4-tuple of source and destination IPv6 and port. It's
// used as a lookup key in filterState.
type tuple6 struct {
SrcIP packet.IP6
DstIP packet.IP6
SrcPort uint16
DstPort uint16
// tuple is a 4-tuple of source and destination IP and port. It's used
// as a lookup key in filterState.
type tuple struct {
Src netaddr.IPPort
Dst netaddr.IPPort
}
// filterState is a state cache of past seen packets.
type filterState struct {
mu sync.Mutex
lru *lru.Cache // of tuple4 or tuple6
lru *lru.Cache // of tuple
}
// lruMax is the size of the LRU cache in filterState.
@@ -148,30 +136,58 @@ func NewAllowNone(logf logger.Logf) *Filter {
// shares state with the previous one, to enable changing rules at
// runtime without breaking existing stateful flows.
func New(matches []Match, localNets []netaddr.IPPrefix, shareStateWith *Filter, logf logger.Logf) *Filter {
var state4, state6 *filterState
var state *filterState
if shareStateWith != nil {
state4 = shareStateWith.state4
state6 = shareStateWith.state6
state = shareStateWith.state
} else {
state4 = &filterState{
lru: lru.New(lruMax),
}
state6 = &filterState{
state = &filterState{
lru: lru.New(lruMax),
}
}
f := &Filter{
logf: logf,
matches4: newMatches4(matches),
matches6: newMatches6(matches),
local4: nets4FromIPPrefixes(localNets),
local6: nets6FromIPPrefixes(localNets),
state4: state4,
state6: state6,
matches4: matchesFamily(matches, netaddr.IP.Is4),
matches6: matchesFamily(matches, netaddr.IP.Is6),
local4: netsFamily(localNets, netaddr.IP.Is4),
local6: netsFamily(localNets, netaddr.IP.Is6),
state: state,
}
return f
}
func netsFamily(nets []netaddr.IPPrefix, keep func(netaddr.IP) bool) []netaddr.IPPrefix {
var ret []netaddr.IPPrefix
for _, net := range nets {
if keep(net.IP) {
ret = append(ret, net)
}
}
return ret
}
// matchesFamily returns the subset of ms for which keep(srcNet.IP)
// and keep(dstNet.IP) are both true.
func matchesFamily(ms matches, keep func(netaddr.IP) bool) matches {
var ret matches
for _, m := range ms {
var retm Match
for _, src := range m.Srcs {
if keep(src.IP) {
retm.Srcs = append(retm.Srcs, src)
}
}
for _, dst := range m.Dsts {
if keep(dst.Net.IP) {
retm.Dsts = append(retm.Dsts, dst)
}
}
if len(retm.Srcs) > 0 && len(retm.Dsts) > 0 {
ret = append(ret, retm)
}
}
return ret
}
func maybeHexdump(flag RunFlags, b []byte) string {
if flag == 0 {
return ""
@@ -229,19 +245,17 @@ func (f *Filter) CheckTCP(srcIP, dstIP netaddr.IP, dstPort uint16) Response {
return Drop
case srcIP.Is4():
pkt.IPVersion = 4
pkt.SrcIP4 = packet.IP4FromNetaddr(srcIP)
pkt.DstIP4 = packet.IP4FromNetaddr(dstIP)
case srcIP.Is6():
pkt.IPVersion = 6
pkt.SrcIP6 = packet.IP6FromNetaddr(srcIP)
pkt.DstIP6 = packet.IP6FromNetaddr(dstIP)
default:
panic("unreachable")
}
pkt.Src.IP = srcIP
pkt.Dst.IP = dstIP
pkt.IPProto = packet.TCP
pkt.TCPFlags = packet.TCPSyn
pkt.SrcPort = 0
pkt.DstPort = dstPort
pkt.Src.Port = 0
pkt.Dst.Port = dstPort
return f.RunIn(pkt, 0)
}
@@ -287,7 +301,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
// A compromised peer could try to send us packets for
// destinations we didn't explicitly advertise. This check is to
// prevent that.
if !ip4InList(q.DstIP4, f.local4) {
if !ipInList(q.Dst.IP, f.local4) {
return Drop, "destination not allowed"
}
@@ -320,11 +334,11 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
return Accept, "tcp ok"
}
case packet.UDP:
t := tuple4{q.SrcIP4, q.DstIP4, q.SrcPort, q.DstPort}
t := tuple{q.Src, q.Dst}
f.state4.mu.Lock()
_, ok := f.state4.lru.Get(t)
f.state4.mu.Unlock()
f.state.mu.Lock()
_, ok := f.state.lru.Get(t)
f.state.mu.Unlock()
if ok {
return Accept, "udp cached"
@@ -342,7 +356,7 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
// A compromised peer could try to send us packets for
// destinations we didn't explicitly advertise. This check is to
// prevent that.
if !ip6InList(q.DstIP6, f.local6) {
if !ipInList(q.Dst.IP, f.local6) {
return Drop, "destination not allowed"
}
@@ -375,11 +389,11 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
return Accept, "tcp ok"
}
case packet.UDP:
t := tuple6{q.SrcIP6, q.DstIP6, q.SrcPort, q.DstPort}
t := tuple{q.Src, q.Dst}
f.state6.mu.Lock()
_, ok := f.state6.lru.Get(t)
f.state6.mu.Unlock()
f.state.mu.Lock()
_, ok := f.state.lru.Get(t)
f.state.mu.Unlock()
if ok {
return Accept, "udp cached"
@@ -399,20 +413,11 @@ func (f *Filter) runOut(q *packet.Parsed) (r Response, why string) {
return Accept, "ok out"
}
switch q.IPVersion {
case 4:
t := tuple4{q.DstIP4, q.SrcIP4, q.DstPort, q.SrcPort}
var ti interface{} = t // allocate once, rather than twice inside mutex
f.state4.mu.Lock()
f.state4.lru.Add(ti, ti)
f.state4.mu.Unlock()
case 6:
t := tuple6{q.DstIP6, q.SrcIP6, q.DstPort, q.SrcPort}
var ti interface{} = t // allocate once, rather than twice inside mutex
f.state6.mu.Lock()
f.state6.lru.Add(ti, ti)
f.state6.mu.Unlock()
}
t := tuple{q.Dst, q.Src}
var ti interface{} = t // allocate once, rather than twice inside mutex
f.state.mu.Lock()
f.state.lru.Add(ti, ti)
f.state.mu.Unlock()
return Accept, "ok out"
}
@@ -436,6 +441,8 @@ func (d direction) String() string {
}
}
var gcpDNSAddr = netaddr.IPv4(169, 254, 169, 254)
// pre runs the direction-agnostic filter logic. dir is only used for
// logging.
func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) Response {
@@ -448,25 +455,13 @@ func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) Response {
return Drop
}
switch q.IPVersion {
case 4:
if q.DstIP4.IsMulticast() {
f.logRateLimit(rf, q, dir, Drop, "multicast")
return Drop
}
if q.DstIP4.IsMostLinkLocalUnicast() {
f.logRateLimit(rf, q, dir, Drop, "link-local-unicast")
return Drop
}
case 6:
if q.DstIP6.IsMulticast() {
f.logRateLimit(rf, q, dir, Drop, "multicast")
return Drop
}
if q.DstIP6.IsLinkLocalUnicast() {
f.logRateLimit(rf, q, dir, Drop, "link-local-unicast")
return Drop
}
if q.Dst.IP.IsMulticast() {
f.logRateLimit(rf, q, dir, Drop, "multicast")
return Drop
}
if q.Dst.IP.IsLinkLocalUnicast() && q.Dst.IP != gcpDNSAddr {
f.logRateLimit(rf, q, dir, Drop, "link-local-unicast")
return Drop
}
switch q.IPProto {
@@ -493,12 +488,5 @@ func omitDropLogging(p *packet.Parsed, dir direction) bool {
return false
}
switch p.IPVersion {
case 4:
return p.DstIP4.IsMulticast() || p.DstIP4.IsMostLinkLocalUnicast() || p.IPProto == packet.IGMP
case 6:
return p.DstIP6.IsMulticast() || p.DstIP6.IsLinkLocalUnicast()
default:
return false
}
return p.Dst.IP.IsMulticast() || (p.Dst.IP.IsLinkLocalUnicast() && p.Dst.IP != gcpDNSAddr) || p.IPProto == packet.IGMP
}

View File

@@ -94,9 +94,9 @@ func TestFilter(t *testing.T) {
if test.p.IPProto == packet.TCP {
var got Response
if test.p.IPVersion == 4 {
got = acl.CheckTCP(test.p.SrcIP4.Netaddr(), test.p.DstIP4.Netaddr(), test.p.DstPort)
got = acl.CheckTCP(test.p.Src.IP, test.p.Dst.IP, test.p.Dst.Port)
} else {
got = acl.CheckTCP(test.p.SrcIP6.Netaddr(), test.p.DstIP6.Netaddr(), test.p.DstPort)
got = acl.CheckTCP(test.p.Src.IP, test.p.Dst.IP, test.p.Dst.Port)
}
if test.want != got {
t.Errorf("#%d CheckTCP got=%v want=%v packet:%v", i, got, test.want, test.p)
@@ -194,7 +194,7 @@ func TestNoAllocs(t *testing.T) {
}
}
func TestParseIP(t *testing.T) {
func TestParseIPSet(t *testing.T) {
tests := []struct {
host string
bits int
@@ -203,23 +203,33 @@ func TestParseIP(t *testing.T) {
}{
{"8.8.8.8", 24, pfx("8.8.8.8/24"), ""},
{"2601:1234::", 64, pfx("2601:1234::/64"), ""},
{"8.8.8.8", 33, nil, `invalid CIDR size 33 for host "8.8.8.8"`},
{"8.8.8.8", -1, nil, `invalid CIDR size -1 for host "8.8.8.8"`},
{"2601:1234::", 129, nil, `invalid CIDR size 129 for host "2601:1234::"`},
{"0.0.0.0", 24, nil, `ports="0.0.0.0": to allow all IP addresses, use *:port, not 0.0.0.0:port`},
{"::", 64, nil, `ports="::": to allow all IP addresses, use *:port, not [::]:port`},
{"8.8.8.8", 33, nil, `invalid CIDR size 33 for IP "8.8.8.8"`},
{"8.8.8.8", -1, pfx("8.8.8.8/32"), ""},
{"8.8.8.8", 32, pfx("8.8.8.8/32"), ""},
{"8.8.8.8/24", -1, nil, "8.8.8.8/24 contains non-network bits set"},
{"8.8.8.0/24", 18, pfx("8.8.8.0/24"), ""}, // the 18 is ignored
{"1.0.0.0-1.255.255.255", 5, pfx("1.0.0.0/8"), ""},
{"1.0.0.0-2.1.2.3", 5, pfx("1.0.0.0/8", "2.0.0.0/16", "2.1.0.0/23", "2.1.2.0/30"), ""},
{"1.0.0.2-1.0.0.1", -1, nil, "invalid IP range \"1.0.0.2-1.0.0.1\""},
{"2601:1234::", 129, nil, `invalid CIDR size 129 for IP "2601:1234::"`},
{"0.0.0.0", 24, pfx("0.0.0.0/24"), ""},
{"::", 64, pfx("::/64"), ""},
{"*", 24, pfx("0.0.0.0/0", "::/0"), ""},
}
for _, tt := range tests {
got, err := parseIP(tt.host, tt.bits)
var bits *int
if tt.bits != -1 {
bits = &tt.bits
}
got, err := parseIPSet(tt.host, bits)
if err != nil {
if err.Error() == tt.wantErr {
continue
}
t.Errorf("parseIP(%q, %v) error: %v; want error %q", tt.host, tt.bits, err, tt.wantErr)
t.Errorf("parseIPSet(%q, %v) error: %v; want error %q", tt.host, tt.bits, err, tt.wantErr)
}
if diff := cmp.Diff(got, tt.want, cmp.Comparer(func(a, b netaddr.IP) bool { return a == b })); diff != "" {
t.Errorf("parseIP(%q, %v) = %s; want %s", tt.host, tt.bits, got, tt.want)
t.Errorf("parseIPSet(%q, %v) = %s; want %s", tt.host, tt.bits, got, tt.want)
continue
}
}
@@ -335,19 +345,19 @@ func TestOmitDropLogging(t *testing.T) {
},
{
name: "v4_multicast_out_low",
pkt: &packet.Parsed{IPVersion: 4, DstIP4: mustIP4("224.0.0.0")},
pkt: &packet.Parsed{IPVersion: 4, Dst: mustIPPort("224.0.0.0:0")},
dir: out,
want: true,
},
{
name: "v4_multicast_out_high",
pkt: &packet.Parsed{IPVersion: 4, DstIP4: mustIP4("239.255.255.255")},
pkt: &packet.Parsed{IPVersion: 4, Dst: mustIPPort("239.255.255.255:0")},
dir: out,
want: true,
},
{
name: "v4_link_local_unicast",
pkt: &packet.Parsed{IPVersion: 4, DstIP4: mustIP4("169.254.1.2")},
pkt: &packet.Parsed{IPVersion: 4, Dst: mustIPPort("169.254.1.2:0")},
dir: out,
want: true,
},
@@ -377,18 +387,16 @@ func parsed(proto packet.IPProto, src, dst string, sport, dport uint16) packet.P
var ret packet.Parsed
ret.Decode(dummyPacket)
ret.IPProto = proto
ret.SrcPort = sport
ret.DstPort = dport
ret.Src.IP = sip
ret.Src.Port = sport
ret.Dst.IP = dip
ret.Dst.Port = dport
ret.TCPFlags = packet.TCPSyn
if sip.Is4() {
ret.IPVersion = 4
ret.SrcIP4 = packet.IP4FromNetaddr(sip)
ret.DstIP4 = packet.IP4FromNetaddr(dip)
} else {
ret.IPVersion = 6
ret.SrcIP6 = packet.IP6FromNetaddr(sip)
ret.DstIP6 = packet.IP6FromNetaddr(dip)
}
return ret
@@ -397,8 +405,8 @@ func parsed(proto packet.IPProto, src, dst string, sport, dport uint16) packet.P
func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen int) []byte {
u := packet.UDP6Header{
IP6Header: packet.IP6Header{
SrcIP: packet.IP6FromNetaddr(mustIP(src)),
DstIP: packet.IP6FromNetaddr(mustIP(dst)),
Src: mustIP(src),
Dst: mustIP(dst),
},
SrcPort: sport,
DstPort: dport,
@@ -426,8 +434,8 @@ func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen in
func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength int) []byte {
u := packet.UDP4Header{
IP4Header: packet.IP4Header{
SrcIP: packet.IP4FromNetaddr(mustIP(src)),
DstIP: packet.IP4FromNetaddr(mustIP(dst)),
Src: mustIP(src),
Dst: mustIP(dst),
},
SrcPort: sport,
DstPort: dport,
@@ -478,12 +486,12 @@ func parseHexPkt(t *testing.T, h string) *packet.Parsed {
return p
}
func mustIP4(s string) packet.IP4 {
ip, err := netaddr.ParseIP(s)
func mustIPPort(s string) netaddr.IPPort {
ipp, err := netaddr.ParseIPPort(s)
if err != nil {
panic(err)
}
return packet.IP4FromNetaddr(ip)
return ipp
}
func pfx(strs ...string) (ret []netaddr.IPPrefix) {

View File

@@ -9,6 +9,7 @@ import (
"strings"
"inet.af/netaddr"
"tailscale.com/net/packet"
)
// PortRange is a range of TCP and UDP ports.
@@ -71,3 +72,46 @@ func (m Match) String() string {
}
return fmt.Sprintf("%v=>%v", ss, ds)
}
type matches []Match
func (ms matches) match(q *packet.Parsed) bool {
for _, m := range ms {
if !ipInList(q.Src.IP, m.Srcs) {
continue
}
for _, dst := range m.Dsts {
if !dst.Net.Contains(q.Dst.IP) {
continue
}
if !dst.Ports.contains(q.Dst.Port) {
continue
}
return true
}
}
return false
}
func (ms matches) matchIPsOnly(q *packet.Parsed) bool {
for _, m := range ms {
if !ipInList(q.Src.IP, m.Srcs) {
continue
}
for _, dst := range m.Dsts {
if dst.Net.Contains(q.Dst.IP) {
return true
}
}
}
return false
}
func ipInList(ip netaddr.IP, netlist []netaddr.IPPrefix) bool {
for _, net := range netlist {
if net.Contains(ip) {
return true
}
}
return false
}

View File

@@ -1,151 +0,0 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package filter
import (
"fmt"
"math/bits"
"strings"
"inet.af/netaddr"
"tailscale.com/net/packet"
)
type net4 struct {
ip packet.IP4
mask packet.IP4
}
func net4FromIPPrefix(pfx netaddr.IPPrefix) net4 {
if !pfx.IP.Is4() {
panic("net4FromIPPrefix given non-ipv4 prefix")
}
return net4{
ip: packet.IP4FromNetaddr(pfx.IP),
mask: netmask4(pfx.Bits),
}
}
func nets4FromIPPrefixes(pfxs []netaddr.IPPrefix) (ret []net4) {
for _, pfx := range pfxs {
if pfx.IP.Is4() {
ret = append(ret, net4FromIPPrefix(pfx))
}
}
return ret
}
func (n net4) Contains(ip packet.IP4) bool {
return (n.ip & n.mask) == (ip & n.mask)
}
func (n net4) Bits() int {
return 32 - bits.TrailingZeros32(uint32(n.mask))
}
func (n net4) String() string {
b := n.Bits()
if b == 32 {
return n.ip.String()
} else if b == 0 {
return "*"
} else {
return fmt.Sprintf("%s/%d", n.ip, b)
}
}
type npr4 struct {
net net4
ports PortRange
}
func (npr npr4) String() string {
return fmt.Sprintf("%s:%s", npr.net, npr.ports)
}
type match4 struct {
srcs []net4
dsts []npr4
}
type matches4 []match4
func (ms matches4) String() string {
var b strings.Builder
for _, m := range ms {
fmt.Fprintf(&b, "%s => %s\n", m.srcs, m.dsts)
}
return b.String()
}
func newMatches4(ms []Match) (ret matches4) {
for _, m := range ms {
var m4 match4
for _, src := range m.Srcs {
if src.IP.Is4() {
m4.srcs = append(m4.srcs, net4FromIPPrefix(src))
}
}
for _, dst := range m.Dsts {
if dst.Net.IP.Is4() {
m4.dsts = append(m4.dsts, npr4{net4FromIPPrefix(dst.Net), dst.Ports})
}
}
if len(m4.srcs) > 0 && len(m4.dsts) > 0 {
ret = append(ret, m4)
}
}
return ret
}
// match returns whether q's source IP and destination IP:port match
// any of ms.
func (ms matches4) match(q *packet.Parsed) bool {
for _, m := range ms {
if !ip4InList(q.SrcIP4, m.srcs) {
continue
}
for _, dst := range m.dsts {
if !dst.net.Contains(q.DstIP4) {
continue
}
if !dst.ports.contains(q.DstPort) {
continue
}
return true
}
}
return false
}
// matchIPsOnly returns whether q's source and destination IP match
// any of ms.
func (ms matches4) matchIPsOnly(q *packet.Parsed) bool {
for _, m := range ms {
if !ip4InList(q.SrcIP4, m.srcs) {
continue
}
for _, dst := range m.dsts {
if dst.net.Contains(q.DstIP4) {
return true
}
}
}
return false
}
func netmask4(bits uint8) packet.IP4 {
b := ^uint32((1 << (32 - bits)) - 1)
return packet.IP4(b)
}
func ip4InList(ip packet.IP4, netlist []net4) bool {
for _, net := range netlist {
if net.Contains(ip) {
return true
}
}
return false
}

View File

@@ -1,171 +0,0 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package filter
import (
"fmt"
"math/bits"
"strings"
"inet.af/netaddr"
"tailscale.com/net/packet"
)
type net6 struct {
ip packet.IP6
mask packet.IP6
}
func net6FromIPPrefix(pfx netaddr.IPPrefix) net6 {
if !pfx.IP.Is6() {
panic("net6FromIPPrefix given non-ipv6 prefix")
}
var mask packet.IP6
if pfx.Bits > 64 {
mask.Hi = ^uint64(0)
mask.Lo = (^uint64(0) << (128 - pfx.Bits))
} else {
mask.Hi = (^uint64(0) << (64 - pfx.Bits))
}
return net6{
ip: packet.IP6FromNetaddr(pfx.IP),
mask: mask,
}
}
func nets6FromIPPrefixes(pfxs []netaddr.IPPrefix) (ret []net6) {
for _, pfx := range pfxs {
if pfx.IP.Is6() {
ret = append(ret, net6FromIPPrefix(pfx))
}
}
return ret
}
func (n net6) Contains(ip packet.IP6) bool {
// This is equivalent to the more straightforward implementation:
// ((n.ip.Hi & n.mask.Hi) == (ip.Hi & n.mask.Hi) &&
// (n.ip.Lo & n.mask.Lo) == (ip.Lo & n.mask.Lo))
//
// This implementation runs significantly faster because it
// eliminates branches and minimizes the required
// bit-twiddling.
a := (n.ip.Hi ^ ip.Hi) & n.mask.Hi
b := (n.ip.Lo ^ ip.Lo) & n.mask.Lo
return (a | b) == 0
}
func (n net6) Bits() int {
return 128 - bits.TrailingZeros64(n.mask.Hi) - bits.TrailingZeros64(n.mask.Lo)
}
func (n net6) String() string {
switch n.Bits() {
case 128:
return n.ip.String()
case 0:
return "*"
default:
return fmt.Sprintf("%s/%d", n.ip, n.Bits())
}
}
type npr6 struct {
net net6
ports PortRange
}
func (npr npr6) String() string {
return fmt.Sprintf("%s:%s", npr.net, npr.ports)
}
type match6 struct {
srcs []net6
dsts []npr6
}
type matches6 []match6
func (ms matches6) String() string {
var b strings.Builder
for _, m := range ms {
fmt.Fprintf(&b, "%s => %s\n", m.srcs, m.dsts)
}
return b.String()
}
func newMatches6(ms []Match) (ret matches6) {
for _, m := range ms {
var m6 match6
for _, src := range m.Srcs {
if src.IP.Is6() {
m6.srcs = append(m6.srcs, net6FromIPPrefix(src))
}
}
for _, dst := range m.Dsts {
if dst.Net.IP.Is6() {
m6.dsts = append(m6.dsts, npr6{net6FromIPPrefix(dst.Net), dst.Ports})
}
}
if len(m6.srcs) > 0 && len(m6.dsts) > 0 {
ret = append(ret, m6)
}
}
return ret
}
func (ms matches6) match(q *packet.Parsed) bool {
outer:
for i := range ms {
srcs := ms[i].srcs
for j := range srcs {
if srcs[j].Contains(q.SrcIP6) {
dsts := ms[i].dsts
for k := range dsts {
if dsts[k].net.Contains(q.DstIP6) && dsts[k].ports.contains(q.DstPort) {
return true
}
}
// We hit on src, but missed on all
// dsts. No need to try other srcs,
// they'll never fully match.
continue outer
}
}
}
return false
}
func (ms matches6) matchIPsOnly(q *packet.Parsed) bool {
outer:
for i := range ms {
srcs := ms[i].srcs
for j := range srcs {
if srcs[j].Contains(q.SrcIP6) {
dsts := ms[i].dsts
for k := range dsts {
if dsts[k].net.Contains(q.DstIP6) {
return true
}
}
// We hit on src, but missed on all
// dsts. No need to try other srcs,
// they'll never fully match.
continue outer
}
}
}
return false
}
func ip6InList(ip packet.IP6, netlist []net6) bool {
for _, net := range netlist {
if net.Contains(ip) {
return true
}
}
return false
}

View File

@@ -1,37 +0,0 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package filter
import "testing"
// Verifies that the fast bit-twiddling implementation of Contains
// works the same as the easy-to-read implementation. Since we can't
// sensibly check it on 128 bits, the test runs over 4-bit
// "IPs". Bit-twiddling is the same at any width, so this adequately
// proves that the implementations are equivalent.
func TestOptimizedContains(t *testing.T) {
for ipHi := 0; ipHi < 0xf; ipHi++ {
for ipLo := 0; ipLo < 0xf; ipLo++ {
for nIPHi := 0; nIPHi < 0xf; nIPHi++ {
for nIPLo := 0; nIPLo < 0xf; nIPLo++ {
for maskHi := 0; maskHi < 0xf; maskHi++ {
for maskLo := 0; maskLo < 0xf; maskLo++ {
a := (nIPHi ^ ipHi) & maskHi
b := (nIPLo ^ ipLo) & maskLo
got := (a | b) == 0
want := ((nIPHi&maskHi) == (ipHi&maskHi) && (nIPLo&maskLo) == (ipLo&maskLo))
if got != want {
t.Errorf("mask %1x%1x/%1x%1x %1x%1x got=%v want=%v", nIPHi, nIPLo, maskHi, maskLo, ipHi, ipLo, got, want)
}
}
}
}
}
}
}
}

View File

@@ -6,6 +6,7 @@ package filter
import (
"fmt"
"strings"
"inet.af/netaddr"
"tailscale.com/tailcfg"
@@ -22,11 +23,11 @@ func MatchesFromFilterRules(pf []tailcfg.FilterRule) ([]Match, error) {
m := Match{}
for i, s := range r.SrcIPs {
bits := 32
var bits *int
if len(r.SrcBits) > i {
bits = r.SrcBits[i]
bits = &r.SrcBits[i]
}
nets, err := parseIP(s, bits)
nets, err := parseIPSet(s, bits)
if err != nil && erracc == nil {
erracc = err
continue
@@ -35,11 +36,7 @@ func MatchesFromFilterRules(pf []tailcfg.FilterRule) ([]Match, error) {
}
for _, d := range r.DstPorts {
bits := 32
if d.Bits != nil {
bits = *d.Bits
}
nets, err := parseIP(d.IP, bits)
nets, err := parseIPSet(d.IP, d.Bits)
if err != nil && erracc == nil {
erracc = err
continue
@@ -65,35 +62,65 @@ var (
zeroIP6 = netaddr.IPFrom16([16]byte{})
)
func parseIP(host string, defaultBits int) ([]netaddr.IPPrefix, error) {
if host == "*" {
// User explicitly requested wildcard dst ip.
// parseIPSet parses arg as one:
//
// * an IP address (IPv4 or IPv6)
// * the string "*" to match everything (both IPv4 & IPv6)
// * a CIDR (e.g. "192.168.0.0/16")
// * a range of two IPs, inclusive, separated by hyphen ("2eff::1-2eff::0800")
//
// bits, if non-nil, is the legacy SrcBits CIDR length to make a IP
// address (without a slash) treated as a CIDR of *bits length.
//
// TODO(bradfitz): make this return an IPSet and plumb that all
// around, and ultimately use a new version of IPSet.ContainsFunc like
// Contains16Func that works in [16]byte address, so we we can match
// at runtime without allocating?
func parseIPSet(arg string, bits *int) ([]netaddr.IPPrefix, error) {
if arg == "*" {
// User explicitly requested wildcard.
return []netaddr.IPPrefix{
{IP: zeroIP4, Bits: 0},
{IP: zeroIP6, Bits: 0},
}, nil
}
ip, err := netaddr.ParseIP(host)
if strings.Contains(arg, "/") {
pfx, err := netaddr.ParseIPPrefix(arg)
if err != nil {
return nil, err
}
if pfx != pfx.Masked() {
return nil, fmt.Errorf("%v contains non-network bits set", pfx)
}
return []netaddr.IPPrefix{pfx}, nil
}
if strings.Count(arg, "-") == 1 {
i := strings.Index(arg, "-")
ip1s, ip2s := arg[:i], arg[i+1:]
ip1, err := netaddr.ParseIP(ip1s)
if err != nil {
return nil, err
}
ip2, err := netaddr.ParseIP(ip2s)
if err != nil {
return nil, err
}
r := netaddr.IPRange{From: ip1, To: ip2}
if !r.Valid() {
return nil, fmt.Errorf("invalid IP range %q", arg)
}
return r.Prefixes(), nil
}
ip, err := netaddr.ParseIP(arg)
if err != nil {
return nil, fmt.Errorf("ports=%#v: invalid IP address", host)
return nil, fmt.Errorf("invalid IP address %q", arg)
}
if ip == zeroIP4 {
// For clarity, reject 0.0.0.0 as an input
return nil, fmt.Errorf("ports=%#v: to allow all IP addresses, use *:port, not 0.0.0.0:port", host)
bits8 := ip.BitLen()
if bits != nil {
if *bits < 0 || *bits > int(bits8) {
return nil, fmt.Errorf("invalid CIDR size %d for IP %q", *bits, arg)
}
bits8 = uint8(*bits)
}
if ip == zeroIP6 {
// For clarity, reject :: as an input
return nil, fmt.Errorf("ports=%#v: to allow all IP addresses, use *:port, not [::]:port", host)
}
if defaultBits < 0 || (ip.Is4() && defaultBits > 32) || (ip.Is6() && defaultBits > 128) {
return nil, fmt.Errorf("invalid CIDR size %d for host %q", defaultBits, host)
}
return []netaddr.IPPrefix{
{
IP: ip,
Bits: uint8(defaultBits),
},
}, nil
return []netaddr.IPPrefix{{IP: ip, Bits: bits8}}, nil
}

View File

@@ -0,0 +1,522 @@
// Copyright (c) 2019 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 magicsock
import (
"encoding/binary"
"errors"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/tailscale/wireguard-go/conn"
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/wgcfg"
"inet.af/netaddr"
"tailscale.com/ipn/ipnstate"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/wgkey"
)
var errNoDestinations = errors.New("magicsock: no destinations")
func (c *Conn) createLegacyEndpointLocked(pk key.Public, addrs string) (conn.Endpoint, error) {
a := &addrSet{
Logf: c.logf,
publicKey: pk,
curAddr: -1,
}
if addrs != "" {
for _, ep := range strings.Split(addrs, ",") {
ipp, err := netaddr.ParseIPPort(ep)
if err != nil {
return nil, fmt.Errorf("bogus address %q", ep)
}
a.ipPorts = append(a.ipPorts, ipp)
a.addrs = append(a.addrs, *ipp.UDPAddr())
}
}
// If this endpoint is being updated, remember its old set of
// endpoints so we can remove any (from c.addrsByUDP) that are
// not in the new set.
var oldIPP []netaddr.IPPort
if preva, ok := c.addrsByKey[pk]; ok {
oldIPP = preva.ipPorts
}
c.addrsByKey[pk] = a
// Add entries to c.addrsByUDP.
for _, ipp := range a.ipPorts {
if ipp.IP == derpMagicIPAddr {
continue
}
c.addrsByUDP[ipp] = a
}
// Remove previous c.addrsByUDP entries that are no longer in the new set.
for _, ipp := range oldIPP {
if ipp.IP != derpMagicIPAddr && c.addrsByUDP[ipp] != a {
delete(c.addrsByUDP, ipp)
}
}
return a, nil
}
func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, addr *net.UDPAddr) conn.Endpoint {
// Pre-disco: look up their addrSet.
if as, ok := c.addrsByUDP[ipp]; ok {
return as
}
// Pre-disco: the peer that sent this packet has roamed beyond
// the knowledge provided by the control server. If the
// packet is valid wireguard will call UpdateDst on the
// original endpoint using this addr.
return (*singleEndpoint)(addr)
}
func (c *Conn) resetAddrSetStatesLocked() {
for _, as := range c.addrsByKey {
as.curAddr = -1
as.stopSpray = as.timeNow().Add(sprayPeriod)
}
}
func (c *Conn) sendSingleEndpoint(b []byte, se *singleEndpoint) error {
addr := (*net.UDPAddr)(se)
if addr.IP.Equal(derpMagicIP) {
c.logf("magicsock: [unexpected] DERP BUG: attempting to send packet to DERP address %v", addr)
return nil
}
_, err := c.sendUDPStd(addr, b)
return err
}
func (c *Conn) sendAddrSet(b []byte, as *addrSet) error {
var addrBuf [8]netaddr.IPPort
dsts, roamAddr := as.appendDests(addrBuf[:0], b)
if len(dsts) == 0 {
return errNoDestinations
}
var success bool
var ret error
for _, addr := range dsts {
sent, err := c.sendAddr(addr, as.publicKey, b)
if sent {
success = true
} else if ret == nil {
ret = err
}
if err != nil && addr != roamAddr && c.sendLogLimit.Allow() {
if c.connCtx.Err() == nil { // don't log if we're closed
c.logf("magicsock: Conn.Send(%v): %v", addr, err)
}
}
}
if success {
return nil
}
return ret
}
func shouldSprayPacket(b []byte) bool {
if len(b) < 4 {
return false
}
msgType := binary.LittleEndian.Uint32(b[:4])
switch msgType {
case device.MessageInitiationType,
device.MessageResponseType,
device.MessageCookieReplyType: // TODO: necessary?
return true
}
return false
}
const sprayPeriod = 3 * time.Second
// appendDests appends to dsts the destinations that b should be
// written to in order to reach as. Some of the returned IPPorts may
// be fake addrs representing DERP servers.
//
// It also returns as's current roamAddr, if any.
func (as *addrSet) appendDests(dsts []netaddr.IPPort, b []byte) (_ []netaddr.IPPort, roamAddr netaddr.IPPort) {
spray := shouldSprayPacket(b) // true for handshakes
now := as.timeNow()
as.mu.Lock()
defer as.mu.Unlock()
as.lastSend = now
// Some internal invariant checks.
if len(as.addrs) != len(as.ipPorts) {
panic(fmt.Sprintf("lena %d != leni %d", len(as.addrs), len(as.ipPorts)))
}
if n1, n2 := as.roamAddr != nil, as.roamAddrStd != nil; n1 != n2 {
panic(fmt.Sprintf("roamnil %v != roamstdnil %v", n1, n2))
}
// Spray logic.
//
// After exchanging a handshake with a peer, we send some outbound
// packets to every endpoint of that peer. These packets are spaced out
// over several seconds to make sure that our peer has an opportunity to
// send its own spray packet to us before we are done spraying.
//
// Multiple packets are necessary because we have to both establish the
// NAT mappings between two peers *and use* the mappings to switch away
// from DERP to a higher-priority UDP endpoint.
const sprayFreq = 250 * time.Millisecond
if spray {
as.lastSpray = now
as.stopSpray = now.Add(sprayPeriod)
// Reset our favorite route on new handshakes so we
// can downgrade to a worse path if our better path
// goes away. (https://github.com/tailscale/tailscale/issues/92)
as.curAddr = -1
} else if now.Before(as.stopSpray) {
// We are in the spray window. If it has been sprayFreq since we
// last sprayed a packet, spray this packet.
if now.Sub(as.lastSpray) >= sprayFreq {
spray = true
as.lastSpray = now
}
}
// Pick our destination address(es).
switch {
case spray:
// This packet is being sprayed to all addresses.
for i := range as.ipPorts {
dsts = append(dsts, as.ipPorts[i])
}
if as.roamAddr != nil {
dsts = append(dsts, *as.roamAddr)
}
case as.roamAddr != nil:
// We have a roaming address, prefer it over other addrs.
// TODO(danderson): this is not correct, there's no reason
// roamAddr should be special like this.
dsts = append(dsts, *as.roamAddr)
case as.curAddr != -1:
if as.curAddr >= len(as.addrs) {
as.Logf("[unexpected] magicsock bug: as.curAddr >= len(as.addrs): %d >= %d", as.curAddr, len(as.addrs))
break
}
// No roaming addr, but we've seen packets from a known peer
// addr, so keep using that one.
dsts = append(dsts, as.ipPorts[as.curAddr])
default:
// We know nothing about how to reach this peer, and we're not
// spraying. Use the first address in the array, which will
// usually be a DERP address that guarantees connectivity.
if len(as.ipPorts) > 0 {
dsts = append(dsts, as.ipPorts[0])
}
}
if logPacketDests {
as.Logf("spray=%v; roam=%v; dests=%v", spray, as.roamAddr, dsts)
}
if as.roamAddr != nil {
roamAddr = *as.roamAddr
}
return dsts, roamAddr
}
// addrSet is a set of UDP addresses that implements wireguard/conn.Endpoint.
//
// This is the legacy endpoint for peers that don't support discovery;
// it predates discoEndpoint.
type addrSet struct {
publicKey key.Public // peer public key used for DERP communication
// addrs is an ordered priority list provided by wgengine,
// sorted from expensive+slow+reliable at the begnining to
// fast+cheap at the end. More concretely, it's typically:
//
// [DERP fakeip:node, Global IP:port, LAN ip:port]
//
// But there could be multiple or none of each.
addrs []net.UDPAddr
ipPorts []netaddr.IPPort // same as addrs, in different form
// clock, if non-nil, is used in tests instead of time.Now.
clock func() time.Time
Logf logger.Logf // must not be nil
mu sync.Mutex // guards following fields
lastSend time.Time
// roamAddr is non-nil if/when we receive a correctly signed
// WireGuard packet from an unexpected address. If so, we
// remember it and send responses there in the future, but
// this should hopefully never be used (or at least used
// rarely) in the case that all the components of Tailscale
// are correctly learning/sharing the network map details.
roamAddr *netaddr.IPPort
roamAddrStd *net.UDPAddr
// curAddr is an index into addrs of the highest-priority
// address a valid packet has been received from so far.
// If no valid packet from addrs has been received, curAddr is -1.
curAddr int
// stopSpray is the time after which we stop spraying packets.
stopSpray time.Time
// lastSpray is the last time we sprayed a packet.
lastSpray time.Time
// loggedLogPriMask is a bit field of that tracks whether
// we've already logged about receiving a packet from a low
// priority ("low-pri") address when we already have curAddr
// set to a better one. This is only to suppress some
// redundant logs.
loggedLogPriMask uint32
}
// derpID returns this addrSet's home DERP node, or 0 if none is found.
func (as *addrSet) derpID() int {
for _, ua := range as.addrs {
if ua.IP.Equal(derpMagicIP) {
return ua.Port
}
}
return 0
}
func (as *addrSet) timeNow() time.Time {
if as.clock != nil {
return as.clock()
}
return time.Now()
}
var noAddr, _ = netaddr.FromStdAddr(net.ParseIP("127.127.127.127"), 127, "")
func (a *addrSet) dst() netaddr.IPPort {
a.mu.Lock()
defer a.mu.Unlock()
if a.roamAddr != nil {
return *a.roamAddr
}
if len(a.addrs) == 0 {
return noAddr
}
i := a.curAddr
if i == -1 {
i = 0
}
return a.ipPorts[i]
}
// packUDPAddr packs a UDPAddr in the form wanted by WireGuard.
func packUDPAddr(ua *net.UDPAddr) []byte {
ip := ua.IP.To4()
if ip == nil {
ip = ua.IP
}
b := make([]byte, 0, len(ip)+2)
b = append(b, ip...)
b = append(b, byte(ua.Port))
b = append(b, byte(ua.Port>>8))
return b
}
func (a *addrSet) DstToBytes() []byte {
return packIPPort(a.dst())
}
func (a *addrSet) DstToString() string {
dst := a.dst()
return dst.String()
}
func (a *addrSet) DstIP() net.IP {
return a.dst().IP.IPAddr().IP // TODO: add netaddr accessor to cut an alloc here?
}
func (a *addrSet) SrcIP() net.IP { return nil }
func (a *addrSet) SrcToString() string { return "" }
func (a *addrSet) ClearSrc() {}
func (a *addrSet) UpdateDst(new *net.UDPAddr) error {
if new.IP.Equal(derpMagicIP) {
// Never consider DERP addresses as a viable candidate for
// either curAddr or roamAddr. It's only ever a last resort
// choice, never a preferred choice.
// This is a hot path for established connections.
return nil
}
a.mu.Lock()
defer a.mu.Unlock()
if a.roamAddrStd != nil && equalUDPAddr(new, a.roamAddrStd) {
// Packet from the current roaming address, no logging.
// This is a hot path for established connections.
return nil
}
if a.roamAddr == nil && a.curAddr >= 0 && equalUDPAddr(new, &a.addrs[a.curAddr]) {
// Packet from current-priority address, no logging.
// This is a hot path for established connections.
return nil
}
newa, ok := netaddr.FromStdAddr(new.IP, new.Port, new.Zone)
if !ok {
return nil
}
index := -1
for i := range a.addrs {
if equalUDPAddr(new, &a.addrs[i]) {
index = i
break
}
}
publicKey := wgkey.Key(a.publicKey)
pk := publicKey.ShortString()
old := "<none>"
if a.curAddr >= 0 {
old = a.addrs[a.curAddr].String()
}
switch {
case index == -1:
if a.roamAddr == nil {
a.Logf("magicsock: rx %s from roaming address %s, set as new priority", pk, new)
} else {
a.Logf("magicsock: rx %s from roaming address %s, replaces roaming address %s", pk, new, a.roamAddr)
}
a.roamAddr = &newa
a.roamAddrStd = new
case a.roamAddr != nil:
a.Logf("magicsock: rx %s from known %s (%d), replaces roaming address %s", pk, new, index, a.roamAddr)
a.roamAddr = nil
a.roamAddrStd = nil
a.curAddr = index
a.loggedLogPriMask = 0
case a.curAddr == -1:
a.Logf("magicsock: rx %s from %s (%d/%d), set as new priority", pk, new, index, len(a.addrs))
a.curAddr = index
a.loggedLogPriMask = 0
case index < a.curAddr:
if 1 <= index && index <= 32 && (a.loggedLogPriMask&1<<(index-1)) == 0 {
a.Logf("magicsock: rx %s from low-pri %s (%d), keeping current %s (%d)", pk, new, index, old, a.curAddr)
a.loggedLogPriMask |= 1 << (index - 1)
}
default: // index > a.curAddr
a.Logf("magicsock: rx %s from %s (%d/%d), replaces old priority %s", pk, new, index, len(a.addrs), old)
a.curAddr = index
a.loggedLogPriMask = 0
}
return nil
}
func equalUDPAddr(x, y *net.UDPAddr) bool {
return x.Port == y.Port && x.IP.Equal(y.IP)
}
func (a *addrSet) String() string {
a.mu.Lock()
defer a.mu.Unlock()
buf := new(strings.Builder)
buf.WriteByte('[')
if a.roamAddr != nil {
buf.WriteString("roam:")
sbPrintAddr(buf, *a.roamAddrStd)
}
for i, addr := range a.addrs {
if i > 0 || a.roamAddr != nil {
buf.WriteString(", ")
}
sbPrintAddr(buf, addr)
if a.curAddr == i {
buf.WriteByte('*')
}
}
buf.WriteByte(']')
return buf.String()
}
func (as *addrSet) populatePeerStatus(ps *ipnstate.PeerStatus) {
as.mu.Lock()
defer as.mu.Unlock()
ps.LastWrite = as.lastSend
for i, ua := range as.addrs {
if ua.IP.Equal(derpMagicIP) {
continue
}
uaStr := ua.String()
ps.Addrs = append(ps.Addrs, uaStr)
if as.curAddr == i {
ps.CurAddr = uaStr
}
}
if as.roamAddr != nil {
ps.CurAddr = udpAddrDebugString(*as.roamAddrStd)
}
}
func (a *addrSet) Addrs() []wgcfg.Endpoint {
var eps []wgcfg.Endpoint
for _, addr := range a.addrs {
eps = append(eps, wgcfg.Endpoint{
Host: addr.IP.String(),
Port: uint16(addr.Port),
})
}
a.mu.Lock()
defer a.mu.Unlock()
if a.roamAddr != nil {
eps = append(eps, wgcfg.Endpoint{
Host: a.roamAddr.IP.String(),
Port: uint16(a.roamAddr.Port),
})
}
return eps
}
// singleEndpoint is a wireguard-go/conn.Endpoint used for "roaming
// addressed" in releases of Tailscale that predate discovery
// messages. New peers use discoEndpoint.
type singleEndpoint net.UDPAddr
func (e *singleEndpoint) ClearSrc() {}
func (e *singleEndpoint) DstIP() net.IP { return (*net.UDPAddr)(e).IP }
func (e *singleEndpoint) SrcIP() net.IP { return nil }
func (e *singleEndpoint) SrcToString() string { return "" }
func (e *singleEndpoint) DstToString() string { return (*net.UDPAddr)(e).String() }
func (e *singleEndpoint) DstToBytes() []byte { return packUDPAddr((*net.UDPAddr)(e)) }
func (e *singleEndpoint) UpdateDst(dst *net.UDPAddr) error {
return fmt.Errorf("magicsock.singleEndpoint(%s).UpdateDst(%s): should never be called", (*net.UDPAddr)(e), dst)
}
func (e *singleEndpoint) Addrs() []wgcfg.Endpoint {
return []wgcfg.Endpoint{{
Host: e.IP.String(),
Port: uint16(e.Port),
}}
}

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,7 @@ import (
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/nettype"
"tailscale.com/types/wgkey"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/tstun"
)
@@ -56,7 +57,7 @@ func init() {
// conditions in tests. In particular, you can't expect two test magicsocks
// to be able to connect to each other through a test DERP unless they are
// both fully initialized before you try.
func (c *Conn) WaitReady(t *testing.T) {
func (c *Conn) WaitReady(t testing.TB) {
t.Helper()
timer := time.NewTimer(10 * time.Second)
defer timer.Stop()
@@ -119,7 +120,7 @@ func runDERPAndStun(t *testing.T, logf logger.Logf, l nettype.PacketListener, st
// necessary to send and receive packets to test e2e wireguard
// happiness.
type magicStack struct {
privateKey wgcfg.PrivateKey
privateKey wgkey.Private
epCh chan []string // endpoint updates produced by this peer
conn *Conn // the magicsock itself
tun *tuntest.ChannelTUN // TUN device to send/receive packets
@@ -130,10 +131,10 @@ type magicStack struct {
// newMagicStack builds and initializes an idle magicsock and
// friends. You need to call conn.SetNetworkMap and dev.Reconfig
// before anything interesting happens.
func newMagicStack(t *testing.T, logf logger.Logf, l nettype.PacketListener, derpMap *tailcfg.DERPMap) *magicStack {
func newMagicStack(t testing.TB, logf logger.Logf, l nettype.PacketListener, derpMap *tailcfg.DERPMap) *magicStack {
t.Helper()
privateKey, err := wgcfg.NewPrivateKey()
privateKey, err := wgkey.NewPrivate()
if err != nil {
t.Fatalf("generating private key: %v", err)
}
@@ -248,13 +249,13 @@ func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) {
nm := &controlclient.NetworkMap{
PrivateKey: me.privateKey,
NodeKey: tailcfg.NodeKey(me.privateKey.Public()),
Addresses: []wgcfg.CIDR{{IP: wgcfg.IPv4(1, 0, 0, byte(myIdx+1)), Mask: 32}},
Addresses: []netaddr.IPPrefix{{IP: netaddr.IPv4(1, 0, 0, byte(myIdx+1)), Bits: 32}},
}
for i, peer := range ms {
if i == myIdx {
continue
}
addrs := []wgcfg.CIDR{{IP: wgcfg.IPv4(1, 0, 0, byte(i+1)), Mask: 32}}
addrs := []netaddr.IPPrefix{{IP: netaddr.IPv4(1, 0, 0, byte(i+1)), Bits: 32}}
peer := &tailcfg.Node{
ID: tailcfg.NodeID(i + 1),
Name: fmt.Sprintf("node%d", i+1),
@@ -347,7 +348,7 @@ func TestNewConn(t *testing.T) {
}
defer conn.Close()
conn.SetDERPMap(stuntest.DERPMapOf(stunAddr.String()))
conn.SetPrivateKey(wgcfg.PrivateKey(key.NewPrivate()))
conn.SetPrivateKey(wgkey.Private(key.NewPrivate()))
conn.Start()
go func() {
@@ -377,7 +378,7 @@ collectEndpoints:
}
}
func pickPort(t *testing.T) uint16 {
func pickPort(t testing.TB) uint16 {
t.Helper()
conn, err := net.ListenPacket("udp4", "127.0.0.1:0")
if err != nil {
@@ -442,8 +443,8 @@ func TestPickDERPFallback(t *testing.T) {
// But move if peers are elsewhere.
const otherNode = 789
c.addrsByKey = map[key.Public]*AddrSet{
key.Public{1}: &AddrSet{addrs: []net.UDPAddr{{IP: derpMagicIP, Port: otherNode}}},
c.addrsByKey = map[key.Public]*addrSet{
key.Public{1}: &addrSet{addrs: []net.UDPAddr{{IP: derpMagicIP, Port: otherNode}}},
}
if got := c.pickDERPFallback(); got != otherNode {
t.Errorf("didn't join peers: got %v; want %v", got, someNode)
@@ -454,16 +455,16 @@ func makeConfigs(t *testing.T, addrs []netaddr.IPPort) []wgcfg.Config {
t.Helper()
var privKeys []wgcfg.PrivateKey
var addresses [][]wgcfg.CIDR
var addresses [][]netaddr.IPPrefix
for i := range addrs {
privKey, err := wgcfg.NewPrivateKey()
privKey, err := wgkey.NewPrivate()
if err != nil {
t.Fatal(err)
}
privKeys = append(privKeys, privKey)
privKeys = append(privKeys, wgcfg.PrivateKey(privKey))
addresses = append(addresses, []wgcfg.CIDR{
addresses = append(addresses, []netaddr.IPPrefix{
parseCIDR(t, fmt.Sprintf("1.0.0.%d/32", i+1)),
})
}
@@ -496,9 +497,9 @@ func makeConfigs(t *testing.T, addrs []netaddr.IPPort) []wgcfg.Config {
return cfgs
}
func parseCIDR(t *testing.T, addr string) wgcfg.CIDR {
func parseCIDR(t *testing.T, addr string) netaddr.IPPrefix {
t.Helper()
cidr, err := wgcfg.ParseCIDR(addr)
cidr, err := netaddr.ParseIPPrefix(addr)
if err != nil {
t.Fatal(err)
}
@@ -1075,7 +1076,7 @@ func testTwoDevicePing(t *testing.T, d *devices) {
})
}
// TestAddrSet tests AddrSet appendDests and UpdateDst.
// TestAddrSet tests addrSet appendDests and UpdateDst.
func TestAddrSet(t *testing.T) {
tstest.PanicOnLog()
rc := tstest.NewResourceCheck()
@@ -1134,13 +1135,13 @@ func TestAddrSet(t *testing.T) {
}
tests := []struct {
name string
as *AddrSet
as *addrSet
steps []step
logCheck func(t *testing.T, logged []byte)
}{
{
name: "reg_packet_no_curaddr",
as: &AddrSet{
as: &addrSet{
addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"),
curAddr: -1, // unknown
roamAddr: nil,
@@ -1151,7 +1152,7 @@ func TestAddrSet(t *testing.T) {
},
{
name: "reg_packet_have_curaddr",
as: &AddrSet{
as: &addrSet{
addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"),
curAddr: 1, // global IP
roamAddr: nil,
@@ -1162,7 +1163,7 @@ func TestAddrSet(t *testing.T) {
},
{
name: "reg_packet_have_roamaddr",
as: &AddrSet{
as: &addrSet{
addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"),
curAddr: 2, // should be ignored
roamAddr: mustIPPortPtr("5.6.7.8:123"),
@@ -1175,7 +1176,7 @@ func TestAddrSet(t *testing.T) {
},
{
name: "start_roaming",
as: &AddrSet{
as: &addrSet{
addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"),
curAddr: 2,
},
@@ -1191,7 +1192,7 @@ func TestAddrSet(t *testing.T) {
},
{
name: "spray_packet",
as: &AddrSet{
as: &addrSet{
addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"),
curAddr: 2, // should be ignored
roamAddr: mustIPPortPtr("5.6.7.8:123"),
@@ -1207,7 +1208,7 @@ func TestAddrSet(t *testing.T) {
},
{
name: "low_pri",
as: &AddrSet{
as: &addrSet{
addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"),
curAddr: 2,
},
@@ -1254,9 +1255,9 @@ func TestAddrSet(t *testing.T) {
}
}
// initAddrSet initializes fields in the provided incomplete AddrSet
// initAddrSet initializes fields in the provided incomplete addrSet
// to satisfying invariants within magicsock.
func initAddrSet(as *AddrSet) {
func initAddrSet(as *addrSet) {
if as.roamAddr != nil && as.roamAddrStd == nil {
as.roamAddrStd = as.roamAddr.UDPAddr()
}
@@ -1344,3 +1345,75 @@ func TestDiscoEndpointAlignment(t *testing.T) {
t.Error("expected false on second call")
}
}
func BenchmarkReceiveFrom(b *testing.B) {
port := pickPort(b)
conn, err := NewConn(Options{
Logf: b.Logf,
Port: port,
EndpointsFunc: func(eps []string) {
b.Logf("endpoints: %q", eps)
},
})
if err != nil {
b.Fatal(err)
}
defer conn.Close()
sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0")
if err != nil {
b.Fatal(err)
}
defer sendConn.Close()
var dstAddr net.Addr = conn.pconn4.LocalAddr()
sendBuf := make([]byte, 1<<10)
for i := range sendBuf {
sendBuf[i] = 'x'
}
buf := make([]byte, 2<<10)
for i := 0; i < b.N; i++ {
if _, err := sendConn.WriteTo(sendBuf, dstAddr); err != nil {
b.Fatalf("WriteTo: %v", err)
}
n, ep, addr, err := conn.ReceiveIPv4(buf)
if err != nil {
b.Fatal(err)
}
_ = n
_ = ep
_ = addr
}
}
func BenchmarkReceiveFrom_Native(b *testing.B) {
recvConn, err := net.ListenPacket("udp4", "127.0.0.1:0")
if err != nil {
b.Fatal(err)
}
defer recvConn.Close()
recvConnUDP := recvConn.(*net.UDPConn)
sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0")
if err != nil {
b.Fatal(err)
}
defer sendConn.Close()
var dstAddr net.Addr = recvConn.LocalAddr()
sendBuf := make([]byte, 1<<10)
for i := range sendBuf {
sendBuf[i] = 'x'
}
buf := make([]byte, 2<<10)
for i := 0; i < b.N; i++ {
if _, err := sendConn.WriteTo(sendBuf, dstAddr); err != nil {
b.Fatalf("WriteTo: %v", err)
}
if _, _, err := recvConnUDP.ReadFromUDP(buf); err != nil {
b.Fatalf("ReadFromUDP: %v", err)
}
}
}

View File

@@ -52,10 +52,7 @@ func newOSMon(logf logger.Logf) (osMon, error) {
return &nlConn{logf: logf, conn: conn}, nil
}
func (c *nlConn) Close() error {
c.conn.SetDeadline(time.Unix(0, 0)) // abort any Receive in flight
return c.conn.Close()
}
func (c *nlConn) Close() error { return c.conn.Close() }
func (c *nlConn) Receive() (message, error) {
if len(c.buffered) == 0 {

View File

@@ -66,7 +66,7 @@ type ManagerConfig struct {
// Logf is the logger for the manager to use.
// It is wrapped with a "dns: " prefix.
Logf logger.Logf
// InterfaceNAme is the name of the interface with which DNS settings should be associated.
// InterfaceName is the name of the interface with which DNS settings should be associated.
InterfaceName string
// Cleanup indicates that the manager is created for cleanup only.
// A no-op manager will be instantiated if the system needs no cleanup.

View File

@@ -11,8 +11,6 @@ import (
"syscall"
"time"
"github.com/tailscale/wireguard-go/tun"
wgregistry "github.com/tailscale/wireguard-go/tun/wintun/registry"
"golang.org/x/sys/windows/registry"
"tailscale.com/types/logger"
)
@@ -31,7 +29,7 @@ type windowsManager struct {
func newManager(mconfig ManagerConfig) managerImpl {
return windowsManager{
logf: mconfig.Logf,
guid: tun.WintunGUID,
guid: mconfig.InterfaceName,
}
}
@@ -39,10 +37,10 @@ func newManager(mconfig ManagerConfig) managerImpl {
// appear. For some reason, registry keys tied to ephemeral interfaces
// can take a long while to appear after interface creation, and we
// can end up racing with that.
const keyOpenTimeout = time.Minute
const keyOpenTimeout = 20 * time.Second
func setRegistryString(path, name, value string) error {
key, err := wgregistry.OpenKeyWait(registry.LOCAL_MACHINE, path, registry.SET_VALUE, keyOpenTimeout)
key, err := openKeyWait(registry.LOCAL_MACHINE, path, registry.SET_VALUE, keyOpenTimeout)
if err != nil {
return fmt.Errorf("opening %s: %w", path, err)
}

View File

@@ -10,30 +10,14 @@ import (
"bufio"
"bytes"
"context"
"encoding/binary"
"fmt"
"os"
"os/exec"
"unsafe"
"github.com/godbus/dbus/v5"
"tailscale.com/util/endian"
)
var nativeEndian binary.ByteOrder
func init() {
// TODO(dmytro): use DBus endianness flag when available.
// A more elegant way to do this is by looking at the first byte of a raw DBus message.
// However, that requires a change in godbus, which has slow maintainer response.
i := uint32(1)
p := unsafe.Pointer(&i)
if *(*byte)(p) == 1 {
nativeEndian = binary.LittleEndian
} else {
nativeEndian = binary.BigEndian
}
}
// isNMActive determines if NetworkManager is currently managing system DNS settings.
func isNMActive() bool {
// This is somewhat tricky because NetworkManager supports a number
@@ -145,7 +129,7 @@ func (m nmManager) Up(config Config) error {
for _, ip := range config.Nameservers {
b := ip.As16()
if ip.Is4() {
dnsv4 = append(dnsv4, nativeEndian.Uint32(b[12:]))
dnsv4 = append(dnsv4, endian.Native.Uint32(b[12:]))
} else {
dnsv6 = append(dnsv6, b[:])
}

View File

@@ -0,0 +1,76 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// The code in this file originates from https://git.zx2c4.com/wireguard-go:
// Copyright (C) 2017-2020 WireGuard LLC. All Rights Reserved.
// Copying license: https://git.zx2c4.com/wireguard-go/tree/COPYING
package dns
import (
"fmt"
"runtime"
"strings"
"time"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
func openKeyWait(k registry.Key, path string, access uint32, timeout time.Duration) (registry.Key, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
deadline := time.Now().Add(timeout)
pathSpl := strings.Split(path, "\\")
for i := 0; ; i++ {
keyName := pathSpl[i]
isLast := i+1 == len(pathSpl)
event, err := windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
return 0, fmt.Errorf("windows.CreateEvent: %v", err)
}
defer windows.CloseHandle(event)
var key registry.Key
for {
err = windows.RegNotifyChangeKeyValue(windows.Handle(k), false, windows.REG_NOTIFY_CHANGE_NAME, event, true)
if err != nil {
return 0, fmt.Errorf("windows.RegNotifyChangeKeyValue: %v", err)
}
var accessFlags uint32
if isLast {
accessFlags = access
} else {
accessFlags = registry.NOTIFY
}
key, err = registry.OpenKey(k, keyName, accessFlags)
if err == windows.ERROR_FILE_NOT_FOUND || err == windows.ERROR_PATH_NOT_FOUND {
timeout := time.Until(deadline) / time.Millisecond
if timeout < 0 {
timeout = 0
}
s, err := windows.WaitForSingleObject(event, uint32(timeout))
if err != nil {
return 0, fmt.Errorf("windows.WaitForSingleObject: %v", err)
}
if s == uint32(windows.WAIT_TIMEOUT) { // windows.WAIT_TIMEOUT status const is misclassified as error in golang.org/x/sys/windows
return 0, fmt.Errorf("timeout waiting for registry key")
}
} else if err != nil {
return 0, fmt.Errorf("registry.OpenKey(%v): %v", path, err)
} else {
if isLast {
return key, nil
}
defer key.Close()
break
}
}
k = key
}
}

View File

@@ -39,12 +39,8 @@ import (
// address a few rare corner cases, but is unlikely to significantly
// help with MTU issues compared to a static 1280B implementation.
func monitorDefaultRoutes(tun *tun.NativeTun) (*winipcfg.RouteChangeCallback, error) {
guid := tun.GUID()
ourLuid, err := winipcfg.LUIDFromGUID(&guid)
ourLuid := winipcfg.LUID(tun.LUID())
lastMtu := uint32(0)
if err != nil {
return nil, fmt.Errorf("error mapping GUID %v to LUID: %w", guid, err)
}
doIt := func() error {
mtu, err := getDefaultRouteMTU()
if err != nil {
@@ -91,7 +87,7 @@ func monitorDefaultRoutes(tun *tun.NativeTun) (*winipcfg.RouteChangeCallback, er
}
return nil
}
err = doIt()
err := doIt()
if err != nil {
return nil, err
}
@@ -159,7 +155,7 @@ func getDefaultRouteMTU() (uint32, error) {
// setPrivateNetwork marks the provided network adapter's category to private.
// It returns (false, nil) if the adapter was not found.
func setPrivateNetwork(ifcGUID *windows.GUID) (bool, error) {
func setPrivateNetwork(ifcLUID winipcfg.LUID) (bool, error) {
// NLM_NETWORK_CATEGORY values.
const (
categoryPublic = 0
@@ -167,6 +163,11 @@ func setPrivateNetwork(ifcGUID *windows.GUID) (bool, error) {
categoryDomain = 2
)
ifcGUID, err := ifcLUID.GUID()
if err != nil {
return false, fmt.Errorf("ifcLUID.GUID: %v", err)
}
// Lock OS thread when using OLE, which seems to be a requirement
// from the Microsoft docs. go-ole doesn't seem to handle it automatically.
// https://github.com/tailscale/tailscale/issues/921#issuecomment-727526807
@@ -222,12 +223,8 @@ func setPrivateNetwork(ifcGUID *windows.GUID) (bool, error) {
return false, nil
}
// interfaceFromGUID returns IPAdapterAddresses with specified GUID.
func interfaceFromGUID(guid *windows.GUID, flags winipcfg.GAAFlags) (*winipcfg.IPAdapterAddresses, error) {
luid, err := winipcfg.LUIDFromGUID(guid)
if err != nil {
return nil, err
}
// interfaceFromLUID returns IPAdapterAddresses with specified LUID.
func interfaceFromLUID(luid winipcfg.LUID, flags winipcfg.GAAFlags) (*winipcfg.IPAdapterAddresses, error) {
addresses, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, flags)
if err != nil {
return nil, err
@@ -237,13 +234,13 @@ func interfaceFromGUID(guid *windows.GUID, flags winipcfg.GAAFlags) (*winipcfg.I
return addr, nil
}
}
return nil, fmt.Errorf("interfaceFromGUID: interface with LUID %v (from GUID %v) not found", luid, guid)
return nil, fmt.Errorf("interfaceFromLUID: interface with LUID %v not found", luid)
}
func configureInterface(cfg *Config, tun *tun.NativeTun) error {
const mtu = 0
guid := tun.GUID()
iface, err := interfaceFromGUID(&guid,
luid := winipcfg.LUID(tun.LUID())
iface, err := interfaceFromLUID(luid,
// Issue 474: on early boot, when the network is still
// coming up, if the Tailscale service comes up first,
// the Tailscale adapter it finds might not have the
@@ -260,7 +257,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error {
// does.
const tries = 20
for i := 0; i < tries; i++ {
found, err := setPrivateNetwork(&guid)
found, err := setPrivateNetwork(luid)
if err != nil {
log.Printf("setPrivateNetwork(try=%d): %v", i, err)
} else {
@@ -271,7 +268,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error {
}
time.Sleep(1 * time.Second)
}
log.Printf("setPrivateNetwork: adapter %v not found after %d tries, giving up", guid, tries)
log.Printf("setPrivateNetwork: adapter LUID %v not found after %d tries, giving up", luid, tries)
}()
var firstGateway4 *net.IP
@@ -353,7 +350,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) error {
}
// Re-read interface after syncAddresses.
iface, err = interfaceFromGUID(&guid,
iface, err = interfaceFromLUID(luid,
// Issue 474: on early boot, when the network is still
// coming up, if the Tailscale service comes up first,
// the Tailscale adapter it finds might not have the

View File

@@ -21,16 +21,16 @@ type fakeRouter struct {
}
func (r fakeRouter) Up() error {
r.logf("Warning: fakeRouter.Up: not implemented.")
r.logf("[v1] warning: fakeRouter.Up: not implemented.")
return nil
}
func (r fakeRouter) Set(cfg *Config) error {
r.logf("Warning: fakeRouter.Set: not implemented.")
r.logf("[v1] warning: fakeRouter.Set: not implemented.")
return nil
}
func (r fakeRouter) Close() error {
r.logf("Warning: fakeRouter.Close: not implemented.")
r.logf("[v1] warning: fakeRouter.Close: not implemented.")
return nil
}

View File

@@ -6,6 +6,7 @@ package router
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
@@ -113,8 +114,15 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) (
return nil, err
}
supportsV6 := supportsV6()
v6err := checkIPv6()
if v6err != nil {
logf("disabling IPv6 due to system IPv6 config: %v", v6err)
}
supportsV6 := v6err == nil
supportsV6NAT := supportsV6 && supportsV6NAT()
if supportsV6 {
logf("v6nat = %v", supportsV6NAT)
}
var ipt6 netfilterRunner
if supportsV6 {
@@ -1003,46 +1011,53 @@ func cleanup(logf logger.Logf, interfaceName string) {
// TODO(dmytro): clean up iptables.
}
// supportsV6 returns whether the system appears to have a working
// IPv6 network stack.
func supportsV6() bool {
// checkIPv6 checks whether the system appears to have a working IPv6
// network stack. It returns an error explaining what looks wrong or
// missing. It does not check that IPv6 is currently functional or
// that there's a global address, just that the system would support
// IPv6 if it were on an IPv6 network.
func checkIPv6() error {
_, err := os.Stat("/proc/sys/net/ipv6")
if os.IsNotExist(err) {
return false
return err
}
bs, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6")
if err != nil {
// Be conservative if we can't find the ipv6 configuration knob.
return false
return err
}
disabled, err := strconv.ParseBool(strings.TrimSpace(string(bs)))
if err != nil {
return false
return errors.New("disable_ipv6 has invalid bool")
}
if disabled {
return false
return errors.New("disable_ipv6 is set")
}
// Older kernels don't support IPv6 policy routing.
bs, err = ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_policy")
if err != nil {
// Absent knob means policy routing is unsupported.
return false
return err
}
disabled, err = strconv.ParseBool(strings.TrimSpace(string(bs)))
if err != nil {
return false
return errors.New("disable_policy has invalid bool")
}
if disabled {
return false
return errors.New("disable_policy is set")
}
// Some distros ship ip6tables separately from iptables.
if _, err := exec.LookPath("ip6tables"); err != nil {
return false
return err
}
return true
if err := checkIPRuleSupportsV6(); err != nil {
return err
}
return nil
}
// supportsV6NAT returns whether the system has a "nat" table in the
@@ -1060,3 +1075,22 @@ func supportsV6NAT() bool {
return bytes.Contains(bs, []byte("nat\n"))
}
func checkIPRuleSupportsV6() error {
// First add a rule for "ip rule del" to delete.
// We ignore the "add" operation's error because it can also
// fail if the rule already exists.
exec.Command("ip", "-6", "rule", "add",
"pref", "123", "fwmark", tailscaleBypassMark, "table", fmt.Sprint(tailscaleRouteTable)).Run()
out, err := exec.Command("ip", "-6", "rule", "del",
"pref", "123", "fwmark", tailscaleBypassMark, "table", fmt.Sprint(tailscaleRouteTable)).CombinedOutput()
if err != nil {
out = bytes.TrimSpace(out)
var detail interface{} = out
if len(out) == 0 {
detail = err.Error()
}
return fmt.Errorf("ip -6 rule failed: %s", detail)
}
return nil
}

View File

@@ -81,7 +81,7 @@ func (r *openbsdRouter) Set(cfg *Config) error {
var errq error
if localAddr != r.local {
if r.local != (netaddr.IPPrefix{}) {
if !r.local.IsZero() {
addrdel := []string{"ifconfig", r.tunname,
"inet", r.local.String(), "-alias"}
out, err := cmd(addrdel...).CombinedOutput()

View File

@@ -81,7 +81,7 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error {
// Update the address.
if localAddr != r.local {
// If the interface is already set, remove it.
if r.local != (netaddr.IPPrefix{}) {
if !r.local.IsZero() {
addrdel := []string{"ifconfig", r.tunname,
"inet", r.local.String(), "-alias"}
out, err := cmd(addrdel...).CombinedOutput()

View File

@@ -37,10 +37,15 @@ func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Devic
}
nativeTun := tundev.(*tun.NativeTun)
guid := nativeTun.GUID().String()
luid := winipcfg.LUID(nativeTun.LUID())
guid, err := luid.GUID()
if err != nil {
return nil, err
}
mconfig := dns.ManagerConfig{
Logf: logf,
InterfaceName: guid,
InterfaceName: guid.String(),
}
return &winRouter{

View File

@@ -381,22 +381,26 @@ func marshalResponse(resp *response) ([]byte, error) {
builder := dns.NewBuilder(nil, resp.Header)
err := builder.StartQuestions()
if err != nil {
return nil, err
}
isSuccess := resp.Header.RCode == dns.RCodeSuccess
err = builder.Question(resp.Question)
if err != nil {
return nil, err
if resp.Question.Type != 0 || isSuccess {
err := builder.StartQuestions()
if err != nil {
return nil, err
}
err = builder.Question(resp.Question)
if err != nil {
return nil, err
}
}
// Only successful responses contain answers.
if resp.Header.RCode != dns.RCodeSuccess {
if !isSuccess {
return builder.Finish()
}
err = builder.StartAnswers()
err := builder.StartAnswers()
if err != nil {
return nil, err
}
@@ -576,7 +580,11 @@ func (r *Resolver) respond(query []byte) ([]byte, error) {
err := parseQuery(query, resp)
// We will not return this error: it is the sender's fault.
if err != nil {
r.logf("parsing query: %v", err)
if errors.Is(err, dns.ErrSectionDone) {
r.logf("parseQuery(%02x): no DNS questions", query)
} else {
r.logf("parseQuery(%02x): %v", query, err)
}
resp.Header.RCode = dns.RCodeFormatError
return marshalResponse(resp)
}

View File

@@ -748,3 +748,13 @@ func BenchmarkFull(b *testing.B) {
})
}
}
func TestMarshalResponseFormatError(t *testing.T) {
resp := new(response)
resp.Header.RCode = dns.RCodeFormatError
v, err := marshalResponse(resp)
if err != nil {
t.Errorf("marshal error: %v", err)
}
t.Logf("response: %q", v)
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/types/logger"
"tailscale.com/wgengine/filter"
@@ -67,7 +68,7 @@ type TUN struct {
lastActivityAtomic int64 // unix seconds of last send or receive
destIPActivity atomic.Value // of map[packet.IP]func()
destIPActivity atomic.Value // of map[netaddr.IP]func()
// buffer stores the oldest unconsumed packet from tdev.
// It is made a static buffer in order to avoid allocations.
@@ -136,7 +137,7 @@ func WrapTUN(logf logger.Logf, tdev tun.Device) *TUN {
// destination (the map keys).
//
// The map ownership passes to the TUN. It must be non-nil.
func (t *TUN) SetDestIPActivityFuncs(m map[packet.IP4]func()) {
func (t *TUN) SetDestIPActivityFuncs(m map[netaddr.IP]func()) {
t.destIPActivity.Store(m)
}
@@ -282,8 +283,8 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) {
defer parsedPacketPool.Put(p)
p.Decode(buf[offset : offset+n])
if m, ok := t.destIPActivity.Load().(map[packet.IP4]func()); ok {
if fn := m[p.DstIP4]; fn != nil {
if m, ok := t.destIPActivity.Load().(map[netaddr.IP]func()); ok {
if fn := m[p.Dst.IP]; fn != nil {
fn()
}
}

View File

@@ -20,12 +20,20 @@ import (
"tailscale.com/wgengine/filter"
)
func udp(src, dst packet.IP4, sport, dport uint16) []byte {
func udp4(src, dst string, sport, dport uint16) []byte {
sip, err := netaddr.ParseIP(src)
if err != nil {
panic(err)
}
dip, err := netaddr.ParseIP(dst)
if err != nil {
panic(err)
}
header := &packet.UDP4Header{
IP4Header: packet.IP4Header{
SrcIP: src,
DstIP: dst,
IPID: 0,
Src: sip,
Dst: dip,
IPID: 0,
},
SrcPort: sport,
DstPort: dport,
@@ -252,12 +260,12 @@ func TestFilter(t *testing.T) {
}{
{"junk_in", in, true, []byte("\x45not a valid IPv4 packet")},
{"junk_out", out, true, []byte("\x45not a valid IPv4 packet")},
{"bad_port_in", in, true, udp(0x05060708, 0x01020304, 22, 22)},
{"bad_port_out", out, false, udp(0x01020304, 0x05060708, 22, 22)},
{"bad_ip_in", in, true, udp(0x08010101, 0x01020304, 89, 89)},
{"bad_ip_out", out, false, udp(0x01020304, 0x08010101, 98, 98)},
{"good_packet_in", in, false, udp(0x05060708, 0x01020304, 89, 89)},
{"good_packet_out", out, false, udp(0x01020304, 0x05060708, 98, 98)},
{"bad_port_in", in, true, udp4("5.6.7.8", "1.2.3.4", 22, 22)},
{"bad_port_out", out, false, udp4("1.2.3.4", "5.6.7.8", 22, 22)},
{"bad_ip_in", in, true, udp4("8.1.1.1", "1.2.3.4", 89, 89)},
{"bad_ip_out", out, false, udp4("1.2.3.4", "8.1.1.1", 98, 98)},
{"good_packet_in", in, false, udp4("5.6.7.8", "1.2.3.4", 89, 89)},
{"good_packet_out", out, false, udp4("1.2.3.4", "5.6.7.8", 98, 98)},
}
// A reader on the other end of the TUN.
@@ -337,7 +345,7 @@ func BenchmarkWrite(b *testing.B) {
ftun, tun := newFakeTUN(b.Logf, true)
defer tun.Close()
packet := udp(0x05060708, 0x01020304, 89, 89)
packet := udp4("5.6.7.8", "1.2.3.4", 89, 89)
for i := 0; i < b.N; i++ {
_, err := ftun.Write(packet, 0)
if err != nil {

View File

@@ -8,7 +8,6 @@ import (
"bufio"
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
@@ -38,6 +37,7 @@ import (
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/wgkey"
"tailscale.com/version"
"tailscale.com/version/distro"
"tailscale.com/wgengine/filter"
@@ -58,10 +58,9 @@ import (
// discovery.
const minimalMTU = 1280
const (
magicDNSIP = 0x64646464 // 100.100.100.100
magicDNSPort = 53
)
const magicDNSPort = 53
var magicDNSIP = netaddr.IPv4(100, 100, 100, 100)
// Lazy wireguard-go configuration parameters.
const (
@@ -99,7 +98,7 @@ type userspaceEngine struct {
// localAddrs is the set of IP addresses assigned to the local
// tunnel interface. It's used to reflect local packets
// incorrectly sent to us.
localAddrs atomic.Value // of map[packet.IP4]bool
localAddrs atomic.Value // of map[netaddr.IP]bool
wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below
lastCfgFull wgcfg.Config
@@ -108,16 +107,16 @@ type userspaceEngine struct {
lastEngineSigTrim string // of trimmed wireguard config
recvActivityAt map[tailcfg.DiscoKey]time.Time
trimmedDisco map[tailcfg.DiscoKey]bool // set of disco keys of peers currently excluded from wireguard config
sentActivityAt map[packet.IP4]*int64 // value is atomic int64 of unixtime
destIPActivityFuncs map[packet.IP4]func()
sentActivityAt map[netaddr.IP]*int64 // value is atomic int64 of unixtime
destIPActivityFuncs map[netaddr.IP]func()
mu sync.Mutex // guards following; see lock order comment below
closing bool // Close was called (even if we're still closing)
statusCallback StatusCallback
linkChangeCallback func(major bool, newState *interfaces.State)
peerSequence []wgcfg.Key
peerSequence []wgkey.Key
endpoints []string
pingers map[wgcfg.Key]*pinger // legacy pingers for pre-discovery peers
pingers map[wgkey.Key]*pinger // legacy pingers for pre-discovery peers
linkState *interfaces.State
// Lock ordering: magicsock.Conn.mu, wgLock, then mu.
@@ -204,9 +203,9 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
waitCh: make(chan struct{}),
tundev: tstun.WrapTUN(logf, conf.TUN),
resolver: tsdns.NewResolver(rconf),
pingers: make(map[wgcfg.Key]*pinger),
pingers: make(map[wgkey.Key]*pinger),
}
e.localAddrs.Store(map[packet.IP4]bool{})
e.localAddrs.Store(map[netaddr.IP]bool{})
e.linkState, _ = getLinkState()
logf("link state: %+v", e.linkState)
@@ -280,17 +279,19 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
// Ping every single-IP that peer routes.
// These synthetic packets are used to traverse NATs.
var ips []wgcfg.IP
var ips []netaddr.IP
allowedIPs := deviceAllowedIPs.EntriesForPeer(peer)
for _, ipNet := range allowedIPs {
if ones, bits := ipNet.Mask.Size(); ones == bits && ones != 0 {
var ip wgcfg.IP
copy(ip.Addr[:], ipNet.IP.To16())
ip, ok := netaddr.FromStdIP(ipNet.IP)
if !ok {
continue
}
ips = append(ips, ip)
}
}
if len(ips) > 0 {
go e.pinger(peerKey, ips)
go e.pinger(wgkey.Key(peerKey), ips)
} else {
logf("[unexpected] peer %s has no single-IP routes: %v", peerKey.ShortString(), allowedIPs)
}
@@ -397,7 +398,7 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.TUN) fil
return filter.Drop
}
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && e.isLocalAddr(p.DstIP4) {
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && e.isLocalAddr(p.Dst.IP) {
// macOS NetworkExtension directs packets destined to the
// tunnel's local IP address into the tunnel, instead of
// looping back within the kernel network stack. We have to
@@ -410,8 +411,8 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.TUN) fil
return filter.Accept
}
func (e *userspaceEngine) isLocalAddr(ip packet.IP4) bool {
localAddrs, ok := e.localAddrs.Load().(map[packet.IP4]bool)
func (e *userspaceEngine) isLocalAddr(ip netaddr.IP) bool {
localAddrs, ok := e.localAddrs.Load().(map[netaddr.IP]bool)
if !ok {
e.logf("[unexpected] e.localAddrs was nil, can't check for loopback packet")
return false
@@ -421,10 +422,10 @@ func (e *userspaceEngine) isLocalAddr(ip packet.IP4) bool {
// handleDNS is an outbound pre-filter resolving Tailscale domains.
func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Response {
if p.DstIP4 == magicDNSIP && p.DstPort == magicDNSPort && p.IPProto == packet.UDP {
if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == packet.UDP {
request := tsdns.Packet{
Payload: append([]byte(nil), p.Payload()...),
Addr: netaddr.IPPort{IP: p.SrcIP4.Netaddr(), Port: p.SrcPort},
Addr: netaddr.IPPort{IP: p.Src.IP, Port: p.Src.Port},
}
err := e.resolver.EnqueueRequest(request)
if err != nil {
@@ -449,8 +450,8 @@ func (e *userspaceEngine) pollResolver() {
h := packet.UDP4Header{
IP4Header: packet.IP4Header{
SrcIP: packet.IP4(magicDNSIP),
DstIP: packet.IP4FromNetaddr(resp.Addr.IP),
Src: magicDNSIP,
Dst: resp.Addr.IP,
},
SrcPort: magicDNSPort,
DstPort: resp.Addr.Port,
@@ -487,7 +488,7 @@ func (p *pinger) close() {
<-p.done
}
func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, srcIP packet.IP4) {
func (p *pinger) run(ctx context.Context, peerKey wgkey.Key, ips []netaddr.IP, srcIP netaddr.IP) {
defer func() {
p.e.mu.Lock()
if p.e.pingers[peerKey] == p {
@@ -500,7 +501,7 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, src
header := packet.ICMP4Header{
IP4Header: packet.IP4Header{
SrcIP: srcIP,
Src: srcIP,
},
Type: packet.ICMP4EchoRequest,
Code: packet.ICMP4NoCode,
@@ -513,9 +514,16 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, src
const stopAfter = 3 * time.Second
start := time.Now()
var dstIPs []packet.IP4
var dstIPs []netaddr.IP
for _, ip := range ips {
dstIPs = append(dstIPs, packet.IP4FromNetaddr(netaddr.IPFrom16(ip.Addr)))
if ip.Is6() {
// This code is only used for legacy (pre-discovery)
// peers. They're not going to work right with IPv6 on the
// overlay anyway, so don't bother trying to make ping
// work.
continue
}
dstIPs = append(dstIPs, ip)
}
payload := []byte("magicsock_spray") // no meaning
@@ -533,7 +541,7 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, src
return
}
for _, dstIP := range dstIPs {
header.DstIP = dstIP
header.Dst = dstIP
// InjectOutbound take ownership of the packet, so we allocate.
b := packet.Generate(&header, payload)
p.e.tundev.InjectOutbound(b)
@@ -549,17 +557,17 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, src
//
// This is only used with legacy peers (before 0.100.0) that don't
// have advertised discovery keys.
func (e *userspaceEngine) pinger(peerKey wgcfg.Key, ips []wgcfg.IP) {
e.logf("generating initial ping traffic to %s (%v)", peerKey.ShortString(), ips)
var srcIP packet.IP4
func (e *userspaceEngine) pinger(peerKey wgkey.Key, ips []netaddr.IP) {
e.logf("[v1] generating initial ping traffic to %s (%v)", peerKey.ShortString(), ips)
var srcIP netaddr.IP
e.wgLock.Lock()
if len(e.lastCfgFull.Addresses) > 0 {
srcIP = packet.IP4FromNetaddr(netaddr.IPFrom16(e.lastCfgFull.Addresses[0].IP.Addr))
srcIP = e.lastCfgFull.Addresses[0].IP
}
e.wgLock.Unlock()
if srcIP == 0 {
if srcIP.IsZero() {
e.logf("generating initial ping traffic: no source IP")
return
}
@@ -621,24 +629,25 @@ func forceFullWireguardConfig(numPeers int) bool {
// We can only trim peers that both a) support discovery (because we
// know who they are when we receive their data and don't need to rely
// on wireguard-go figuring it out) and b) for implementation
// simplicity, have only one IP address (an IPv4 /32), which is the
// common case for most peers. Subnet router nodes will just always be
// created in the wireguard-go config.
// simplicity, have only non-subnet AllowedIPs (an IPv4 /32 or IPv6
// /128), which is the common case for most peers. Subnet router nodes
// will just always be created in the wireguard-go config.
func isTrimmablePeer(p *wgcfg.Peer, numPeers int) bool {
if forceFullWireguardConfig(numPeers) {
return false
}
if len(p.AllowedIPs) != 1 || len(p.Endpoints) != 1 {
if len(p.Endpoints) != 1 {
return false
}
if !strings.HasSuffix(p.Endpoints[0].Host, ".disco.tailscale") {
return false
}
aip := p.AllowedIPs[0]
// TODO: IPv6 support, once we support IPv6 within the tunnel. In that case,
// len(p.AllowedIPs) probably will be more than 1.
if aip.Mask != 32 || !aip.IP.Is4() {
return false
// AllowedIPs must all be single IPs, not subnets.
for _, aip := range p.AllowedIPs {
if !aip.IsSingleIP() {
return false
}
}
return true
}
@@ -676,12 +685,11 @@ func (e *userspaceEngine) noteReceiveActivity(dk tailcfg.DiscoKey) {
// had a packet sent to or received from it since t.
//
// e.wgLock must be held.
func (e *userspaceEngine) isActiveSince(dk tailcfg.DiscoKey, ip wgcfg.IP, t time.Time) bool {
func (e *userspaceEngine) isActiveSince(dk tailcfg.DiscoKey, ip netaddr.IP, t time.Time) bool {
if e.recvActivityAt[dk].After(t) {
return true
}
pip := packet.IP4(binary.BigEndian.Uint32(ip.Addr[12:]))
timePtr, ok := e.sentActivityAt[pip]
timePtr, ok := e.sentActivityAt[ip]
if !ok {
return false
}
@@ -738,7 +746,7 @@ func (e *userspaceEngine) maybeReconfigWireguardLocked(discoChanged map[key.Publ
// we'll need to install tracking hooks for to watch their
// send/receive activity.
trackDisco := make([]tailcfg.DiscoKey, 0, len(full.Peers))
trackIPs := make([]wgcfg.IP, 0, len(full.Peers))
trackIPs := make([]netaddr.IP, 0, len(full.Peers))
trimmedDisco := map[tailcfg.DiscoKey]bool{} // TODO: don't re-alloc this map each time
@@ -808,7 +816,7 @@ func (e *userspaceEngine) maybeReconfigWireguardLocked(discoChanged map[key.Publ
// as given to wireguard-go.
//
// e.wgLock must be held.
func (e *userspaceEngine) updateActivityMapsLocked(trackDisco []tailcfg.DiscoKey, trackIPs []wgcfg.IP) {
func (e *userspaceEngine) updateActivityMapsLocked(trackDisco []tailcfg.DiscoKey, trackIPs []netaddr.IP) {
// Generate the new map of which discokeys we want to track
// receive times for.
mr := map[tailcfg.DiscoKey]time.Time{} // TODO: only recreate this if set of keys changed
@@ -823,42 +831,44 @@ func (e *userspaceEngine) updateActivityMapsLocked(trackDisco []tailcfg.DiscoKey
e.recvActivityAt = mr
oldTime := e.sentActivityAt
e.sentActivityAt = make(map[packet.IP4]*int64, len(oldTime))
e.sentActivityAt = make(map[netaddr.IP]*int64, len(oldTime))
oldFunc := e.destIPActivityFuncs
e.destIPActivityFuncs = make(map[packet.IP4]func(), len(oldFunc))
e.destIPActivityFuncs = make(map[netaddr.IP]func(), len(oldFunc))
for _, wip := range trackIPs {
pip := packet.IP4(binary.BigEndian.Uint32(wip.Addr[12:]))
timePtr := oldTime[pip]
updateFn := func(timePtr *int64) func() {
return func() {
now := e.timeNow().Unix()
old := atomic.LoadInt64(timePtr)
// How long's it been since we last sent a packet?
// For our first packet, old is Unix epoch time 0 (1970).
elapsedSec := now - old
if elapsedSec >= int64(packetSendTimeUpdateFrequency/time.Second) {
atomic.StoreInt64(timePtr, now)
}
// On a big jump, assume we might no longer be in the wireguard
// config and go check.
if elapsedSec >= int64(packetSendRecheckWireguardThreshold/time.Second) {
e.wgLock.Lock()
defer e.wgLock.Unlock()
e.maybeReconfigWireguardLocked(nil)
}
}
}
for _, ip := range trackIPs {
timePtr := oldTime[ip]
if timePtr == nil {
timePtr = new(int64)
}
e.sentActivityAt[pip] = timePtr
e.sentActivityAt[ip] = timePtr
fn := oldFunc[pip]
fn := oldFunc[ip]
if fn == nil {
// This is the func that gets run on every outgoing packet for tracked IPs:
fn = func() {
now := e.timeNow().Unix()
old := atomic.LoadInt64(timePtr)
// How long's it been since we last sent a packet?
// For our first packet, old is Unix epoch time 0 (1970).
elapsedSec := now - old
if elapsedSec >= int64(packetSendTimeUpdateFrequency/time.Second) {
atomic.StoreInt64(timePtr, now)
}
// On a big jump, assume we might no longer be in the wireguard
// config and go check.
if elapsedSec >= int64(packetSendRecheckWireguardThreshold/time.Second) {
e.wgLock.Lock()
defer e.wgLock.Unlock()
e.maybeReconfigWireguardLocked(nil)
}
}
fn = updateFn(timePtr)
}
e.destIPActivityFuncs[pip] = fn
e.destIPActivityFuncs[ip] = fn
}
e.tundev.SetDestIPActivityFuncs(e.destIPActivityFuncs)
}
@@ -868,13 +878,9 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
panic("routerCfg must not be nil")
}
localAddrs := map[packet.IP4]bool{}
localAddrs := map[netaddr.IP]bool{}
for _, addr := range routerCfg.LocalAddrs {
// TODO: ipv6
if !addr.IP.Is4() {
continue
}
localAddrs[packet.IP4FromNetaddr(addr.IP)] = true
localAddrs[addr.IP] = true
}
e.localAddrs.Store(localAddrs)
@@ -885,7 +891,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
e.mu.Lock()
e.peerSequence = e.peerSequence[:0]
for _, p := range cfg.Peers {
e.peerSequence = append(e.peerSequence, p.PublicKey)
e.peerSequence = append(e.peerSequence, wgkey.Key(p.PublicKey))
peerSet[key.Public(p.PublicKey)] = struct{}{}
}
e.mu.Unlock()
@@ -927,7 +933,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
// (which is needed by DERP) before wgdev gets it, as wgdev
// will start trying to handshake, which we want to be able to
// go over DERP.
if err := e.magicConn.SetPrivateKey(cfg.PrivateKey); err != nil {
if err := e.magicConn.SetPrivateKey(wgkey.Private(cfg.PrivateKey)); err != nil {
e.logf("wgengine: Reconfig: SetPrivateKey: %v", err)
}
e.magicConn.UpdatePeers(peerSet)
@@ -957,7 +963,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
}
}
e.logf("wgengine: Reconfig done")
e.logf("[v1] wgengine: Reconfig done")
return nil
}
@@ -1034,7 +1040,7 @@ func (e *userspaceEngine) getStatus() (*Status, error) {
errc <- bw.Flush()
}()
pp := make(map[wgcfg.Key]*PeerStatus)
pp := make(map[wgkey.Key]*PeerStatus)
p := &PeerStatus{}
var hst1, hst2, n int64
@@ -1057,7 +1063,7 @@ func (e *userspaceEngine) getStatus() (*Status, error) {
log.Fatalf("IpcGetOperation: invalid key %#v", v)
}
p = &PeerStatus{}
pp[wgcfg.Key(pk)] = p
pp[wgkey.Key(pk)] = p
key := tailcfg.NodeKey(pk)
p.NodeKey = key
@@ -1208,7 +1214,7 @@ func (e *userspaceEngine) LinkChange(isExpensive bool) {
} else if needRebind {
e.logf("LinkChange: major, rebinding. New state: %v", cur)
} else {
e.logf("LinkChange: minor")
e.logf("[v1] LinkChange: minor")
}
e.magicConn.SetNetworkUp(up)

View File

@@ -13,6 +13,7 @@ import (
"github.com/tailscale/wireguard-go/wgcfg"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/wgengine/router"
@@ -99,8 +100,8 @@ func TestUserspaceEngineReconfig(t *testing.T) {
cfg := &wgcfg.Config{
Peers: []wgcfg.Peer{
{
AllowedIPs: []wgcfg.CIDR{
{IP: wgcfg.IPv4(100, 100, 99, 1), Mask: 32},
AllowedIPs: []netaddr.IPPrefix{
{IP: netaddr.IPv4(100, 100, 99, 1), Bits: 32},
},
Endpoints: []wgcfg.Endpoint{
{