# How Tailcat Handles NAT Traversal and Direct Paths: DERP Bootstrap and the Meow Handshake

> Tailcat tackles NAT traversal with DERP bootstrap and the Meow handshake. Learn how it establishes peer-to-peer connections and negotiates direct UDP paths.

- Repository: [Tailscale/tailcat](https://github.com/tailscale/tailcat)
- Tags: internals
- Published: 2026-08-30

---

**Tailcat establishes peer-to-peer connections through a three-stage process: initial bootstrap via DERP relays, a cryptographic Meow handshake over DERP, and endpoint advertisement through CallMeMaybe messages to negotiate direct UDP paths using Tailscale's magicsock hole-punching.**

Tailcat builds a peer-to-peer network layer directly atop Tailscale's battle-tested data plane, implementing NAT traversal without requiring a traditional control plane. Unlike standard Tailscale clients that rely on the coordination server, Tailcat uses a lightweight "Meow" handshake protocol to exchange WireGuard keys and discover direct paths between peers. According to the `tailscale/tailcat` source code, the implementation leverages the existing magicsock component to perform STUN-style hole punching while working even when both endpoints sit behind symmetric NATs.

## The Three-Stage NAT Traversal Process

The NAT traversal implementation in Tailcat consists of three tightly coupled stages that progressively move traffic from relayed DERP connections to direct UDP paths.

### Stage 1: Bootstrap via DERP

When a Tailcat client starts, it has no knowledge of the peer's network location and must use a publicly reachable relay to exchange initial packets. In [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go), the package documentation explains that the DERP relay is used only for "the initial bootstrap" and is later replaced by a direct UDP path if possible. The client first contacts a DERP region (auto-selected or specified in the `ConnBlob`) to begin communication.

This bootstrap mechanism ensures connectivity regardless of NAT configuration, as both outgoing and incoming traffic can traverse the relay server before direct paths are established.

### Stage 2: The Meow Handshake

After the DERP connection is established, Tailcat performs a custom handshake to exchange cryptographic identities and establish WireGuard peering. The client sends a **MeowPing**, which is a small DERP packet containing its node public key and a separate disco public key.

On the server side, `locoBackend.onDERPRecv` receives this packet, validates it, and adds the client as a WireGuard peer. The server then replies with a **Meowed** packet to complete the exchange. This handshake logic is implemented in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (server side) and [`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go) (client side), specifically around lines 455-468 and 1347-1351 in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go), and lines 25-47 in [`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go).

The handshake eliminates the need for Tailscale's control plane by embedding the discovery mechanism directly in the data plane protocol.

### Stage 3: Endpoint Advertisement and Direct-Path Discovery

Once WireGuard peering is established, both sides must discover direct UDP endpoints to bypass the DERP relay. Each side advertises its current UDP endpoints to the other via a **CallMeMaybe** disco message sent over the existing DERP connection.

The function `locoBackend.advertiseEndpoints` gathers the local UDP endpoints (`b.eps`) from the magicsock status callback and transmits them to every known peer. When a peer receives these endpoints, its magicsock component immediately begins pinging them. Successful pings cause magicsock to install a direct UDP path, and subsequent traffic routes through that direct connection instead of the relay.

This process, found in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) lines 119-138 and 1655-1675, enables STUN-style hole punching that works even with symmetric NATs, as the magicsock layer manages the complex state machine of outgoing probes and incoming validations.

## Key Implementation Files

The NAT traversal logic spans several files in the repository:

