Tailcat: Point-to-Point WireGuard and NAT Traversal Without a Control Plane
Tailcat brings Tailscale's magicsock and WireGuard data plane to a lightweight, userspace CLI tool without requiring central coordination.
Tailscale revolutionized mesh VPNs by combining WireGuard with clever NAT traversal (magicsock) and encrypted relay fallback (DERP). However, typical Tailscale setups require registering nodes with a centralized coordination server (the control plane) and running privileged daemons that modify system routing tables and virtual network interfaces (TUN/TAP).
Tailcat changes that paradigm. Built by the Tailscale team, Tailcat packages Tailscale's core data plane components into a standalone, userspace CLI and Go library. It acts like netcat, but every connection is end-to-end encrypted with WireGuard, traverses NAT automatically, and requires neither root privileges nor a Tailscale account.
Under the Hood: The Zero-Root Userspace Architecture
Tailcat strips away the Tailscale control plane completely. Instead of coordinating peer states through a central control server, it passes all rendezvous metadata out-of-band using compact connection tokens.
Inside the process, Tailcat combines four major components:
- Userspace WireGuard: Handles point-to-point encryption (Curve25519, ChaCha20-Poly1305) without touching the Linux kernel WireGuard module or requiring administrative permissions.
magicsock: Tailscale's multi-path transport engine. It dynamically probes UDP endpoints via STUN and executes UDP hole-punching for direct peer-to-peer connections.- DERP (Designated Encrypted Relay for Packets): An HTTPS-based relay mechanism that acts as the rendezvous channel and guaranteed fallback if symmetric NATs prevent direct UDP hole-punching.
- gVisor
netstack: A full TCP/IP stack running inside userspace memory. Becausenetstackterminates connections in-process, Tailcat can accept incoming streams or forward outbound traffic without altering OS routing tables or local network interfaces.
+-------------------------------------------------------------+
| Tailcat CLI |
| +-------------------------------------------------------+ |
| | gVisor netstack (Userspace TCP/IP) | |
| +-------------------------------------------------------+ |
| | Userspace WireGuard | |
| +-------------------------------------------------------+ |
| | magicsock | |
| +---------------------------+---------------------------+ |
| | STUN / UDP Hole Punch | DERP Encrypted Relay | |
+--+---------------------------+---------------------------+--+
Anatomy of a Connection Token (ConnBlob)
When a Tailcat server starts, it prints an ephemeral address token prefixed with tc (for example, tcomFwWCCcjS5nKNqAod034...).
Internally, this string is a base64-encoded CBOR blob containing:
- The server's 32-byte Curve25519 WireGuard public key (
nodekey:...). - DERP rendezvous metadata (either an integer region ID or full node hostnames and IPs).
You can inspect any token using the CLI:
$ tailcat parse tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
{
"ServerPublic": "nodekey:9c8d2e6728da80a1dd37e275a82595b42d9a838610bc53f74a7670d1610f2e34",
"RegionID": 302
}
The Handshake Sequence
- Bootstrap: The client parses the token, extracts the server's public key and DERP region, and connects to the shared DERP relay.
- Discovery Exchange: The client sends a lightweight
Meowpacket over DERP containing its own ephemeral public key. The server registers the client and replies withMeowed. - WireGuard Handshake: Standard WireGuard handshakes take place through the DERP tunnel.
- NAT Traversal: In parallel, both endpoints exchange candidate UDP addresses (STUN endpoints and local IPs) via DERP disco messages. If UDP hole-punching succeeds, the data stream transparently upgrades to direct UDP.
Practical Use Cases
1. Piped Stdin / Stdout (Encrypted Netcat)
On the receiving machine:
$ tailcat
# 🐈 Server listening with new address: tcXXXXXXXXX
On the sending machine:
$ tar -czf - ./project | tailcat tcXXXXXXXXX
2. Forwarding Local Ports Behind Firewalls
Expose a local service (like a staging web server) without port forwarding, UPnP, or ngrok:
# On the server
$ tailcat --serve=8080
# 🐈 Server listening with new address: tcXXXXXXXXX
# On the client
$ tailcat tcXXXXXXXXX 8080
GET / HTTP/1.1
Host: localhost
3. WireGuard-Authenticated SSH via DNS TXT Records
Tailcat can provide SSH access protected by WireGuard keys before the SSH daemon is exposed:
Generate Client Keypair:
client$ tailcat genkey --client # Public key: nodekey:cfb6bfa77a0654d7450947fd6acef17d2cd848da1d30b2540b13dac272ddfd16Run Authenticated SSH Server:
server$ tailcat genkey --fixed-region server$ tailcat --serve=22 --allow=nodekey:cfb6bf...ddfd16Publish via DNS: Create a DNS
TXTrecord pointing to the generated server token:bastion.internal.example.com. 300 IN TXT "tailcat=tcXXXXXXXXX"Connect Directly via Domain:
client$ tailcat ssh bastion.internal.example.com
Unauthorized connection attempts are dropped at the WireGuard layer—port scanners cannot even detect that an SSH daemon is running.
4. SOCKS5 Proxy and Exit Nodes
Route CLI traffic through a remote peer:
# Remote host
$ tailcat --serve=exit-node
# Local machine
$ tailcat socks <token> curl http://internal-service.local/
Using Tailcat as a Go Library
Tailcat is structured to be embedded directly into Go services.
Server Example
package main
import (
"fmt"
"log"
"net"
"github.com/tailscale/tailcat"
)
func main() {
s := &tailcat.Server{
OnTCP: func(port uint16) func(net.Conn) {
return func(c net.Conn) {
defer c.Close()
fmt.Fprintf(c, "Connected to in-process service on port %d\n", port)
}
},
}
if err := s.Start(); err != nil {
log.Fatal(err)
}
// Share this token with clients
fmt.Println("Connection Token:", s.ConnBlob())
select {}
}
Client Example
package main
import (
"context"
"io"
"log"
"os"
"github.com/tailscale/tailcat"
)
func main() {
token := tailcat.ConnBlob(os.Args[1])
cl := tailcat.NewClient(token)
defer cl.Close()
conn, err := cl.DialTCPPort(context.Background(), 80)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
io.Copy(os.Stdout, conn)
}
BYO Relay: Running Without Public Infrastructure
Tailcat uses public DERP relays by default (https://tailcat.dev/derpmap.json), but you are not locked into them. You can bind server tokens directly to your self-hosted DERP server:
server$ tailcat genkey --region=derp.myinfra.net
# Token generated embedding derp.myinfra.net
Because the DERP node address is stored inside the token itself, connecting clients resolve the rendezvous relay automatically without configuring local flags.
Key Takeaways
- Zero Configuration: No accounts, API tokens, or central coordinators.
- Userspace Safety: Runs unprivileged; no virtual network adapters, DNS overrides, or firewall mutations.
- WireGuard Performance: Tunnels upgrade from DERP relays to peer-to-peer UDP connections through automated NAT traversal.
- Composable: Works as a drop-in command-line tool, an ad-hoc proxy, or an embeddable Go library for distributed services.
Source
tailscale/tailcat: like netcat, but over Tailscale's data plane, without Tailscale's control plane