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
RegionIDonly (e.g.,tc:a1b2c3d4…). Clients must resolve the region ID to actual relay endpoints. - Self‑contained (full) form: Embeds complete
Regiondata 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.Contextfor cancellation and timeout control. - Optional variadic parameters allow customizing DERP map sources.
- Expansion is idempotent: calling
Expandon 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:
- Generates a fresh
ConnInfowithRegionIDset. - Calls
ConnInfo.Expand()before encoding. - 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 expandedConnInfoback 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-addressfor server output, orConnInfo.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →