# How to Explicitly Perform the Meow Handshake with Tailcat

> Explicitly perform the Meow handshake in Tailcat. Learn how to construct a Meow Ping packet, send it via MagicSock, and register your node as a WireGuard peer.

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

---

**You explicitly perform the Meow handshake by constructing a raw Meow Ping packet using `EncodeMeowPing`, transmitting it via `MagicSock.SendDERPPacketTo`, and blocking until the server responds with a Meowed acknowledgment that registers your node as a WireGuard peer.**

Tailcat is an experimental Tailscale component that bootstraps WireGuard connections through a lightweight "Meow" handshake executed before the standard WireGuard handshake begins. While the handshake runs automatically inside the `Ping` method, explicitly managing the packet flow lets you control timing, debug connectivity issues, or integrate the handshake into custom networking stacks. This guide details the exact source code paths in the `tailscale/tailcat` repository required to implement this flow manually.

## What Is the Meow Handshake?

The **Meow handshake** is a minimal key-exchange protocol that transmits a client’s **NodePublic** and **DiscoPublicKey** to a Tailcat server using raw DERP packets. Unlike standard Disco protocol frames, Meow packets are sent unwrapped directly over the DERP connection. The server validates the magic `meow` prefix, registers the client as a WireGuard peer, and replies with a `Meowed` acknowledgment. Once the client receives this acknowledgment, the regular WireGuard handshake proceeds using the newly established peer configuration.

## Step-by-Step Handshake Flow

### Step 1: Construct and Send the Meow Ping

The client initiates the handshake by encoding its public keys into a Meow Ping payload. In `[disco.go](https://github.com/tailscale/tailcat/blob/main/disco.go#L30-L38)`, the `EncodeMeowPing` function serializes the keys with the `meowMagic` prefix:

```go
pkt := tailcat.EncodeMeowPing(clientNodeKey, magicSock.DiscoPublicKey())

```

This packet must be sent as a raw DERP frame, not inside a Disco wrapper. The transmission happens in `[tailcat.go](https://github.com/tailscale/tailcat/blob/main/tailcat.go#L70-L77)` via the `SendDERPPacketTo` method on the underlying `MagicSock`:

```go
sent, err := magicSock.SendDERPPacketTo(
    serverNodePublic,
    derpRegionID,
    pkt,
)
if err != nil || !sent {
    return fmt.Errorf("failed to transmit Meow Ping: %w", err)
}

```

### Step 2: Server-Side Validation and Peer Registration

When the server receives the raw DERP packet, it first checks for the Meow magic constant. The helper `IsMeowPacket` in `[disco.go](https://github.com/tailscale/tailcat/blob/main/disco.go#L25-L28)` performs this validation:

```go
if tailcat.IsMeowPacket(pkt) {
    nodeKey, discoKey, ok := tailcat.ParseMeowPing(pkt)
    // Implementation in disco.go lines 49-62
}

```

If parsing succeeds, the server invokes `onMeow` (implemented in `[tailcat.go](https://github.com/tailscale/tailcat/blob/main/tailcat.go#L426-L431)`) to add the client as a WireGuard peer. Upon successful registration, the server constructs a Meowed acknowledgment using `EncodeMeowed` from `[disco.go](https://github.com/tailscale/tailcat/blob/main/disco.go#L41-L46)`:

```go
ack := tailcat.EncodeMeowed()
magicSock.SendDERPPacketTo(srcNodeKey, regionID, ack)

```

### Step 3: Receiving the Meowed Acknowledgment

The client blocks on a dedicated channel (`c.meowWait`) until the Meowed packet arrives. This synchronization logic resides in `[tailcat.go](https://github.com/tailscale/tailcat/blob/main/tailcat.go#L80-L85)` inside the `ping` method:

```go
select {
case <-c.meowWait:
    // Handshake complete; server has registered us as a peer
    return nil
case <-ctx.Done():
    return ctx.Err()
}

```

Once the channel closes, the handshake is complete and the WireGuard interface can begin standard key exchange with the newly configured peer.

## Code Examples

### Client-Side Explicit Handshake

The following snippet demonstrates how to manually trigger the Meow handshake without relying on the high-level `Ping` convenience method:

