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
The tailscale/gokrazy-kernel module was a fork of rtr7/kernel that
stalled at Linux 6.8.9 (July 2024). All of the kernel config options we
had added in that fork (ENA, Xen for EC2, virtio-mmio for qemu microvm,
virtio RNG, IPv6 policy routing, netlink diag, etc) are now present in
the gokrazy project's own gokrazy/kernel.amd64 module, which tracks
current kernel.org releases (Linux 7.1.3 as of this change) and is the
gokrazy project's supported kernel for x86_64 PCs and VMs.
Switch the tsapp and natlabapp images, the natlab VM tests, and the
CI workflow to gokrazy/kernel.amd64, drop the tailscale/gokrazy-kernel
dependency, and update gokrazy/kernel.arm64 to latest while here.
Verified with TestEasyEasy, TestJustIPv6, and TestTailscaleSSH in
tstest/natlab/vmtest with --run-vm-tests.
Updates #1866
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I90c3765a4e18f5609b4d77b51ac38d17c8e3688a
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
Do not label plain TCP forwarding as TLS over TCP. Render status
annotations only when TLS termination or PROXY protocol is configured.
Fixes#20367
Change-Id: I3f6507365ceedc2950451810e9715afb85176fc5
Signed-off-by: coyaSONG <66289470+coyaSONG@users.noreply.github.com>
Make CheckAWSAuth, UploadToS3, ImportSnapshot, and RegisterAMI public so
a build server can run them individually (e.g. Marketplace publishing
after RegisterAMI). Rename BuildAMI to BuildAndImportAMI, now a thin
orchestrator over them. Each step records into Result and returns its
artifact; the AWS steps guard on their predecessor and error clearly if
called out of order.
Updates #1866
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
Generated by make updatedeps and ./tool/go run ./tool/updateflakes
after adding service/ec2 (and the smithy-go bump it pulls in).
Updates #1866
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
Replace the four aws CLI shell-outs (s3 cp, ec2 import-snapshot,
describe-import-snapshot-tasks, register-image) with aws-sdk-go-v2 S3
and EC2 clients. Credentials come from the SDK default chain, so
existing aws sso login / aws configure / AWS_PROFILE / env / aws-vault
sessions keep working.
Verify auth via sts:GetCallerIdentity before the slow image build so a
logged-out user fails in seconds, not minutes. Upload via the S3
manager (concurrent multipart) with a native progress reader, log
[n/4] step lines, and bail on terminal import-snapshot failure states
instead of polling forever.
Updates #1866
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
Capture the AWS Progress/StatusMessage fields and report them instead of
reprinting the full describe-import-snapshot-tasks JSON every 5s. On a
terminal, repaint one live percentage line; otherwise log one line per
phase change so CI stays terse.
Updates #1866
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
Move the appliance/AMI build logic into tailscale.com/gokrazy/build so
Go callers (e.g. flash-appliance) can call it directly instead of
driving build.go over --json. build.go is now a thin flag wrapper; flags
and --json output are unchanged. Package-level state becomes a Builder
with an exported Config, ctx-first steps, and a Build one-shot.
Updates #1866
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
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>
The tun-layer per-peer data plane (jailed packet filter selection and
masquerade NAT rewrites) had no end-to-end coverage: nothing asserted
that a peer the control server marks jailed actually has its flows
dropped, or that masquerade addresses assigned by control actually
carry rewritten traffic in both directions.
Add a two-gokrazy-node natlab test driving both knobs from the test
control server and probing with HTTP requests between the nodes'
webservers after each netmap transition, asserting each response
carries the serving node's greeting so masqueraded flows provably
reach the intended node. TSMP pings are deliberately not used as
probes: tstun answers those before running the packet filter, so
they succeed even for jailed peers.
Updates #12542
Change-Id: Ia978e8d368f08a5a1117280f12bd50310969d0ec
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
The BIRD (BGP) integration previously lived half in cmd/tailscaled
(which created a chirp client via a build-tag-gated file on some
platforms) and half in wgengine (which carried the client in its
Config and toggled the "tailscale" protocol as the node gained or
lost primary subnet router duty).
Move it all to a new feature/bird package, installed on the engine
via the new wgengine.HookNewBird hook, like other feature/* packages.
wgengine.Config.BIRDClient (and the wgengine.BIRDClient interface)
are replaced by a BIRDSocket path from which the engine constructs
the feature's Bird handle at startup. The subnet router overlap
detection and protocol toggling move into feature/bird, preserving
the previous ordering: state is recomputed before Reconfig's
ErrNoChanges early return and applied after the router is configured.
tailscaled keeps BIRD support by default on the platforms that
previously had it (linux, darwin, freebsd, openbsd) via
feature/condregister.
Also, add an integration test, as this feature lacked much test
coverage previously.
Updates #12614
Change-Id: I7866a50779e454c87933b358735f7dcd9e2b126f
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This reverts commit 468a7f4973 on request to @ChaosInTheCRD
Although passing all our CI checks, @ChaosInTheCRD would like to plan manual testing as part of incorporating these updates.
Updates #cleanup
Change-Id: I3f007b571b884c9538a97ac5d3ded782bcba2347
Signed-off-by: Mike Jensen <mikej@tailscale.com>
noisy logs
Remove most logs in mapDNSResponse() that could potentially be spammed by
a misbehaving or abusive DNS client or resolver.
Keep logs, and complement with metrics, for failed rewrites, as they
likely point to an internal error, e.g. ip pools exhausted. Metrics
allow for potential alerting in the future.
Updates tailscale/corp#40125
Updates tailscale/corp#40126
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
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>
Make build.go drivable and consumable by a CI builder. --json prints one
machine-readable result line to stdout while all logs/progress go to stderr,
so scripts can capture data cleanly. --region (honoring $AWS_REGION, default
us-east-1) pins import+register deterministically. AMI names derive from git:
<app>-<tag> on a tagged commit, else <app>-<describe>-<unixtime>.
Updates #1866
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
We've occasionally seen CI jobs retry broken commits for a long time
because we only implement a budget per test. Add a cap to ensure we
never spend an unreasonable amount of time on retries.
Updates tailscale/corp#43604
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
* cmd/{k8s-operator,containerboot,k8s-proxy},kube: support IPv6 in egress ProxyGroup
Add support for dual-stack and IPv6 clusters in egress ProxyGroup.
Previously, egress ProxyGroup only supported IPv4: the operator and
containerboot assumed IPv4 for ClusterIP Services, EndpointSlices,
and health check headers.
This change introduces the following:
- Create a per-family EndpointSlice instead of a single IPv4
EndpointSlice.
- Update the egress services readiness reconciler to account for
both IPv4 and IPv6 EndpointSlices.
- Update the pod readiness reconciler to use the primary Pod IP
(PodIPs[0]) for readiness checks, instead of hard-coding to use
IPv4.
- Update the /healthz handler to return both PodIPv4Header and
PodIPv6Header.
- Add an IPv6 address field to egress status.
- Update containerboot and k8s-proxy to use the new health check
logic.
Updates tailscale/corp#41677
Change-Id: If66a3146df48c75b1e65a71632bbc9fc75feded2
Signed-off-by: Becky Pauley <becky@tailscale.com>
* cmd/{k8s-operator,containerboot}: improve dual-stack egress ProxyGroup
On dual-stack clusters, an egress ProxyGroup Service has one EndpointSlice
per IP family (IPv4 and IPv6). However, EndpointSlices were only recreated
when the ExternalName Service configuration changed, so a deleted
EndpointSlice was not recreated. The egress readiness reconciler also had
no mechanism to identify which IP families should exist (previously only
an IPv4 EndpoitSlice was required).
We now create an EndpointSlice for every IP family the ClusterIP Service
supports.
Also mark an egress Service NotReady when an EndpointSlice for an expected IP
family (derived from the Service's ClusterIPs) is missing, so a
dual-stack Service missing a family's EndpointSlice is no longer reported
Ready.
Clarify that the egress pre-shutdown and Pod readiness health checks
verify only one IP family on dual-stack clusters.
Change-Id: I35b03daf76ac817cd516e9a731770b2d85f6ee16
Signed-off-by: Becky Pauley <becky@tailscale.com>
---------
Signed-off-by: Becky Pauley <becky@tailscale.com>
Rate-limit responses from the CA now use the Retry-After hint (via
client/local.RateLimitRetryAfter) instead of walking the local retry
schedule.
Failures that never reached the CA -- context deadline/cancel,
ECONNREFUSED, ECONNRESET, EHOSTUNREACH, EPIPE, and net.Error
timeouts -- retry at retrySchedule[0] without advancing retryCount.
Updates tailscale/corp#42164
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
The cert loop only stops when the domain leaves the ServeConfig.
Deleting the VIPService first left the loop hammering ACME for a
domain the control plane no longer recognised, burning retry slots.
Reorder to: remove from serve config, unadvertise, delete VIPService,
clean cert resources.
Updates #20288
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
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>
register-image defaults to paravirtual: arm64 rejects it outright and
amd64 won't boot on Nitro. Force HVM; pick UEFI boot mode per arch.
Updates #1866
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
One AMI now self-configures from user-data or, when absent, enrolls over
serial.
Updates #1866
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
optional:vm:user-data boots unconfigured when the source is absent
instead of failing; an invalid config still fails.
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>
For dns.controld.com premium resolvers we synthesized per-resolver IPv6
addresses by encoding the resolver ID into the 2606:1a40::/48 range, but
those are legacy plaintext-DNS (port 53) endpoints that refuse TCP :443.
DoH now dials Control D's shared anycast IPs (the resolver ID stays in the
URL path), fixing SERVFAIL on IPv6-only/NAT64 networks where the v4
anycast fallback isn't reachable.
Fixes#20430
Signed-off-by: Brendan Creane <bcreane@gmail.com>
AddReportHistoryForTest temporarily swapped out Client.TimeNow without
holding any lock, racing with concurrent GetReport calls reading it
from ReSTUN goroutines during tests. Instead, pass the current time to
addReportHistoryAndSetPreferredDERP explicitly so the test helper never
needs to touch the field.
Fixes#20438
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9c4f0a2f9427b5f1d3e8b06a49f0d2b71c3ee8a4
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>
Add TestTailscaleSSH to tstest/natlab/vmtest, exercising the Tailscale
SSH server (tailscale up --ssh, not a system sshd) end to end: an
Ubuntu client node SSHes over the tailnet into an Ubuntu server node
as both root and a non-root user, and into a gokrazy node.
The gokrazy sessions exercise the gokrazy special cases in the SSH
code: util/osuser hard-codes the login shell to serial-busybox ash and
synthesizes a root user when lookup fails (so any username works and
becomes root, unlike Ubuntu where nonexistent users are rejected), and
the incubator's findSU refuses su on gokrazy, handling sessions
in-process.
To support this, testcontrol gains an SSHPolicy field that's sent in
MapResponses along with the CapabilitySSH node capability, tta's /up
handler accepts an ssh=true parameter, and vmtest gains a
TailscaleSSH node option that wires the two together with a
permissive any-principal policy.
Updates tailscale/corp#44813
Updates #13038
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I3f6b9c41a72e05d8c94dd7f6ab1937cf24b81c92
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>
The Discover tests step runs under set -euo pipefail, so when every
Test function in a file is filtered out by the exclude regex (as with
vnetperf_test.go, whose TestVnetPerf* tests need special invocation),
the final grep -vE produces no output and exits 1, failing the whole
step. Tolerate empty results from both greps so such files simply
contribute no matrix entries. Same for a hypothetical future test
file containing only helpers and no Test functions.
Updates tailscale/corp#44805
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: If20c9a1de37e97b1553bd7d5559e2b876a45c19b
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
This reverts commit ca9f6971e5.
The dependency updates broke the K8s E2E tests. Reverting so the
updates can be re-landed with the tests passing.
flake.nix, shell.nix, and flakehashes.json were regenerated with
tool/updateflakes rather than reverted, since a later commit
(6fdffd9e5) also updated them for the gowebdav bump.
Change-Id: Id4afd7788d305a674841168e2a66a0009212ffd3
Signed-off-by: Fernando Serboncini <fserb@tailscale.com>