- **[`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go)**: Core server/client orchestration, DERP bootstrap, Meow handshake implementation, and endpoint advertisement via `advertiseEndpoints`.
- **[`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go)**: Definition of Meow packets (MeowPing / Meowed) and helper functions to encode and decode them for wire transmission.
- **[`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go)**: CBOR wire format for `ConnBlob` that embeds DERP region information used during the initial bootstrap phase.

These files collectively implement the full NAT-traversal lifecycle without requiring the Tailscale coordination server.

## Practical Code Examples

### Running a Server

The following example starts a Tailcat server that listens on any available DERP region:

```go
package main

import (
	"log"

	"github.com/tailscale/tailcat"
)

func main() {
	srv := &tailcat.Server{
		// Optional: restrict which client keys may connect.
		// AllowedClients: []tailscale.com/types/key.NodePublic{myClientKey},
	}
	if err := srv.Start(); err != nil {
		log.Fatalf("server start: %v", err)
	}
	defer srv.Close()

	// The server exposes an address that clients can use.
	log.Printf("server ConnBlob: %s", srv.ConnBlob())
}

```

### Connecting a Client

This client automatically discovers a direct UDP path after completing the Meow handshake:

```go
package main

import (
	"context"
	"log"

	"github.com/tailscale/tailcat"
)

func main() {
	// Replace this with the ConnBlob printed by the server.
	const serverBlob = "tc..."

	cli := tailcat.NewClient(tailcat.ConnBlob(serverBlob))
	if err := cli.DialTCPPort(context.Background(), "example.com", 80); err != nil {
		log.Fatalf("dial: %v", err)
	}
	// At this point the client has:
	//   1) completed the Meow handshake,
	//   2) advertised its UDP endpoints,
	//   3) received the server’s endpoints,
	//   4) established a direct UDP path (if NAT traversal succeeded).
}

```

### Debugging Network State

To inspect the resolved network map and verify direct path establishment:

```go
nm := cli.lb.nm      // internal; only for debugging
log.Printf("client sees %d peers, own addr %s", len(nm.Peers), cli.lb.addr)

```

## Summary

- **DERP bootstrap** provides initial connectivity when direct paths are unknown, using Tailscale's existing relay infrastructure as a temporary data channel.
- The **Meow handshake** (`MeowPing`/`Meowed`) replaces the control plane by exchanging WireGuard keys and disco keys directly over DERP.
- **CallMeMaybe** messages trigger endpoint discovery, allowing magicsock to probe advertised UDP endpoints and install direct paths via STUN-style hole punching.
- The implementation reuses Tailscale's `magicsock` and `wireguard-go` libraries, ensuring production-grade NAT traversal without custom control plane logic.

## Frequently Asked Questions

### How does Tailcat handle symmetric NATs that block direct connections?

Tailcat handles symmetric NATs by using the DERP relay as a fallback path while continuously attempting hole punching. The magicsock component sends periodic keep-alives and probes to discovered endpoints. Even when direct UDP paths fail due to strict symmetric NATs, the encrypted DERP connection remains functional, ensuring the peer-to-peer link never drops entirely.

### What is the purpose of the CallMeMaybe message in the protocol?

The **CallMeMaybe** message serves as an explicit invitation for the receiving peer to start probing the sender's UDP endpoints. When `locoBackend.advertiseEndpoints` gathers local endpoints from `b.eps`, it encapsulates them in this disco message and transmits it over DERP. Upon receipt, the peer's magicsock immediately begins pinging those endpoints, creating the necessary NAT mappings to establish a direct path.

### Why does Tailcat use DERP at all if the goal is direct peer-to-peer connections?

DERP acts as a **reliable bootstrap channel** when both endpoints lack knowledge of each other's public IP addresses or port mappings. It carries the initial Meow handshake and the first CallMeMaybe messages required to coordinate the direct path discovery. Once magicsock establishes a lower-latency direct UDP path, traffic automatically migrates away from the relay, though DERP remains available as a resilient backup.

### How is Tailcat's NAT traversal different from standard Tailscale clients?

Standard Tailscale clients rely on the **coordination server** (control plane) to distribute node public keys and endpoints, whereas Tailcat performs these functions in-band using the **Meow handshake** over DERP. The direct-path discovery mechanism itself is identical—both use magicsock's STUN implementation and CallMeMaybe signaling—but Tailcat eliminates the external dependency on Tailscale's control plane by embedding the discovery logic directly in the data plane protocol.