Tailcat: WireGuard punto a punto y NAT traversal sin plano de control
Tailcat lleva magicsock y el plano de datos de WireGuard de Tailscale a una herramienta CLI ligera en espacio de usuario, sin necesidad de coordinación central.
Tailscale revolucionó las redes VPN en malla combinando WireGuard con un ingenioso sistema de NAT traversal (magicsock) y un mecanismo de respaldo mediante relays cifrados (DERP). Sin embargo, las configuraciones habituales de Tailscale requieren registrar los nodos en un servidor de coordinación centralizado (el plano de control) y ejecutar demonios con privilegios que modifican las tablas de enrutamiento del sistema y las interfaces de red virtuales (TUN/TAP).
Tailcat cambia ese paradigma. Creado por el equipo de Tailscale, Tailcat empaqueta los componentes principales del plano de datos de Tailscale en una CLI independiente en espacio de usuario y una biblioteca de Go. Funciona de manera similar a netcat, pero cada conexión está cifrada de extremo a extremo con WireGuard, atraviesa NAT automáticamente y no requiere privilegios de root ni una cuenta de Tailscale.
Bajo el capó: Arquitectura en espacio de usuario sin privilegios de root
Tailcat elimina por completo el plano de control de Tailscale. En lugar de coordinar el estado de los pares mediante un servidor de control central, transmite todos los metadatos de encuentro (rendezvous) fuera de banda mediante tokens de conexión compactos.
Dentro del proceso, Tailcat combina cuatro componentes principales:
- WireGuard en espacio de usuario: Gestiona el cifrado punto a punto (Curve25519, ChaCha20-Poly1305) sin interactuar con el módulo de WireGuard del kernel de Linux ni requerir permisos administrativos.
magicsock: El motor de transporte multirruta de Tailscale. Sondea dinámicamente endpoints UDP mediante STUN y ejecuta UDP hole-punching para establecer conexiones directas entre pares.- DERP (Designated Encrypted Relay for Packets): Un mecanismo de relay basado en HTTPS que actúa como canal de encuentro y respaldo garantizado si las NAT simétricas impiden el UDP hole-punching directo.
netstackde gVisor: Una pila TCP/IP completa que se ejecuta en la memoria del espacio de usuario. Dado quenetstacktermina las conexiones dentro del propio proceso, Tailcat puede aceptar flujos entrantes o reenviar tráfico saliente sin alterar las tablas de enrutamiento del sistema operativo ni las interfaces de red locales.
+-------------------------------------------------------------+
| CLI de Tailcat |
| +-------------------------------------------------------+ |
| | gVisor netstack (TCP/IP en espacio de usuario) | |
| +-------------------------------------------------------+ |
| | WireGuard en espacio de usuario |
| +-------------------------------------------------------+ |
| | magicsock | |
| +---------------------------+---------------------------+ |
| | STUN / UDP Hole Punch | Relay cifrado DERP | |
+--+---------------------------+---------------------------+--+
Anatomía de un token de conexión (ConnBlob)
Cuando se inicia un servidor Tailcat, imprime un token de dirección efímero con el prefijo tc (por ejemplo, tcomFwWCCcjS5nKNqAod034...).
Internamente, esta cadena es un blob CBOR codificado en base64 que contiene:
- La clave pública WireGuard Curve25519 de 32 bytes del servidor (
nodekey:...). - Metadatos de encuentro DERP (ya sea un ID de región numérico o nombres de host e IPs completos de los nodos).
Puedes inspeccionar cualquier token utilizando la CLI:
$ tailcat parse tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
{
"ServerPublic": "nodekey:9c8d2e6728da80a1dd37e275a82595b42d9a838610bc53f74a7670d1610f2e34",
"RegionID": 302
}
La secuencia de negociación (Handshake)
- Bootstrap: El cliente analiza el token, extrae la clave pública del servidor y la región DERP, y se conecta al relay DERP compartido.
- Intercambio de descubrimiento: El cliente envía un paquete ligero
Meowa través de DERP con su propia clave pública efímera. El servidor registra al cliente y responde conMeowed. - Handshake de WireGuard: El handshake estándar de WireGuard se realiza a través del túnel DERP.
- NAT Traversal: En paralelo, ambos extremos intercambian direcciones UDP candidatas (endpoints STUN e IPs locales) mediante mensajes de descubrimiento DERP. Si el UDP hole-punching tiene éxito, el flujo de datos pasa de forma transparente a una conexión UDP directa.
Casos de uso prácticos
1. Tuberías Stdin / Stdout (Netcat cifrado)
En la máquina receptora:
$ tailcat
# 🐈 Server listening with new address: tcXXXXXXXXX
En la máquina emisora:
$ tar -czf - ./project | tailcat tcXXXXXXXXX
2. Reenvío de puertos locales tras cortafuegos
Expón un servicio local (como un servidor web de pruebas) sin necesidad de reenvío de puertos, UPnP ni ngrok:
# En el servidor
$ tailcat --serve=8080
# 🐈 Server listening with new address: tcXXXXXXXXX
# En el cliente
$ tailcat tcXXXXXXXXX 8080
GET / HTTP/1.1
Host: localhost
3. SSH autenticado por WireGuard mediante registros DNS TXT
Tailcat puede proporcionar acceso SSH protegido por claves de WireGuard antes de que el demonio SSH quede expuesto:
Generar el par de claves del cliente:
client$ tailcat genkey --client # Public key: nodekey:cfb6bfa77a0654d7450947fd6acef17d2cd848da1d30b2540b13dac272ddfd16Ejecutar el servidor SSH autenticado:
server$ tailcat genkey --fixed-region server$ tailcat --serve=22 --allow=nodekey:cfb6bf...ddfd16Publicar vía DNS: Crea un registro DNS
TXTque apunte al token generado por el servidor:bastion.internal.example.com. 300 IN TXT "tailcat=tcXXXXXXXXX"Conectarse directamente mediante el dominio:
client$ tailcat ssh bastion.internal.example.com
Los intentos de conexión no autorizados se descartan en la capa de WireGuard; los escáneres de puertos ni siquiera podrán detectar que se está ejecutando un demonio SSH.
4. Proxy SOCKS5 y nodos de salida
Enruta el tráfico de la CLI a través de un par remoto:
# Host remoto
$ tailcat --serve=exit-node
# Máquina local
$ tailcat socks <token> curl http://internal-service.local/
Uso de Tailcat como biblioteca de Go
Tailcat está diseñado para poder integrarse directamente en servicios de Go.
Ejemplo de Servidor
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, "Conectado al servicio en proceso en el puerto %d\n", port)
}
},
}
if err := s.Start(); err != nil {
log.Fatal(err)
}
// Comparte este token con los clientes
fmt.Println("Token de conexión:", s.ConnBlob())
select {}
}
Ejemplo de Cliente
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: Ejecución sin infraestructura pública
Tailcat utiliza relays DERP públicos por defecto (https://tailcat.dev/derpmap.json), pero no estás obligado a usarlos. Puedes vincular los tokens del servidor directamente a tu propio servidor DERP autoalojado:
server$ tailcat genkey --region=derp.myinfra.net
# Token generado integrando derp.myinfra.net
Dado que la dirección del nodo DERP se almacena dentro del propio token, los clientes que se conectan resuelven el relay de encuentro automáticamente sin necesidad de configurar parámetros locales adicionales.
Conclusiones clave
- Cero configuración: Sin cuentas, tokens de API ni coordinadores centrales.
- Seguridad en espacio de usuario: Se ejecuta sin privilegios; sin adaptadores de red virtuales, modificaciones de DNS ni cambios en el cortafuegos.
- Rendimiento de WireGuard: Los túneles pasan automáticamente de relays DERP a conexiones UDP directas entre pares mediante NAT traversal automatizado.
- Modular y adaptable: Funciona como herramienta de línea de comandos lista para usar, como proxy ad-hoc o como biblioteca de Go integrable en servicios distribuidos.
Fuente
tailscale/tailcat: like netcat, but over Tailscale's data plane, without Tailscale's control plane