How to Resolve a Tailcat Address to a Self‑Contained Form: CLI and Go API Guide

Use the tailcat expand subcommand or the ConnInfo.Expand() method to resolve a short tailcat address into a self‑contained form that embeds full DERP region data.

A tailcat address is a compact, URL‑safe string encoding a [ConnInfo] structure used by Tailscale's peer‑to‑peer networking tool. By default, addresses contain only a DERP region ID, requiring clients to fetch a DERP map separately. Resolving the address to a self‑contained form embeds the full DERP region information directly into the address string, eliminating external lookups.

Understanding Tailcat Address Expansion

The tailcat repository defines two address forms:

  • Short form: Contains RegionID only (e.g., tc:a1b2c3d4…). Clients must resolve the region ID to actual relay endpoints.
  • Self‑contained (full) form: Embeds complete Region data including embedded IPv4/IPv6 addresses, eliminating runtime dependencies on DERP map fetching.

The expansion process populates the Region field of ConnInfo with full DERP data, transforming a short address into a long, portable one.

The Expand Method in tailcat.go

The core expansion logic resides in the ConnInfo type. According to the source code, the method signature is:

func (ci *ConnInfo) Expand(ctx context.Context, opts ...any) error

Expand queries the Tailscale DERP map to resolve RegionID into full Region data, mutating ci in place. Once expanded, ci.TailcatAddr() returns the self‑contained address string.

Key implementation details from [tailcat.go](https://github.com/tailscale/tailcat/blob/main/tailcat.go):

  • The method accepts context.Context for cancellation and timeout control.
  • Optional variadic parameters allow customizing DERP map sources.
  • Expansion is idempotent: calling Expand on an already‑resolved address returns immediately.

Using the tailcat expand Subcommand

The CLI provides a dedicated subcommand for address resolution. Located in [cmd/tailcat/tailcat.go](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go), the expand command parses a short address, calls ConnInfo.Expand(), and outputs JSON.

Example:


# Resolve a short address to self‑contained form

$ tailcat expand tc:a1b2c3d4e5f6g7h8i9j0k...
{
  "ServerPublic": "key:...",
  "ServerDiscoPublic": "key:...",
  "PresharedKey": "base64:...",
  "Region": [
    {
      "RegionID": 1,
      "EmbeddedIPv4": "1.2.3.4",
      "EmbeddedIPv6": "[2001:db8::1]",
      "Name": "us-east"
    }
  ],
  "RegionID": 0
}

The subcommand handles base64 decoding, CBOR deserialization via wire.go, DERP map fetching, and JSON re‑serialization.

The --full-address Flag on tailcat serve

For server operations, the --full-address flag automatically emits self‑contained addresses. The flag is defined in [cmd/tailcat/tailcat.go](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) as:

flagFullAddress bool

When enabled, the server:

  1. Generates a fresh ConnInfo with RegionID set.
  2. Calls ConnInfo.Expand() before encoding.
  3. Prints the long address containing embedded DERP data.

Example:


# Start server with self‑contained address output

$ tailcat serve --full-address --psk
tailcat address: tcomFwWCA…[long-base64-string]

This flag eliminates the need for clients to run expand separately.

Programmatic Expansion in Go

For custom tooling, use the tailcat package directly:

package main

import (
	"context"
	"log"

	"tailscale.com/tailcat"
)

func main() {
	// Parse a short address string
	ci, err := tailcat.ParseAddr("tc:a1b2c3d4e5…")
	if err != nil {
		log.Fatal(err)
	}

	// Resolve to self‑contained form
	if err := ci.Expand(context.Background()); err != nil {
		log.Fatal(err)
	}

	// Re‑encode as full address
	fullAddr, err := ci.TailcatAddr()
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Self‑contained address: %s", fullAddr)
}

Key API points:

  • tailcat.ParseAddr(string): Decodes base64 and CBOR into *ConnInfo.
  • (*ConnInfo).Expand(context.Context, ...any): Fetches and embeds DERP region data.
  • (*ConnInfo).TailcatAddr(): Encodes expanded ConnInfo back to string format.

When to Use Each Approach

Approach Use Case
tailcat expand One‑off address conversion, debugging, or script automation.
--full-address flag Server deployment where emitted addresses must work without client‑side DERP lookups.
Go API (Expand) Custom clients, testing, or integration with existing Go services.

Key Source Files

File Purpose
tailcat.go ConnInfo struct, Expand() method, and TailcatAddr() encoding.
cmd/tailcat/tailcat.go CLI implementation including expand subcommand and --full-address flag.
wire.go CBOR serialization/deserialization for address encoding.
derpmap.go (implied) DERP map fetching for region resolution.

Summary

  • Short tailcat addresses contain only region IDs and require external DERP map lookup.
  • Self‑contained addresses embed full region data, making them portable and offline‑capable.
  • Use tailcat expand <addr> for CLI conversion, --full-address for server output, or ConnInfo.Expand() programmatically.
  • All three approaches converge on the same implementation in tailcat.go.

Frequently Asked Questions

What is the difference between a short and self‑contained tailcat address?

A short address encodes only ServerPublic, ServerDiscoPublic, PresharedKey, and RegionID. A self‑contained address additionally includes the full Region structure with embedded IPv4/IPv6 endpoints, eliminating the need for clients to fetch DERP maps.

Does Expand() modify the original address string?

No. Expand() mutates the *ConnInfo struct in memory. You must call TailcatAddr() to obtain the new encoded string. The original address remains valid but unexpanded.

Can I expand an address without network access?

No. Expand() requires network access to fetch the DERP map from Tailscale's coordination server or a custom source. The self‑contained output, however, works entirely offline once generated.

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 →