```go
package main

import (
    "context"
    "fmt"
    "time"

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

func ExplicitMeowHandshake(
    ctx context.Context,
    c *tailcat.Client,
    serverKey key.NodePublic,
    regionID uint16,
) error {
    // 1. Encode the Meow Ping packet
    meowPkt := tailcat.EncodeMeowPing(
        c.NodePublic(),
        c.MagicSock().DiscoPublicKey(),
    )

    // 2. Send raw DERP packet
    sent, err := c.MagicSock().SendDERPPacketTo(serverKey, regionID, meowPkt)
    if err != nil {
        return fmt.Errorf("send failed: %w", err)
    }
    if !sent {
        return fmt.Errorf("packet queued but not sent")
    }

    // 3. Wait for Meowed acknowledgment (blocks until server replies)
    select {
    case <-c.MeowWaitChannel():
        fmt.Println("Meow handshake succeeded")
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    
    // Assumes c is an initialized *tailcat.Client
    // err := ExplicitMeowHandshake(ctx, c, serverKey, regionID)
}

```

### Server-Side Handler Implementation

Use this handler inside your DERP receive loop to process incoming Meow Pings and respond automatically:

```go
func (b *Backend) HandleRawDERP(
    pkt []byte,
    src key.NodePublic,
    regionID uint16,
) {
    // Early detection of Meow protocol
    if !tailcat.IsMeowPacket(pkt) {
        // Process standard Disco or WireGuard traffic
        return
    }

    nodeKey, discoKey, ok := tailcat.ParseMeowPing(pkt)
    if !ok {
        b.logf("invalid Meow ping from %v", src)
        return
    }

    // Register peer and send acknowledgment
    if b.onMeow(nodeKey, discoKey) {
        ack := tailcat.EncodeMeowed()
        b.magicSock.SendDERPPacketTo(src, regionID, ack)
    }
}

```

## Key Implementation Files

| File | Purpose | Key Functions |
|------|---------|---------------|
| `[disco.go](https://github.com/tailscale/tailcat/blob/main/disco.go)` | Defines Meow packet structures and serialization | `EncodeMeowPing`, `ParseMeowPing`, `EncodeMeowed`, `IsMeowPacket` |
| `[tailcat.go](https://github.com/tailscale/tailcat/blob/main/tailcat.go)` | Client ping logic and server `onMeow` handler | `Client.ping`, `onMeow`, `meowWait` channel handling |
| `[wire.go](https://github.com/tailscale/tailcat/blob/main/wire.go)` | DERP packet routing and raw frame delivery | `SendDERPPacketTo` integration |

## Summary

- **Encode explicitly** using `tailcat.EncodeMeowPing` to build the raw packet containing your NodePublic and DiscoPublicKey.
- **Transmit via raw DERP** using `MagicSock.SendDERPPacketTo` rather than wrapped Disco frames.
- **Validate with `IsMeowPacket`** on the server to distinguish Meow traffic from standard protocol data.
- **Register peers** via `onMeow` and reply with `EncodeMeowed` to complete the handshake.
- **Synchronize on `meowWait`** in the client to confirm the server has accepted the peer registration.

## Frequently Asked Questions

### What is the difference between the Meow handshake and a regular WireGuard handshake?

The Meow handshake is a Tailcat-specific prerequisite that exchanges NodePublic and DiscoPublicKey identities over DERP before WireGuard crypto-key routing begins. It uses a custom `meow` magic prefix and raw DERP packets, whereas the regular WireGuard handshake performs Noise protocol key exchange encapsulated in UDP (or DERP once the peer mapping is established).

### Why does Tailcat use raw DERP packets instead of Disco frames for the Meow handshake?

Raw DERP packets bypass the Disco protocol layer to minimize overhead during the initial peer discovery phase. According to the source code in [`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go), the `meowMagic` constant allows the server to identify bootstrap traffic immediately without parsing Disco headers, enabling faster peer registration before the full WireGuard stack initializes.

### How do I know if the Meow handshake succeeded?

The client’s `ping` method returns successfully only after the `meowWait` channel receives the Meowed acknowledgment from the server, as implemented in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) lines 80-85. If this channel closes or times out, the handshake failed. You can also verify server-side by checking the logs inside `onMeow` to confirm the peer was added to the WireGuard configuration.

### Can I trigger the Meow handshake automatically without manual packet construction?

Yes. Calling `client.Ping(ctx)` automatically performs the handshake internally by invoking `EncodeMeowPing` and managing the `SendDERPPacketTo` call and `meowWait` synchronization. Manual construction is only necessary when you need to interpose custom logic between packet creation and transmission or when debugging DERP connectivity issues.