The funnel command checked Funnel access with a hardcoded port 443, so
any requested port passed and the CLI reported it as available on the
internet. Check the port the user asked for instead. Turning Funnel
off skips the check so existing configs on other ports can still be
removed.
Fixes#10742Fixes#21989
Signed-off-by: Brendan Creane <bcreane@gmail.com>
* cmd/k8s-operator: grant dnsconfigs/finalizers so the nameserver provisions on OpenShift
The nameserver ServiceAccount, Deployment, Service and ConfigMap are created
with an ownerReference to the cluster-scoped DNSConfig and
blockOwnerDeletion: true. Clusters running the
OwnerReferencesPermissionEnforcement admission plugin - which OpenShift enables
by default and upstream Kubernetes does not - require the creating identity to
hold update on the owner finalizers subresource before accepting such a child.
The operator ClusterRole grants dnsconfigs and dnsconfigs/status but no rule on
any finalizers subresource, so every nameserver reconcile is rejected:
serviceaccounts "nameserver" is forbidden: cannot set blockOwnerDeletion if
an ownerReference refers to a resource you cannot set finalizers on
It retries forever, the DNSConfig never gets a status, and dns-records-reconciler
waits indefinitely. Granting the rule lets the reconcile succeed on the next
attempt; it is inert where the admission plugin is not enabled.
Static manifest regenerated with ./tool/go run ./cmd/k8s-operator/generate staticmanifests
Fixes#21275
Signed-off-by: Hamza Khokhar <hamza.khokhar660@gmail.com>
* cmd/k8s-operator: grant proxygroups, recorders and services finalizers
Extend the OwnerReferencesPermissionEnforcement fix to the other owners whose
children are created with blockOwnerDeletion: true: ProxyGroup and Recorder
(tailscale.com) and Service (core). Without update on their finalizers
subresource, creates of the owned resources are rejected on clusters that
enable the admission plugin, such as OpenShift.
Static manifest regenerated with ./tool/go run ./cmd/k8s-operator/generate staticmanifests
Updates #21275
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Hamza Khokhar <hamza.khokhar660@gmail.com>
---------
Signed-off-by: Hamza Khokhar <hamza.khokhar660@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Restructure the L3, L3 HA and L7 ingress e2e tests into IP family subtests
to cover IPv4 and IPv6, plus DualStack for ProxyGroup.
Updates tailscale/corp#38029
Signed-off-by: Becky Pauley <becky@tailscale.com>
Serve deletes client-supplied `Tailscale-User-Login` (and similar) headers
before setting its own. However, `http.Header.Del` only matches the exact
canonical name. A client could send Tailscale_User_Login instead, which
was passed through to the backend.
For Go (and some other) backends, that's not an issue, but backends that
map headers to CGI-style variables (HTTP_TAILSCALE_USER_LOGIN) treat
both names the same. Thus, for Funnel requests (or tagged nodes), where
serve sets no identity headers, the backend could trust the spoofed
value. The same applies to Tailscale-App-Capabilities.
This fixes the issue by also deleting any header whose name matches
case-insensitively after replacing '_' with '-'.
This is similar to how other projects do this:
- Django drops them: https://www.djangoproject.com/weblog/2015/jan/13/security/
- nginx does not allow them by default: https://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
- Puma had a CVE and now drops them: https://nvd.nist.gov/vuln/detail/cve-2024-45614Fixestailscale/corp#50821
Signed-off-by: Andrew Dunham <andrew@tailscale.com>
Add an ipn.Prefs.Tailnet field, passed through controlclient.Options
into tailcfg.RegisterRequest.Tailnet, so a client can recommend or
require a tailnet at registration (e.g. to skip the tailnet picker when
the intent is already known) without impersonating MDM.
Previously this value could only come from the "Tailnet" syspolicy
setting. That setting still takes precedence as an administrative
override; the prefs value is used only when no policy is set.
Updates #22093
Signed-off-by: Adriano Sela Aviles <adriano@tailscale.com>
Adds an e2e test for proxy cleanup when the state Secret has no
device_id, as the unit tests use a fake client and can't show that
control honours the machineKey filter. It fills the tailnet with other
devices, checks the filter returns only the proxy's device, then removes
device_id from the state Secret and unexposes the Service. It checks
that the proxy's device is deleted and the other devices are left alone.
Updates #18922
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Adds tests that ProxyGroup scale-down and cleanup, Recorder cleanup and
PeerRelay cleanup delete the device matching the machine key in a state
Secret that has no device ID. The Recorder test puts that Secret on
replica 0, so it also checks that cleanup carries on to the other
replicas.
Updates #18922
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Adds a test that proxy cleanup deletes the device matching the machine
key in a state Secret that has no device_id. It also checks that
device_id is still used when present, that Secrets with a missing or
invalid machine key are skipped, and that Connector status does not
list a proxy before it reports its device ID.
Updates #18922
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
ProxyGroup, Recorder and PeerRelay cleanup and replica scale-down only
delete a replica's device if its state Secret holds the device's ID. A
replica that is shut down after it registered with control but before it
learnt its ID leaves its device in the tailnet.
This fixes this by deleting the device by the machine key in the state
Secret in each of these places when there is no ID.
Updates #18922
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Recorder cleanup deletes the device of each replica in turn. When a
replica had no state Secret or no device ID in it, cleanup stopped there
and reported itself done, so the devices of later replicas were left in
the tailnet.
This fixes this by moving on to the next replica instead.
Updates #18922
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Service, Ingress and Connector proxy cleanup finds the devices to delete
through the device_id key in each proxy's state Secret, which
containerboot only writes after the proxy's first netmap. If the proxy is
shut down after it registered with control but before that (for example,
a Service exposed and then unexposed in quick succession), cleanup finds
no device, removes the state Secret and the finalizer, and the device is
left in the tailnet. Connector replica scale-down has the same problem.
This fixes this by deleting the device by the machine key in the state
Secret when there is no device_id. Service, Ingress and Connector status
still only list devices that have an ID.
Updates #18922
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
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>
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>
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>
New self-contained code for the per-connection privileged ssh-monitor.
No existing behavior or wiring changes. Later commits wire the
forwarding and session paths onto it.
rpcFrame is the newline-delimited JSON wire struct with request IDs.
It covers dial, listen, exec, signal, and ping requests, ok and err
replies, and unsolicited exit notifications. rpcConn is the framing
codec over one socketpair end. Reads go only through ReadMsgUnix with
a CmsgSpace(3*sizeof(int)) OOB buffer. A plain Read or an undersized
buffer silently drops ancillary data, and the kernel closes the dropped
descriptors. A stream read never returns data beyond an
SCM_RIGHTS-carrying message, so a descriptor arrives with the first
chunk of a split line and survives reassembly. Writes attach
descriptors to the first byte of a line and never to an empty payload.
A short write resends the remainder as plain data, because resending
the ancillary data would duplicate the descriptors. Received
descriptors are marked close-on-exec at parse time, by the codec rather
than by the stdlib's platform-dependent handling. Herald attribution
validates a whole read's herald batch before queueing any set for a
frame, so a failed batch closes its sets exactly once and never
double-closes a set already queued for a frame. Lines are capped at
256 KiB, and NUL bytes and frames carrying more descriptors than their
op declares are rejected on receive. A read timeout returns
os.ErrDeadlineExceeded and preserves any partially received line, so
the monitor's poll loop can sweep between reads without losing the
frame. A read timeout is the only error that preserves state. Every
other error closes all held descriptors and clears the buffer.
sendFds, recvFds, and wrapUnixConn are shared by the monitor and client
sides. wrapUnixConn consumes its fd, because os.NewFile's finalizer
cannot be neutralized from outside.
Tests exercise ping and fd round-trips with liveness and FD_CLOEXEC
checks, multi-fd frames, kernel framing pins for first-chunk ancillary
delivery and undersized-OOB MSG_CTRUNC rejection, 64 KiB-plus
split-line reassembly under a shrunken SO_SNDBUF, oversized and NUL
rejection, the short-write resend path, the read-timeout
state-preservation contract, and 50-way multiplexed request/reply
traffic.
The monitor scrubs every descriptor from 3 up to close-on-exec at
entry and again before each exec. Go's exec closes nothing beyond the
descriptors it passes, so a single non-close-on-exec fd anywhere in the
spawner's table would otherwise reach every child the monitor execs.
close_range(CLOSE_RANGE_CLOEXEC) does the sweep in one syscall on
kernels 5.11+; older kernels scan the self-fd directory. A new test
injects a non-close-on-exec pipe at spawn and pins the clean-child
contract.
spawnRPCChild takes the child's environment explicitly: a nil env
gives the child an empty environment, never the spawner's. The monitor
child is spawned with the daemon's environment; user-identity children
are spawned with none, so the daemon's environment never reaches a
child running as the connection's user.
Updates tailscale/corp#50380
Signed-off-by: James Tucker <james@tailscale.com>
runServeSetConfig built every non-file, non-unix endpoint target as
"<protocol>://<host:port>", so a tls-terminated-tcp endpoint produced the
target tls-terminated-tcp://host:port. applyTCPServe accepts only the tcp
and unix target schemes, and it takes TLS termination from the serve type
rather than from the target. Every such endpoint therefore failed with
"unable to expand target". The flag form already works because it passes
--tls-terminated-tcp=<port> together with a tcp://host:port target.
Build the target with the tcp scheme whenever the serve type is TCP or
TLS-terminated TCP. Plain tcp endpoints already used that scheme, so only
tls-terminated-tcp endpoints change. https+insecure needs no mapping, since
the web path accepts it as a target scheme, and unix targets skip scheme
construction entirely.
Fixes#21786
Signed-off-by: Vivek1898 <72497717+Vivek1898@users.noreply.github.com>
On a running node with AdvertiseTags set, a bare "tailscale up" never
returned. updatePrefs compared the stored tags with the flag-derived
prefs, which have no tags since no flags were given, concluded that
the tags had changed, and skipped the justEdit path. runUp then sent
the simple up's WantRunning=true edit, a no-op on a running node, and
waited on the IPN bus for a running state that was never sent.
A simple up keeps the current settings, so don't treat its missing
tags as a change.
Fixes#21774
RELNOTE: Fix "tailscale up" with no flags hanging on a running node with advertised tags.
Signed-off-by: Evan Lowry <evan@tailscale.com>
Co-authored-by: Zach Buchheit <zbchht@gmail.com>
This makes the syspolicy cli use the caller's user scope instead of the
generic user scope. In tailscale/tailscale#21701, we made the LocalAPI
policy endpoint resolve unscoped policy requests to the connected user's
policy. However, the cli serialized setting.DefaultScope() as
/policy/User, which selected the general user policy and not the User(SID) policy used by
IPN watchers.
This was tested by changing the AlwaysOn policy and verifying that the
User(SID) policy was reloaded and the gui's IPN policy change callbacks
fired.
Updates tailscale/corp#41463
Signed-off-by: kari <kari@tailscale.com>
Make link queue writes nonblocking: gVisor can generate replies on the
reader needed to drain the queue. A full queue drops only the rejected
packet and reports ErrNoBufferSpace, which gVisor counts and TCP handles
by arming its retransmit timer. UDP sockets do not see the error unless
IP_RECVERR is set, matching Linux. Count WireGuard-bound drops as
reason="queue_full" in tailscaled_outbound_dropped_packets_total, and
per-queue drops in debug and client metrics.
Add Wrapper.TryInjectOutbound for best-effort replies generated while
processing packets (TSMP rejects and pongs, conn25 rejects, fake-mode
echo replies), so those paths drop instead of stalling behind the
injection queue's reader. Its drops are also counted as queue_full.
Make outbound injection waits cancellable and close link queues before
abort-generated replies. Stop the WireGuard injection pump on ErrClosed.
Release injected packets still queued at Close, outside closeOnce to
allow reentrant release callbacks; a sender whose send races with Close
releases the packet itself. Release temporary capture buffers.
Copy injected payload views directly into the WireGuard slab, avoiding
an intermediate allocation and copy.
Test queue saturation, multiqueue progress, cancellation, concurrent
shutdown, reference ownership, fragmented payloads, and GSO.
Performance versus the parent commit, on a 4-core Xeon 6975P-C:
inject+read 64B / 1280B 245ns / 479ns -> 187ns / 195ns, 0 allocs
4 injectors, 1 reader 843ns -> 451ns
64KiB GSO inject+split 10.9us -> 4.4us, 64KiB/op -> 0
link queue write+read 81.4ns -> 81.5ns
tsnet TCP, 1 / 8 streams 448 / 580 MB/s -> 492 / 674 MB/s
BenchmarkTCPQueueBottleneck shows that dropping on full queues keeps
throughput at the bottleneck rate (117 MB/s at 100k packets/s, as with
blocking writes), at the cost of more retransmits with many streams.
Fixes#21631
Updates #21638
Updates #21632
Updates #21639
Signed-off-by: James Tucker <james@tailscale.com>
Updates tailscale/corp#49775
Track authoritative NXDOMAINs for at least 60 seconds on macOS and iOS (where our short SOA ttls are not properly respected), and flush when a cached name becomes resolvable. Compile a no-op tracker elsewhere and avoid duplicate VPN reconfiguration on link changes.
The reconciler forced the AWS load balancer address type to ipv4, so on
an IPv6 EKS cluster, where pod targets are IPv6, no Network Load
Balancer was ever provisioned.
Make the address type a default that spec.service.annotations can
override, defaulting IPv6 source NAT on when dualstack is requested.
Resolve load balancer hostnames for both address families and requeue
ready PeerRelays every five minutes, since DNS records for a new load
balancer arrive over several minutes. Add spec.service.ipFamilyPolicy
and ipFamilies so dual-stack GKE and AKS clusters can request a
dual-stack load balancer too.
Shorten the PeerRelay field descriptions and link to the PeerRelay docs
for the detail.
Fixes#21399
Change-Id: Idf22eb02f8ed9a46ff4abea8d4faef4b8c12c111
Signed-off-by: David Bond <davidsbond93@gmail.com>
tailscale debug ts2021 now starts with an advisory TLS check against
the control plane over port 443: it compares a TLS 1.3 handshake using
Go's default key exchange groups (which include ML-KEM hybrid
post-quantum groups and produce a large ClientHello) with one
restricted to classic groups (a small ClientHello). If the large
ClientHello fails while the small one succeeds, the network path does
not support large (post-quantum) ClientHellos, likely due to a
middlebox dropping ClientHellos that span multiple TCP segments.
The check honors --connect-ip (dialing that IP while keeping the host
for SNI), can be skipped with --skip-tls-check, and never aborts the
surrounding command.
Updates #21708
Signed-off-by: James Tucker <james@tailscale.com>
To faciliate roaming a few things had to happen as tta communicated over
the same links as tailscaled communicates over, meaning that the
connection to tta gets cut when cutting the interface.
- Whenever we have connected to the client after waiting for it to come
up, clean up idle connections.
- Have tta use keepalives to reconnect.
- Delete default route on just via, not the interface.
- Add capabilities to QMP for setting the interface state.
- Implement the test.
Fixestailscale/corp#49639
Signed-off-by: Claus Lensbøl <claus@tailscale.com>
Add --tcp-save-syn on Linux to record client MSS in a Prometheus histogram. Count retrieval outcomes separately and limit saved-SYN error logs to one per ten minutes across clients. IPv6 extension headers are unsupported.
Estimate the network-namespace SYN rate from kernel counters once per second. Disable TCP_SAVE_SYN on listeners above 100,000/s or if rate measurement fails, leaving it off until restart.
Updates tailscale/corp#49139
Signed-off-by: James Tucker <james@tailscale.com>
LocalBackend named appc.AppConnector in its appConnector field, in the
field's constructor call, and in the AppConnector accessor, so even
builds with ts_omit_appconnectors linked the appc package and its
dependencies. The buildfeatures.HasAppConnectors checks let the linker
drop the code but could not remove the import.
Add a build-tag-gated type for the field: appcAppConnector is an alias
for appc.AppConnector by default, and in ts_omit_appconnectors builds it
is an empty struct with no-op versions of the methods local.go calls, so
the shared code still type checks. The constructor call moves behind a
newAppConnector helper in the same gated file, and the AppConnector
accessor keeps its *appc.AppConnector signature there, with a nil-
returning version in the omitted build.
This takes tailscale.com/appc out of depaware-min.txt and
depaware-minbox.txt. Add a DepChecker test to keep it out; it also sets
ts_omit_conn25, since conn25 uses appc as well.
Updates #12614
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ia7e2d9c4f1b8e6a3d5c0f2b7e9a1c4d6f8b0e2a5
The NL prefix is one of the last references to the "Network Lock" name
which we're cleaning up.
Updates tailscale/corp#37904
Change-Id: I751194e0b49447e97a65ed6975e3da680226ce85
Signed-off-by: Alex Chan <alexc@tailscale.com>
Building cigocacher from the corp repo failed because its go.mod was
missing a reference to go-tool-cache. Migrate cigocacher over to tb so
it's not required.
Updates tailscale/corp#49137
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
updates tailscale/corp#33007
When a selected exit node can't carry internet traffic due to a misconfiguration (wrong ID, node
deleted, routes removed, etc), blackhole default routes are installed and all internet traffic is dropped.
That is the correct behavior -- better to drop than leak to the local network -- but it was entirely silent:
the stable-ID lookup in nodeBackend.updateRouteManagerPrefs misses without a log line, and Status
leaves ExitNodeStatus nil. The user sees a healthy Tailscale with no internet. If an admin fat-fingers the
exit node name in an IT policy, for example, it's easy to break every node with zero feedback.
This adds an exit-node-unavailable health warning covering every way the selection can fail to carry
traffic (short of reachability which is a separate concern), reported via exitnodehealth.ArgExitNodeReason.
The warning names the exit node, caching its display name while it is still a peer so the name survives
its departure, and falls back to the stable ID or IP. When the selection is mandated by the ExitNodeID
or ExitNodeIP policy settings, the message tells the user to contact their network administrator instead
of suggesting they pick another exit node.
To test this, set a forced exit node policy with some random ip or node id. The warning has a 5 second
threshold. It should clear as soon as you change to a proper exit node.
Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
Previously ProxyClass spec.staticEndpoints was only implemented for
ProxyGroups and silently ignored when the ProxyClass was referenced by
a Connector. Extract the NodePort Service provisioning, port
allocation, and node ExternalIP discovery logic shared with ProxyGroup
into a new k8s-operator/reconciler/staticendpoints package, and wire
it into the StatefulSet reconciler so that each Connector replica gets
a per-pod NodePort Service, the discovered endpoints are written to
its tailscaled config as static endpoints, and tailscaled listens on
the Services' target port via the PORT env var. Also reconcile
Connectors on Node changes, clean up NodePort Services on scale down,
Connector deletion, and when static endpoints are removed, and
surface the endpoints in the Connector's device status.
Add e2e tests for static endpoints on both Connectors and ProxyGroups.
They verify the per-replica NodePort Services, the PORT env var, the
endpoints reported in status and advertised to control, and the
cleanup on scale down and on removal of the static endpoints
configuration. The tests skip on clusters whose Nodes have no
ExternalIP addresses, such as kind.
Fixes#18819
Change-Id: Ia134916062471e6c0088692834257aafabd0e713
Signed-off-by: David Bond <davidsbond93@gmail.com>
Add vmtests that boot pairs of gokrazy VMs whose tailscale and tailscaled
are minimal builds and check that IP traffic flows between them over
WireGuard, so we notice if a minimal build stops doing its one job:
build_dist.sh --extra-small (direct path through NATs), that minus NAT
traversal (DERP across NATs, direct on a shared LAN), and DERP-only.
They found that "tailscale up" built with ts_omit_ipnbus returned before
calling Start or EditPrefs, making it a no-op. Fix that, and have it wait
for the Running state by polling tailscaled's status instead of watching
the IPN bus, so scripts can rely on it as with normal builds.
The extra-small feature list moves to featuretags.ExtraSmall so
build_dist.sh (via a new "featuretags --extra-small") and vmtest share
it. The gokrazy builder gains --output and --go-build-tags flags to build
the variant images, and natlabprep --gokrazy prebuilds them all for CI.
Updates #13038
Updates #12614
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I0caa2c7b3adf2399da09b6d07a942f1dc34e8486
Previously, ipconfig /flushdns and ipconfig /registerdns were called
from multiple sites without coordination: the DNS manager's SetDNS
(in a fire-and-forget goroutine), the router's winRouter.Set (synchronously
on every route change), the link change handler, and session unlock.
These could fire concurrently or in rapid succession, spawning multiple
ipconfig processes and putting excessive pressure on the Dnscache service.
In this PR, we introduce a new util/coalescedop.CoalescedOp type that provides
"one in-flight, at most one pending" semantics: Do() is non-blocking,
at most one execution runs at a time, and concurrent/rapid successive
requests coalesce into a single follow-up execution.
We then use CoalescedOp for both ipconfig /flushdns and ipconfig /registerdns,
replacing the fire-and-forget goroutine in windowsManager.SetDNS
and in the session unlock handler in tailscaled.
We also remove the redundant dns.Flush() call from winRouter.Set, since every
meaningful router.Set is followed by dns.Set in Reconfig, which already
flushes.
Updates #21001
Signed-off-by: Nick Khyl <nickk@tailscale.com>
The egress Pods readiness reconciler decided whether a Pod could route
traffic by calling each egress Service's health check through its
ClusterIP up to replicas*3 times and looking for a response from that
Pod. That is O(replicas) calls per Service per reconcile, and a Pod
joining a large ProxyGroup is only sampled 3 times on average, so late
Pods often miss and wait for another 5s requeue. The call only goes
through kube-proxy on the operator's own node, so it does not show that
other nodes route to the Pod either.
egress-eps-reconciler already adds a Pod to an egress Service's
EndpointSlice only once the Pod's state Secret shows the proxy has set up
routing for that Service. This sets the readiness condition once the Pod
is an endpoint in an EndpointSlice of every egress Service for the
ProxyGroup, and removes the health check calls.
Updates tailscale/corp#39464
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
cmd/featuretags --min, cmd/tsconnect/wasmbuild, and a cmd/tailscaled
test each computed "omit every feature except these" on their own.
Move that into featuretags.MinTags so other Go code (such as upcoming
natlab vmtests of minimal builds) can compute the tags without running
cmd/featuretags. No behavior change.
Updates #12614
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ie0a5faf1f1d3e4e035326572b16f20180671d1c4
mkversion derives the version from git history, which a shallow clone
lacks (the change count silently comes out as 0) and a tree without
.git can't provide at all. If TS_VERSION_LONG is set, InfoFrom now
takes the version from it plus TS_VERSION_GIT_HASH (and the EXTRA/DATE
variants), runs no git, and derives everything else through the same
mkOutput as the git path. The result's Long must reproduce the input
exactly or InfoFrom errors.
Updates tailscale/corp#44945
Change-Id: I9c1247ea5d1c6c52c823c2205447b5c7ee8e778c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
The egress Pod readiness reconciler polls each Service's health check
endpoint up to replicas*3 times per Pod. A freshly created Pod is initially
unreachable, so these backoff log lines carry no value and generate
significant noise.
Discard these logs so the per-poll message is no longer logged (the more
useful message "Pod is not yet added as an endpoint for all egress targets,
waiting..." is still logged once per reconcile).
Updates tailscale/tailscale#21079
Signed-off-by: Becky Pauley <becky@tailscale.com>
When the tailscale.com/proxy-group annotation is removed from a Service
that was exposed on a ProxyGroup, the HA Service reconciler returned early
on the empty annotation before reaching its cleanup path. The Tailscale
Service was left advertised and the operator's finalizer was never removed,
so the Tailscale Service leaked and the Kubernetes Service wedged forever in
Terminating once deleted.
The annotation is the only place the ProxyGroup name was recorded, and it is
gone by the time cleanup needs it. This commit encodes the ProxyGroup name in
the finalizer, which survives any modification of the resources annotations.
On reconcile, an empty annotation with our finalizer present now recovers the
ProxyGroup name from the finalizer and runs cleanup. Services carrying the bare
legacy finalizer are migrated to the encoded form while the annotation is still
present.
Updates #19922
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Contining the trend of making everything be optional that could
possibly be optional, this makes the magicsock UDP underlay transport
be optional. (mostly replacing a bunch of GOOS != "js" checks in the process)
Omitting udptransport (ts_omit_udptransport) makes magicsock DERP-only: no
UDP sockets, no advertised endpoints, and netcheck measures DERP latency
over HTTPS. Omitting only nattraversal keeps disco ping/pong to peers'
advertised endpoints but drops STUN, call-me-maybe, the peer relay client,
the endpoint tracker, and UDP lifetime probing.
Both are plain linker dead-code elimination via buildfeatures
constants. (as oppposed to moving the code all over into feature
packages and indirecting through hooks) The minimal linux/amd64
tailscaled shrinks by 258 KB without nattraversal and 561 KB without
both.
Updates #12614
Change-Id: Ife10804b48a168d9481e4321c18ed8fc85fa71f1
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This adds a tailscaled string flag "--windows-mode" which accepts two
possible values: the empty string (default) to get the normal behavior
(tailscaled running as an admin, usually as a service), and "dev", to
make the safesocket named pipe path be at a location that regular
users (non-admins) can create.
It then modifies the safesocket client side (as used by the CLI) to
try the dev mode path too on failure.
This lets people work on tailscaled.exe+tailscale.exe in a terminal
easily during development, as either an admin or non-admin. (This
used to work prior to the move away from TCP localhost to named pipes
for safesocket on Windows)
Updates #2791
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I9c1e3a5b7d0f2a4c6e8b0d2f4a6c8e0b2d4f6a8c
* derp/derphttp: reject invalid DERP node hostname before proxy CONNECT
When a DERP client reaches a node through an HTTP(S) proxy,
dialNodeUsingProxy writes the CONNECT request by hand and puts
net.JoinHostPort(n.HostName, port) into both the request line and the
Host header. n.HostName comes from the control-supplied DERP map and
net.JoinHostPort does no sanitizing, so a hostname carrying CR/LF was
written verbatim into the plaintext request sent to the proxy. That let
whoever populated the DERP map inject extra headers, or a second
pipelined request, into the connection to the operator's proxy.
Validate n.HostName with httpguts.ValidHostHeader at the top of
dialNodeUsingProxy, before the proxy is dialed, and also reject the
empty hostname, which ValidHostHeader accepts. DNS names and IP
literals continue to work.
Add a table-driven test that runs accepted and rejected hostnames
against a fake proxy and checks the CONNECT target that goes out.
Fixestailscale/corp#48122
Signed-off-by: basavaraj-sm05 <basavaraj@digiscrypt.com>
Co-authored-by: Mike Jensen <mikej@tailscale.com>
Signed-off-by: Mike Jensen <mikej@tailscale.com>
The derper debug pages had no way to see which clients were connected.
The expvar gauges only give counts, /debug/check only says whether the
counts agree, and /debug/traffic only reports connections that moved
bytes since its last tick, and only if ss is installed.
Add /debug/clients/, which by default serves an index page with a form
to pick one of four filters: ?all lists every connection, ?ip=1.2.3.4
and ?cidr=1.2.0.0/16 list connections from an address or prefix, and
?key=nodekey:... lists the connection(s) for one node key. Each row
shows the connection number, key, remote address, connection age,
flags (home, mesh, prober, notideal, dup/active/disabled), protocol
version, app name, per-connection rx/tx packet and byte counts, and
the estimated unique sender count.
Big derpers have far too many connections for one page, so results
are paginated with keyset cursors rather than page numbers: sort=key,
ip, conn, rx, tx, rxpkts, or txpkts (with a leading - for descending)
picks the walk order, limit=N the page size, and after=X resumes after
that value of the sort field. The next-page links add afterconn=N so a
page boundary that falls among connections sharing a value (duplicate
keys, one IP with many ports, equal counters) resumes exactly. Column
headers link to the other sort orders.
The walk under Server.mu does only a filter match, a cursor comparison,
and at most a bounded-heap operation per connection, so connections
before the cursor are discarded without being copied and at most limit
entries are ever kept. Only the summary counts (matching connections
and keys) look at every connection. Snapshots are taken and the page
rendered after the lock is released, so a slow debug client can't
stall the server. A benchmark with 100k connections takes about 10ms
per page.
There were no per-connection traffic counters before, only the
server-wide ones, so sclient gains four atomic.Uint64 counters (rx/tx
packets and bytes, counting data packets like the server-wide ones)
bumped alongside them. That's 32 bytes per connection. For the counter
sorts, the value is loaded once per connection during the walk and
used for both the cursor test and the heap order, so the order stays
consistent while the counters keep changing.
The sclient preferred field becomes an atomic.Bool so the page can
report which connections are the client's home DERP; it was previously
only touched by the run goroutine.
Updates tailscale/corp#48933
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I4e9b7c2d5a83f61b0e7d2c94a5f8b3e16d7c0a29
FreeBSD handles subnet routes in netstack by default, but the router
still installed its pf NAT rules whenever SNATSubnetRoutes was set,
which is the default. Every FreeBSD node, subnet router or not, loaded
and enabled pf, inserted anchor references into the host's main
ruleset and loaded NAT rules that netstack never needed, since
netstack dials subnet destinations from the host's own addresses.
It also flipped the forwarding sysctls for any advertised route.
Only do either when the kernel path is opted into with
TS_DEBUG_NETSTACK_SUBNETS=false and routes are advertised.
TestSubnetRouterFreeBSDManyFlows ran in netstack mode since the default
changed, so it no longer exercised pf at all. Opt it into the kernel
path and assert the anchor holds NAT rules, and have
TestSubnetRouterFreeBSD assert the default mode leaves pf untouched.
Also drop the stale claim in handleSubnetsInNetstack that the pf NAT
rule never matches; that was the (self) pool bug, since fixed.
Updates #21450
Change-Id: Ibf91a676a026cebb29f64e39a64fe8e75488360b
Signed-off-by: Martin Minkus <martin.minkus@sonic.com>
An Ingress annotated with
`tailscale.com/experimental-forward-cluster-traffic-via-ingress` forwards
cluster traffic to the proxy's Pod IP, which is DNATed to the node's Tailscale IP
where serve answers it. On Linux the serve listener is bound to the tunnel
interface and drops that traffic, so set TS_SERVE_ALLOW_ALL_INTERFACES on the
proxy when this annotation is used, which makes serve answer it again.
Document on the annotation how the traffic reaches serve and that it bypasses
tailnet ACLs, and regenerate the CRD and operator manifests.
Updates tailscale/corp#48248
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
When containerboot falls behind on the IPN bus, tailscaled closes the
watch. containerboot treated the EOF as fatal and SIGTERMed a healthy
tailscaled, which is easy to hit on large, churny tailnets.
Instead, reconnect and rebuild state from the new watch's initial
status, and only request peer changes in modes that use them. If the
watch can't be reopened for a minute, exit so a dead tailscaled still
restarts the container.
Fixes#21373
Change-Id: Iad7749e4fd0f43eabdb471d6e64bb43f37ff70ff
Signed-off-by: Raj Singh <raj@tailscale.com>
The e2e suite covered the operator's in-process API server proxy but
not the ProxyGroup-based one. Add tests for both proxy modes that
drive a ConfigMap through its lifecycle via the proxy, verify a
forbidden request is rejected, and check that deleting the ProxyGroup
cleans up its StatefulSet and Tailscale Service.
Fixestailscale/corp#38009
Change-Id: Ifc0be47ce32dd96f8daa748af6785a8b82ec19a7
Signed-off-by: David Bond <davidsbond93@gmail.com>
Linux is a weak-host stack, so a LAN-adjacent machine can complete a
TCP handshake with a node's peerapi listener by sending a packet to
the node's Tailscale IP, with no credentials and no tailnet
membership.
macOS and iOS already bind the listener to the tunnel interface, and
Windows is protected by its strong host model, so Linux tun mode was
the only platform that leaked.
Bind the Linux listener to the tunnel interface as well, so the
kernel only answers connections that arrive from the tunnel or from
the local host. A natlab VM test verifies that a same-LAN machine can
no longer complete the handshake, while local and peer peerapi keep
working.
FreeBSD has the same weak-host exposure but no per-socket equivalent,
so handling it there with pf is a TODO (#21419).
Updates tailscale/corp#48248
Reported-By: Samuel Keeley (@keeleysam)
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I5f8501b0938c9f7aa39c4c12ebddd988c72e89bf
Loosens the connector route assertion to allow concurrent tests against
the same tailnet to more reliably pass, while still asserting the
client itself is advertising those routes and they're recognised in the
API.
Also make createOrUpdate more resilient to a test that didn't fully tear
down on the same cluster previously. By respecting the existing resource
version and finalizers, we can update resources that didn't get deleted
from a previous run.
Updates tailscale/corp#45426
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
Make sure the tests select matching-family IP addresses for ingress
tests so they're able to pass on IPv6 clusters. We should probably
follow up with another change for the operator that stops clients from
needing to do this sort of selection, but updating the tests is the easy
option to get them passing on IPv6 clusters in the short term.
Updates tailscale/corp#45426
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>