There are two places in the code where we need to compare the
latencies of a netcheck report. In both cases, the comparison wasn’t
well tested.
This PR extracts that logic into a Compare method of the new
RegionLatency type. This type wraps the map of latency measurements
keyed by region ID.
Updates #cleanup
Change-Id: I7f248988973007c2f452283c1b82f84b03068f77
Signed-off-by: Simon Law <sfllaw@tailscale.com>
When a node is renamed in the admin console, control sends peers a
single MapResponse delta: a PeersChanged entry carrying the full
updated node with its new Name, and no new DNSConfig (MagicDNS
records are computed client-side from peer names). That arrives as a
NodeMutationUpsert, but nodeBackend's upsert path only added the new
node's index entries and never removed the replaced node's, so
nodeByName retained the old name, and nodeByAddr, nodeByKey, and
nodeByStableID could likewise go stale if those fields changed.
Since 7e609b258 the quad-100 resolver serves MagicDNS answers on
demand from those live indexes, so a renamed peer's old name kept
resolving until something rebuilt the indexes from a full netmap,
such as toggling Tailscale off and on.
Evict the replaced node's index entries before adding the new ones.
Also consolidate the natlab DNS coverage into a single TestMagicDNS
that boots one VM and exercises extra records, search domains, and
peer add/rename/remove end to end, injecting the same MapResponse
shapes that production control sends.
Updates tailscale/corp#45631
Change-Id: I8a418317d930ec8ce112f7bd19bfd5778117a65e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Introduce a per-tailnet shared ACME account key so that all ingress
ProxyGroup replicas on a tailnet present the same account identity to
Let's Encrypt. This lets renewals claim the ARI "replaces" exemption
from the 50-certs-per-week rate limit, surviving Pod restarts,
ProxyGroup recreation, and cluster migrations.
The operator provisions a "tailscale-acme-accounts" Secret in its
namespace, guarded by a finalizer and a deletion warning event, and
watched so it is recreated promptly if removed. Proxies migrate any
pre-existing per-pod key into the shared Secret on first boot, adopt
the shared key on subsequent boots, and restore it on cert writes if
the Secret was recreated empty. Certs are stamped with the fingerprint
of the issuing account so renewals skip the "replaces" claim when the
account doesn't match.
Opt-in per-ProxyGroup via the tailscale.com/share-acme-account
annotation, or operator-wide via OPERATOR_SHARED_ACME_ACCOUNT_KEY.
Updates #18251
Updates #20288
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
We were early-returning when the node was using an exit node, before
Connectors 2025 split DNS routes were calculated and installed.
Now we assemble the routes first, then install them in both exit node
and non-exit-node contexts. The returned resolvers set UseWithExitNode
to true even though as of today, we believe they should be installed in
all cases without regard to that boolean value. With the boolean, we
preserve the flexibility to toggle behavior without touching ipnlocal.
We also add a TODO to turn the extra split DNS route gathering into a
feature hook (tailscale/corp#37125).
This does not affect appc connectors, which receive split DNS routes,
and the UseWithExitNode value directly from control.
Updates #16384
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
Add serviceclientprefs, an optional feature that stores and loads the
desktop clients' saved service launch preferences, one file per login
profile.
- Add GET|POST /localapi/v0/prefs/service-clients to load and save the
current profile's service client prefs.
- Add local client GetServiceClientPrefs and SetServiceClientPref that
call the new local api endpoint.
- Store the prefs with the ipn/store FileStore at
TailscaleVarRoot()/profile-data/<profileID>/service-client-prefs/<hex-encoded-key>,
so DeleteProfile cleans them up for free. Fall back to an in-memory
store when there's no var root.
- Register the feature and its local api route from build tagged files
so the whole thing drops out under ts_omit_serviceclientprefs.
- Add the serviceclient package holding Pref and Prefs (saved client,
username, database name, and last used time), so the local api client
and desktop apps can import the types without the feature machinery.
Change-Id: I340a99c1b332d181fb1556fbf3e8003bb3b95a08
Updates: https://github.com/tailscale/tailscale/issues/20429
Signed-off-by: Rollie Ma <rollie@tailscale.com>
Unsigned peers aren't covered by tailnet lock, so they must never hold peer capabilities even if the packet filter grants them. This change extends the check for unsigned-peers to ensure full coverage in capabilities.
Fixestailscale/corp#45116
Change-Id: I918af24f0b9855e55921cbdad109cc68e745e125
Signed-off-by: Mike Jensen <mikej@tailscale.com>
The client/local package doc said its API is not necessarily stable, but
that caveat was easy to miss and only a few cert methods said anything
explicit either way. People have been surprised by IPN bus changes
between releases.
Add explicit "API maturity" notes, matching the existing wording on the
cert methods, marking stable: BugReport, BugReportWithOpts, CertDomains,
CheckUpdate, CurrentDERPMap, DialTCP, UserDial, DisconnectControl,
GetPrefs, EditPrefs, Status, StatusWithoutPeers, SetUseExitNode,
SwitchProfile, UserProfile, and the WhoIs* methods. Mark unstable:
ipn.Notify, WatchIPNBus, DoLocalRequest, the Debug*, Drive*, Check*,
EventBus*, and Stream* methods, SetComponentDebugLogging,
TailDaemonLogs, ShutdownTailscaled, GetDNSOSConfig, GetEffectivePolicy,
GetServeConfig, and GetAppConnectorRouteInfo.
Also note on tailcfg.DERPMap that the type is subject to minor changes
over time though its general shape is stable, document that
ipn.Prefs.CorpDNS is the internal name for "tailscale set --accept-dns",
and add a package doc paragraph to client/local saying that methods
without an explicit API maturity note should be assumed unstable.
Updates #20406
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9333c58ae312e392c61d7de77987282e84ce2aeb
This change ensures `packetFilterPermitsUnlockedNodes` also considers SrcCaps-based grants when checking for unsigned peer access.
Fixestailscale/corp#45116
Change-Id: I0ac938367888f67ed6f355fc19959cc8c31722a2
Signed-off-by: Mike Jensen <mikej@tailscale.com>
The Apple clients' last consumer of the legacy Notify.NetMap field was
converted to peer deltas in tailscale/corp#44962, so tailscaled no
longer needs to build and emit full netmaps on the IPN bus for darwin
and ios. Windows is now the only remaining platform on the legacy path.
Updates #12542
Change-Id: I295d826735191bb601d2b69d8d85d37a5a82b6c9
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
The nodeBackend's netMap.Peers slice is frozen at the last full netmap
install; the live per-peer state lives in the nodeBackend.peers map,
updated by delta mutations. Three spots still read the stale slice or
paid to materialize a fresh one:
AppendMatchingPeers iterated netMap.Peers and re-looked-up each ID in
the peers map (with a lock round-trip per peer), so peers added by a
delta since the last full netmap were invisible to it. That affected
its callers: taildrop's file-target list, exit node suggestions, and
conn25's connector discovery. It now snapshots the peers map directly
(sorted by node ID, matching the old netmap ordering).
DebugPeerDiscoKeys read netMap.Peers and so returned stale disco keys
after deltas. It now reads the peers map via the new
nodeBackend.peerDiscoKeys.
pingPeerAPI called NetMapWithPeers, building and sorting the full
O(n) peer slice, just to linearly scan it for one IP. It now uses the
nodeBackend's existing by-address index and the new O(1) PeerByID
accessor, and passes the peers-free netmap to peerAPIBase, which only
reads the self node's addresses.
Updates #12542
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I2e57527d64733b4eb17006f896faaa907b1d128c
Processing a peer add/remove delta still materialized the full netmap
(an O(n) slicesx.MapValues plus sort over all peers, at 10k+
peers in a large tailnet) twice per delta: once in UpdateNetmapDelta
purely to hand the self node to Engine.SetSelfNode, and once in
authReconfigLocked.
Neither needs peers anymore. SetSelfNode gets the self node from the
existing nodeBackend.Self accessor. authReconfigLocked only reads
self-node fields (SelfNode, NodeKey, GetAddresses, HasCap) now that
WireGuard peers ride the incremental route manager and per-peer config
source, so it can use the peers-free NetMap accessor.
That also makes nmcfg.WGCfg vestigial: since wgcfg.Config lost its
Peers field, its peer walk existed only to emit the [v1] skip logs
(expired peers, unselected exit nodes, unaccepted subnet routes),
duplicating filtering the route manager already does. Delete the
package and construct the two-field wgcfg.Config inline. The skip
logs go away; if they're missed, the route manager can log them
incrementally at upsert time instead of rescanning every peer on
every reconfig.
With this, the runtime.DidRange analysis (see the ts_rangehook test)
shows a delta netmap update performing no O(n) range loops except
updateRouteManagerExtras, and the delta phase of that test drops from
1.09s to 0.14s for 400 deltas at n=10000 (from 4.79s at the
start of this effort, before the incremental route manager work).
Updates #12542
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ia0e03ef9db0c988790b2c29de1f0505305e93f58
The ExtraWireGuardAllowedIPs hook was called once per peer on every
authReconfig, so each netmap delta paid an O(n) scan over all peers
even when conn25 (the only implementer) wasn't configured and every
call returned nothing.
Invert the API: the hook now receives an iter.Seq2 of the current
peers and returns the extra prefixes keyed by node ID. An idle
extension returns nil without iterating, so the unconfigured case
does no per-peer work at all.
With this, the runtime.DidRange analysis (see the ts_rangehook test)
no longer reports the updateRouteManagerExtras peer scan on netmap
deltas.
Updates #12542
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9181e77416fa22f4c904620d42e9bcb934165216
Captive portal detection was half-migrated: it had a build tag and
buildfeatures constant, but its code still lived in build-tag-gated
files in ipn/ipnlocal and net/netcheck, with its per-backend state
(context, cancel func, signaling channel) as fields on LocalBackend.
Move it under feature/captiveportal. The health-driven detection loop
becomes an ipnext.Extension holding its own state: it starts and
stops the loop from the BackendStateChange hook and subscribes to
health.Change events on the eventbus itself, removing the captive
portal hooks and special cases from LocalBackend entirely. The DERP
map now comes from a new ipnext.NodeBackend.DERPMap method, and the
preferred DERP region from magicsock's last netcheck report (the
same underlying source as the previously used Hostinfo.NetInfo).
The netcheck probe hook is now exported with a signature free of
netcheck internals, and its implementation moves to the small
feature/captiveportal/netcheckhook package, which installs the hook
as an import side effect. That package stays free of tsd/wgengine
dependencies so the tailscale CLI can keep probing for captive
portals in "tailscale netcheck" without linking the daemon-side
extension. The net/captivedetection library itself is unchanged and
stays put; after this change it is only linked when something pulls
in netcheckhook or the feature extension.
tailscaled links the feature by default via condregister as before,
but tsnet no longer does (shrinking tsnet, k8s-operator, and tsidp);
tsnet users who want it can blank-import the feature package, and
tsnet's dep test now locks that in.
Updates #12614
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I3f1d09f9dc03e18f9a648ab5e42d16fa540b3fa9
The wireguard-go device now learns its peer set solely from the live
per-peer config source that LocalBackend installs with
Engine.SetPeerConfigFunc, backed by the route manager. Peers are
created lazily on first packet and converged per peer with
Engine.SyncDevicePeer, so the full-peer-list snapshot in wgcfg.Config
and the diff-and-reconfigure machinery around it (wgcfg.Peer,
ReconfigDevice, and the engine's full device sync in
maybeReconfigWireguardLocked) are dead weight: they duplicated state
that the route manager already owns and forced every netmap change to
rebuild and rehash the entire peer list.
Delete the Peers field and the Peer type from wgcfg, along with
ReconfigDevice and maybeReconfigWireguardLocked. Engine.Reconfig no
longer does any device peer work; it only manages the private key,
addresses, and the non-peer subsystems. Full-netmap application converges the device by
syncing exactly the peers whose routes the route manager reports as
changed or removed.
Updates #12542
Change-Id: Ic776e42cfaa5be6b9329b3d381d5cbde17d7078b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
The map[key.NodePublic][]netip.Prefix that flows from route manager
commits to WireGuard device syncs has subtle semantics (a nil value
means the peer was removed or no longer contributes any prefixes)
that were documented on routemanager.Result.AllowedIPs and then
re-documented, or not, at each signature that passed it along. Give
it a named type, PeersWithRouteChanges, and document the nil
semantics once on it.
Updates #12542
Change-Id: I2566361a5331eb11b2b70a5bcdb497cc20ee561d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Previously tstun.Wrapper.SetWGConfig walked wgcfg.Config.Peers on every
netmap to rebuild its own IP-to-peer table for masquerade NAT rewrites
and jailed-peer classification. Now the tun layer instead consumes the
route manager's shared immutable outbound snapshot directly, via a new
Engine.SetPeerRoutes method: LocalBackend pushes the snapshot (plus this
node's native Tailscale addresses) after every route manager commit that
can change it, and per-packet lookups read the interned PeerRoute
attributes from that table.
When no current peer is jailed or masqueraded, LocalBackend installs a
nil table (gated on RouteManager.HasDataPlaneAttrs), preserving the
per-packet nil-check fast path. The exitNodeRequiresMasq machinery is
deleted: its purpose was populating the table with all peers so that
more-specific entries shadow an exit node's /0, and the always-full
route manager table gives that shadowing inherently.
This is the last step before removing the Peers field from wgcfg.Config.
Updates #12542
Change-Id: Ifce09ca929a3f2511303ca1d6efdd583739494ce
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Two adjacent bool arguments at call sites are easy to transpose and
hard to read. Use an unexported bitmask type instead, per review
feedback on PR #20414.
Updates #cleanup
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I0417270c9c3f4b2522911c39aad375cbaf025a82
Every netmap change, including an incremental delta of a single peer,
rebuilt the full MagicDNS state twice: dnsConfigForNetmap walked all
peers to build the dns.Config.Hosts map, and resolver.SetConfig then
walked that map again to build its reverse (PTR) index. On a tailnet
with 10k peers that is a lot of garbage per delta.
Instead, add a resolver.MagicDNSHosts hook, installed once by
LocalBackend, that the quad-100 resolver consults on demand at query
time. It is backed by nodeBackend's nodeByName, nodeByAddr, and peers
indexes, which are already maintained incrementally as netmap deltas
arrive. The subdomain-resolve capability check also moves to the hook
(checking the node's CapMap at query time), so dns.Config's
SubdomainHosts is no longer populated.
dns.Config.Hosts remains for control's DNS.ExtraRecords, which are
few and which feed the split-DNS decisions in dns.Manager's
compileConfig, and on Windows it still carries every node's records
because the hosts-file fallback path (compileHostEntries) needs the
complete enumerable set. Those compileConfig decisions also consulted
the per-node Hosts entries (hasHostsWithoutSplitDNSRoutes), so a new
Config.MagicDNSHostsUnrouted bit preserves that signal now that node
records are not listed: with MagicDNS names present but MagicDNS
domain routing off, quad-100 stays in the OS resolver path.
One small behavior change: reverse (PTR) lookups now also answer for
node addresses whose forward records are filtered out by the
IPv6-suppression rule (issue #1152), since nodeByAddr indexes all node
addresses. Previously such addresses were absent from the pushed
Hosts map and thus from the reverse index.
Updates #12542
Updates tailscale/corp#43949
Change-Id: I63b99199c2b3b124c08cb8bbaea1f63165095294
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
The conn25 extension's ExtraWireGuardAllowedIPs hook (Transit IPs)
was only appended to wgcfg.Config.Peers in authReconfig. Now that
outbound peer selection comes from the route manager's outbound
table via the engine's PeerByIPPacketFunc (which, when installed,
replaces wireguard-go's AllowedIPs trie lookup entirely) and lazily
created peers get their allowed IPs from the route manager via the
engine's peer config func, those extras never reached either path:
outbound packets to Transit IPs matched no peer, and lazily created
peers didn't accept inbound Transit IP sources.
Teach the route manager a per-peer set of extra allowed IPs, staged
by Mutation.SetExtraAllowedIPs. They appear in the outbound table
and in PeerAllowedIPs (so both outbound routing and per-peer allowed
source prefixes see them) but are excluded from the OS route set,
preserving the hook's contract that the extras reach WireGuard but
not the OS routing table. authReconfig now feeds the hook's results
into the route manager and incrementally syncs any changed peers to
the WireGuard device; the append to cfg.Peers remains only so Reconfig's
full per-peer device sync doesn't strip the extras from active
peers, and goes away with Config.Peers.
Updates #12542
Change-Id: I06c8fa30929fbf8fe171a2d34c47c6fcc3abfa16
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Engine.Reconfig previously diffed cfg.Peers disco keys against the
previous config to find restarted peers and flush their WireGuard
sessions, with a TSMP-learned-key map to suppress resets for key
changes that arrived over a working session. That was the last
per-peer state computed from wgcfg.Config.Peers inside the engine,
and it only ran on full reconfigs, so incremental netmap deltas
never got session resets at all.
Move the detection into nodeBackend, which sees every peer change:
full netmaps in SetNetMap and incremental upserts in
UpdateNetmapDelta both now report which peers changed disco keys,
with the same TSMP suppression and mismatch accounting as before.
LocalBackend acts on the result via a new Engine.ResetDevicePeer
method, which just removes the peer from the WireGuard device and
lets the peer lookup func lazily re-create it with fresh state.
LocalBackend.PatchDiscoKey now records TSMP-learned keys in
nodeBackend instead of forwarding to the engine, so the engine's
PatchDiscoKey method and tsmpLearnedDisco map are gone. The
controlclient patchDiscoKeyer interface becomes the exported
DiscoKeyUpdater so LocalBackend can compile-time assert that it
implements it, alongside its NetmapDeltaUpdater friends, replacing
the test that asserted the same of the engine.
This is one of the last steps toward removing Peers from wgcfg.Config.
Updates #12542
Change-Id: I6b42e460f42924816beae89ca43731cb91b66054
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Add ConfigVAlpha.RemoteConfig so a user-data/cloud-init config can
delegate remote control to the tailnet admin (see Prefs.RemoteConfig).
Updates #1866
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
Load wraps read-phase failures with it so callers can distinguish a
missing config from an invalid one.
Updates #1866
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
routerConfigLocked previously computed router.Config.Routes with
peerRoutes, a from-scratch pass over cfg.Peers on every reconfig.
The route manager already maintains the same set incrementally (ULA
and CGNAT coarsening included) and updateRouteManagerPrefs runs
earlier in authReconfig, so its OS route set is current by the time
the router config is built. Read it from there instead, and delete
peerRoutes and its tests; the routemanager package tests cover the
same behavior. This removes a consumer of cfg.Peers, which is on
its way out.
Updates #12542
Change-Id: I4a5b7a63d530e3fe1b70f0faf3f49def6a10be2e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit updates the path matching logic in getServeHandler for malformed
request targets like "*" (e.g. "GET *") and "" (e.g. "CONNECT" authority-form).
Those paths never reduce to "/" as absolute path would. An absolute path check
was added and an additional check on no further reduce was added in the loop.
Fixestailscale/corp#44814
Signed-off-by: kevinliang10 <kevinliang@tailscale.com>
Engine.PeerForIP was pure delegation to the callback that LocalBackend
installs via SetPeerForIPFunc, so external callers going through the
engine were taking a pointless round trip: LocalBackend called
b.e.PeerForIP, which called right back into LocalBackend, and the
netstack UseNetstackForIP hooks in tsnet and tailscaled did the same
dance one layer removed.
Export LocalBackend.PeerForIP and make those callers use it directly.
The engine-internal cold paths (Ping, TSMP disco advertisements,
pendopen diagnostics) still need the lookup and have no netmap of
their own, so SetPeerForIPFunc stays on the interface, but the lookup
method itself is now unexported and gone from the Engine interface.
In tailscaled the UseNetstackForIP hook moves from netstack setup to
just after the LocalBackend is created, since the backend doesn't
exist yet when netstack is wired up.
Updates #12542
Change-Id: Ib1e1a4fa5c84ee0dcb9ce5d1910047f2bab9453c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
The engine kept its own longest-prefix-match table (peerByIPRoute),
rebuilt from the full peer list on every reconfig, to route outbound
packets and answer PeerKeyForIP. That's now the route manager's job:
LocalBackend already installs a PeerByIPPacketFunc backed by the
RouteManager's incrementally-maintained outbound table, so the
engine's copy was redundant state with redundant O(n peers) rebuild
work.
Delete the table, the PeerKeyForIP interface method, and the BART-only
default callback. LocalBackend's peerForIP now queries the
RouteManager's outbound table directly for the subnet-route and
exit-node fallback. Engines running without a LocalBackend (such as
wgengine/bench) must install their own outbound peer lookup, since the
device's standard AllowedIPs trie only covers peers that already
exist and can't lazily create them.
Updates #12542
Change-Id: I25100399e273ed6c2bb1f6136b7cd81bc83e7313
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Previously, any peer added or removed by an incremental netmap delta
was only visible to wireguard-go after a full authReconfig: wgcfg's
ReconfigDevice re-installed a PeerLookupFunc closing over a freshly
built map of every peer's allowed IPs, doing O(n) work per change.
Instead, install the wireguard-go device hooks once, backed by live
state. Engine.SetPeerConfigFunc installs a single long-lived
PeerLookupFunc that queries LocalBackend's per-node RouteManager on
demand, and Engine.SyncDevicePeer does O(1) per-peer device sync
(remove, or update allowed IPs) as each delta mutation is applied.
Full reconfigs keep an O(n peers) device sync for now, but with no
lookup closure to reinstall and no removed-peer resurrection race; a
later change removes full-config peer syncing entirely.
The RouteManager's PeerAllowedIPs accessor backs the new hooks: its
sorted output makes unchanged state a no-op update, and its peer
filtering mirrors nmcfg.WGCfg, so expired peers and peers predating
both DERP and disco contribute no prefixes and thus cannot be lazily
created in the device, which matters because wireguard-go validates
inbound source IPs against per-peer allowed IPs.
The engine's SetPeerByIPPacketFunc callback is now authoritative when
installed, since LocalBackend's implementation covers subnet routes
and exit-node routes via the RouteManager's outbound table; the
engine's own reconfig-time BART table only serves engines running
without a LocalBackend.
The forced authReconfig on peer add/remove stays for now: the
WireGuard device no longer needs it, but OS routes, the quad-100
resolver's MagicDNS hosts map, and tstun's masquerade/jailed peer
config are still derived from the full peer set. Making those
delta-aware is the next step before gating it.
Updates #12542
Change-Id: I3ba8c7c324bca0ad0269279d03f53b1f17fb63a2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Give nodeBackend a RouteManager and keep it in sync as routing
inputs change: full netmaps resync the whole peer set (removals plus
no-op-cheap upserts), incremental netmap deltas mirror their peer
upserts and removes into the same mutation batch, and
authReconfigLocked pushes the routing-relevant prefs (exit node,
subnet route acceptance, OneCGNAT) after resolving the exit node's
stable ID to its current numeric node ID.
A selected exit node that doesn't resolve to a current peer (a
nonexistent node, or MDM's "auto:any" placeholder awaiting
resolution) is not the same as no exit node: per the long-standing
ipn.Prefs.ExitNodeID contract, it blackholes internet traffic rather
than letting it escape to the local network. RouteManager's Prefs
gains an ExitNodeSelected bit so its OS route set keeps the default
routes in that case, with no outbound peer to carry them, matching
what routerConfigLocked does today, as pinned by TestRouterConfigExitNodeBlackhole in the previous commit.
All mutations happen with nodeBackend.mu held, satisfying the
RouteManager's serialized Begin/Commit contract.
Nothing consumes its snapshots yet; the wgengine data plane and OS
router wiring come next.
Updates #12542
Change-Id: I677b6b2c9efb8e41b3d27071bd9db73e01640d3b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Selecting an exit node that doesn't resolve to any current peer (a
nonexistent node, or MDM's "auto:any" placeholder before it is
resolved) installs the blackhole default routes, so internet traffic
is dropped rather than escaping to the local network. That behavior
is documented on ipn.Prefs.ExitNodeID and five-plus years old, but
nothing tested it. Lock it in ahead of an upcoming change that moves
OS route computation to net/routemanager and must preserve it.
Updates #12542
Change-Id: I0f63b0d5ce46061a74c69b75f7f83f115da7c3d4
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
LocalBackend.Start previously shut down the previous control client in
a goroutine, letting it run concurrently with the new one. An in-flight
lite map update carrying stale Hostinfo.RequestTags could then be
processed by the control plane after the new client had already changed
the node's tags. Control treats such a request as an invalid tag
transition and expires the node key to force a reauth, so retagging a
node with "tailscale up --advertise-tags" intermittently logged the
machine out.
Instead, detach the old client under b.mu and shut it down
synchronously with the lock released, before creating the new client.
Shutdown cancels the old client's in-flight requests and waits for its
goroutines to exit, so the cancellation of any stale update reaches the
server before the new client sends its first request. Per the deadlock
history in #18052, Shutdown must not be called with b.mu held; this
uses the same pattern as DisconnectControl.
Also teach the testcontrol server to model the control plane's tag
transition handling (including expiring the node key on an invalid
transition and ignoring updates from canceled requests), add an
integration test reproducing the race, and add an ipnlocal test
verifying that Start waits for the old client to shut down.
Updates #20365
Updates #18052
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: If8c8e145bdadcef1b1b8fe6209453cf5f5a8d616
PeerByStableID did an O(n peers) scan, and an upcoming change needs
the same StableNodeID-to-NodeID resolution whenever prefs change (to
resolve the selected exit node for the route manager, which keys
peers by NodeID because that is the identity netmap delta mutations
carry). Maintain a nodeByStableID index alongside the existing
nodeByAddr and nodeByKey indexes, updated on full netmaps and on
delta mutations.
Updates #12542
Change-Id: Id1e5105a7470b02312533f0f46b69e6945cd62f0
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Like the earlier RemoteConfig change, gate Hostinfo.AllowsUpdate on
feature.IsRegistered("clientupdate") in addition to the
buildfeatures.HasClientUpdate build-tag const. tsnet binaries don't
import feature/clientupdate even though ts_omit_clientupdate isn't
set, so they shouldn't tell control they can be remotely updated.
Add the previously missing feature.Register call to
feature/clientupdate, document the binary-support requirement on
tailcfg.Hostinfo.AllowsUpdate, and make tsnet's dep test verify it
doesn't depend on feature/clientupdate.
Updates #12614
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I526ef11f2a4141f5fce161b1f77263324014b5c4
The netmap.NetworkMap type is deprecated and going away, and
reconfigAppConnectorLocked only needed its SelfNode field anyway.
Take a tailcfg.NodeView instead and check its validity in place of
the old nil netmap check.
Updates #12542
Change-Id: Id617845b67416404500cca438ce4ac0372cd8a8e
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
wgcfg.Config.NetworkLogging carried the network flow logging identity
inside the WireGuard config, where it was unrelated to WireGuard; it
lived there mainly so that identity changes would defeat Reconfig's
ErrNoChanges check and reach the netlog startup/shutdown logic.
Remove the field and move the whole netlog lifecycle into a new
feature/netlog package, installed on the engine via the new
wgengine.HookNewNetLogger hook, like other feature/* packages. The
logging identity now comes from LocalBackend's current netmap via the
widened NetLogSource interface (replacing Engine.SetNetLogNodeSource),
so nmcfg no longer parses audit log IDs into the config. The engine
still calls the hook before its ErrNoChanges return and before
router.Set (to capture initial packets), and again after router.Set
(to capture final packets), preserving the previous ordering.
Core wgengine no longer imports wgengine/netlog, so minimal builds
drop it entirely. tailscaled keeps netlog via feature/condregister,
and tsnet imports feature/condregister/netlog explicitly to keep
netlog enabled by default in tsnet-based binaries (tsidp,
k8s-operator).
This is pulled out of a future change that removes wgcfg.Config.Peers,
to make that PR smaller.
Updates #12542
Updates #12614
Change-Id: I41ca7dfe43c51e977c41b5f8e934bd1f0e6e6e24
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
* ipn/localapi,ipnlocal,feature/acme,client/local: honour Retry-After on cert rate-limit
serveCert now responds with 429 + Retry-After when the underlying ACME
error is a rate limit, instead of a generic 500. client/local surfaces
this as a typed RateLimitedError with the parsed hint so callers can
back off intelligently.
Updates tailscale/corp#42164
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
* tsweb,feature/acme,ipn/localapi,ipnlocal: generalise cert error → HTTP mapping via tsweb.HTTPStatuser
Introduces a tsweb.HTTPStatuser interface, any error can implement
to describe its intended HTTP response (code, message, headers).
Moves CertRateLimitedError from ipnlocal to feature/acme where it's
constructed, and it now uses HTTPStatuser to return 429 + Retry-After.
serveCert now checks for tsweb.HTTPStatuser rather than the specific
error type, so it no longer needs to know about the ACME rate-limit
type.
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
---------
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Add a new Prefs.RemoteConfig bool. When true, a c2n endpoint at
/remoteapi/localapi/* proxies into this node's LocalAPI at
/localapi/* with full read/write permission, giving the tailnet
admin the same API surface a local root/admin user has via the
tailscale CLI. All LocalAPI versions (v0, v1, ...) proxy through.
RemoteConfig is an alternative to Tailscale's default per-feature
double opt-in, in which both the tailnet admin and the local machine
owner must consent to each individual setting change. It is a single
client-side "I trust the tailnet admin" switch that, once on, hands
over full remote management of this node's settings and LocalAPI
without any further local prompt or confirmation.
This is only appropriate when the tailnet admin already owns the
machine (e.g. a corporate fleet device) or the local user has
explicitly delegated full control. It should never be enabled on a
personal/BYOD device with an untrusted tailnet admin. The trust
model is documented on the pref, on the hidden --remote-config CLI
flag, and on the feature/remoteconfig package.
The node advertises its RemoteConfig state to the control plane via
a new Hostinfo.RemoteConfig bool. This is only true when the feature
is both compiled in (buildfeatures.HasRemoteConfig) and its init
actually ran (feature.IsRegistered("remoteconfig")); tsnet builds
have the former but not the latter and correctly report false.
The handler lives in feature/remoteconfig and can be omitted with the
ts_omit_remoteconfig build tag. tsnet's TestDeps guards against
accidentally pulling it in.
Updates tailscale/corp#18043
Change-Id: I72ce10a90a0e4e738c72c940af3af64c986160b2
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Found with the regex `\b([A-Za-z]+) \1\b`.
Updates #cleanup
Change-Id: I4cc51784d9b6437d3d0c66b531828707f87f7fd5
Signed-off-by: Alex Chan <alexc@tailscale.com>
Add a new `--force-probe` flag to `tailscale exit-node suggest` that
waits for a routecheck.Refresh to finish before suggesting an exit
node.
This flag is currently hidden from the help text, but this flag is a
hint to the user that exit-node suggestions are based on routecheck
reachability reports.
Updates #17366
Updates tailscale/corp#33033
Signed-off-by: Simon Law <sfllaw@tailscale.com>
Now that the routecheck subsystem is continuously collecting
reachability reports in the background, we can add a hook to
LocalBackend for fetching its report. That allows
suggestExitNodeUsingTrafficSteering to consult that report when
disqualifying candidates, instead of blocking on an immediate probe.
Exit node suggestions will only consult the report when the
`client-side-reachability` and `client-side-reachability-routecheck`
node attributes are both set on the current node.
Updates #17366
Updates tailscale/corp#33033
Signed-off-by: Simon Law <sfllaw@tailscale.com>
This patch adds a new ipnext.NotifyWatcher interface that exposes
ipn.LocalBackend.WatchNotifications so that extensions inside
tailscaled can subscribe to the IPN bus, much like how the GUI
clients subscribe to it through the Local API.
This interface is used by the new feature/routecheck.RouterTracker to
watch for changes in the peer map that affect routers. RouterTracker
uses dead reckoning to incrementally maintain the set of routers. We
do this to avoid looping over the peer map repeatedly. See #17366.
RouterTracker supports two hooks:
- OnNetMapAvailable signals that the initial netmap has been received,
so that the routecheck.Client can wake up goroutines that are
waiting for it.
- OnRoutersChange signals that the set of routers has changed, so that
the routecheck.Client can decide to probe a subset of the routers
instead of all of them. Currently, this optimization hasn’t been
implemented yet.
Updates #17366
Updates #20062
Updates tailscale/corp#33033
Co-authored-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Signed-off-by: Simon Law <sfllaw@tailscale.com>
Add consistent definitions and tests so that watchers of the IPN bus
can keep track of routers when listening for NotifyInitialStatus and
NotifyPeerChanges.
Updates #17366
Updates tailscale/corp#33033
Signed-off-by: Simon Law <sfllaw@tailscale.com>
We borked this in 30a89ad378
and started including skipped extensions (e.g., conn25 when
TAILSCALE_USE_WIP_CODE != 1) in the list of active ones.
This doesn't have any impact other than on logging, though.
Updates #cleanup
Signed-off-by: Nick Khyl <nickk@tailscale.com>
This adds the NotifyInitialPolicy watch option and the Policy field in
Notify so that clients can receive the effective policy snapshot via IPN
bus.
This extends policyclient.Client so ipnlocal can get and watch policy
snapshots, which is used by sysPolicyChanged to notify watchers.
User-scoped policy store registration, management, and cleanup will be
added in a follow-up
Updates tailscale/corp#42259
Signed-off-by: kari <kari@tailscale.com>
This adds a Created field to LoginProfile to normalize the sort order
of login profiles presented in the various client GUIs. The default
sort order for existing profiles remains unchanged and continues to be
based on Name. Newly added profiles will be stamped at creation time
and returned at the top of the list of unstamped profiles, sorted by
creation date in descending order.
The rationale is to ensure that all clients present the user's profile
list in the same order, regardless of newly added accounts, name
changes, or nickname overrides.
The Mac client was recently updated to remove various custom profile
sorting behaviors (https://github.com/tailscale/corp/pull/43847).
iOS, Android, and Windows do not currently perform GUI-level sorting,
so this change should propagate to them seamlessly.
updates tailscale/corp#43843
Signed-off-by: Will Hannah <willh@tailscale.com>