"Tailscale without Tailscale, by Tailscale"
Tailcat is simply a remix of Tailscale unfastened root pieces to enactment like netcat, but complete Tailscale's information plane, without Tailscale's power plane. Tailscale's information level (magicsock, internally) gives you point-to-point WireGuard®-encrypted tunnels betwixt two machines pinch DERP arsenic the NAT-hole-punching connection broadside transmission and the ultimate relay-of-last-resort if NAT traversal fails. Instead of utilizing the Tailscale power plane, each tailcat relationship metadata is exchanged retired of band, nevertheless you want.
The tailcat CLI (in cmd/tailcat) is built connected the tailcat Go library (importable arsenic github.com/tailscale/tailcat).
Whether you usage tailcat arsenic a CLI instrumentality aliases library, 1 broadside runs a tailcat server (listener) and gets backmost a short relationship token. The different broadside passes that token to tailcat's customer broadside to connect. All postulation betwixt the 2 is encrypted end-to-end pinch WireGuard. The first relationship bootstraps through Tailscale's DERP relay network, and past magicsock performs NAT traversal to upgrade to a nonstop peer-to-peer UDP relationship erstwhile imaginable (usually!).
You don't request a Tailscale account, root/admin entree connected the machine (it doesn't change your machine's routing tables, DNS, etc.). It's just a userspace room and CLI tool.
And it's each unfastened source.
You tin usage our free rate-limited DERP relays (the default DERP representation is https://tailcat.dev/derpmap.json) aliases you tin run your own.
Pipe stdin/stdout betwixt 2 machines
Server starts, printing retired its ephemeral address:
And past the customer can:
Then the server unblocks:
Expose section ports done the tunnel
Or you tin service a section TCP port, forwarded to localhost:
And past the client:
On Linux and macOS, you tin tally an SSH server excessively pinch nary auth. (If you want auth, you tin conscionable tailcat --serve=22 and proxy to your strategy SSH server)
And connected the customer side:
Ping to trial connectivity; each pong reports whether it arrived via a DERP relay aliases a nonstop path. --until-direct keeps pinging (up to --timeout, default 10s) until a nonstop way works, exiting non-zero if 1 doesn't:
Run a bid done a SOCKS5 proxy routed complete the tunnel:
Tokens besides activity straight arsenic URL hostnames: the SOCKS proxy recognizes and dials them, truthful the token statement is optional. (Tokens are case-sensitive; this useful pinch curl and astir CLI tools, but not with browsers, which lowercase hostnames.)
Act arsenic an exit node truthful the customer tin scope the server's network:
Parse a relationship token and people its contents (the server's WireGuard public cardinal and DERP info) arsenic JSON, without connecting to anything:
Resolve a short token (which references a DERP region by ID, requiring clients to fetch the DERP map) into a longer self-contained 1 pinch the DERP server info embedded, letting clients link much quickly:
Parsing that resolved token shows the embedded DERP info:
A server tin people the agelong self-contained shape straight pinch the --full-address flag.
A server's reside (connection token) is derived from its WireGuard key, so the cardinal you usage determines who tin scope you:
-
Ephemeral keys (the default): each server tally generates a caller cardinal in memory and prints an reside cipher has ever seen. When the process exits, the cardinal is discarded and the reside is dormant forever. This is the safe default: sharing that reside only ever refers to that 1 run.
-
Saved keys: tailcat genkey generates a cardinal saved to disk truthful the address stays unchangeable crossed restarts. The flip side: anyone you've ever shared that reside pinch tin link to immoderate early server utilizing that key, unless you restrict clients pinch --allow (see tailcat genkey --client).
The CLI says astatine startup which benignant it's using, truthful you cognize whether you're starting a caller single-use server aliases re-listening connected an reside you may have shared successful the past.
That is, default is simply a magic cardinal name: erstwhile it exists, plain tailcat silently uses it alternatively of generating an ephemeral key, and the startup line supra is what tells you which happened. Use --key=new to get an ephemeral cardinal anyway, --key=<name> to usage a different saved key, or tailcat genkey --delete --key=default to region the saved default key. tailcat genkey --list lists your saved keys.
Tokens tin besides beryllium published arsenic DNS TXT records and looked up by name; a DNS sanction useful anyplace the CLI takes a token:
Protected SSH server complete DNS
Who needs larboard forwarding aliases larboard knocking? This runs an SSH server reachable from anyplace by name, pinch nary unfastened inbound ports connected the server, wherever WireGuard authenticates the customer earlier the SSH server ever sees a packet.
On the customer machine, make a customer personality keypair. It prints the nationalist key, which is each the server needs to know:
On the server, make a server keypair pinned to its nearest DERP region (see why below), past service SSH to only that client:
Publish the token successful DNS arsenic a TXT record:
And past the customer broadside is just:
Client modes automatically usage the saved client-default cardinal erstwhile it exists, truthful nary other flags are needed to coming the allowed identity. Anyone else's handshake is silently ignored: they can't scope the SSH server, aliases moreover study that 1 is running.
Why --fixed-region: it discovers the nearest DERP region once, at genkey time, and bakes its ID into some the printed token and the saved cardinal file, truthful server restarts hindrance to the aforesaid region (keeping the published token valid) without re-probing. Plain tailcat genkey defaults to --region=auto, which alternatively bakes successful "pick at startup": good for one-off use, but a token published successful DNS should name a fixed region truthful clients and early server restarts all rendezvous successful the aforesaid place. (--region=<name> pins an definitive one instead; --region=list shows the choices.)
TODO: make the customer much robust present if the DERP representation changes over time: #7
A minimal server that answers immoderate TCP larboard done the passageway and prints its token. The zero worth Server picks defaults for anything unset: a caller ephemeral key, the nearest region of the default DERP map, and log.Printf logging (set Logf to logger.Discard for quiet):
And a minimal customer that dials it, fixed that token arsenic its argument. Like Server, the Client zero worth useful pinch conscionable its Server token field group (tailcat.NewClient is shorthand for precisely that), and the passageway is established lazily by the first dial:
A Tailcat server is identified by a connection token (called a ConnBlob internally). It looks for illustration tcXYZ... and is simply a "tc" prefix followed by base64-encoded CBOR containing:
- The server's WireGuard nationalist cardinal (Curve25519, 32 bytes)
- DERP info. Either:
- a mini integer referencing 1 of the default Tailscale-run tailcat servers), or
- full DERP server metadata, to either usage a civilization DERP server, aliases to debar the customer needing a imaginable round-trip to fetch the latest DERP representation (the server's --full-address emblem and the tailcat resoluteness subcommand nutrient this form)
A emblematic token pinch conscionable an integer region ID is astir 50 bytes. With embedded DERP node specifications it's longer but self-contained.
Tailcat reuses Tailscale's customer networking components but without the power plane.
- WireGuard -- a userspace WireGuard implementation for encrypting each passageway traffic. It doesn't usage a kernel TUN/TAP instrumentality (nor does it configure immoderate networking routes aliases DNS settings), truthful guidelines isn't required.
- magicsock -- Tailscale's carrier furniture that multiplexes traffic over nonstop UDP and DERP relays. It handles STUN-based endpoint discovery and UDP hole-punching for NAT traversal.
- Netstack (gVisor) -- a userspace TCP/IP stack that terminates TCP connections wrong the process. This is what lets Tailcat accept inbound connections and dial outbound ones without immoderate OS network configuration.
- DERP relay -- Tailscale's encrypted relay protocol, utilized arsenic a rendezvous transmission and arsenic a fallback information way erstwhile direct connectivity isn't possible.
-
Server starts. It generates (or loads) a WireGuard keypair, connects to a DERP relay, and prints its relationship token to stderr. It past waits for clients.
-
Client parses the token to study the server's nationalist cardinal and DERP region. It generates its ain ephemeral keypair and connects to the aforesaid DERP relay.
-
Discovery handshake. The customer sends a "Meow" ping message to the server done the DERP relay. This connection carries the client's node nationalist key. The server receives it, adds the customer to its WireGuard adjacent database and network map, reconfigures the WireGuard engine, and replies pinch a "Meowed" acknowledgment.
-
WireGuard tunnel. With some sides configured arsenic WireGuard peers, the modular WireGuard handshake proceeds (routed through DERP initially). Once complete, the passageway is up and encrypted traffic tin flow.
-
NAT traversal. In parallel, each broadside advertises its UDP endpoints (public IP:port learned via STUN, positive section interface addresses) to the different successful disco call-me-maybe messages complete DERP, re-advertising whenever they change. Both sides past tally Tailscale's disco protocol and effort UDP hole-punching. If successful, postulation upgrades from the DERP relay to a direct peer-to-peer path. If hole-punching fails, DERP continues arsenic a fallback and the relationship still works, conscionable pinch rate-limited throughput if you're utilizing our nationalist hosted DERP relays.
-
Data transfer. The customer dials a TCP larboard connected the server through the tunnel. gVisor's TCP/IP stack connected some sides handles connection setup. On the server, the incoming relationship is dispatched to a handler based connected the port: forwarding to localhost, piping to stdout, moving an SSH session, etc.
Each adjacent presently derives a deterministic IPv6 reside from its WireGuard public key, but that's an implementation item not exposed to extremity users and might change. (e.g. we mightiness region those bytes from the IP headers wholly and recover that redundant MTU)
Tailcat is free to use, but it comes pinch nary API aliases CLI stability promises: the Go API, the CLI flags and output, and the ligament format may all change. The nationalist rate-limited Tailcat DERP relays person nary uptime SLAs aliases throughput targets, and we whitethorn revoke entree to them astatine any time, for immoderate reason. Everything is provided champion effort, without a contractual narration (e.g. dedicated DERP relays and/or support) saying otherwise.
If you don't want to tally and support things connected your own, aliases want any help, contact sales and we can exchange money for goods and services.
Tailcat began life successful September 2023 arsenic "derpcat", written connected a long flight while catching up connected bad movies: the first sketch was commit 9e4d925cc ("cmd/dc: commencement of derpcat tool"), and it first worked successful commit 911915fbb ("derpcat: it's alive!", whose perpetrate connection notes "UA 605 PDX-ORD en way to Ireland. yay not buying the wifi."). Back past it lived inside a fork of the tailscale.com repo and it bitrot respective times arsenic the Tailscale internals moved connected without it. We've since brought it backmost to life and refactored it to beryllium a regular Go module customer of the tailscale.com repo alternatively of a fork of it.
It was unfastened originated August 2026 astatine the TailscaleUp conference.
English (US) ·
Indonesian (ID) ·