How Tailcat Achieves UDP Hole-Punching for NAT Traversal: Inside the Disco Protocol

Tailcat leverages Tailscale's lightweight "disco" protocol to exchange "meow" packets and STUN probes, enabling direct UDP connectivity between NAT-bound peers with seamless DERP fallback.

NAT traversal remains a critical hurdle for peer-to-peer networking tools. The tailscale/tailcat repository solves this through a sophisticated UDP hole-punching implementation that combines cryptographic endpoint verification with intelligent fallback strategies, all orchestrated through the Tailscale control plane.

The Disco Protocol Architecture

The foundation of Tailcat's NAT traversal lies in the disco package, a lightweight discovery protocol derived from netcode. This protocol handles endpoint discovery without requiring manual port configuration.

At connection initiation, peers exchange their node public key and disco public key through the Tailscale control server. This cryptographic handshake establishes identity before any UDP packets flow, ensuring that hole-punching attempts target the correct endpoints.

The "Meow" Packet Handshake

Central to the discovery process are "meow" packets—special DERP frames marked by the magic ASCII prefix m e o w that signal NAT-traversal intent. When a Tailcat client initiates a connection, it constructs these packets containing its public keys and transmits them toward the peer.

In disco.go, the IsMeowPacket function identifies these frames by scanning for the meow magic prefix, distinguishing them from standard DERP relay traffic. This allows the receiver to parse the embedded cryptographic credentials and prepare for direct UDP communication.

STUN Probing and Address Mapping

Before attempting direct connection, Tailcat must learn its external network topology. The client issues a STUN-only probe configured with STUNOnly: true in the wire protocol to determine its public-facing UDP address as seen by the server.

This external address mapping propagates back through the meow ping/pong exchange, giving both peers the necessary endpoint information to attempt direct communication. The STUN probe logic resides in disco.go, where it handles the binding request/response cycle required for NAT mapping discovery.

Executing UDP Hole-Punching

Armed with reciprocal external addresses, both peers simultaneously transmit UDP datagrams to each other's publicly mapped ports. This synchronized transmission exploits the behavior of most NAT devices, which temporarily open return-path holes upon observing outbound traffic.

The implementation sends these datagrams through the raw UDP socket managed by the wire layer, attempting to establish a full-duplex channel that bypasses the DERP relay entirely. Success depends on NAT behavior—cone NATs typically allow this direct path, while symmetric NATs may block it.

DERP Fallback Mechanism

When direct UDP connectivity fails—common with symmetric NATs or aggressive firewall rules—Tailcat seamlessly transitions to DERP relay mode. The wire.go file manages this fallback, handling low-level packet framing and relay selection without interrupting the application-layer connection.

This ensures that even when hole-punching fails, SSH or other tunneled traffic continues flowing through the relay infrastructure, providing reliability without user intervention.

Practical Usage Examples

Deploying Tailcat automatically engages these NAT traversal mechanisms. No manual configuration is required to enable hole-punching.

Start a server that listens for inbound connections and automatically attempts UDP hole-punching with each client:

sudo tailcat -listen :22 -ssh

Connect a client to the server. The client executes the full NAT-traversal handshake, including meow packet exchange and STUN probing, transparently to the user:

tailcat -connect user@my-server.example.com -ssh

Both commands work even when both endpoints reside behind NAT devices. Tailcat will either establish a direct UDP tunnel or fall back to DERP relay automatically.

Summary

  • Tailcat uses the disco protocol from Tailscale to coordinate NAT traversal between peers.
  • "Meow" packets with the magic prefix m e o w initiate the handshake and exchange cryptographic identities via disco.go.
  • STUN probing with the STUNOnly flag determines external UDP addresses required for direct connection attempts.
  • Synchronized UDP transmission performs the actual hole-punching through NAT devices.
  • If direct connection fails, the system automatically falls back to DERP relay via wire.go.

Frequently Asked Questions

How does Tailcat differ from standard STUN-based hole-punching tools?

Tailcat integrates STUN probing with a cryptographic identity layer (disco keys) and a reliable fallback to DERP relays. Unlike standalone STUN tools that only provide address discovery, Tailcat handles the complete connection lifecycle including encrypted tunnel establishment and automatic relay failover as implemented in tailscale/tailcat.

What happens if both peers are behind symmetric NATs?

Symmetric NATs assign unique external ports for each destination, preventing standard hole-punching. In this scenario, Tailcat's direct UDP attempts fail, and the connection immediately transitions to the DERP relay mode managed by wire.go, maintaining connectivity without user action.

Are the "meow" packets encrypted?

The meow packets themselves carry disco public keys for endpoint verification but are not the primary encryption layer. Once the direct UDP path or DERP tunnel establishes, all application traffic flows through the Tailscale WireGuard tunnel, ensuring data confidentiality regardless of the discovery method used.

Can I disable hole-punching and force DERP-only mode?

The current implementation in tailcat.go automatically attempts hole-punching for optimal performance. While there is no command-line flag to disable direct UDP attempts in the provided interface, the system automatically prioritizes DERP fallback when direct paths prove unreachable.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →