Tailcat : WireGuard point-à-point et traversée de NAT sans plan de contrôle

Tailcat transpose le plan de données magicsock et WireGuard de Tailscale dans un outil CLI léger en espace utilisateur, sans nécessiter de coordination centrale.

Tailscale a révolutionné les VPN maillés (mesh VPN) en associant WireGuard à une traversée de NAT ingénieuse (magicsock) et à un relais de secours chiffré (DERP). Cependant, les configurations classiques de Tailscale nécessitent d'enregistrer les nœuds auprès d'un serveur de coordination centralisé (le plan de contrôle) et d'exécuter des démons privilégiés qui modifient les tables de routage système et les interfaces réseau virtuelles (TUN/TAP).

Tailcat change ce paradigme. Développé par l'équipe de Tailscale, Tailcat regroupe les composants essentiels du plan de données de Tailscale dans une CLI autonome en espace utilisateur et une bibliothèque Go. Il fonctionne comme netcat, mais chaque connexion est chiffrée de bout en bout avec WireGuard, traverse automatiquement les NAT et ne requiert ni privilèges root ni compte Tailscale.


Sous le capot : l'architecture en espace utilisateur sans accès root

Tailcat s'affranchit totalement du plan de contrôle de Tailscale. Au lieu de coordonner l'état des pairs via un serveur de contrôle centralisé, il transmet l'ensemble des métadonnées de rendez-vous hors bande à l'aide de jetons de connexion compacts.

Au sein du processus, Tailcat associe quatre composants majeurs :

  1. WireGuard en espace utilisateur : gère le chiffrement point-à-point (Curve25519, ChaCha20-Poly1305) sans toucher au module WireGuard du noyau Linux ni nécessiter de droits d'administration.
  2. magicsock : le moteur de transport multi-chemins de Tailscale. Il sonde dynamiquement les terminaisons UDP via STUN et effectue du poinçonnage UDP (UDP hole-punching) pour établir des connexions directes de pair à pair.
  3. DERP (Designated Encrypted Relay for Packets) : un mécanisme de relais basé sur HTTPS qui sert de canal de rendez-vous et de solution de secours garantie si des NAT symétriques empêchent le poinçonnage UDP direct.
  4. netstack de gVisor : une pile TCP/IP complète s'exécutant directement dans la mémoire de l'espace utilisateur. Étant donné que netstack termine les connexions au sein même du processus, Tailcat peut accepter des flux entrants ou relayer du trafic sortant sans altérer les tables de routage de l'OS ni les interfaces réseau locales.
+-------------------------------------------------------------+
|                        Tailcat CLI                          |
|  +-------------------------------------------------------+  |
|  |              gVisor netstack (TCP/IP Espace Utilisateur) |  |
|  +-------------------------------------------------------+  |
|  |              WireGuard en Espace Utilisateur          |  |
|  +-------------------------------------------------------+  |
|  |                      magicsock                        |  |
|  +---------------------------+---------------------------+  |
|  |   STUN / Poinçonnage UDP  |    Relais Chiffré DERP    |  |
+--+---------------------------+---------------------------+--+

Anatomie d'un jeton de connexion (ConnBlob)

Lorsqu'un serveur Tailcat démarre, il affiche un jeton d'adresse éphémère préfixé par tc (par exemple, tcomFwWCCcjS5nKNqAod034...).

En interne, cette chaîne est un blob CBOR encodé en base64 contenant :

  • La clé publique WireGuard Curve25519 de 32 octets du serveur (nodekey:...).
  • Les métadonnées de rendez-vous DERP (soit un ID de région sous forme d'entier, soit les noms d'hôtes et adresses IP complets des nœuds).

Vous pouvez inspecter n'importe quel jeton à l'aide de la CLI :

$ tailcat parse tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
{
  "ServerPublic": "nodekey:9c8d2e6728da80a1dd37e275a82595b42d9a838610bc53f74a7670d1610f2e34",
  "RegionID": 302
}

La séquence de négociation (Handshake)

  1. Initialisation (Bootstrap) : Le client analyse le jeton, extrait la clé publique du serveur ainsi que la région DERP, puis se connecte au relais DERP partagé.
  2. Échange de découverte : Le client envoie un paquet léger Meow via DERP contenant sa propre clé publique éphémère. Le serveur enregistre le client et répond avec Meowed.
  3. Handshake WireGuard : Les négociations WireGuard standard s'effectuent à travers le tunnel DERP.
  4. Traversée de NAT : En parallèle, les deux extrémités échangent les adresses UDP candidates (adresses STUN et IP locales) via des messages de découverte DERP. Si le poinçonnage UDP réussit, le flux de données bascule de manière transparente vers une connexion UDP directe.

