conn25 needs to add routes to the operating system to direct handling
of the addresses in the magic IP range to the tailscale0 TUN and
tailscaled.
The way we do this for exit nodes and VIP services is that we add routes
to the Routes field of router.Config, and then the config is passed to
the WireGuard engine Reconfig.
conn25 is implemented as an ipnext.Extension and so this commit adds a
hook to ipnext.Hooks to allow any extension to provide routes to the
config. The hook if provided is called in routerConfigLocked, similarly
to exit nodes and VIP services.
Fixestailscale/corp#38123
Signed-off-by: Fran Bull <fran@tailscale.com>
On Linux batching.Conn will now write a vector of
coalesced buffers via sendmmsg(2) instead of copying
fragments into a single buffer.
Scatter-gather I/O has been available on Linux since the
earliest days (reworked in 2.6.24). Kernel passes fragments
to the driver if it supports it, otherwise linearizes
upon receiving the data.
Removing the copy overhead from userspace yields up to 4-5%
packet and bitrate improvement on Linux with GSO enabled:
46Gb/s 4.4m pps vs 44Gb/s 4.2m pps w/32 Peer Relay client flows.
Updates tailscale/corp#36989
Change-Id: Idb2248d0964fb011f1c8f957ca555eab6a6a6964
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
Adds the ability to detect when running on AWS ECS and fetch tokens from
the ECS metadata endpoints in addition to IMDSv2
Fixes#18909
Signed-off-by: Patrick Guinard <patrick@public.com>
Previous to this change, closing the listener returned by
Server.ListenService would free system resources, but not clean up state
in the Server's local backend. With this change, the local backend state
is now cleaned on close.
Fixestailscale/corp#35860
Signed-off-by: Harry Harpham <harry@tailscale.com>
TestListenService needs to setup state (capabilities, advertised routes,
ACL tags, etc.). It is imperative that this state propagates to all nodes
in the test tailnet before proceeding with the test. To achieve this,
TestListenService currently polls each node's local backend in a loop.
Using local.Client.WatchIPNBus improves the situation by blocking until
a new netmap comes in.
Fixestailscale/corp#36244
Signed-off-by: Harry Harpham <harry@tailscale.com>
This helps us distribute tests across CI runners. Most tsnet tests call
tstest.Shard, but two recently added tests do not: tsnet.TestFunnelClose
and tsnet.TestListenService. This commit resolves the oversight.
Fixestailscale/corp#36242
Signed-off-by: Harry Harpham <harry@tailscale.com>
If errors occured, the updater could end up deadlocked.
Closing the done channel rather than adding to it, fixes a deadlock in
the corp tests.
Updates #19111
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
Use bufio.Writer.AvailableBuffer to write the 32-byte public key
directly into bufio's internal buffer as a single append+Write,
avoiding 32 separate WriteByte calls. Fall back to the existing
byte-at-a-time path when the buffer has insufficient space.
```
name old ns/op new ns/op speedup
NodeWriteRawWithoutAllocating-8 121 12.5 ~9.7x
(0 allocs/op in both)
```
Add BenchmarkNodeWriteRawWithoutAllocating and expand
TestNodeWriteRawWithoutAllocating to cover both fast (AvailableBuffer)
and slow (WriteByte fallback) paths with correctness and allocation
checks.
Updates tailscale/corp#38509
Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
Use bufio.Writer.AvailableBuffer to write the frame header directly
into bufio's internal buffer as a single append+Write, avoiding 5
separate WriteByte calls. Fall back to the existing writeUint32
byte-at-a-time path when the buffer has insufficient space.
```
name old ns/op new ns/op speedup
WriteFrameHeader-8 18.8 7.8 ~2.4x
(0 allocs/op in both)
```
Add TestWriteFrameHeader with correctness
checks, allocation assertions, and coverage of both fast and slow
write paths. Move BenchmarkReadFrameHeader from client_test.go to
derp_test.go alongside BenchmarkWriteFrameHeader, co-located with
the functions under test.
Updates tailscale/corp#38509
Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
If AutoUpdate.Check is false, the client has opted out of checking for updates, so we shouldn't broadcast ClientVersion. If the client has opted in, it should be included in the initial Notify.
Updates tailscale/corp#32629
Signed-off-by: kari-ts <kari@tailscale.com>
A client with an active streaming session would break if using the same
client for a non-streaming session. Allow the client 1 streaming and n
non-streaming sessions at the same time.
Fixes#19105
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
When the client of a connector assigns transit IP addresses for a
connector we need to let wireguard know that packets for the transit IPs
should be sent to the connector node. We do this by:
* keeping a map of node -> transit IPs we've assigned for it
* setting a callback hook within wireguard reconfig to ask us for these
extra allowed IPs.
* forcing wireguard to do a reconfig after we have assigned new transit
IPs.
And this commit is the last part: forcing the wireguard reconfig after a
new address assignment.
Fixestailscale/corp#38124
Signed-off-by: Fran Bull <fran@tailscale.com>
By polling RTM_GETSTATS via netlink. RTM_GETSTATS is a relatively
efficient and targeted (single device) polling method available since
Linux v4.7.
The tundevstats "feature" can be extended to other platforms in the
future, and it's trivial to add new rtnl_link_stats64 counters on
Linux.
Updates tailscale/corp#38181
Signed-off-by: Jordan Whited <jordan@tailscale.com>
Replace byte-at-a-time ReadByte loops with Peek+Discard in the DERP
read path. Peek returns a slice into bufio's internal buffer without
allocating, and Discard advances the read pointer without copying.
Introduce util/bufiox with a BufferedReader interface and ReadFull
helper that uses Peek+copy+Discard as an allocation-free alternative
to io.ReadFull.
- derp.ReadFrameHeader: replace 5× ReadByte with Peek(5)+Discard(5),
reading the frame type and length directly from the peeked slice.
Remove now-unused readUint32 helper.
name old ns/op new ns/op speedup
ReadFrameHeader-8 24.2 12.4 ~2x
(0 allocs/op in both)
- key.NodePublic.ReadRawWithoutAllocating: replace 32× ReadByte with
bufiox.ReadFull. Addresses the "Dear future" comment about switching
away from byte-at-a-time reads once a non-escaping alternative exists.
name old ns/op new ns/op speedup
NodeReadRawWithoutAllocating-8 140 43.6 ~3.2x
(0 allocs/op in both)
- derpserver.handleFramePing: replace io.ReadFull with bufiox.ReadFull.
Updates tailscale/corp#38509
Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
When we don't care about the payload value and are just checking whether
a set contains an IP/prefix, we can use `bart.Lite` for the same lookup
times but a lower memory footprint.
Fixes#19075
Change-Id: Ia709e8b718666cc61ea56eac1066467ae0b6e86c
Signed-off-by: Alex Chan <alexc@tailscale.com>
tsnet has a 5s sleep as part of its logic waiting to log successful auth.
Add an additional channel that will interrupt this sleep early if the
local backend's state changes before then. This is early enough in the
bootstrap logic that the local client has not been set up yet, so we
subscribe directly on the local backend in keeping with the rest of the
function, but it would be nice to port the whole function to the new
eventbus in a separate change.
Note this does not affect how quickly auth actually happens, it just
ensures we more responsively log the fact that auth state has changed.
Updates #16340
Change-Id: I7a28fd3927bbcdead9a5aad39f4a3596b5f659b0
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
When racing multiple upstream DNS resolvers, a REFUSED (RCode 5) response
from a broken or misconfigured resolver could win the race and be returned
to the client before healthier resolvers had a chance to respond with a
valid answer. This caused complete DNS failure in cases where, e.g., a
broken upstream resolver returned REFUSED quickly while a working resolver
(such as 1.1.1.1) was still responding.
Previously, only SERVFAIL (RCode 2) was treated as a soft error. REFUSED
responses were returned as successful bytes and could win the race
immediately. This change also treats REFUSED as a soft error in the UDP
and TCP forwarding paths, so the race continues until a better answer
arrives. If all resolvers refuse, the first REFUSED response is returned
to the client.
Additionally, SERVFAIL responses from upstream resolvers are now returned
verbatim to the client rather than replaced with a locally synthesized
packet. Synthesized SERVFAIL responses were authoritative and guaranteed
to include a question section echoing the original query; upstream
responses carry no such guarantees but may include extended error
information (e.g. RFC 8914 extended DNS errors) that would otherwise
be lost.
Fixes#19024
Signed-off-by: Brendan Creane <bcreane@gmail.com>
This path is currently only used by DERP servers that have also
enabled `verify-clients` to ensure that only authorized clients
within a Tailnet are allowed to use said DERP server.
The previous naive linear scan in NodeByKey would almost
certainly lead to bad outcomes with a large enough netmap, so
address an existing todo by building a map of node key -> node ID.
Updates #19042
Signed-off-by: Amal Bansode <amal@tailscale.com>
This repo's module is tailscale.com, and the tailscale-client-go-v2 repo
uses tailscale.com/client/tailscale/v2. It seems from #19010 that if we
have the client module as a dependency in this module, go vet will start
to consider the client module as part of tailscale.com/...
I'm not sure if this is a bug in go vet, but for now let's take the easy
fix and specify ./... instead. In my testing, it seems like this is
sufficient to make sure it just walks the file hierarchy and doesn't
find the client module as a sub-path.
Updates tailscale/corp#38418
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
That will be able to be plugged into the hooks in
wgengine/filter/filter.go to let connector packets flow.
Fixestailscale/corp#37144Fixestailscale/corp#37145
Signed-off-by: Fran Bull <fran@tailscale.com>
Rather than printing `unknown subcommand: drive` for any Taildrive
commands run in the macOS GUI, print an error message directing the user
to the GUI client and the docs page.
Updates #17210Fixes#18823
Change-Id: I6435007b5911baee79274b56e3ee101e6bb6d809
Signed-off-by: Alex Chan <alexc@tailscale.com>
Updates #19050
When tsnet.Server.start() is called with both Hostname and Dir explicitly
set, os.Executable() failure should not prevent the server from starting.
Extend the existing ios fallback to also cover darwin, where the same
failure occurs when the Go runtime is embedded in a framework launched
via Xcode's debug launcher.
Signed-off-by: Prakash Rudraraju <prakashrj@yahoo.com>
Also implement a limit of one on the number of goroutines that can be
waiting to do a reconfig via AuthReconfig, to prevent extensions from
calling too fast and taxing resources.
Even with the protection, the new method should only be used in
experimental or proof-of-concept contexts. The current intended use is
for an extension to be able force a reconfiguration of WireGuard, and
have the reconfiguration call back into the extension for extra Allowed
IPs.
If in the future if WireGuard is able to reconfigure individual peers more
dynamically, an extension might be able to hook into that process, and
this method on ipnext.Host may be deprecated.
Fixestailscale/corp#38120
Updates tailscale/corp#38124
Updates tailscale/corp#38125
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
The actual secret is passed through argon2 first, so a timing attack is
not feasible remotely, and pretty unlikely locally. Still, clean this
up.
Fixes#19063
Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
* ipn: reject advertised routes with non-address bits set
The config file path, EditPrefs local API, and App Connector API were
accepting invalid subnet route prefixes with non-address bits set (e.g.,
2a01:4f9:c010:c015::1/64 instead of 2a01:4f9:c010:c015::/64). All three
paths now reject prefixes where prefix != prefix.Masked() with an error
message indicating the expected masked form.
Updates tailscale/corp#36738
Signed-off-by: Brendan Creane <bcreane@gmail.com>
* address review comments
Signed-off-by: Brendan Creane <bcreane@gmail.com>
---------
Signed-off-by: Brendan Creane <bcreane@gmail.com>
Rename variables to match their types after the server -> connector
rename.
Updates tailscale/corp#37144
Updates tailscale/corp#37145
Signed-off-by: Fran Bull <fran@tailscale.com>
When a client starts up without being able to connect to control, it
sends its discoKey to other nodes it wants to communicate with over
TSMP. This disco key will be a newer key than the one control knows
about.
If the client that can connect to control gets a full netmap, ensure
that the disco key for the node not connected to control is not
overwritten with the stale key control knows about.
This is implemented through keeping track of mapSession and use that for
the discokey injection if it is available. This ensures that we are not
constantly resetting the wireguard connection when getting the wrong
keys from control.
This is implemented as:
- If the key is received via TSMP:
- Set lastSeen for the peer to now()
- Set online for the peer to false
- When processing new keys, only accept keys where either:
- Peer is online
- lastSeen is newer than existing last seen
If mapSession is not available, as in we are not yet connected to
control, punt down the disco key injection to magicsock.
Ideally, we will want to have mapSession be long lived at some point in
the near future so we only need to inject keys in one location and then
also use that for testing and loading the cache, but that is a yak for
another PR.
Updates #12639
Signed-off-by: Claus Lensbøl <claus@tailscale.com>