How Tailcat Establishes WireGuard Tunnels: A Deep Dive into the Tailscale Client Integration
Tailcat establishes WireGuard tunnels by starting a Tailscale client that creates a virtual interface, negotiates DERP fallback routes via compact CBOR structures defined in wire.go, and manages peer configuration for direct UDP or relayed connections.
Tailcat is a lightweight tool from the tailscale/tailcat repository that builds its own WireGuard mesh using the same networking stack as the full Tailscale client. Unlike traditional VPN daemons, Tailcat spins up ephemeral, secure tunnels on demand for remote commands like SSH or SFTP, then tears them down when the session ends.
Initializing the Tailscale Client and Virtual Interface
The tunnel establishment process begins in tailcat.go, the main entry point that parses command-line arguments and instantiates the networking stack.
When the tailcat binary starts, it creates a Tailscale client using the tailscale.com/client package. This client immediately brings up a virtual WireGuard interface named tailscale0. In the source code, this happens through the NewClient constructor followed by the Start method:
func main() {
// … flag parsing omitted …
client, err := tailscale.NewClient(context.Background(), cfg)
if err != nil { log.Fatalf("client: %v", err) }
// The client boots the WireGuard interface and registers the peer.
if err := client.Start(); err != nil {
log.Fatalf("start: %v", err)
}
// Now run the requested sub‑command (e.g., SSH) over the tunnel.
runSSH(client, target, args)
}
Once client.Start() executes, the local instance has a functioning WireGuard interface ready to accept peer configurations.
Negotiating Connection Details with the Control Plane
Before any data flows, the client must contact the Tailscale control server to obtain its public key and a list of available DERP (relay) nodes. This negotiation step is critical for fallback routing when direct connections fail.
The DERP information travels in a compact CBOR format defined in wire.go. This file contains the wire-format struct definitions that mirror the upstream DERP region map:
wireConnInfo– Carries connection metadata between instanceswireRegion– Represents a DERP geographic regionwireNode– Describes individual relay nodes within a region
Using CBOR instead of JSON keeps the handshake payload small, which matters when tunneling over constrained networks.
Creating the WireGuard Peer Configuration
With control plane data in hand, Tailcat creates the actual WireGuard peer. The peer’s public key derives from the remote instance’s ConnInfo, which is processed in disco.go.
The client adds this peer to the virtual tailscale0 interface with specific allowed IPs set to the remote host’s IP range. This configuration ensures that any traffic destined for the remote host is automatically captured by the WireGuard interface, encrypted, and transmitted to the peer.
Implementing DERP Fallback Routing
Tailcat attempts to establish a direct UDP path first, which provides the lowest latency. However, when NAT devices or firewalls block direct connectivity, the client falls back to a DERP relay.
The fallback logic relies on the conversion functions in wire.go that translate between Tailscale’s internal tailcfg.DERPRegion structures and Tailcat’s compact wire format:
// Convert a DERP region from the control plane into the compact wire format.
func wireRegionOf(r *tailcfg.DERPRegion) *wireRegion {
w := &wireRegion{
RegionID: r.RegionID.Int64(),
RegionCode: r.RegionCode,
RegionName: r.RegionName,
}
for _, n := range r.Nodes {
if n.STUNOnly { continue } // ignore STUN‑only nodes
w.Nodes = append(w.Nodes, &wireNode{
Name: n.Name,
RegionID: n.RegionID.Int64(),
HostName: n.HostName,
CertName: n.CertName,
IPv4: n.IPv4,
IPv6: n.IPv6,
STUNPort: n.STUNPort,
DERPPort: n.DERPPort,
InsecureForTests: n.InsecureForTests,
})
}
return w
}
The CBOR serialization performed by these types is essential for exchanging the minimal metadata needed to establish fallback paths. The source code explicitly warns about format stability in lines 16-20 of wire.go:
“The short CBOR field names are the wire format: do not change or reuse them. … TestWireFieldNames locks them in.”
Maintaining Tunnel Connectivity
Once established, the tunnel must remain active for the duration of the remote command. The Tailscale client embedded in Tailcat handles this by:
- Refreshing peer configuration periodically to handle key rotation or network changes
- Sending keep-alive packets to prevent NAT mappings from expiring
- Monitoring path health to switch between direct and DERP routing as network conditions change
This maintenance happens automatically in the background, allowing the user’s SSH or SFTP session to continue uninterrupted even when underlying network paths shift.
Summary
tailcat.goserves as the entry point, creating a Tailscale client that brings up thetailscale0virtual interface.wire.godefines compact CBOR structures (wireRegion,wireNode,wireConnInfo) that encode DERP relay information for efficient wire transmission.- The client first attempts direct UDP WireGuard connections, falling back to DERP relays when NAT or firewalls prevent direct connectivity via the conversion logic in
wireRegionOf. - Peer configuration uses remote
ConnInfopublic keys and allowed IPs to route traffic into the encrypted tunnel. - Keep-alive packets and periodic refreshes maintain session stability throughout the remote command execution.
Frequently Asked Questions
What file formats does Tailcat use for DERP configuration?
Tailcat uses CBOR (Concise Binary Object Representation) for serializing DERP region and node data, as defined in wire.go. This compact binary format reduces the payload size compared to JSON, which is crucial for efficient tunnel establishment over constrained networks.
How does Tailcat handle NAT traversal when direct connections fail?
When direct UDP paths fail due to NAT or firewall restrictions, Tailcat falls back to DERP (Designated Encrypted Relay for Packets) relays. The client uses the wireRegion and wireNode structures from wire.go to select the optimal relay node, converting upstream tailcfg.DERPRegion data via the wireRegionOf function to maintain compatibility with the compact wire format.
What is the role of wire.go in Tailcat's tunnel establishment?
wire.go defines the canonical wire-format structs (wireConnInfo, wireRegion, wireNode) that encode connection and DERP metadata into CBOR. This file is critical for both the initial control plane handshake and fallback routing decisions. The field names in these structs are frozen by TestWireFieldNames to ensure backward compatibility, as noted in lines 16-20 of the source.
How does Tailcat maintain WireGuard tunnel stability during long-running sessions?
The embedded Tailscale client in Tailcat maintains tunnel stability by periodically refreshing peer configurations and transmitting keep-alive packets. These mechanisms prevent NAT mapping timeouts and handle key rotations automatically, ensuring that SSH or SFTP sessions remain connected even when network conditions change or sessions last for extended periods.
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 →