Cas d'utilisation pratiques

1. Redirection Stdin / Stdout (Netcat chiffré)

Sur la machine réceptrice :

$ tailcat
# 🐈 Server listening with new address: tcXXXXXXXXX

Sur la machine émettrice :

$ tar -czf - ./project | tailcat tcXXXXXXXXX

2. Redirection de ports locaux derrière un pare-feu

Exposez un service local (comme un serveur web de staging) sans redirection de port, UPnP ou ngrok :

# Sur le serveur
$ tailcat --serve=8080
# 🐈 Server listening with new address: tcXXXXXXXXX

# Sur le client
$ tailcat tcXXXXXXXXX 8080
GET / HTTP/1.1
Host: localhost

3. SSH authentifié par WireGuard via des enregistrements DNS TXT

Tailcat peut fournir un accès SSH protégé par des clés WireGuard avant même que le démon SSH ne soit exposé :

  1. Générer la paire de clés client :

    client$ tailcat genkey --client
    # Public key: nodekey:cfb6bfa77a0654d7450947fd6acef17d2cd848da1d30b2540b13dac272ddfd16
    
  2. Exécuter le serveur SSH authentifié :

    server$ tailcat genkey --fixed-region
    server$ tailcat --serve=22 --allow=nodekey:cfb6bf...ddfd16
    
  3. Publier via DNS : Créez un enregistrement DNS TXT pointant vers le jeton de serveur généré :

    bastion.internal.example.com. 300 IN TXT "tailcat=tcXXXXXXXXX"
    
  4. Se connecter directement via le nom de domaine :

    client$ tailcat ssh bastion.internal.example.com
    

Les tentatives de connexion non autorisées sont abandonnées dès la couche WireGuard : les scanners de ports ne peuvent même pas détecter la présence d'un démon SSH en cours d'exécution.

4. Proxy SOCKS5 et nœuds de sortie (Exit Nodes)

Acheminez le trafic CLI à travers un pair distant :

# Hôte distant
$ tailcat --serve=exit-node

# Machine locale
$ tailcat socks <token> curl http://internal-service.local/

Utiliser Tailcat comme bibliothèque Go

Tailcat est conçu pour être directement intégré dans des services Go.

Exemple de serveur

package main

import (
	"fmt"
	"log"
	"net"

	"github.com/tailscale/tailcat"
)

func main() {
	s := &tailcat.Server{
		OnTCP: func(port uint16) func(net.Conn) {
			return func(c net.Conn) {
				defer c.Close()
				fmt.Fprintf(c, "Connecté au service in-process sur le port %d\n", port)
			}
		},
	}

	if err := s.Start(); err != nil {
		log.Fatal(err)
	}

	// Partagez ce jeton avec les clients
	fmt.Println("Jeton de connexion :", s.ConnBlob())
	select {}
}

Exemple de client

package main

import (
	"context"
	"io"
	"log"
	"os"

	"github.com/tailscale/tailcat"
)

func main() {
	token := tailcat.ConnBlob(os.Args[1])
	cl := tailcat.NewClient(token)
	defer cl.Close()

	conn, err := cl.DialTCPPort(context.Background(), 80)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	io.Copy(os.Stdout, conn)
}

BYO Relay : s'affranchir de l'infrastructure publique

Tailcat utilise par défaut des relais DERP publics (https://tailcat.dev/derpmap.json), mais vous n'êtes pas contraint de les utiliser. Vous pouvez lier les jetons du serveur directement à votre propre serveur DERP auto-hébergé :

server$ tailcat genkey --region=derp.myinfra.net
# Jeton généré intégrant derp.myinfra.net

Étant donné que l'adresse du nœud DERP est stockée directement dans le jeton, les clients qui s'y connectent résolvent automatiquement le relais de rendez-vous sans avoir à configurer de paramètres locaux.


Points clés à retenir

  • Zéro configuration : Aucun compte, jeton d'API ou coordinateur centralisé.
  • Sécurité en espace utilisateur : S'exécute sans privilèges ; aucun adaptateur réseau virtuel, modification DNS ou altération de pare-feu.
  • Performances WireGuard : Les tunnels basculent automatiquement des relais DERP vers des connexions UDP pair-à-pair grâce à la traversée automatique de NAT.
  • Composable : S'utilise comme un outil en ligne de commande prêt à l'emploi, un proxy ad-hoc ou une bibliothèque Go intégrable pour les architectures distribuées.

Source

tailscale/tailcat: like netcat, but over Tailscale's data plane, without Tailscale's control plane