# How Tailcat Establishes WireGuard Tunnels: A Deep Dive into the Tailscale Client Integration

> Learn how Tailcat establishes WireGuard tunnels by initiating a Tailscale client, managing virtual interfaces, and handling peer configurations for reliable network connections.

- Repository: [Tailscale/tailcat](https://github.com/tailscale/tailcat)
- Tags: deep-dive
- Published: 2026-09-08

---

**Tailcat establishes WireGuard tunnels by starting a Tailscale client that creates a virtual interface, negotiates DERP fallback routes via compact CBOR structures defined in [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go), and manages peer configuration for direct UDP or relayed connections.**

Tailcat is a lightweight tool from the `tailscale/tailcat` repository that builds its own WireGuard mesh using the same networking stack as the full Tailscale client. Unlike traditional VPN daemons, Tailcat spins up ephemeral, secure tunnels on demand for remote commands like SSH or SFTP, then tears them down when the session ends.

## Initializing the Tailscale Client and Virtual Interface

The tunnel establishment process begins in **[`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go)**, the main entry point that parses command-line arguments and instantiates the networking stack.

When the `tailcat` binary starts, it creates a Tailscale client using the `tailscale.com/client` package. This client immediately brings up a virtual WireGuard interface named `tailscale0`. In the source code, this happens through the `NewClient` constructor followed by the `Start` method:

```go
func main() {
    // … flag parsing omitted …
    client, err := tailscale.NewClient(context.Background(), cfg)
    if err != nil { log.Fatalf("client: %v", err) }
    
    // The client boots the WireGuard interface and registers the peer.
    if err := client.Start(); err != nil {
        log.Fatalf("start: %v", err)
    }
    
    // Now run the requested sub‑command (e.g., SSH) over the tunnel.
    runSSH(client, target, args)
}

```

Once `client.Start()` executes, the local instance has a functioning WireGuard interface ready to accept peer configurations.

## Negotiating Connection Details with the Control Plane

Before any data flows, the client must contact the Tailscale control server to obtain its public key and a list of available DERP (relay) nodes. This negotiation step is critical for fallback routing when direct connections fail.

The DERP information travels in a compact **CBOR format** defined in **[`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go)**. This file contains the wire-format struct definitions that mirror the upstream DERP region map:

- **`wireConnInfo`** – Carries connection metadata between instances
- **`wireRegion`** – Represents a DERP geographic region
- **`wireNode`** – Describes individual relay nodes within a region

Using CBOR instead of JSON keeps the handshake payload small, which matters when tunneling over constrained networks.

## Creating the WireGuard Peer Configuration

With control plane data in hand, Tailcat creates the actual WireGuard peer. The peer’s public key derives from the remote instance’s `ConnInfo`, which is processed in **[`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go)**.

The client adds this peer to the virtual `tailscale0` interface with specific **allowed IPs** set to the remote host’s IP range. This configuration ensures that any traffic destined for the remote host is automatically captured by the WireGuard interface, encrypted, and transmitted to the peer.

## Implementing DERP Fallback Routing

Tailcat attempts to establish a direct **UDP path** first, which provides the lowest latency. However, when NAT devices or firewalls block direct connectivity, the client falls back to a DERP relay.

The fallback logic relies on the conversion functions in **[`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go)** that translate between Tailscale’s internal `tailcfg.DERPRegion` structures and Tailcat’s compact wire format:

```go
// Convert a DERP region from the control plane into the compact wire format.
func wireRegionOf(r *tailcfg.DERPRegion) *wireRegion {
    w := &wireRegion{
        RegionID:   r.RegionID.Int64(),
        RegionCode: r.RegionCode,
        RegionName: r.RegionName,
    }
    for _, n := range r.Nodes {
        if n.STUNOnly { continue } // ignore STUN‑only nodes
        w.Nodes = append(w.Nodes, &wireNode{
            Name:             n.Name,
            RegionID:         n.RegionID.Int64(),
            HostName:         n.HostName,
            CertName:         n.CertName,
            IPv4:             n.IPv4,
            IPv6:             n.IPv6,
            STUNPort:         n.STUNPort,
            DERPPort:         n.DERPPort,
            InsecureForTests: n.InsecureForTests,
        })
    }
    return w
}

```

The CBOR serialization performed by these types is essential for exchanging the minimal metadata needed to establish fallback paths. The source code explicitly warns about format stability in lines 16-20 of [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go):

> “The short CBOR field names are the wire format: do not change or reuse them. … TestWireFieldNames locks them in.”

## Maintaining Tunnel Connectivity

Once established, the tunnel must remain active for the duration of the remote command. The Tailscale client embedded in Tailcat handles this by:

- **Refreshing peer configuration** periodically to handle key rotation or network changes
- **Sending keep-alive packets** to prevent NAT mappings from expiring
- **Monitoring path health** to switch between direct and DERP routing as network conditions change

This maintenance happens automatically in the background, allowing the user’s SSH or SFTP session to continue uninterrupted even when underlying network paths shift.

## Summary

- **[`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go)** serves as the entry point, creating a Tailscale client that brings up the `tailscale0` virtual interface.
- **[`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go)** defines compact CBOR structures (`wireRegion`, `wireNode`, `wireConnInfo`) that encode DERP relay information for efficient wire transmission.
- The client first attempts direct **UDP WireGuard** connections, falling back to **DERP relays** when NAT or firewalls prevent direct connectivity via the conversion logic in `wireRegionOf`.
- **Peer configuration** uses remote `ConnInfo` public keys and allowed IPs to route traffic into the encrypted tunnel.
- **Keep-alive packets** and periodic refreshes maintain session stability throughout the remote command execution.

## Frequently Asked Questions

### What file formats does Tailcat use for DERP configuration?

Tailcat uses **CBOR** (Concise Binary Object Representation) for serializing DERP region and node data, as defined in **[`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go)**. This compact binary format reduces the payload size compared to JSON, which is crucial for efficient tunnel establishment over constrained networks.

### How does Tailcat handle NAT traversal when direct connections fail?

When direct UDP paths fail due to NAT or firewall restrictions, Tailcat falls back to **DERP (Designated Encrypted Relay for Packets) relays**. The client uses the `wireRegion` and `wireNode` structures from [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go) to select the optimal relay node, converting upstream `tailcfg.DERPRegion` data via the `wireRegionOf` function to maintain compatibility with the compact wire format.

### What is the role of wire.go in Tailcat's tunnel establishment?

**[`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go)** defines the canonical wire-format structs (`wireConnInfo`, `wireRegion`, `wireNode`) that encode connection and DERP metadata into CBOR. This file is critical for both the initial control plane handshake and fallback routing decisions. The field names in these structs are frozen by `TestWireFieldNames` to ensure backward compatibility, as noted in lines 16-20 of the source.

### How does Tailcat maintain WireGuard tunnel stability during long-running sessions?

The embedded Tailscale client in Tailcat maintains tunnel stability by **periodically refreshing peer configurations** and **transmitting keep-alive packets**. These mechanisms prevent NAT mapping timeouts and handle key rotations automatically, ensuring that SSH or SFTP sessions remain connected even when network conditions change or sessions last for extended periods.