Monitor teardown SIGHUPed and SIGKILLed every remembered child group
when the transport closed. sshd signals nothing at a non-PTY session
end; background jobs with redirected stdio survive and reparent to
init. Clients rely on that: VS Code Remote-SSH starts its remote
server as a plain background job in the session's group and expects
it to outlive connection drops, and tailscale upgrades run as
non-PTY execs whose work should survive a daemon restart.
Use an explicit graceful monitor close to preserve session jobs on
ordinary disconnect and daemon shutdown. Close PTY masters normally,
letting the terminal driver signal the foreground group, but do not
signal non-PTY jobs. Remove Linux Pdeathsig from session children;
forwarding children retain it and always die with the connection.
Commit duration and recording-failure termination to the monitor
before publishing cancellation or writing the termination message.
The monitor owns per-session HUP-to-KILL escalation even after the
leader exits or the connection closes. Revocation arms connection-wide
cleanup. Pin every termination target's leader until after SIGKILL,
including during teardown, so group signaling cannot hit reused PIDs.
Select teardown targets explicitly and leave graceful survivors alone.
Unexpected RPC loss fails closed. If a termination mandate cannot be
delivered, close the RPC rather than subsequently requesting graceful
teardown; this may also kill sibling jobs on a broken channel. Keep
both the escalation and monitor shutdown waits bounded.
Add coverage for default-HUP graceful survivors, actual duration and
recording-failure termination followed by immediate disconnect, sibling
survival, HUP-exiting leaders with immune group members, broken RPCs,
and leader pinning through teardown's SIGKILL pass.
Fixes#22076
Signed-off-by: James Tucker <james@tailscale.com>
Change-Id: I70b4ef40035330dd542d256ba79d889a12426a74
Retry UDP reads returning EHOSTUNREACH or ENETUNREACH, including their
Winsock equivalents, without returning those errors to wireguard-go.
Count them in magicsock_recv_udp_error_reachability. Other read errors are unchanged.
Updates #20616
Updates #19504
Signed-off-by: James Tucker <james@tailscale.com>
Several reports including a severe internal issue have demonstrated use
cases where the current RACK implementation still fails too poorly to be
our enabled default.
Updates #21946
Updates #21651
Updates #18307
Signed-off-by: James Tucker <james@tailscale.com>
This test encodes the current behaviour of the Tailscale client.
It only covers the creation of new mappings.
Updates #21713
Change-Id: Ia44877036569660f4129484d5059ed22ffbf9cda
Signed-off-by: Francois Marier <francois@tailscale.com>
Similar to 7304dd4 for host-scoped destinations, conn25 needs to
block private destinations: a connector forwards to app destinations,
and the packet filter only ever sees the transit IP, never the
destination, so a private destination would let a client reach into the
connector's own network.
Updates https://github.com/tailscale/corp/issues/50118
Updates https://github.com/tailscale/corp/issues/46169
Change-Id: I700b0b5a270723f5a77907e35c52d729f88c44b6
Signed-off-by: Mazdak Nasab <mazdak.nasab@gmail.com>
Logging is quite aggressive and most of the logs can be hidden behind
component logging. A few logs are instead gated on if they changed or if
the original state was zero.
Updates #cleanup
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
Bound the server shutdown wait and the output drain after a session
process exits. Descendants can keep stdio open after the session
process exits, so neither wait can depend on EOF alone. Abort the SSH
channel on drain timeout without sending EOF concurrently with an
outstanding output write.
Close all PTY master descriptors on cancellation and let the terminal
driver send SIGHUP. Duplicate the masters with CLOEXEC and O_NONBLOCK
before wrapping them with os.NewFile so the runtime poller can
interrupt pending I/O on Close. Keep the PTY child's blocking mode intact.
Use RawConn.Control for duplication and window changes to synchronize
with Close and avoid restoring blocking mode on the original master
through File.Fd.
Non-PTY sessions receive SIGHUP explicitly. Keep SIGKILL escalation
for monitor sessions and add it for directly launched sessions. Use
os.Process.Signal on the direct path to synchronize signaling with
reaping. Make the escalation delay configurable alongside the
shutdown and I/O drain timeouts.
Use a process-completion channel to stop the cancellation worker on
normal exit, and join the worker before sending exit status so any
cancellation message is written before channel teardown. Cover normal
completion and cancellation through live monitored SSH sessions.
Fixes tailscale/corp#50380
Fixes#22059
Updates #20628
Updates #21827
Signed-off-by: James Tucker <james@tailscale.com>
Change-Id: I2a66741ba156a607dc3df70a79e140f19737b7a5
Resolve Server.Dial and Server.HTTPClient names using the tailnet's DNS
configuration, including MagicDNS, search domains, split DNS routes, and
global resolvers. Preserve the dialer's local MagicDNS fast path.
Add Server.LookupIP for direct lookups returning netip.Addr values. Keep
the resolver implementation private to tsnet. Search expansion uses
ndots=1, includes the MagicDNS suffix when enabled, and respects trailing
dots. Each lookup uses the current DNS configuration.
Use the internal resolver before considering host fallback. Configured
upstream failures and authoritative local denials must not fall back to
the host resolver; the host's hosts file is consulted only during fallback.
Distinguish configured routes from ambient cloud-host fallback resolvers.
Cover search expansion, split and global DNS, MagicDNS-disabled operation,
configuration updates, hosts-file precedence, and dialing in tests.
Fixes#5840
Updates #7984
Updates #15260
Updates #4677
Change-Id: I832bc4c68658d2d25c5dc29b79acf611fc11e59e
Signed-off-by: James Tucker <james@tailscale.com>
Since 1.104.0 (#21303), netstack's TCP uses gVisor's RACK-TLP loss
detection again. In the gVisor that go.mod pins, RACK's loss marks do
not reduce the SACK recovery pipe, and after an RTO, RACK starts fast
recovery for the same flight, with ssthresh 2 and cwnd 5. After a
burst loss, the sender can then retransmit about two segments per RTO
for seconds to tens of seconds, and for minutes when the client does
not offer TCP timestamps (Windows' default), because the RTO then
keeps backing off. That stalls TCP that netstack terminates, such as
tailscale serve, Tailscale SSH and tsnet, while kernel TCP over the
same path does not stall.
Add TS_NETSTACK_ENABLE_RACK as an explicit opt-out without changing
the default. Unset keeps gVisor's default, RACK on; true keeps it on
explicitly. Setting it to false brings back the RFC 6675 SACK loss
recovery that netstack used from 1.82 through 1.102, until go.mod has
a gVisor with the fixes (google/gvisor#15432 and google/gvisor#16081).
Keep RACK enabled by default because disabling it is several times
slower on paths that reorder packets (#20893).
Test the default, explicitly enabled, and explicitly disabled cases.
Updates #21946Fixes#22045
Signed-off-by: Aditya Merchant <adityamerchant08@gmail.com>
Signed-off-by: James Tucker <james@tailscale.com>
Co-authored-by: James Tucker <james@tailscale.com>
Change-Id: I7d8012beed8a5722fb026203625299b5597fdadc
Also includes a minor change to a packet identification helper in magicsock,
to make it correctly identify retried hybrid handshake packets.
Updates tailscale/corp#50068
Signed-off-by: David Anderson <danderson@tailscale.com>
Change-Id: I6204b8f6b60342fe3c0b7b63c4b90c8c6a6a6964
Similar to #20866 for 4via6, conn25 needs to block host-scoped
destinations. Due to the similarity between the two situations, the
matching is no longer in a 4via6-specific location. Since there is no
backwards compatibility to satisfy, there is no exception list for
conn25.
ipn/ipnlocal (for 4via6) and feature/conn25 now both use the new
package.
Updates tailscale/corp#50118
Updates tailscale/corp#46169
Change-Id: Ic3f9cccd2f5823a2ca48bd78607792b0fd6d7db9
Signed-off-by: Adrian Dewhurst <adrian@tailscale.com>
termWriter converted each chunk of the SSH output to a JS string on its
own which incorrectly handles multi-byte characters across chunk
boundaries. Move it to a separate cmd/tsconnect/termwriter package with
tests, and hold back incomplete sequences for the next write.
Fixes#22007
Signed-off-by: Aarnav Tale <git@tale.me>
Addresses handed out by ippool.next() are marked in use, and the only path
back to the pool is the popExpired loop in reserveAddresses, which can
only see an addrs once it's in the client's assignments. So a reservation
that fails after taking an address leaks it from the pool
for the lifetime of the process.
The leak is the result of interaction between the magic and transit pools.
First an address from magic pool is picked. Then another address from
the transit pool is picked. If an error happens during picking an address
from transit pool, the address from the magic pool needs to be returned,
otherwise that address is leaked, as the address in not in the client
assignments and won't be released.
Fixes https://github.com/tailscale/tailscale/pull/22019
Change-Id: I3d98282762576cc57c135761859902a1d0a63c31
Signed-off-by: Mazdak Nasab <mazdak.nasab@gmail.com>
Allocate a fresh 16-bit ID for each QueryDNS request instead of using the
constant ID 1.
Updates tailscale/corp#48187
Change-Id: I9b47a3fbbef70f0462f1da4f3b29eb77cf31f9f0
Signed-off-by: James Tucker <james@tailscale.com>
Accept a caller context in LocalBackend.QueryDNS and pass it to the
internal resolver. Use the HTTP request context for localapi DNS queries
so cancellation and deadlines bound the lookup.
Updates #8338
Change-Id: I1bb8ec0b779c883ec40d52063adf25ff36cf43e3
Signed-off-by: James Tucker <james@tailscale.com>
These are subject to vulnerability report GO-2026-6860:
https://pkg.go.dev/vuln/GO-2026-6860
We don't use the affected functions anywhere in oss and there's only one
fairly minor non-test use in corp, but a vulnerability scanner in corp
is failing on this package.
Updates #cleanup
Change-Id: I2b441c5f5624090e6bf2d3cc014c9dd4663cf2fc
Signed-off-by: Alex Chan <alexc@tailscale.com>
tailscaled runs under a Type=notify unit, so NOTIFY_SOCKET is in its
environment. The desktop probe ran loginctl with that environment
inherited. loginctl sends sd_notify(3) messages on exit and error, and
lives in the daemon's cgroup, so systemd took each message as coming
from the tailscaled unit and rejected it:
tailscaled.service: Got notification message from PID 1234, but
reception only permitted for main PID 1000
One failing exec sends two messages, ERRNO=... and EXIT_STATUS=... That
is the pair of rejected lines seen at each SSH session on Debian-family
hosts. The per-connection ssh-monitor child also received the daemon's
full environment.
Helpers exec'd by tailscaled now get the daemon's environment without
the systemd service-instance variables: NOTIFY_SOCKET, WATCHDOG_PID,
WATCHDOG_USEC, LISTEN_FDS, LISTEN_PID, LISTEN_FDNAMES, INVOCATION_ID,
JOURNAL_STREAM. The scrub covers the loginctl and getenforce probes,
the direct-exec fallback probe, and the ssh-monitor spawn.
Session children still never receive the daemon's environment. The
monitor still gives children running as the connection's user no daemon
variables. The daemon's own environment is unchanged.
Updates tailscale/corp#50380
Change-Id: Ic686726473e851e2855e75eea11d7b3f75440311
Signed-off-by: James Tucker <james@tailscale.com>
Check mode grants recently approved users a prompt-free Accept rule
with a short RuleExpires. When that rule expired, the policy recheck
matched the base HoldAndDelegate rule instead. The recheck read that as
a policy edit and closed the connection. Live sessions died when the
prompt-free window lapsed.
Remember the rule that matched at authentication. If it has expired and
a hold rule matches instead, keep the connection. Only new connections
re-authenticate.
The expiry timer now fires at least 1ms past a lapsed deadline. A
zero-delay refire lands at the deadline, where the rule is not yet
expired, and re-arms. The real clock moves on between refires; a stopped
clock, such as a synctest bubble, does not.
The fallback test runs in a synctest bubble, so the expiry cannot race
the handshake on a slow runner. A bubble cannot hold child processes:
the test opens a session channel but runs no command, and the empty PATH
keeps the conn lifecycle free of subprocesses. The session-kill side of
expiry stays pinned on the real clock.
A policy edit that swaps a still-valid accept rule for a hold rule still
closes the connection. Recorder, identity, and accept-env checks still
run on the fallback path.
Updates tailscale/corp#50380
Change-Id: I46fa7d42142e90d00843744427c91ba29d68a467
Signed-off-by: James Tucker <james@tailscale.com>
This bare-bones implementation of a UPnP server covers only what
the Tailscale client needs in order to successfully create a
new mapping.
A new flag is introduced for the test control server so that UPnP
can continue to be disabled in all tests except natlab.
Updates #21713
Change-Id: I57779e07c6a628a12bb30dccabd94f7bb21270c4
Signed-off-by: Francois Marier <francois@tailscale.com>
* wgengine/magicsock: implement conn.MultiSocketBind
Bind a group of UDP sockets sharing one local port per address family. A
flow index is plumbed through the send path to pick which socket a batch
leaves from.
Group size is fixed at Options.UDPSockets for the life of the Conn. In
case rebind comes short, the group is padded out with conns that block
until closed.
No caller sets Options.UDPSockets yet, every group holds a single socket
and this is inert.
Additionally, add test coverage for the now exercised net/sockopts paths.
Updates tailscale/corp#48168
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
Change-Id: I253646c753fa46e2efd0ad2158d24c416a6a6964
Add experimental tool for generating a matrix of tests to be run by CI.
This tool will parse the test files in the vmtest folder, and find
candidates that can be with with a set of variations. For now that is
expressed as different flavours of Linux, but it can be expanded across
OS'es but also other things like NAT configurations and portmapper
extensions available.
Updates #21817
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
The unix-socket reverse proxy checked its backend against the tailscaled
socket only when the handler was built, but each backend connection
re-resolved the path from scratch. A backend that was benign at config
time could later be replaced with a symlink to the tailscaled socket by
anyone able to write to its directory, and the next dial followed it.
The proxy dials as root, so the same swap could redirect a serve or
funnel backend at any root-only unix socket on the host.
Move the check to the dial itself, in a dialServeUnixSocket method
shared by the HTTP transport, the h2c transport and the TCP-forward
handler, all of which previously dialed unix on their own. It refuses
tailscaled's socket with ErrProxyToTailscaledSocket immediately before
connecting.
On Linux 5.6 and newer, the dial pins the resolved socket inode with
openat2(2): an O_PATH descriptor opened with RESOLVE_NO_MAGICLINKS,
and RESOLVE_BENEATH under the socket's parent directory where the path
allows it, or by whole path otherwise. The pinned file is checked
against the tailscaled socket, and the connection is made to the pinned
file through /proc/self/fd, so replacing the path cannot retarget the
connect. On kernels without openat2(2), and on other platforms, the
path is dialed directly.
The connected peer is re-checked after the dial on every platform. The
peer address of a path-bound listener is the path the socket was bound
to, not the path that was dialed, so a symlink swapped in before the
dial is identified after the connect. This check is best-effort:
connections that have no determinable peer name are allowed.
Fixestailscale/corp#50481
Change-Id: Iae5db207519db9c139a512c0427dce3dfa3ca672
Co-authored-by: James Tucker <james@tailscale.com>
Signed-off-by: Sam Jean <samj@tailscale.com>
Signed-off-by: James Tucker <james@tailscale.com>
response when DNS request goes to PeerAPI DoH
To support feature/conn25 and other extensions or consumers needing to
know which PeerAPI node handled a DNS request, e.g. for app connectors
or exit nodes, and needing extra data from the server that can be
transmitted in the headers.
DNS responses from other upstream resolvers do not get any metadata
populated, even if those servers are DoH.
Add a new Response type in net/dns/resolver that includes the raw
response bytes and the optional metadata, and replace the bare bytes
with it in some places.
Pass this new type to to net/dns.ResponseMapper instead of the bare
bytes to the extensions can see the metadata.
Updates tailscale/corp#46169
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
Add end-to-end tests for egress to a 4via6 target (an address behind a
subnet router advertising a 4via6 route). Tests include:
- TestConnector4via6Egress/HAEgress: egress to an in-cluster Connector
subnet router. Test requires a dual stack cluster.
- TestHostSubnetRouter4via6Egress/HAEgress: egress to a subnet router that
runs on the test host as a tsnet node. Use RegisterFallbackTCPHandler to
mock the backend response without requiring forwarding on to a backend node
(for test simplicity).
Also update end-to-end test acl for 4via6 routes, and install a dedicated
ProxyClass to accept routes.
Updates tailscale/corp#45732
Signed-off-by: Becky Pauley <becky@tailscale.com>
A peer can advertise 100.64.0.0/10 or fd7a:115c:a1e0::/48 as a subnet
route. Those prefixes are also the coarse routes that stand in for peers'
individual addresses, so one OS route entry has two reasons to exist.
applyDirty set that entry from one reason at a time, so removing the
advertising peer deleted the /10 while coarsening still wanted it, and
turning coarsening off deleted the /10 while a peer still advertised it.
Set each coarse prefix once, from both inputs, as rebuildAll already does.
Fixes#21890
Signed-off-by: Brendan Creane <bcreane@gmail.com>
The operator set each kube-apiserver ProxyGroup's LogLevel from its own
logger's Level(). Unless TS_NO_LOGS_NO_SUPPORT is set, that logger is
teed into the log upload core at debug, so Level() is always debug and
every k8s-proxy logged at debug whatever OPERATOR_LOGGING said. In our
fleet that is millions of debug lines an hour from the proxies.
Read the level before adding the tee and hand it to the ProxyGroup
reconciler. Log uploads are unchanged.
Fixes#21887
Change-Id: I3dd791549c60ba2a4f3c2651da6716623da571a6
Signed-off-by: sugarraysam <sbd@fireworks.ai>
Co-authored-by: Cursor <cursoragent@cursor.com>
Explicitly close the other end of the losing connection.
This is a follow-up to 31baee90c0.
Updates #cleanup
Change-Id: I5bf36a7451da05545e7242ee5c1bff02fa44b362
Signed-off-by: Francois Marier <francois@tailscale.com>
Since the incubator parses the UID in base 0 via flags.IntVar()
and isRootUser() uses base 10, it was possible to get inconsistent
results between the two using non-base 10 numbers.
Updates tailscale/corp#50380
Change-Id: I9b2fb16bc37153df7b695533cc73a3499cb774eb
Signed-off-by: Francois Marier <francois@tailscale.com>
tailscale serve --tcp=<port> unix:<path> dialed the target socket with
no check, so pointing it at tailscaled's own LocalAPI socket proxied a
tailnet peer straight into the LocalAPI. The LocalAPI authorizes a
unix-socket caller by the peer credentials of the dialing process --
tailscaled itself, as root -- so the peer got a write-capable LocalAPI
and could change the node's settings.
The HTTP proxy path already refused this via isTailscaledSocket; the
TCP-forward path, added later, did not. Reject it at config-set time in
setServeConfigLocked so "tailscale serve" fails up front, and keep a
dial-site guard returning ErrProxyToTailscaledSocket as a backstop for
configs set by other means.
RELNOTE: tailscale serve no longer proxies to tailscaled's own socket
Fixestailscale/corp#50425
Change-Id: I5ed02e83d32bdfb09c8164907bf38cabcc406169
Signed-off-by: Sam Jean <samj@tailscale.com>
connect read a non-101 response body with an unbounded io.ReadAll and
inlined it into the returned error.
* Cap the read, format resp.Status instead of the always-nil err, and use %q.
* Add a regression test for the bound
Updates: tailscale/corp#50441
Change-Id: Ie35fada982ccda4328fd4e9adb795947dee3a283
Signed-off-by: Pawel Rutkowski <pawel@tailscale.com>
Close the specific forwarded listener on connection cancellation and stop its callback when the accept loop exits. Canceling a forward no longer retains a connection-lifetime waiter or looks up a replacement listener by address.
Test repeated cancel and rebind operations on one connection, along with callback cleanup when the listener closes independently. Make the existing connection-cancellation test fail if its listener stays open.
Updates tailscale/corp#50380
Change-Id: Ife7fb0acde012b0abf1a8d804b9c6a8994ee5703
Signed-off-by: James Tucker <james@tailscale.com>
Additionals and authorities are two optional sections
in a DNS response. Conn25 treats DNS responses differently,
based on the response type. Adding test coverage for these
optional sections to assert the expected behavior.
Updates #cleanup
Change-Id: I778af5b368f53f6209fe2934aa98d9009c7fa395
Signed-off-by: Mazdak Nasab <mazdak.nasab@gmail.com>
When the ssh incubator runs the shell itself instead of using `su` or `login`,
it needs to set the `$LOGNAME` environment variable. Just like OpenSSH `sshd`,
it sets this to the same value as `$USER`.
Updates tailscale/corp#50380
Signed-off-by: Percy Wegmann <percy@tailscale.com>
Let fetchSSHAction own the per-fetch deadline and pass the connection cancellation context directly from clientAuth. Remove the duplicate timeout and its deferred cancellation inside the action loop. Derive the longer transport watchdog from the fetch timeout, keeping both durations unchanged.
Use virtual time to test the fetch deadline across retries, earlier parent deadlines, and parent cancellation. Verify that a successful fetch releases its request context.
Updates tailscale/corp#50380
Change-Id: I185958ca19566f04cbd68f33e4b1dad3d18aebb7
Signed-off-by: James Tucker <james@tailscale.com>
Move lazy monitor startup, credentials, and cleanup into connmonitor.go and rename the connection state and accessors. The monitor owns session processes as well as forwarding children. Move the session exit registry alongside session monitor handling and keep the Plan 9 stub in a matching file.
Update callers, tests, and diagnostic prefixes without changing monitor lifecycle behavior.
Updates tailscale/corp#50380
Change-Id: I760fa009176491da426639615907d129b68026fc
Signed-off-by: James Tucker <james@tailscale.com>
Describe exit statuses, output ordering, and descriptor ownership directly. Keep the Darwin descriptor acknowledgment rationale in the protocol documentation instead of repeating it at each handoff.
Clarify that the exec spawn limit is checked after Start returns, not an interrupting deadline. Remove claims that session exit statuses are unique or that unreaped children must be uninterruptible.
Updates tailscale/corp#50380
Change-Id: I613b83ac2ae92d7bf8a582c4e4b326ebbe55852d
Signed-off-by: James Tucker <james@tailscale.com>
Check shutdown and register initialized connections under the same lock. Reject a connection if shutdown ran during its construction, stopping its login-grace timer and closing the transport. Also close the transport when connection construction fails.
Cover shutdown before and during connection construction without starting an SSH handshake.
Updates tailscale/corp#50380
Change-Id: I634036a7e073b24d7ff80d6a6d0ae991c40f3c69
Signed-off-by: James Tucker <james@tailscale.com>
Route direct-tcpip channels, used by TCP -L and SOCKS -D forwards, through the per-connection monitor after the existing policy checks. Resolve names and create/connect TCP sockets in a child running with the authenticated local user credentials instead of the root daemon.
Hand the connected TCP descriptor back through the existing acknowledged fd protocol. TCP socket ownership survives the handoff, so UID-based firewall policy sees the mapped user. TCP has no per-message SCM_CREDENTIALS and needs no persistent relay: the child exits after handing off the socket. Bound resolution/dialing by the monitor forwarding deadline.
Share the channel-close-aware forwarding copy loop with Unix forwards, retaining bidirectional half-closes and bounded draining on full close. Plan 9 retains its existing in-process forwarding path.
Add root-run IPv4/IPv6 socket-owner regression tests, hostname and half-close coverage, policy/disable gates, invalid/refused dial handling, and a pin that the child is reaped while its socket remains usable. Update the TCP policy revocation fixture for child spawning and synchronize its assertion on transport shutdown.
Updates tailscale/corp#50380
Change-Id: I89159fd31e75c22446096587471afcd2bb4e78d4
Signed-off-by: James Tucker <james@tailscale.com>
Drain any stale buffered size before sending the latest window update. The adapter is the only sender to a one-slot channel, so the final resize is retained even if the consumer races with the drain.
Cover late and slow readers over a real SSH session with deterministic synctest synchronization. Keep the final two resizes unread to exercise replacement of the buffered size.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Change-Id: I8c077114a6d4765c4208d42a22f384c82f73f75c
Keep the dialed service socket in the credentialed forwarding child and return a private socketpair endpoint instead. Passing the service socket to tailscaled preserves connect-time peer credentials but causes payload SCM_CREDENTIALS to identify the root daemon.
Keep dial children alive for bidirectional forwarding. Return a lifetime
pipe alongside the data socket so full closure or a discarded dial reply
ends the relay independently of data EOF. Preserve half-closes and the
monitor's descriptor acknowledgment/teardown ownership for both fds.
Listener children retain their existing descriptor handoff behavior.
Handle local Unix forwarding channel closure independently of the SSH
connection: the channel request stream closes on CHANNEL_CLOSE, not
CHANNEL_EOF. Stop waiting for service output while giving queued client
payload a bounded drain in each forwarding stage, so a silent or
non-reading service cannot retain the relay indefinitely.
Test relay exit/reaping with silent services, discarded replies, queued
payload and backpressure, including end-to-end SSH channel closure while
the transport stays connected. Fix a canary test's unsynchronized marker
assertion to wait for the reporting goroutine.
Add a root-run cross-UID SO_PASSCRED regression test and lifecycle coverage for both half-close directions and monitor shutdown with an idle relay.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Change-Id: I54b8ddf02b9d82434e474fea2ec283c0e39fab3e
The check for finding unambiguous disco pings was not comparing the
disco key and node keys correctly. Fix the error and add a test that
asserts the ping is not seen as unambiguous when the node key does not
match the disco key, and the disco key has multiple matches.
Fixestailscale/corp#50343
Thank you to Brody Franks for reporting this issue.
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
Cancel fail-closed sessions before sending recording upload failure
notifications. Bound each notifyControl request to five seconds, including
recorder connection failures on the synchronous session startup path.
An unresponsive control request must not keep an unrecorded session running
or indefinitely delay rejection or fail-open startup.
Keep the timeout local to each notification, independent of the session
and upload lifetime, without adding another goroutine. Test blocked
startup notifications in both rejection and fail-open modes, and use
virtual time to cover earlier and simultaneous cancellation/deadlines.
Send SFTP recording rejection diagnostics to stderr, preserving the binary protocol on stdout and exit status 254. Add real SSH/recorder tests for fail-closed and fail-open behavior with blocked notifications, and an SFTP client regression test.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Change-Id: I270d624975a6a9115502a4915ee5ff28a67d714b
And merge the repeated don't fragment codepaths between Linux and Darwin.
Updates tailscale/corp#48168
Change-Id: I5aef408f037c8def99522b4d820c853a6a6a6964
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
Allocate the exclusion set before adding client-supplied usernames. A true Exclude entry otherwise panics when SSH is enabled.
Exercise the c2n handler with true, false, absent, and unknown exclusions, and cover the result cap and disabled-SSH behavior.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
Change-Id: I1d9faf36e5809ccff888fc402329652e8b7d88c8
Re-evaluate authenticated connections on policy updates and at session
and forwarding gates. Close connections whose grants are revoked or
narrowed, including forwarding permissions, user identity, duration,
AcceptEnv, and effective recording requirements. Recheck rule-derived
AcceptEnv and recording configuration for delegated connections too.
Compare recorder lists with duplicate counts preserved so duplicate
entries cannot hide a change of recording destination.
Treat recording-failure changes in either direction as material,
including fail-open to fail-closed transitions. An inconclusive user
lookup retains the previous identity but does not skip other policy
checks, including for `"*": "="` mappings. An absent account or a changed
username, UID, or primary GID revokes the connection.
Preserve explicit getent failures when the os/user fallback cannot find
the account, instead of treating every lookup failure as account deletion.
Numeric UID mappings use LookupId in the fallback, not Lookup by name.
Known limitation: NSS can report a directory-only account as not found
(getent exit status 2) during an sssd/LDAP outage. If the fallback also
reports the account missing, this is indistinguishable from deletion and
still revokes the connection. This availability tradeoff remains accepted
for now; explicit command failures, timeouts, and malformed output do not
by themselves establish that an account was deleted.
Reject forwarding requests using the latched Allow bit before doing a
policy recheck. Forwarding gates still re-evaluate policy, but use the
latched account identity rather than spawning getent for every forwarded
connection (including each SSH -D request). Account identity is rechecked
at authentication completion, policy updates, rule expiry, and session
gates.
Re-arm rule-expiry timers under the policy recheck lock and prevent
re-arming after teardown. Add connection-level login grace, extending
it during delegation and stopping it only on SSH authentication
success, not on unsigned key queries or partial authentication.
Close the raw transport and cancel delegated authentication on teardown.
Add regression tests for these transitions, wait for the auth-completion
recheck before test policy flips, and release stalled recorder handlers
even on test failure.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>