What Is a Tailcat Connection Token (ConnBlob)? A Complete Technical Guide
A Tailcat connection token (ConnBlob) is a compact, URL-safe string that encodes a server's public key and DERP relay information, allowing clients to establish connections without additional network lookups or DNS resolution.
The tailscale/tailcat repository implements this token format to enable portable, self-contained server addresses that can be embedded in URLs, command-line arguments, or DNS TXT records. Understanding the ConnBlob structure is essential for developers integrating Tailcat servers and clients into custom networking solutions.
What Is a ConnBlob?
A ConnBlob (defined as type ConnBlob string in tailcat.go) serves as an opaque connection token handed out by Tailcat servers to clients. According to the source code comments at lines 133–136, it represents "a compact, URL-safe string... the tc-prefixed base64url encoding of CBOR-encoded [ConnInfo]."
The token embeds everything a client requires to reach the server:
- ServerPublic – The server's public key (
NodePublic) for cryptographic identity verification - Region/RegionID – The DERP relay region or regions the server uses for NAT traversal
- Optional DERP details – Self-contained host and port information when the minimal region ID is expanded
Because the blob is URL-safe and compact, it eliminates the need for clients to fetch additional configuration data before initiating a connection.
ConnBlob Structure and Encoding
The internal format follows a strict binary-to-text encoding pipeline implemented in tailcat.go.
Wire Format and Serialization
The actual serialization logic resides in ConnInfo.ConnBlob() (lines 58–89). This method performs four distinct steps to minimize token size:
- Field Pruning – Converts the internal
ConnInfostructure to wire-compatible structs (wireConnInfo,wireRegion,wireNode), dropping unused fields to keep the payload minimal - CBOR Encoding – Serializes the pruned wire struct using Concise Binary Object Representation (CBOR)
- Base64url Encoding – Converts the CBOR bytes to URL-safe Base64 (without padding)
- Prefixing – Prepends the literal string
"tc"to identify the token type
The resulting format is: tc + <base64url-encoded CBOR data>.
CBOR Wire Definitions
The underlying data structures that define the CBOR schema live in wire.go. These include wireConnInfo, wireRegion, and wireNode, which provide stable, versioned serialization targets for the connection information.
Generating a ConnBlob on the Server
Servers generate connection tokens through the Server.ConnBlob() method, which delegates to locoBackend.connBlob() (lines 607–613) to construct a ConnInfo instance before calling ConnInfo.ConnBlob().
Here is how to generate a token from a running server:
// Create and start a new Tailcat server
srv, err := tailcat.NewServer(nil)
if err != nil {
log.Fatal(err)
}
srv.Start(context.Background())
// Obtain the connection token for distribution to clients
blob := srv.ConnBlob()
fmt.Println("Server connection token:", blob)
// Output: tcM7W1iYXNlNjR1cmxzdHJpbmc...
The server automatically embeds its public key and current DERP region configuration into the returned ConnBlob value.
Parsing and Using ConnBlob on Clients
Clients reverse the encoding process using ParseConnBlob (lines 744–747 in tailcat.go), which CBOR-decodes the token back into a ConnInfo structure containing the server's public key and network coordinates.
Basic Token Parsing
// Receive a token from CLI argument, DNS TXT record, or URL parameter
blob := tailcat.ConnBlob("tcBASE64URLENCODEDSTRING")
// Decode into structured connection information
ci, err := tailcat.ParseConnBlob(blob)
if err != nil {
log.Fatalf("Invalid connection token: %v", err)
}
fmt.Printf("Server public key: %s\n", ci.ServerPublic)
fmt.Printf("DERP Region ID: %d\n", ci.RegionID)
Establishing Client Connections
The NewClient function (lines 1455–1471) accepts a ConnBlob directly to configure a client instance that knows how to reach the specified server:
// Initialize client with the server token
client := tailcat.NewClient(blob)
defer client.Close()
// Dial a specific port on the server through the DERP relay
conn, err := client.DialTCPPort(context.Background(), 80)
if err != nil {
log.Fatalf("Dial failed: %v", err)
}
defer conn.Close()
Resolving Short Tokens
Some tokens contain only a RegionID without full DERP details. The ConnBlob.Resolve method (lines 91–99) fetches the complete DERP map and returns a fully embedded, self-contained blob:
// Expand a minimal token into a complete connection descriptor
resolved, err := blob.Resolve(context.Background())
if err != nil {
log.Fatalf("Failed to resolve DERP regions: %v", err)
}
fmt.Println("Resolved token:", resolved)
Key Source Files and Implementation Details
The ConnBlob implementation spans several files in the tailscale/tailcat repository:
tailcat.go– Core type definitions (ConnBlob,ConnInfo), serialization logic (ConnInfo.ConnBlob), parsing functions (ParseConnBlob), and high-level API methods (Server.ConnBlob,NewClient)wire.go– CBOR wire format definitions (wireConnInfo,wireRegion,wireNode) that ensure stable binary encoding across versionstailcat_test.go– Comprehensive tests validating ConnBlob generation, round-trip parsing, and edge casescmd/tailcat/tailcat.go– Command-line interface handling for<addrblob>arguments, including parsing and DNS resolution workflows
Summary
- ConnBlob is a typed string (
type ConnBlob string) that functions as a self-contained server address in the Tailcat ecosystem. - Encoding follows a strict pipeline: CBOR serialization of wire structs → Base64url encoding →
"tc"prefix. - Servers generate tokens via
Server.ConnBlob(), which internally callsConnInfo.ConnBlob()to create the final string representation. - Clients parse tokens using
ParseConnBlob()to extract the server's public key and DERP region information required for NAT traversal. - Short tokens can be resolved using
ConnBlob.Resolve()to fetch full DERP map details and create self-contained connection descriptors. - The implementation prioritizes URL safety and compactness, making tokens suitable for command-line arguments, DNS records, and URL query parameters.
Frequently Asked Questions
What does the "tc" prefix in a ConnBlob signify?
The literal "tc" prefix identifies the string as a Tailcat connection token and distinguishes it from other base64url-encoded data. When ParseConnBlob processes a token, it validates this prefix before stripping it and decoding the remaining CBOR payload.
How does a ConnBlob enable server connections without DNS?
By embedding the server's public key (NodePublic) and DERP relay region information directly in the token, the ConnBlob eliminates the need for traditional DNS resolution or service discovery. Clients extract the DERP region ID and optional host details from the decoded ConnInfo, then establish connections through the specified relay infrastructure using only the data contained in the token.
What is the difference between a short and resolved ConnBlob?
A short ConnBlob contains only a RegionID referencing a DERP region defined in a global map, requiring a network fetch to obtain full relay addresses. A resolved ConnBlob embeds the complete DERP hostnames, ports, and coordinates directly in the CBOR payload, making it fully self-contained. The ConnBlob.Resolve() method in tailcat.go (lines 91–99) converts short tokens into resolved ones by fetching the DERP map and inlining the region details.
Where is the ConnBlob type defined in the Tailcat source code?
The ConnBlob type definition and its associated methods are located in tailcat.go at lines 133–136, where it is declared as type ConnBlob string with documentation explaining its purpose as a URL-safe, CBOR-encoded connection descriptor. Serialization logic appears at lines 58–89 (ConnInfo.ConnBlob), while parsing logic resides at lines 744–747 (ParseConnBlob).
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 →