Skip to content
HN On Hacker News ↗

GitHub - tailscale/tailcat: like netcat, but over Tailscale's data plane, without Tailscale's control plane

▲ 687 points 134 comments by nderjung 2w ago HN discussion ↗

Pangram verdict · v3.3

We believe that this text is a mix of AI and human-written content.

20 %

AI likelihood · overall

Mixed
82% human-written 18% AI-generated
SEGMENTS · HUMAN 1 of 2
SEGMENTS · AI 1 of 2
WORD COUNT 787
PEAK AI % 70% · §2
Analyzed
Aug 26
backend: pangram/v3.3
Segments scanned
2 windows
avg 394 words each
Distribution
82 / 18%
human / AI fraction
Verdict
Mixed
Pangram v3.3

Article text · 787 words · 2 segments analyzed

Human AI-generated
§1 Human · 4%

"Tailscale without Tailscale, by Tailscale" Tailcat is a remix of Tailscale open source pieces to act like netcat, but over Tailscale's data plane, without Tailscale's control plane. Tailscale's data plane (magicsock, internally) gives you point-to-point WireGuard®-encrypted tunnels between two machines with DERP as the NAT-hole-punching communication side channel and the ultimate relay-of-last-resort if NAT traversal fails. Instead of using the Tailscale control plane, all tailcat connection metadata is exchanged out of band, however you want. The tailcat CLI (in cmd/tailcat) is built on the tailcat Go library (importable as github.com/tailscale/tailcat). Whether you use tailcat as a CLI tool or library, one side runs a tailcat server (listener) and gets back a short connection token. The other side passes that token to tailcat's client side to connect. All traffic between the two is encrypted end-to-end with WireGuard. The initial connection bootstraps through Tailscale's DERP relay network, and then magicsock performs NAT traversal to upgrade to a direct peer-to-peer UDP connection when possible (usually!). You don't need a Tailscale account, root/admin access on the machine (it doesn't alter your machine's routing tables, DNS, etc.). It's just a userspace library and CLI tool. And it's all open source. You can use our free rate-limited DERP relays (the default DERP map is https://tailcat.dev/derpmap.json) or you can run your own. Usage Pipe stdin/stdout between two machines Server starts, printing out its ephemeral address: $ tailcat # Selected bootstrap relay region 302, San Francisco # 🐈 Server listening with new address: tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu (hangs, waiting...) And then the client can: $ echo hello | tailcat tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu $ Then the server unblocks: $ tailcat # Selected bootstrap relay region 302, San Francisco # 🐈 Server listening with new address: tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu hello $ Expose local ports through the tunnel Or you can serve a local TCP port, forwarded to localhost: $ tailcat --serve=8080,8443 # or --serve=all # 🐈 Server listening with new address: tcXXXXXXXXX And then the client: $ tailcat tcXXXXXXXXX 8080 GET / HTTP/1.1 Host: foo HTTP/1.1 200 OK .... Auth-free SSH server On Linux and macOS, you can run an SSH server too with no auth. (If you want auth, you can just tailcat --serve=22 and proxy to your system SSH server) $ tailcat --serve=no-auth-ssh # 🐈 Server listening with new address: tcXXXXXXXXX And on the client side: $ tailcat ssh tcXXXXXXXXX $ tailcat ssh tcXXXXXXXXX ls -la Misc commands Ping to test connectivity; each pong reports whether it arrived via a DERP relay or a direct path. --until-direct keeps pinging (up to --timeout, default 10s) until a direct path works, exiting non-zero if one doesn't: $ tailcat ping --until-direct <token> pong in 42.1ms via DERP(sfo) pong in 1.2ms via 203.0.113.7:41641 Run a command through a SOCKS5 proxy routed over the tunnel: $ tailcat socks <token> curl http://server.tailcat:8081/ Tokens also work directly as URL hostnames: the SOCKS proxy recognizes and dials them, so the token argument is optional. (Tokens are case-sensitive; this works with curl and most CLI tools, but not with browsers, which lowercase hostnames.) $ tailcat socks curl http://<token>:8081/ Act as an exit node so the client can reach the server's network: $ tailcat --serve=exit-node Parse a connection token and print its contents (the server's WireGuard public key and DERP info) as JSON, without connecting to anything: $ tailcat parse tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu { "ServerPublic": "nodekey:9c8d2e6728da80a1dd37e275a82595b42d9a838610bc53f74a7670d1610f2e34", "RegionID": 302 } Resolve a short token (which references a DERP region by ID, requiring clients to fetch the DERP map) into a longer self-contained one with the DERP server info embedded, letting clients connect more quickly: $ tailcat resolve tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFygaFhToGjYWhudGMzMDJhLmlwbi5kZXZhNG0yMDguMTExLjM5LjM4YTZzMjYwNzpmNzQwOjA6M2Y6OjcyMA Parsing that resolved token shows the embedded DERP info: $ tailcat parse tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFygaFhToGjYWhudGMzMDJhLmlwbi5kZXZhNG0yMDguMTExLjM5LjM4YTZzMjYwNzpmNzQwOjA6M2Y6OjcyMA { "ServerPublic": "nodekey:9c8d2e6728da80a1dd37e275a82595b42d9a838610bc53f74a7670d1610f2e34", "Region": [ { "Nodes": [ { "HostName": "tc302a.ipn.dev", "IPv4": "208.111.39.38", "IPv6": "2607:f740:0:3f::720" } ] } ] } A server can print the long self-contained form directly with the --full-address flag.

§2 AI · 70%

Key Management A server's address (connection token) is derived from its WireGuard key, so the key you use determines who can reach you: Ephemeral keys (the default): each server run generates a fresh key in memory and prints an address nobody has ever seen. When the process exits, the key is discarded and the address is dead forever. This is the safe default: sharing that address only ever refers to that one run. Saved keys: tailcat genkey generates a key saved to disk so the address stays stable across restarts. The flip side: anyone you've ever shared that address with can connect to any future server using that key, unless you restrict clients with --allow (see tailcat genkey --client). The CLI says at startup which kind it's using, so you know whether you're starting a fresh single-use server or re-listening on an address you may have shared in the past.