# How a Tailcat Client Connects to a Server: WireGuard Tunnel Establishment

> Learn how a Tailcat client establishes a secure WireGuard tunnel to a server. Explore ConnBlob tokens, DERP relays, and the handshake process for seamless P2P connections.

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

---

**A Tailcat client establishes a secure, peer-to-peer WireGuard tunnel to a server using only a compact ConnBlob token, resolving the server address, building a local networking stack, and completing a "meow" handshake over DERP relays.**

The Tailcat project (`tailscale/tailcat`) demonstrates how to establish lightweight, secure connections using Tailscale's networking primitives. Unlike traditional VPN clients that require complex configuration files and certificate authorities, a Tailcat client connects to a server using only a compact token containing the server's public key and relay information. This article explores the three-phase connection process implemented in the source code.

## Phase 1: Resolving the Server Address

The connection process begins with parsing the ConnBlob token provided by the server. In `tailcat.go:71-74`, the `ParseConnBlob` function decodes this compact representation to extract the server's public key (`ServerPublic`) and optional DERP region information.

```go
ci, err := ParseConnBlob(c.Server)   // tailcat.go:71-74

```

If the ConnBlob does not embed a specific DERP region, the client must discover available relays. The `ConnInfo.Expand` method (`tailcat.go:99-112`) fetches the DERP map from `DefaultDERPMapURL` (or a user-provided URL) and selects the optimal region. This resolution step ensures the client knows which relay endpoint to use for the initial connection, even when traversing restrictive NATs or firewalls.

## Phase 2: Building the Local Networking Stack

Once the server location is resolved, the client constructs a minimal, embedded version of the Tailscale networking stack. This involves three critical components configured in sequence:

**The locoBackend.** The `newLocoBackend` function (`tailcat.go:75-87`) creates a lightweight backend structure that holds the node's private key, derives its unique IPv6 address via `tcAddrForKey`, and initializes logging facilities.

**The WireGuard Engine.** The `createEngine` function (`tailcat.go:57-70`) instantiates a userspace WireGuard engine with a `DERPAppName` of `"tailcat-client"`. This engine is specifically configured to force the disco key to match the node key, ensuring cryptographic consistency across the connection.

**The Virtual Network Stack.** The `newNetstack` function (`tailcat.go:46-52`) wires the WireGuard engine to a `netstack.Impl`, creating a virtual network interface that allows standard TCP/IP operations over the encrypted tunnel.

Together, these components create a self-contained networking environment capable of routing all traffic through the DERP relay until direct peer-to-peer paths are established.

## Phase 3: The Meow Handshake

With the networking stack initialized, the client performs a custom handshake protocol to register itself with the server. This "meow" handshake operates over the DERP relay and consists of a simple request-response pattern.

The client initiates the handshake through `Client.Ping` (`tailcat.go:388-404`), which sends a `MeowPing` packet to the server's DERP endpoint. The client's `onDERPRecv` callback (`tailcat.go:158-168`) filters incoming traffic, specifically listening for the `Meowed` response packet.

On the server side, the `onDERPRecv` handler (`tailcat.go:256-267`) processes the incoming `MeowPing`, invokes `onMeow` to add the client as a WireGuard peer, and replies with the `Meowed` acknowledgment. This exchange simultaneously authenticates the client and establishes the peer relationship in the WireGuard configuration.

Once the handshake completes and `c.upDone` is marked, the engine's `peerAllowedIPs` and `peerByIP` mappings route traffic through the encrypted tunnel, enabling transparent TCP connections via `Client.DialTCP` or `Client.Dial`.

## Complete Implementation Example

The following example demonstrates the entire connection flow, from token parsing to establishing a TCP tunnel:

```go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"tailscale.com/types/key"
	"tailscale.com/tailcat"
)

func main() {
	// 1️⃣ The server gave us this token (ConnBlob).  In practice copy it from the server's output.
	const serverToken tailcat.ConnBlob = "tcomFWcYg…"

	// 2️⃣ Create a client; the token is all we need.
	c := tailcat.NewClient(serverToken)

	// 3️⃣ Optional: set a custom logger.
	c.Logf = log.Printf

	// 4️⃣ Test connectivity – this will start the stack, resolve the DERP region,
	//    perform the meow handshake, and then send a test packet.
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	if res, err := c.Ping(ctx); err != nil {
		log.Fatalf("ping failed: %v", err)
	} else {
		fmt.Printf("handshake latency: %v\n", res.Latency)
	}

	// 5️⃣ Open a TCP connection to the server (e.g. port 22 for the optional SSH server).
	conn, err := c.DialTCPPort(ctx, "127.0.0.1:22")
	if err != nil {
		log.Fatalf("dial failed: %v", err)
	}
	defer conn.Close()
	fmt.Println("TCP tunnel to server established")
}

```

## Summary

- A Tailcat client requires only a **ConnBlob token** to connect, eliminating the need for configuration files or certificate authorities.
- The connection process involves three phases: **address resolution** (parsing the token and fetching DERP maps), **stack construction** (building the locoBackend, WireGuard engine, and netstack), and the **meow handshake** (exchanging MeowPing/Meowed packets over DERP).
- The handshake simultaneously authenticates the client and registers it as a WireGuard peer, after which standard TCP/IP operations work transparently over the encrypted tunnel.
- Key source locations include [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) for the core logic and [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go) for the CBOR serialization format used in ConnBlob encoding.

## Frequently Asked Questions

### What is a ConnBlob in the Tailcat protocol?

A ConnBlob is a compact, CBOR-encoded token that contains the server's public key and optional DERP region information. According to the `tailcat` source code, this token is parsed by `ParseConnBlob` (`tailcat.go:71-74`) to extract the `ConnInfo` necessary to locate and authenticate the server, serving as the sole credential required for connection.

### Why does Tailcat use a "meow" handshake instead of standard WireGuard key exchange?

The meow handshake provides application-layer registration over the DERP relay before WireGuard traffic begins. As implemented in `tailcat.go:256-267`, this allows the server to dynamically add the client as a peer (`onMeow`) upon receiving the `MeowPing`, ensuring the server knows which public keys to accept before the client attempts to send encrypted WireGuard packets.

### How does Tailcat differ from the standard Tailscale client connection process?

Tailcat embeds a minimal "locoBackend" rather than using the full `LocalBackend` found in the main Tailscale daemon. This lightweight approach, visible in `newLocoBackend` (`tailcat.go:75-87`), creates a userspace-only implementation without requiring system TUN devices or administrative privileges, making it suitable for embedded applications or library usage.

### Can a Tailcat client connect through corporate firewalls and NAT?

Yes. The client uses DERP (Designated Encrypted Relay for Packets) relays to establish the initial connection. The `ConnInfo.Expand` method (`tailcat.go:99-112`) discovers available relays, and all initial handshake traffic traverses HTTPS-like connections through these relays, allowing connectivity even when both peers are behind strict NATs or egress firewalls.