The PeerRelay reconciler ignored a ProxyClass's spec.metrics, so enabling metrics created no metrics Service or ServiceMonitor and the relay Pods never served /metrics. This fixes this by setting TS_ENABLE_METRICS in ApplyProxyClass and calling tailscaled.EnsureMetricsResources for PeerRelay. PeerRelay now also waits for its ProxyClass to be ready before creating anything, as the other proxies do, so the existing ProxyClass test fixture is marked ready.
ApplyProxyClass now replaces an env var that the ProxyClass also sets instead of appending a second one. The PeerRelay StatefulSet is applied with server-side apply, which rejects duplicate env names, so a ProxyClass that sets an env var the operator also sets, such as TS_ENABLE_METRICS, would stop the StatefulSet from being applied.
Updates #22034
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
The metrics Service and ServiceMonitor handling lived in cmd/k8s-operator,
and the ProxyClass reconciler kept its own copy of the ServiceMonitor CRD
check, so reconcilers in k8s-operator/reconciler could not share it. This
moves it into the tailscaled package as EnsureMetricsResources and
EnsureMetricsResourcesDeleted, which take the proxy type and parent, and
switches cmd/k8s-operator and the ProxyClass reconciler over to it. No
behaviour change.
Updates #22034
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Adds tests that a conflict while provisioning a Service, a Service on a
ProxyGroup, a ProxyGroup or a Connector proxy results in a timed
requeue, and that the Connector and ProxyGroup status is set to
creating rather than failed. The test for a Service on a ProxyGroup
fails the update to the shared ingress config ConfigMap, as no watch
maps that ConfigMap back to the Service, so without the requeue the
conflict was never retried.
Updates #22074
Updates #18922
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Reconcilers logged "optimistic lock error, retrying" with the full
conflict error at Info, and Connector, Recorder and ProxyGroup also put
it in their status. That reads like a failure, but a conflict between a
cached read and an update is expected and is retried. The reconcilers
also returned an empty result, so the retry only happened if a watch
event arrived. This fixes this by logging the conflict at Debug as an
update conflict, using a neutral status message and requeueing after a
short delay.
Updates #22074
Updates #18922
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Move the Recorder reconciler and its object specs into
k8s-operator/reconciler/recorder, following the earlier ProxyClass,
DNSConfig and nameserver moves, and switch it to the shared finalizer,
resource tracker and CreateOrUpdate helpers.
Hoist two duplicated helpers into tailscaled.PrefsFromStateSecret and
reconciler.IsOptimisticLockError, and add
k8s-operator/reconciler/reconcilertest for the test helpers each package
had copied, including one fake Tailscale API client in place of four.
No behaviour change; the Recorder's watches and labels are untouched.
Fixestailscale/corp#37086
Signed-off-by: David Bond <davidsbond93@gmail.com>
This commit changes the egress ProxyGroup readiness gate to mark a Pod
ready only when, for each egress Service, the Pod is an endpoint in the
EndpointSlice of every IP family that the proxy reports in its egress
status. Before, the gate accepted a Pod that was in either family's
EndpointSlice. On a dual-stack cluster, the StatefulSet rollout could
then move to the next Pod before the other family routed to the new Pod.
The gate reads the families from the proxy's state Secret, because a
proxy older than v1.102 does not report an IPv6 address. Such a proxy
never joins the IPv6 EndpointSlice, so its Pod must not wait for it.
Fixes#21077
Signed-off-by: David Bond <davidsbond93@gmail.com>
This commit removes the owner reference from ProxyGroup state Secrets,
also from Secrets that an older operator created. On a Foreground
delete, the garbage collector deleted these Secrets before the
finalizers ran. The finalizers read them to find devices, so cleanup
failed and devices stayed in the tailnet.
The ProxyGroup cleanup now deletes the Secrets after the devices, and
the reconciler watches them by label. The kube-apiserver cleanup finds
its cert resources by label, so it does not need the state Secrets. A
new e2e test covers Foreground deletes.
Fixes#22072
Signed-off-by: David Bond <davidsbond93@gmail.com>
In singleton egress mode (TS_TAILNET_TARGET_FQDN), a failed resolution
of the target FQDN logged the error but then broke out of the main
runLoop instead of continuing to the next netmap update. From then on
containerboot blocked in wg.Wait() while the pod still looked healthy,
and the egress proxy kept forwarding to stale addresses until restart.
A missing target is typically transient (peer not yet in the netmap,
ACL change in flight), so log and continue to let the next netmap
update retry the resolution and reinstall the forwarding rules.
Fixes#22102
Signed-off-by: Santhi Prakash <b.santhiprakash@gmail.com>
When tailscaled denies a serve config, the CLI advises configuring an
operator to avoid root. That advice is wrong for path and Unix socket
targets, which require root regardless. Replace the access-denied
error for those targets with one that says to use sudo.
Updates #21204
Signed-off-by: Brendan Creane <bcreane@gmail.com>
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