What Happens When NAT Traversal Fails in Tailcat: DERP Relay Fallback Explained
When NAT traversal fails in Tailcat, the connection automatically falls back to a DERP (Designated Encrypted Relay Point) relay server, maintaining the encrypted WireGuard tunnel while reporting the fallback status through path-detection commands.
Tailcat, an open-source networking tool within the Tailscale ecosystem, relies on the magicsock layer to establish direct peer-to-peer UDP paths between clients and servers. When symmetric NATs or restrictive firewall configurations prevent successful hole-punching, Tailcat handles these failures transparently without interrupting application traffic.
Automatic DERP Relay Fallback
Magicsock Layer Handling
In tailcat.go (lines 13-15), the core library implements the logic that detects when direct UDP path establishment fails. The magicsock stack automatically transitions the encrypted WireGuard tunnel to use a DERP (Designated Encrypted Relay Point) server as a permanent fallback. This transition is transparent to the application layer, ensuring TCP and UDP traffic continues to flow without manual intervention or configuration changes.
Path-Type Reporting
Tailcat exposes connection topology through the ping command, which actively probes whether traffic traverses a direct path or routes via a DERP hop. When NAT traversal fails, the command output explicitly marks the connection as using DERP, providing immediate visibility into the network path quality.
Detecting NAT Traversal Failures via CLI
Checking Current Path Status
Run the ping command to verify if the connection is operating over a relay:
# Ping the server; the output ends with "DERP" if NAT traversal failed.
tailcat ping ts://tcom... # replace with your tailcat address
# Example output:
# ping 127.0.0.1:22: pong (DERP)
When the output includes (DERP) instead of a direct IP address, NAT traversal has failed and the connection is using the fallback relay.
Retrying Until Direct Connection
For automation scripts requiring direct peer-to-peer connectivity, use the --until-direct flag with a specified timeout. In cmd/tailcat/tailcat.go (lines 885-900), the CLI implements logic that repeatedly pings the server until a direct path is established or the timeout expires.
# Keep pinging until a direct path is established or 15s elapse.
tailcat ping --until-direct --timeout=15s ts://tcom...
# If NAT traversal never succeeds:
# log.Fatalf("no direct path to the server after 15s")
# The command exits with status 1.
If the timeout expires without establishing a direct path, the command exits with a non-zero status, allowing shell scripts to detect and handle the failure.
Programmatic Fallback Detection
Go applications integrating Tailcat can check the connection type programmatically. The connection object exposes state indicating whether it is currently routing through a DERP relay:
// In Go code, after establishing a Tailcat client:
if conn.UsingDERP() {
fmt.Println("Operating over DERP relay – NAT traversal failed.")
} else {
fmt.Println("Direct peer-to-peer path established.")
}
This enables applications to log connectivity issues or adjust behavior based on path quality.
Key Source Files Implementing NAT Handling
The following files in the tailscale/tailcat repository implement the NAT traversal failure logic:
-
tailcat.go(lines 13-15): Contains the core library logic for falling back to DERP relays when direct paths cannot be established. -
cmd/tailcat/tailcat.go(lines 885-900): Implements the CLIpingcommand, the--until-directflag, and timeout handling that exits with an error when direct connections fail. -
disco.go(line ~2064): Handles discovery packets that actively trigger attempts to establish direct paths, containing the comment "actively triggers direct path discovery" near this section. -
wire.go: Defines the encrypted WireGuard protocol used for both direct and DERP-routed tunnels, ensuring cryptographic continuity regardless of path type. -
README.md: Documents the high-level architecture, explicitly noting that DERP serves as the fallback mechanism when NAT traversal fails.
Summary
- Automatic fallback: When direct UDP hole-punching fails, Tailcat transparently switches to DERP relays without dropping the WireGuard tunnel.
- CLI visibility: The
pingcommand reports(DERP)in its output to indicate relay usage, while--until-directenables scripts to wait for or fail on direct path establishment. - Programmatic access: Go code can call connection methods to detect DERP usage and adjust application logic accordingly.
- Continuous operation: Even with failed NAT traversal, encrypted connectivity persists through the relay, though with potentially higher latency.
Frequently Asked Questions
Does Tailcat stop working if NAT traversal fails?
No. Tailcat remains fully functional when NAT traversal fails because it automatically falls back to a DERP relay server. The encrypted WireGuard tunnel continues to carry TCP and UDP traffic, though latency may increase due to the relay hop.
How can I tell if my Tailcat connection is using a DERP relay?
Run the tailcat ping command and check the output suffix. If the response ends with (DERP), the traffic is routing through a relay. Direct connections display the actual IP address and port of the peer instead.
What causes NAT traversal to fail in Tailcat?
NAT traversal typically fails when both peers operate behind symmetric NATs or when firewalls strictly block UDP hole-punching. These network configurations prevent the magicsock layer from establishing a direct peer-to-peer path, triggering the DERP fallback.
Can I force Tailcat to use only direct connections?
Yes. Use the --until-direct flag with the ping command and specify a timeout. If a direct path cannot be established within the timeout period, the command exits with a non-zero status. However, there is no option to prevent DERP fallback during normal operation without failing the connection entirely.
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 →