What Is the Role of DERP in Tailcat's Connection Establishment?
DERP (Designated Encrypted Relay for Packets) functions as the bootstrap mechanism and NAT traversal fallback for Tailcat, enabling the initial exchange of WireGuard keys via WebSocket-capable relays before direct peer-to-peer paths are established.
Tailcat, an experimental mesh networking project from the tailscale/tailcat repository, integrates Tailscale’s DERP infrastructure to solve connectivity challenges between peers located behind restrictive firewalls and NAT devices. Unlike traditional VPN relays that handle all data plane traffic, DERP in Tailcat operates exclusively as a control-plane service for the initial handshake and as a reliable fallback when UDP hole punching fails.
How DERP Bootstraps Initial Connections
Tailcat utilizes DERP relays solely for the initial bootstrap phase of connection establishment. This process involves encoding relay information into compact addresses, resolving abstract region identifiers to concrete endpoints, and carrying the first discovery packets between peers.
Encoding DERP Regions in ConnInfo
When a Tailcat server starts, it advertises its bootstrap location through the RegionID field in the ConnInfo structure. According to the source in main/tailcat.go (lines 71-88), this field contains either a numeric region identifier or a full DERPRegion description, telling the client exactly which DERP relay to contact first.
The compact Addr type, generated by ci.Encode(), embeds this minimal region metadata into a shareable string that clients can use to initiate connections. This design ensures that clients know where to find the rendezvous point without requiring prior knowledge of the server's network topology.
Resolving Regions via DERP Maps
If the address contains only a RegionID, the client must fetch the public DERP map to resolve the abstract identifier to a concrete relay endpoint. As implemented in main/tailcat.go (lines 101-111), the client retrieves this map from DefaultDERPMapURL or a custom URL supplied via the DERPMapURL option.
The package comment in main/tailcat.go (lines 8-15) explicitly describes this "bootstrap-only" usage: the chosen DERP relay carries the two peers' discovery packets, allowing each side to learn the other's public WireGuard keys and NAT-traversal endpoints before any direct UDP path exists.
DERP as a Fallback Relay
While Tailcat attempts to establish direct peer-to-peer connections after the initial handshake, DERP remains available as a fallback pathway. The implementation emphasizes that when NAT traversal fails—such as when both peers sit behind symmetric NATs—the established DERP tunnel continues to carry traffic reliably.
This fallback behavior is documented in main/tailcat.go (lines 12-15), which notes that DERP serves as the fallback relay when direct paths cannot be formed. In this mode, the WebSocket-capable DERP server continues to encrypt and forward packets, ensuring connectivity even in challenging network environments where UDP hole punching is impossible.
Implementation Architecture
The Tailcat source code provides several mechanisms to optimize DERP discovery and reduce latency during the bootstrap phase.
Automatic Region Detection
When the server initializes, it can automatically detect the optimal DERP region through a netcheck request. This logic, located near the top of main/tailcat.go (lines 99-108), analyzes network conditions and stores the resulting region identifier in ConnInfo.RegionID. This auto-detection ensures that clients connect to the geographically closest or lowest-latency relay without manual configuration.
DERP Map Caching
To minimize external dependencies and reduce network traffic during bootstrap, Tailcat implements the DERPMapCache interface defined in main/tailcat.go (lines 13-32). This cache allows a process to reuse a previously fetched DERP map for up to one hour, eliminating redundant HTTP requests to the map URL during transient reconnections or multiple connection attempts.
Wire Format Serialization
For efficient address encoding, Tailcat serializes minimal DERP data into a compact wire format. The wireRegion and wireNode structs, defined in main/wire.go (lines 34-65), handle the binary representation of region IDs, ports, and node lists when constructing the Addr type. This serialization ensures that DERP metadata remains compact enough to fit in URLs or QR codes while preserving all necessary relay information.
Browser and Test Implementations
In WebAssembly environments, the browser client implementation in main/web/main_js.go demonstrates how JavaScript passes the derpMapURL parameter to the Go runtime, enabling DERP bootstrap from browser-based peers. Integration tests in main/web/wasm_test.go verify this behavior by spinning up local DERP and STUN servers to validate both the bootstrap handshake and fallback scenarios.
Practical Code Examples
The following examples demonstrate how to configure DERP bootstrap behavior when implementing Tailcat clients and servers.
Creating a ConnInfo with DERP Region
To advertise a specific DERP region to clients, populate the RegionID field when constructing server configuration:
// Create a ConnInfo that advertises DERP region 2
ci := tailcat.ConnInfo{
ServerPublic: myNodePublic,
ServerDiscoPublic: myDiscoPublic,
RegionID: 2, // Directs client to use DERP region 2
}
// Encode into a shareable tailcat address
addr := ci.Encode() // Returns tailcat.Addr
Expanding Addresses with Custom DERP Maps
On the client side, resolve the compact address to concrete endpoints using the Expand method. You can use the default Tailcat DERP map or specify a custom source:
// Expand the address for client use with a custom DERP map URL
err := ci.Expand(ctx,
tailcat.ExpandForClient,
tailcat.DERPMapURL("https://example.com/derpmap.json"))
if err != nil {
log.Fatal(err)
}
// After expansion, ConnInfo contains concrete DERP relay endpoints
// The client can now start the handshake; DERP carries the first packets
Implementing Persistent DERP Caching
For long-running CLI applications or embedded clients, implement the DERPMapCache interface to persist DERP maps across restarts:
// Custom disk-based cache implementing DERPMapCache
cache := &myDiskCache{path: "/tmp/derp-cache"}
err := ci.Expand(ctx, tailcat.DERPMapCache(cache))
if err != nil {
log.Fatalf("failed to fetch DERP map: %v", err)
}
Summary
- DERP serves as the control-plane bootstrap for Tailcat, not the primary data plane, handling only the initial WireGuard key exchange and NAT traversal coordination.
- Region identifiers embedded in
ConnInfo(defined inmain/tailcat.go) direct clients to specific DERP relays before direct paths are established. - Fallback capability ensures connectivity persists through DERP relays when direct peer-to-peer UDP connections fail due to symmetric NATs or restrictive firewalls.
- Caching and auto-detection mechanisms in
main/tailcat.gooptimize performance by reusing DERP maps for up to one hour and automatically selecting optimal regions. - Wire format types in
main/wire.goenable compact serialization of DERP metadata for efficient address sharing.
Frequently Asked Questions
What does DERP stand for in the context of Tailcat?
DERP stands for Designated Encrypted Relay for Packets. In Tailcat, these relays are WebSocket-capable servers that facilitate the initial connection bootstrap by carrying discovery packets between peers, allowing them to exchange WireGuard public keys and NAT traversal endpoints when no direct path exists.
Does Tailcat route all traffic through DERP relays?
No. Tailcat uses DERP relays only for the initial bootstrap and as a fallback mechanism. Once peers establish direct peer-to-peer connectivity through UDP hole punching, application traffic flows directly between endpoints. DERP carries traffic only when NAT traversal fails or during the first moments of connection establishment.
How does Tailcat determine which DERP region to use?
Tailcat determines the optimal DERP region through automatic netcheck detection on the server side, which stores the result in ConnInfo.RegionID (implemented in main/tailcat.go). Clients then resolve this region ID to concrete relay endpoints by fetching the DERP map from DefaultDERPMapURL or a custom URL specified via the DERPMapURL option.
Can I use a private DERP relay with Tailcat instead of Tailscale's public relays?
Yes. Tailcat supports custom DERP infrastructure through the DERPMapURL configuration option, allowing you to specify a private JSON endpoint that returns your own DERP region definitions. Additionally, you can implement the DERPMapCache interface to cache these definitions locally, reducing dependency on external services as shown in main/tailcat.go.
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 →